I still see many developers coding huge methods and large classes. The code looks very much procedural, having lots of conditional statements and loops one following the other and eventually even nested. Asking for the reason of such structuring, surprisingly the readability is often mentioned.
Well, when reading or debugging the code, I don't have to jump between classes/methods, but I immediately see what's going on within that method.
Fair enough! But attention, this is the readability from the developer perspective that owns/created
the code. That's quite different:
When the "creator"
of the code reads through it, he won't have to read all of the conditions, the loops in detail because he wrote the code. He'll read the signature of the loop and immediately understand what it is supposed to do. The same for if statements.
On the other side, when some other dev
goes over the code, he may not know the code in detail but possibly just its general purpose. So when that dev goes over the code, he has to read all of the loops, ifs etc in detail
in order to find some bug/get a better understanding.
- ... has a much higher cost of making modifications or extensions
- ... has a much higher risk of containing bugs / introducing new bugs when modifying it
- ... causes more frustration on the developer's part that has to modify/bugfix it.
Often, design problems in source code are uncovered when devs would like to adopt automated testing approaches like unit testing. They have a really hard time to test such code, mainly because first they don't see the opportunities
of how a particular code could be tested and second because testing a 150 LOC+ method is tremendously frustrating
if not impossible.
The major challenge with writing unit tests is to create testable code. A testable code is one where I immediately see what the potential side effects can be and how I can avoid/control them from within my unit tests. Remember, a unit test
should not have any side-effects on its environment, i.e. add/remove a record in the db, write a file to the file system etc. You should be able to run them as often as possible, so they need to be fast and you'd not like to spam your DB or filesystem.
However, already in a 50 LOC+ method I'll have a very hard time to figure out whether I'm able to isolate the code from the rest of its environment s.t. it doesn't cause such undesired effects. On the other side, small methods
or classes with a single responsibility
are much easier to test. That's why refactorings as suggested by Fowler
are very welcome to structure your code more optimally.
In the ideal case you'd even go for a test-first approach where you incrementally build your system by iterating through short test-code-refactor cycles. This will automatically lead to well tested code which proved to result in an overall better, more loosely coupled design. Adopting test-first has however a very steep learning curve.
Anyways, testable and more readable code will be
- easier to read and understand for someone else
- less complex, test cases are immediately recognizable
- cause less frustration during testing
- easier to maintain
- present a minor danger of introducing new bugs