Designing software can be done in different ways. The time and effort spent in designing have an important effect on the result. An interesting question is when should you you allow the software to evolve using Test Driven Development, TDD, and when should you not allow it to grow using TDD?
This blog post is the result of a session at an open space with the Software Development Gang in Ghent, Belgium, June 2015.
Before it is possible to discuss when to use and when not to use evolutionary design we need a definition.
Which domain are we talking about? This blog is about software development. Therefore, we are referring to software development and nothing else. That is, the process of writing a computer program that solves a problem its users have.
What do we mean with evolutionary design, then?
How do you know the difference between big design upfront and evolutionary design?
The line may seem, and is, fuzzy. There are no clear rules that can be applied that will tell you what type of design you are doing. There is no recipe that you can lean against.
Thomas Sundbergs first reaction was that the question "When do you prefer to use and when do I prefer not to use evolutionary design?" is clearly wrong. Of course you would want to use evolutionary design. All the time. No exceptions.
It turns out, however, that it is not that easy. Things are seldom easy in the real world. There are definitely many cases when a test-driven approach to grow the design evolutionary doesn't apply. Many more cases than he could imagine.
These are examples of situations where we most likely would recommend using an evolutionary design.
When you are in learning mode and lack experience of the technology you are trying out. It is a throw away experiment. And you know that you will throw it away. Or reformat it to become a blog post or similar. Then you will probably not have a need to use TDD to drive the design.
When a team has the required experience of a domain and really knows that it is impossible to do mistakes.
We would still cover for regressions at some level, possibly end-to-end tests. Maybe not all of them as the domain and solution is very well known.
We would not use low level TDD when a framework can help with verifying wiring of components.
We would, however, write end-to-end or acceptance tests. End-to-end would use the correct database etc.
Acceptance tests may or may not fake parts of the backend. The system can be accepted with a fake database, at least on a functional level.
An inexperienced team can't use tests to drive its design. Test-driving development is a skill that not every team possesses. If the team doesn't know how to do it, it might be a better idea not to impose this habit onto them.
TDD doesn't necessary lead to a good design. It can lead to good design, or at least prevent bad design. But learning good design is a skill in itself. Testing is another skill. It is possible to have both skills, but one of them will not automatically lead you to the other. In order to use TDD effectively you need to know the concepts of software design and constantly keep them in your mind.
We think it might be worth to point out that time alone does not necessary make you an experienced developer. It is possible to spend many years writing software and not gain a lot of knowledge or experience. Time and practice may make you experienced. Time alone doesn't necessary lead to anything useful.
There is an ongoing debate about test-induced damage to the design. We can't get rid of the feeling that this is an example of a design that could be improved and therefore allow testing.
An example is Rails and Active records that are supposed to be hard to test without breaking the encapsulation.
These are examples of when we probably would use evolutionary design.
When you are in learning mode you could consider to use a test as a starting point for running small portions of the system. It would be very much the same thing as implementing a main method from where you can perform small experiments.
If you instead execute parts from a test, then you are able to keep all experiments. After a while, when you now enough, you can consolidate the knowledge and probably throw away the experiments.
These are example where we would recommend to use an evolutionary design.
It is hard to come up with good examples. That is mainly because we seldom trust our self not to do silly mistakes. We make silly mistakes far too often to be sure when we wouldn't make any.
Adrian Bolbocoa always uses an evolutionary design is when he need to write queries to aggregate data from multiple tables; he always write tests there because he does a lot of mistakes. The queries can be sql, in Hibernate or any other pseudo-language.
Thomas Sundberg always uses an evolutionary design is when he is working with regular expressions. His view is that if you have a problem and solves it using a regular expression, then you have two problems. Allowing the solution to grow using tests solves one of them.
You know the
This is the preferred mode for most development. This is why some developers call test-driven development, TDD, for test-driven design.
Evolutionary design is a great tool in many situations. But even a great tool should not be used for everything. Always remember, if the only tool you have is a hammer, all problems will look like nails. Many problems can be converted to nails. Some problems are, however, never a nail and will therefore require a different set of tooling.
It takes experience to be able to allow a design to evolve and avoid taking premature design decisions. Just saying "Listen to the tests" is not always enough for creating a good design. This is especially true if you don't know the difference between good and bad design.
A long experience as a developer doesn't always help. Many experienced developers still takes important design decision way too early in our opinion.
Evolutionary design is a learnable skill. A skill that requires training.
We think that
could be viewed as synonyms. They mean pretty much the same thing.