DEV Community

loading...
Cover image for SOLID 20 years on: >S  single, or limited responsibility? (1/5)

SOLID 20 years on: >S single, or limited responsibility? (1/5)

Tea
Language and API Design, Game AI, Behavior trees, Procedural Design, Entrepreneurship. Concise code.
ポ4 min read

S.O.L.I.D is/are among the most quoted software design principles; compelling enough that you want to apply them, yet flexible enough that they're open to some interpretation.

Today - in reply to Sabrina Suarez, starts with Ⓢ.

Single Responsibility

At once one of the most actionable and perhaps also one of the fuzziest principles in SOLID.

Actionable because many developers keep adding functionality to existing classes. Hilariously at my first job we had a rule that, when a class is over 1kloc, it is time to break it down. Wait, what?

Do have a look at the Rust compiler source. Or .NET reference implementations. Over 1kloc is actually not rare.

Point being much software can be improved/redesigned by trying to make classes smaller.

Meanwhile another question is: where does it stop?

At... ONE?

1 single, unique, indivisible responsibility and, erm, how do you even define this?

A single part

Seriously, guys, what am I supposed to make out of this? A module is a single part. A function is a single part. What are we designing? Apple Pie? No thanks.

One reason to change

Now that's an interesting take (and perhaps Martin's word on this?); at the same time it is still extremely vague and, in this form not consistently actionable.

The crux here, is it doesn't really make sense for a class to have "only one job" - or rather it perhaps makes more sense from an implementation than from an API design point of view.

A good class name is a noun; A noun refers to a thing/metaphor. In terms of mechanistic decomposition of how stuff works, being able to atomize a complex thing into nameable parts is great. In terms of a thing being useful. Well.

One example that comes to mind is the String class (any string class, whatever language). String is always good and bad. It does too many and too few things. It definitely has too many responsibilities, and that is true from the point of using/abusing strings, and of course from a specification/API point of view.

No strings then?

Limited Responsibility

Perhaps disappointingly, I don't have a definite answer here to how 'single responsibility' optimally applies. Soberly and pragmatically, I use a limited responsibility principle which is a dumb thing best embodied into the 35x70 rule.

Meaning:

  • 70 characters long
  • 35 (physical) lines of code.

"So this is about physical (not logical) design?" - That's right, and yet it does impact logical design, in a way that keeps software clear and sufficiently modular.

There is not much discussion/time wasted filtering LR principle violations:

  • Under 35 loc: whatever, fine.
  • 35~70: danger zone
  • 100+: all hands on deck

(Oh not so) incidentally, a 35x70 characters matrix fits a half screen on a 12” laptop.

🔥 🐽 🔥

Works for me, as I indulge tiny laptops. And of course it'll be working for you too, because your laptop is either the same, or larger.

Adopting the 35x70 rules, I feel extremely confident that other developers will appreciate my code.

  • Each class is self-contained.
  • Not that small that you need to hop anywhere else to learn something about how the software works.
  • Also not so big you'd have to thumb/scroll around to figure things out.

Bonus: at a half screen's worth, there is space in here for you to compare, analyze and, of course, extend the software.

This isn't a thing you can flip on its head. In other words, don't ever try to impose the 35x70 rules. Because, you know, it is, well... arbitrary, and illogical?

Which brings us to the logical aspect of it: unless you are using partials (sometimes I do, although mainly as a business tool 🍉 ) there is only so much responsibility you can inflict on a class fitting 35 loc.

All said and done. Malfeasance will spoil any good principle, and I shall demonstrate this here and now by picking a 35 loc long class from my personal reserve.

⊐ Ex = System.Exception;
⊐ UnityEngine; ⊐̥ UnityEngine.Time; ⊐̥ UnityEngine.Mathf;
⊐ Active.Core; ⊐̥ Active.Core.status;

⊓ Activ.Kabuki{ ‒ ○ Locomotion{

    ‒ ⑂ MoveTo(エ x, メ y, ㅅ speed){
        ⤴ (x˙ ☰ y) ◇̠
        ㅅ d = x.PlanarDist(y), δ = 𝛿𝚝 ᐧ 𝝇;
        ⮐ (δ > d) ? Do( x˙ = y) : Move(x, x.PlanarDir(y), δ, d);
    }

    ‒ ⑂ MoveTowards(エ x, メ y, ㅅ dist, ㅅ speed){
        ㅅ d = x.PlanarDist(y);
        ⮐  (d < dist) ∨ Move(x, x.PlanarDir(y), 𝛿𝚝 ᐧ 𝝇, d);
    }

    ‒ ⑂ MoveTowards(エ x, エ y, ㅅ dist, ㅅ speed){
        ㅅ d = x.PlanarDist(y);
        ⮐  (d < dist) ∨ Move(x, x.PlanarDir(y), 𝛿𝚝 ᐧ 𝝇, d);
    }

    // -------------------------------------------------------

    ⑂ Move(エ み, シ u, ㅅ δ, ㅅ los){
        シ? v = Avoidance.Clear(み˙, u, maxDistance: los);
        ⤴ (v ☰ ∅) ⮐ ■;
        み.⫫ = シ.Lerp(み.⫫, vᖾ, 0.1f);
        ⮐ Run(み˙ += vᖾ ᐧ δ);
    }

    ∘ ⑂ Do(⊡ x) { ⌽ } ∘ ⑂ Run(⊡ x) { ☡̱ }

}}

Enter fullscreen mode Exit fullscreen mode

For practical purposes I make devious allowances to cram functionality in a 35x70 frames. Once I exceed this quota I let the kite fly to 100loc, and when that hits, unless it's a Swiss army styled collection of utilities, I refactor.

🦖 this is Howl

(image: my 'Shizen' project)

Discussion (0)