We want our systems to be composed of many small classes, not a few large ones. Each small class encapsulates a single responsibility, has a single reason to change, and collaborates with a few others to achieve the desired system behaviors.
I have heard this one many times. I strive to accomplish this but I have not been very successful.
Classes should have a small number of instance variables. Each of the methods of a class should manipulate one or more of those variables. In general the more variables a method manipulates the more cohesive that method is to its class.
I have never heard of this before or even thought about it. It makes a lot of sense though. I wish I had of thought of it.
Just the act of breaking large functions into smaller functions causes a proliferation of classes. Consider a large function with many variables declared within it. Let’s say you want to extract one small part of that function into a separate function. However, the code you want to extract uses four of the variables declared in the function. Must you pass all four of those variables into the new function as arguments?
Not at all! If we promoted those four variables to instance variables of the class, then we could extract the code without passing any variables at all. It would be easy to break the function up into small pieces.
Unfortunately, this also means that our classes lose cohesion because they accumulate more and more instance variables that exist solely to allow a few functions to share them.
But wait! If there are a few functions that want to share certain variables, doesn’t that make them a class in their own right? Of course it does. When classes lose cohesion, split them!
This is very well written and ties together a lot of concepts from previous chapters.
The code in each class becomes excruciatingly simple. Our required comprehension time to understand any class decreases to almost nothing. The risk that one function could break another becomes vanishingly small. From a test standpoint, it becomes an easier task to prove all bits of logic in this solution, as the classes are all isolated from one another.
We want to structure our systems so that we muck with as little as possible when we update them with new or changed features. In an ideal system, we incorporate new features by extending the system, not by making modifications to existing code.
Here are the reasons why we bother with all this hard work. I always have to change my code to add new features, and I usually break something. I always remember when someone comes up with a design that handles a new special case without having to rework everything. Those are truly impressive feats by gifted programmers.
If a system is decoupled enough to be tested in this way, it will also be more flexible and promote more reuse. The lack of coupling means that the elements of our system are better isolated from each other and from change. This isolation makes it easier to understand each element of the system.
This is why I think TDD is so important and wished we practiced it in my group. Not because TDD will guarantee you never have bugs, but because TDD forces you to create designs that are decoupled enough to allow testing. These decoupled designs will end up being easier to understand than a design where you never had to consider how you would test it.