Some uplifting criticisms and some encouraging words from “Working Effectively with Legacy Code” of Michael Feathers
“Code without tests is bad code.
It doesn’t matter how well written it is; it doesn’t matter how pretty or object-oriented or well-encapsulated it is.
With tests, we can change the behavior of our code quickly and verifiably.
Without them, we really don’t know if our code is getting better or worse.”
— Michael Feathers
When I work with teams, I often start by asking them to take part in an experiment. For an iteration, we try to make no change to the code without having tests that cover the change. If anyone thinks that they can’t write a test, they have to call a quick meeting in which they ask the group whether it is possible to write the test.
The beginnings of those iterations are terrible. People feel that they aren’t getting all the work done that they need to. But slowly, they start to discover that they are revisiting better code. Their changes are getting easier, and they know in their gut that this is what it takes to move forward in a better way.
It takes time for a team to get over that hump, but if there is one thing that I could instantaneously do for every team in the world, it would be to give them that shared experience, that experience that you can see in their faces: “Boy, we aren’t going back to that again.”
The biggest obstacle to improvement in large code bases is the existing code. “Duh.” you might say. But I’m not talking about how hard it is to work in difficult code; I’m talking about what that code leads you to believe.
If you spend most of your day wading through ugly code, it’s very easy to believe that it will alway be ugly and that any little thing that you do to make it better is simply not worth it. You might think, “What does it matter whether I make this little peice nicer if 90 percent of the time I’ll still be working with murky slime? Sure, I can make this piece better, but what will that do for me this afternoon? Tomorrow?”
Well, if you look at it that way, I’d have to agree with you. No much.
But if you consistently do these little improvements, your system will start to look significantly different over the course of a couple of months. At some point, you’ll come to work in the morning expecting to sink your hands into some slime and discover, “Huh, this code looks pretty good. It looks like someone was in here refactoring recently.” At that point, when you feel the difference between good code and bad code in your gut, you are a changed person…
The human mind has some interesting qualities. If we have to perform a short task (5-10 seconds long) and we can only take a step once every minute, we usually do it and then pause. If we have to do some work to figure out what to do at the next step, we start to plan. After we plan, our minds wander until we can do the next step.
If we compress the time between steps down from a minute to a few seconds, the quality of the mental work becomes different. We can use feedback to try out approaches quickly. Our work becomes more like driving than like waiting at a bus stop. Our concentration is more intense because we aren’t constantly waiting for the next chance to do something. Most important, the amount of time that it takes us to notice and correct mistakes is much smaller
… if we have tests in place, we are able to remove duplication easily.
No, finding bugs in legacy code usually isn’t a problem. In terms of strategy, it can actually be misdirected effort. It is usually better to do something that helps your team start to write correct code consistently. The way to win is to concentrate effort on not putting bugs into code in the first place.
Automated tests are a very important tool, but not for bug finding — not directly, at least. In general, automated tests should specify a goal that we’d like to fulfill or attempt to preserve behavior that is already there. In the natural flow of development, tests that specify become tests that preserve. You will find bugs, but usually not the first time that a test is run. You find bugs in later runs when you change behavior that you didn’t expect to.
The brutal truth is that architecture is too important to be left exclusively to a few people. It’s fine to have an architect, but the key way to keep an architecture intact is to make sure that everyone on the team knows what it is and has a stake in it.
Every person who is touching the code should know the architecture, and everyone else who touches the code should be able to benefit from that that person has learned. When everyone is working off of the same set of ideas, the overall system intelligence of the team is amplified. If you have, say, a team of 20 and only 3 people know the architecture in detail, either those 3 have to do a lot to keep the other 17 people on track of the other 17 people just make mistakes caused by unfamiliarity with the big picture.