The 4 rules of simple design
Join the DZone community and get the full member experience.
Join For FreeA colleague of mine told me a bit ago that Red-green instead of Red-green-refactor was becoming an issue: many commits where made just when the green bar was reached. A reason for this behavior was the fear of overdesigning the system and code for tomorrow instead of today: XP stresses the concept of just solving today's problem instead of anticipating future issues incorrectly and design for the wrong change.
At the same time, code which design is not improved after the green bar is prone to not be clean and sustainable, unless you are very keen in the process of design *during* the red phase (with a clean definition of Mocks and Stubs).
These four famous rules are what will force you to write clean code during TDD instead of stopping at a green bar.
The list
You're done with the refactor 3rd phase when the code (which by the way, is the design):
- Passes all the tests.
- Express every idea we need to express.
- Contains no duplication.
- Minimized the number of classes, methods and other moving parts.
Let's dumb it down what we are developing (a single class for example) and anayze the rules and how they apply to our typical process.
I think there is an order of importance here, where the tradeoff for improving i over i +1 is to be preferred. The order of points 2 and 3 vary according to the sources, but if I had to choose between them I would prefer expressiveness over absence of duplication.
1. Passes all the tests is the most important rule. If you go back to a red bar, your refactoring is not valid and you should return to a green state to start again. Refactoring in red is dangerous and theoretically (we're all human) you shouldn't stay red for more than a few minutes.
This rule also means that if you write code not for satisfying a test (unit or end-to-end or functional or any other kind, it does not matter), you're not even satisfying the first of 4 requisites.
This should be your best friend:
2. Express every idea we need to express. Given that your code passes all the tests, you can explicit concepts to "show" your design. For example concrete classes or delegation of methods that do not change the implementation are acceptable for communication, even if technically they constitute a duplication and violate 3 (which is less important in this case imho).
For example, these two classes:
class AsideBox extends DivTag {} class ArticleText extends DivTag {}
are important for me on the semantic level, even if they do not modify any detail of DivTag. They can also be used for type hints in dynamic languages or finer compile-time checks in statically typed ones.
3. Given that the code passes the tests and expresses your design reliably, you can extract methods and classes all the way down to eliminate duplication. This rule is widely diffused and talked about, I think for its simplicity of application; it's simple to see duplication in the majority of cases: what is not so simple is instead expressing all concepts that came up during development (2).
There is a trade-off between extraction of duplicated code and its clarity. I remember extracting an abstract class similar to this a while ago:
abstract class ChangeOperation { protected ChangeRegistry registry; public function ChangeOperation(ChangeRegistry registry) { this.registry = registry; } }
to eliminate the duplicated constructor. This operation is on the boundary of my list of refactorings: extracting any more code just breaks down objects into abstract concepts instead of a readable object model. If I weren't able to quickly meaningfully name this abstract class, I would just avoided extracting it.
4. Simple design is taking out whatever you can from your code without violating 1, 2, and 3. The bar should remain green, you should not suffer from deleting useful concepts that make the code more readable and understandable, and you shouldn't introduce duplication in the process (for example inlining again a class or method).
This rule fosters evolutionary design instead of design for tomorrow. You can choose to write code foreseeing change, and introduce complexity to handle future changes (Strtegy/Bridge). But since it's impossible to predict axis of change consistently, you end up introducing complexity for daeling with change that doesn't happen, while refactoring is needed anyway for unpredicted changes.
More references
Design is an ability that is picked up in years: but I hope to have shed a little light over these principles, which read by themselves may seem just an ideal composed of buzzwords. As always, these are the giants whose shoulder we stand on.
Opinions expressed by DZone contributors are their own.
Comments