Why you want your classes to be small
Clean Code recommends that classes should be small. (page 136)
But why should anyone care about the class size?
Surely, the size of a class doesn’t hurt any runtime characteristic
of the program. In fact, the less jumping between objects there is the faster it will run (because we minimize v-table lookups and pointer derefs).
And when there’s Locality of behavior
(everything related is close-by, like maybe in the same class),
it’s a lot easier to understand what’s going on, so the readability is better for maintainability and extensibility.
In this article, I’ll explain why I believe that:
As more developers work in the codebase, small classes become extremely important for the team’s velocity.
When a class is large, it tends to be related to many features.
Features that may be worked on in different branches,
perhaps by different developers.
When they’re done, they need to reconcile their changes
to make the master build.
But they may have changed nearby lines of code independently.
Large classes cause merge conflicts.
So what?
I worked as a developer in many companies,
some large (with multiple independent development teams)
and some small (with only a handful of devs, sometimes just one)
Until I came to a larger organization, I didn’t understand why
everyone was so afraid of merge-conflicts.
Developers try to avoid working on the same class, and
even management has an interest in it. “Divide and conquer” or whatever.
Once you know what Git is telling you, and how to tell it what to do about it, it’s usually very simple to resolve them. What is there to fear?
The reason isn’t Git.
It’s not the scary looking output.
Nor the archaic language of its CLI.
Each merge conflict forces you to re-integrate your changes.
Resolving a merge conflict, whether it merges cleanly, or requires changes unrelated to the diff, changes the system and may introduce an inconsistency.
Even if your changes were already tested before the merge conflict,
you must test them again after.
If you don’t, you may break the build. And that’s never fun.
You don’t want to be the reason your team can’t get any work done.
Integration can become a very slow process,
regardless of whether your strategy is manual QA testers,
or automated tests in a Continuous Integration pipeline.
Re-integration is slow, and can be minimzed by avoiding merge conflicts.
Definition of “Single-responsibility principle”:
A class or module should have one, and only one, reason to change.
(Clean Code, page 138)
This naturally leads to smaller, more cohesive classes.
When there is more than one reason for a class to change, it becomes possible for more than one developer to need to change it.
The more reasons to change a class, the higher the
probability of merge conflcits.
The amount of checks that must pass for the team to be confident
about the build, naturally grows over time.
As more features are added to the product,
there are more regressions to test.
One of the strategies for mitigating the natural slowdown of integration,
is to intelligently decide which checks to skip.
Some automated tests frameworks
(like JavaScript’s Jest with —changedSince CLI parameter)
can analyze the source code dependencies of tests, to find which files can affect the test and which can’t.
If the version’s diff doesn’t include any files that are reachable
from a given test, there’s no reason to run that test.
This saves a lot of time.
When a class is large, its parts are coupled, so we must test all of them.
Had the parts been in separate classes, we could have skipped some of the checks, if they were unrelated to our specific changes.
Definition of: Interface segregation.
Avoid depending on things that you don’t use.
(Clean Architecture, page 59)
Large classes tend to have many dependencies.
Anyone who depends on the class implicitly transitively depends on its dependencies.
Dependencies may break dependents, so we must test the class when any of its dependencies change.
When a class is large, more things can require its re-integration.
Had it been split to smaller classes, the amount of tests we’d need to re-run when a dependency changed, could have shrunk.
Large classes cause merge conflicts,
which force re-integration, that can be slow,
and prevent making integration faster.
Everyone needs to integrate eventually,
so slow and wasteful integration slows everybody down.
Large classes block prompt delivery of business value to customers.