When a team practices the discipline of test-driven development, it gives them a new power. When all of the code’s intentions are preserved by the tests, it allows anyone to change any of the code at any time without breaking it. As far as I know, there is no other way to do this. What this means to a developer, is that it is possible to revise existing code whenever it needs to be revised.
The practice of revising existing code without changing its intended behavior is called “Refactoring.” Over the last 15 years or so, I have witnessed the power of refactoring. One of the most obvious things that refactoring does is that it helps us remove duplicated code from the system. This not only makes future change easier, it reduces the lines of code that must be maintained. By way of refactoring, TDD makes the code itself more agile. Refactoring has become such a major tool in my practice, that I now wonder if I have written more lines of code than I have deleted in the last five years. When I come to a system that is completely covered by tests, I am able to remove duplicated code. This allows me to remove duplicated tests as well. If a system hasn’t been refactored well, I may end up removing more lines of code because of duplication, than I add when I make a new features.
Refactoring improves the internal design by allowing us to do simple things like renaming variables and methods. I do realize that some of this can be done using modern refactoring tools without TDD, but there are many cases where dynamic types, strings and reflection cannot be determined by a refactoring tool. You can break a system by renaming when you don’t have tests.
When I first switched to TDD, this power was a game changer. For the first part of my career, I had to be very, very careful not to change things. I looked for ways to not touch things in order to improve my ability to finish a new feature faster. When a design is good, this can still be a good thing to do, but the fear of change usually leads to system deterioration. Doesn’t it make sense that we learn more as we spend more time in a problem domain? Don’t you think that if we know more about a problem domain that it would help us design code that matches that domain better? Then it only makes sense that we will have better ideas for the code’s design after some of the code has already been developed. The more experience we gain, the more we see how the design could be improved. TDD gives you the power to make those improvements.
As I mentioned in the previous article in this series, TDD also helps us design better interfaces because it usually forces us to build something that we can imagine. This helps us to make small, single-responsibility classes. That’s the “S” in the SOLID principles. In fact, TDD tends to naturally help us to follow most of those principles. We tend to make small classes, so they tend to be closely related to a single feature and that makes them more closed to change. That’s the Open Closed Principle. We also tend to inject the objects because they are much easier to test that way. That causes us to reverse the dependencies naturally. That’s the Dependency Inversion Principle. When we near a boundary, it becomes much harder to test, so we tend to use interfaces that represent the difficult boundaries to test. That’s the Interface Segregation Principle. Interesting isn’t it?
Have you ever considered how you might make a spider web? I’m going to guess that it wouldn’t be trivial, especially if I gave you constraints that actual spiders have, such as where certain objects are placed, how much wind there is and so on. Now what if I was able to reduce your brain to the size of a spider’s and then ask you to make those same webs?
The reason I ask such a strange question, is that it seems possible that some things we do can be done with less mental energy if we use simple, repeatable disciplines. TDD appears to be a discipline like that. When we practice it, it helps us to naturally produce beautiful code in a variety of unusual circumstances, much like a spider is able to make a beautiful and functional web in many different places and conditions.