I want to accuse someone for writing bad code. This person has written lots of crap for about 7-8 years. Little time went into refactoring and even less into automated tests. When I look at his code my first thought is “what the hell was this guy thinking?”. In many cases his approach to software design has proven to be harmful for me. For example, when forced to fix a bug that was introduced by my bugfix because I did not understand the code well enough.
This person is, of course, no one else than my younger self. With a dangerous mixture of not better knowing (aka ignorance) and overconfidence in “my mad coding skillzTM” (aka arrogance) I deferred from “doing it right”.
Unfortunately, there is no method to travel to the past and tell my younger self to change his approach to software design1.
In the mean time I’ve learned that Refactoring and TDD are very useful to write better code. I thought it might be funny to think about what my younger self might say to defend himself if I questioned his motives for not doing so well.
(Hypothetical) defense for crappy code
“My code is good enough!”
Of course I know that the code is not good enough. In fact it is crap. Simply due to its lack of an easy way to verify that it (still) works. This argument feels very arrogant for me. He insists that his code is good without any proof. He also implies that anyone else but him is writing bad code.
“TDD is a nice practice when working on a simple project. But it doesn’t work for real world problems, when I have a bunch of dependencies.”
I think this poor guy speaks about things he simply does not understand. It feels like an argument from ignorance. What he is basically saying is
“I do not know any way to use TDD with a bunch of dependencies, therefore it does not work”
In fact that there are good approaches to dealing with dependencies in tests.
- In some cases it might suffice to move some code into a separate function to test it separately without dealing with the other stuff that happens in the same class.
- Sometimes you would use Dependency Injection and Mocking to test a code without caring about its dependencies
- Sometimes you would build a testable Facade around some Third Party API that was not designed to be test-friendly (for example, when its classes and operations are declared as final).
- Sometimes you would isolate the framework/technology independent code from the one that does depend2. This is often called the Humble Object Pattern and there are many interesting variants doing this.
“I have a bunch of Legacy Code that would need too much effort to make it testable”
There is some truth in this statement. Of course there are cases when the effort making legacy code testable does not justify the potential benefits. It might be even a complete waste of time when it just works and you do not need
- to introduce any change (bug fixes, new features)
- to understand how it works
But let us ignore this and move towards the effort for writing tests for legacy code. He got it right that making legacy code testable might be hard. But he got it wrong by insisting that it is too hard that it’s not worth. Again this poor guy is a victim of his own ignorance.
- While writing tests for Legacy Code can be time consuming, it is possible and not very difficult to start. There are approaches like the Golden Master that uses the actual program output3 as a means to verify its behavior. A couple of organizations also succeeded by starting with Acceptance Tests using a specification language such as Gherkin and UI Test Frameworks such as Selenium4.
- Any initial tests you are able to write are a good base for first design improvements towards easier tests which enable you to write even more tests and so on. When you continue the cycle, at some point the code stops being legacy.
- Nowadays, modern IDEs make Refactoring really easy. There is little to no risk to use the refactoring feature in your IDE to improve your design towards better testability.
Don’t try to defend your crappy code. It’s a waste of time! I think that everyone has written code at some time that leaves room for improvements. Try to improve your code with every change you make. Don’t become a victim of your own legacy.
- could anyone please invent the flux capacitor? I’d like to kick this guy in his nuts ;-)
- this is usually the one that is hard to test
- such as a trace log or a database result
- see also Gojko Adzic, Specification by Example, 2011