I’ve learned not to assume a team has experienced a variety of software design skills. Some are writing elegant functional-paradigm code in archaic, challenging languages. Others are writing strongly-coupled, heavily-commented, and procedural static methods in Java or C#.
I’ve found that “good design,” though not really subjective, is certainly contextual. It depends on the composition of the team, their combined experiences, how they communicate with each other (or avoid communicating), the history of their code, and what they’ve learned about Design Patterns, S.O.L.I.D. principles, etc.
Good software design is a career-spanning journey of discovery.
Because there are so many different ways to describe “good software design,” I tend to start with a pragmatic definition, and two simple techniques, that fit nicely into the notion of incremental (or emergent) design. These make up a minimal initial set of software design guidelines, necessary for an Agile team to begin delivering quality and value, and to quickly grow and learn as a team.
The definition: A good design is one that is easy for the team to understand and maintain.
Can the team understand what the system does? Can the team easily extend or repair the system’s functionality?
To get there, I ask teams to absorb Kent Beck’s “Four Rules of Simple Design” and to refine their sensitivity towards “code smells.” From these, I believe, all other attributes, principles, and wisdom could be derived (eventually).
From an “Agilist’s” standpoint, these permit an opportunity for later, deeper learning to be applied to the existing system, without preventing that embryonic system from reaching sufficient robustness.
The Four Rules
Kent Beck’s Four Rules of Simple Design are (in the order of precedence):
- It works! It has tests, and they all pass.
- It’s understandable: The code is expressive, concise, and obvious to the team.
- Once and only once: Behaviors, concepts, and configuration details are defined in one location in the system (code, build scripts, config files). Don’t Repeat Yourself (DRY).
- Minimal: Nothing extra. Nothing designed for some as-yet-unforeseen future need. You Aren’t Going to Need It (YAGNI)
You’ll notice that there is a lot of overlap. For example, rules 2 through 4 could all dissuade you from creating an unnecessary class. Two respected colleagues have been able to reduce even this simple list to a list of two items (and a boatload of caveats or explanatory context). There were also some fiery debates over the order of the middle two rules.
And they’re all equally correct. Instead of arguing philosophically, I prefer to encourage the team to adopt practices that help avoid “breaking” these “rules”: TDD and BDD, Diligent Refactoring, Continuous Integration, Sit Together, Collective Responsibility, Pair Programming, Continuous Learning.
New behaviors introduced by changing requirements leave their mark on the design. This is the very thing that used to drive good developers away from software development: We design our system, and then an unexpected change request alters our preconceived design.
This deterioration happens on a large scale, as it did in my pre-Agile years, or even on a tiny scale, with Test-Driven Development.
When we use TDD, each tiny new micro-test, and the implementation of that specified behavior, has a commensurately tiny negative impact on design. We can repair the damage through Diligent Refactoring, but we have to be able to first recognize and identify the damage.
How to see the damage done on such a tiny scale? You’ve likely been doing it your whole development career: You notice that something isn’t quite right, or that a few small changes could place the new and old behaviors in a better relationship to each other.
In Martin Fowler’s book, Refactoring, Kent Beck and Fowler chose to call these infractions against good design “code smells.” They list many of the common smells and gave them names, similar to Design Pattern names, so teams can communicate effectively about the smells they dislike. They also provided a table (in the back of the Refactoring book, or here, courtesy Joshua Kerievsky at Industrial Logic: http://www.industriallogic.com/wp-content/uploads/2005/09/smellstorefactorings.pdf) mapping code smells to the refactorings that will help.
In the next Developer Essentials newsletter, we’ll see what this looks like in practice. Including, finally, some code!