Einstein supposedly claimed that “if he had one hour to save the world he would spend fifty-five minutes defining the problem and only five minutes finding the solution.” Legacy code often requires imagination. Einstein used the metaphor of a train travelling at the speed of light to explain how time breaks down, to large auditoriums filled with people of all ages and backgrounds. Getting the right metaphor helped him identify a number of hidden ramifications of his Newtonian physics problem. Ultimately, he weeded out all complications with these metaphors. He could also use the metaphors to explain the theory of relativity to schoolchildren.
Metaphors also work in the software world. In larger software companies, code sometimes grows to monstrous proportions. The architecture twists into a grotesque blob. Typically, no one notices until the team hits big problems. At that point, you need a change of perspective. Metaphors to the rescue!
- “I know it when I see it”. If you write it, it’s not legacy code. You understand it. If anyone else writes it, and it isn’t blindly obvious what the code is doing to a new team joiner. To them, it’s legacy. Just like pornography, it’s pleasant for the creator at the time. It’s frowned upon socially, amongst other developers. When developing, you are a bad judge of whether or not your code is legacy code. Confirm whether other team members accept your architectural choices. Do a code review.
- The too big to fail (TBTF) moniker applies to legacy code, just like it applies to big banks. Legacy codebases tend to be big. If these codebases were small, you wouldn’t consider them legacy. In contrast, you can easily determine what is going on if a component is self contained. You can look at each module completely individually. The negative effects of legacy code are then completely self-contained. The code keeps you honest. Use interfaces or abstract base classes.
- Legacy code contains hidden, toxic relationships. These are like a factory that pollutes a nearby stream, because it can, and because it profits; even though most people see the public effects, no one acts. Class members have excessive scope. Everything is public. As a result, making a change usually requires a lot of manual regression testing. If you don’t test enough, you risk an epic fail. You don’t know what you don’t know. Suddenly, guilt trips and emotional blackmail become the team’s preferred way to talk. Push members into private scope.
- When working on legacy code, you feel like you constantly underperform, because you are still like a ship throwing out anchors. As a developer, you keep going slower, and slower, and slower. You need to do one more regression test in what would seem like an unrelated area of the code... A simple bug-fix takes a long time. Look for pinch points, where you can tease out a subcomponent, where conceptually things are separate, where the data always flows in a standard format. Cut the anchors’ cords. It will be easier to work with next time.
- Most importantly, legacy code’s usually your employer’s cash cow, where the cash signals underlying business problems, sometimes caused by the software itself. It pays not only your bills as a developer, but often the whole company’s. While legacy spaghetti code may be a mess, the business system around it (i.e. your employer) relies on it. It reliably satisfies a user need. Users are willing to pay money for it. Cold hard cash. Once the software’s sold, the rest of the company supports it. All departments, other than sales, are a necessary cost to achieve that goal. Your purpose as a developer: keep customers happy. The business system requires it. Like a publish/subscribe model, that cash is like messages on a message bus. They notify the rest of the system about a particular run-time requirement of the business system. Always understand the customer’s need, as it helps you find the best possible solution to your customer’s problem. Then refactor, so your architecture matches your newfound understanding.