Are little rewrites better than refactoring?
In the Detroit school of test-driven development (TDD), there’s a big emphasis on the green-red-refactor workflow cycle. This is one of those aspects of TDD that most people know about, even if they’ve never tried it.
Yes, it’s time for another software engineering diagram that is too often blindly accepted and hardly tells you anything!
In summary:
- Write a unit test which examines a tiny slice of expected behaviour. It fails because you have no implementation (red)
- Write a quick, naïve implementation which passes the test (green)
- Refactor relentlessly for simplicity, elegance, conformance to standards, etc.
- Write a second test and continue…
OK, I don’t really have a big problem with this, except:
- What test do you start with?
- How much up front design work do you do?
- How do we know when we’re done?
Detroit-school TDDers have answers for all these things, but in terms of personal style, I always struggled with step 3, especially after a few cycles.
It is often claimed that a good design falls out of the relentless refactoring work in step 3 as you add more and more tests.
In practice, I always found it difficult to get past my initial assumptions of what the design would look like, and an ‘emergent’ improvement on this isn’t often forthcoming. What’s more, a lot of the programming problems we face have boilerplate solutions that usually don’t benefit from an emergent design.
Yesterday, we saw how discovery testing takes a different approach, by moving ‘outside-in’ and building up a tree of small modules with very specific roles, pushing most logic to the edges (‘leaf nodes’), isolating side effects and keeping collaboration between objects simple. We saw how this matches patterns in modern component-based UI development too.
One of the interesting effects of taking this approach is that refactoring can be a big pain. That’s because the unit tests are coupled tightly with their individual units. As we’ve emphasised small units with few responsibilities, refactoring often means re-designing a part of our tree, making the unit tests that come with them worthless.
Instead, London-school TDD and discovery testing encourage lots of little rewrites. Instead of assiduously improving the details of an existing design, we happily throw away chunks of the design and rewrite it when we see an opportunity for improvement.
This might seem shocking, but I’m strongly of the belief that code of all kinds should not be precious, and that treating it as such has very damaging long-term consequences. We should instead be optimising our code for disposability and get comfortable with lots of repetition and rewriting. Not so that we throw everything away every 2 years, but to enable us to replace small chunks on a regular basis without impacting the rest of the code base.
For me this better captures the reality of UI development, where impermanence is a benefit. We’re not building platform software for stability and reliability here. We usually need to keep moving fast, and getting overly precious about the value of what we’ve created is dangerous.
What can you do to improve the disposability of your code?
All the best,
– Jim