Lessons from Software Engineering at Google: Part 8 - Software Maintenance

Lessons from Software Engineering at Google: Part 8 - Software Maintenance

This is the eighth article in a series where we cover the book Software Engineering at Google by Titus Winters, Tom Manshreck, and Hyrum Wright. 📕 We will go over various aspects of software engineering as a process, including the importance of communication, iteration and continuous learning, well-thought-out documentation, robust testing, and many more.

Today we cover software maintenance. Any successful system will face, sooner than later, some form of a maintenance burden. Unattended, it might turn into technical debt, which, as any other type of debt, is a double-edged sword. It can be an effective tool as long as it is treated with carefulness. Let's dive in!

Assets and liabilities

In the financial realm, the most prevalent categorization of financial resources are assets and liabilities. Assets are things that you own and that have economic value. Liabilities can be contrasted with assets, they refer to things that you owe and are a drag on economic value.

This distinction is pretty clear. Financial resources are either assets or liabilities. It's also an interesting lens through which we can look at code. However, code escapes this categorization. It's as if it had aspects of both assets and liabilities - almost always directly contributing to creating the former, but eventually highly likely to turn into the latter. 🤷‍♂️

As the book says, code itself doesn't bring value: it is the functionality that it provides that brings value. That functionality is an asset if it meets a user need: the code that implements this functionality is simply a means to that end. We use it to create assets.

Once our code no longer provides the functionality users need, it causes significant maintenance problems or we're migrating to a newer solution, it instead starts creating liabilities. We have to maintain it, paying the costs overtakes reaping the rewards, as it no longer provides the value it was there to create for us. 💸

Scalably maintaining complex software systems over time is more than just building and running software: we must also be able to recognize and remove systems that are obsolete or otherwise unused.

System migration

Let's consider a scenario where you observe the first signs of deterioration. Your internal library all of a sudden has many more responsibilities than initially designed for. A key service behind your application is no longer supported. You have a feature on your roadmap that you know will be impossible to implement given the current architecture of your system. 🏗️

One potential answer to these problems might be to migrate the part of the system that causes issues. The book mentions however that migrating to entirely new systems is extremely expensive and the costs are frequently underestimated. It is largely the opposite of the iterative approach to software engineering.

Instead, they suggest a more incremental approach to system migration, relying heavily on deprecation. Incremental deprecation efforts accomplished by in-place refactoring can keep existing systems running while making it easier to deliver value to users. 💎

This won't be easy, as a complete deprecation process involves managing social and technical challenges through policy and tooling. You've got to make sure people are moving away from what they are used to, and such change is never easy. However, deprecating in an organized and well-managed fashion is often overlooked as a source of benefit to an organization, but is essential for its long-term sustainability.

Cost-benefit analysis

Going back to our assets and liabilities analogy, software systems have continuing maintenance costs that should be weighed against the cost of removing them. We should be contrasting the value of the assets that our code produces and the liabilities that it creates for us as often as possible. ⚖️

Some of the best modifications to a codebase are deletions. Getting rid of dead or obsolete code is one of the best ways to improve the overall health of the codebase. You probably know it deep down - it just feels great to remove code. 😌

But as with deprecation, removing things is often more difficult than building them to begin with. Your job here is to ensure the code you're about to remove is not used. That's hard. Finding out that a piece of code isn't used is always more expensive than finding that it is used, as you have to search the entire problem space. On top of that, existing users are often using the system beyond its original design. Discovering all of these implicit dependencies makes your job even trickier, so you've got to pay extra attention before hitting the delete button.

Conclusion

That's it for today. Maintaining complex software systems at scale requires effort but it is critical for keeping them running. Here's a short summary of things we went through:

  • code itself doesn't bring value: it is the functionality that it provides that brings value

  • migrating to entirely new systems is extremely expensive

  • incremental deprecation with in-place refactoring can keep existing systems running while making it easier to deliver value to users

  • some of the best modifications to a codebase are deletions, it is one of the best ways to improve the overall health of the codebase

  • removing things is often more difficult than building them

Next, we will cover dependency management - the costs, and benefits associated with introducing dependencies, how to manage them the right way, and avoid the most common pitfalls. See you! 👋

If you liked the article or you have a question, feel free to reach out to me on Twitter‚ or add a comment below!

Further reading and references