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) { âĄĚą }
}}
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)
Top comments (0)