Method-per-class is a bit smelly
It’s unfortunate sometimes, how a person wants to do well, sets out with the best intentions, but doesn’t really appreciate the context they are operating in and doesn’t really get the results they are looking for. Quite like my own best attempts at reassuring upset friends, unfortunately I just end up making them suicidal.
It’s an intent-outcome combination that crops up quite a lot in software development, too. Whilst there are certainly developers who crank out 2000 line classes in earnest not realising the pain they are causing, there are also devs who mean well by learning all about SOLID and other best practices yet fail to produce optimal results.
One such example is the “method per-class” pattern. I believe this is overuse of, and an incorrect interpretation of SRP. It’s not the worst habit to have, but it can be costly in a number of ways.A case study
I googled SRP and it didn’t take me long to find this blog post with the best of intentions http://blog.gauffin.org/2011/07/single-responsibility-prinicple/.
This blog post demonstrates how a single class, with a single 5 line method violates SRP. The author goes on to create one new class, and suggest there should be two. Each with one method containing paltry logic.
Now stop, and whether or not you think the author is justified, ask yourself — what does SRP mean and why was it created?
Here you go, straight from object mentor
If a class has more then one responsibility, then the responsibilities become coupled.
Changes to one responsibility may impair or inhibit the class’ ability to meet the others.
This kind of coupling leads to fragile designs that break in unexpected ways when
changed.
Groovy — now we know exactly what problem SRP solves. So.. does the problem of “fragile designs that break in unexpected way when changed” apply to 1 class with 1 method that contains 5 lines?But why is this a problem?
**Harder to understand **
Those initial 5 lines of code in the Register() method were unquestionably the simplest possible representation of the functionality. 5 lines of code, one after the other.
Now we have the baggage of additional classes and additional methods. These constructs can be used to abstract away numerous low-level details so we can conceptualise them as single units. But ValidateEmail() and SendEmail() just don’t give me any of that — they make it seem as though there are complex details hidden away.
Imagine a whole code base with this level of unnecessary complexity. For every class there would have been there could now be N + 2 classes, resulting in N + 2 code navigations to work out what the code does and N + 2 test classes… or worse.
**More refactoring friction **
More classes, and thus more public methods, is a greater surface area. Being good devs that means more tests. And of course, more tests means more hurdles to refactoring.
Keeping some logic as private methods and not new classes can remove these hurdles without having much potential to really slow down feedback.
**Slows you down **
Additional to the refactoring friction that slows you down, writing more tests slows you down. Creating more classes, more source control commits, and writing more lines of code just unnecessarily eats up time.
**You miss important design opportunities **
Refactoring single lines of code or small segments is always a risk. You are pre-empting that those bits of code live together, will grow together, and will not fit better with new pieces of the system that are yet to be added in other places.
When you let code clump together, maybe even get a touch bloated, it is much easier to see refactoring design decisions. It saves you having to inline classes, tease them apart and then refactor it.
**Increased coupling **
The more little classes you have, the more chance the code in one will look similar to that required in another part of the system. So you stay nice and DRY and reuse it. But then those two (hopefully just two) clients of the class require it to change for different reasons. There are easy ways out sometimes, but when you have multiple object graphs that are intricately inter-connected, that’s no fun to untangle my friend (well actually it is a bit of fun).Context is important
Hypothetically, the next iteration of the code in that blog post might include a slew of email validation rules ranging from obscenities, to black-lists, to country-specific clauses. Then we really would want a separate component for validation.
But it isn’t needed yet. And it might not ever be needed. In fact, the whole application might not ever be needed. In which case, the code was prematurely optimised, anticipating how the system would change and where new logic would live at the expense of increased complexity and getting the product to market and getting feedback as fast as possible. This is a waste of company finance (who cares then :P ).
Context is therefore crucial in determining the philosophy behind how code should be cranked out. However, a 5 line method is rarely an opportunity to extract classes in any context (in my opinion).
I think developers are often on auto-pilot and SOLID, DRY and other principles are guiding the ship through skies they are not intended to grace solo.Beat the addiction
You’re using SOLID, TDD and all that jazz because you want to write good code. But these techniques don’t guarantee anything, and are a burden if used incorrectly. Good code also comes from motivation and appreciation of context.
Try stopping your reflexes from going abstraction crazy if you have that tendency. Just try and live with code that looks a bit messy. Let it clump together a touch more than you are comfortable with. See if you find what I did — that better design opportunities come along, and if they don’t the code will stay short, simple and all in one place. Same goes for the accompanying tests.
But…. often it’s a team game and you have to conform to team philosophies. An abundance of classes can make life hard work, but it’s ususally WAY better than the other extreme of out of control monoliths.
My point here is not to ridicule anyone, but to suggest we need to be more analytical, more considerate of context, and make fairly good code exceptional by virtue of increased simplicity and context-awareness.
Here’s a bunch of links about keeping things simple, taking context into consideration, what is SRP etc
· http://www.infoq.com/news/2009/02/spolsky-vs-uncle-bob
· http://www.udidahan.com/2012/09/08/build-one-to-throw-away/
· http://lostechies.com/jimmybogard/2012/08/30/evolutionary-project-structure/
And if you stayed to the end you get a quote :
Justify why the simplest possible thing is not good enough — Udi Dahan