Always leave the code better than you found it

Dear new developer,

I’ve spent a lot of my time maintaining working code. I think that is more typical of software developers than working in greenfield development. Yes, there are definitely jobs where you are writing more new code than maintaining, upgrading, bug fixing and improving old code (startups without product market fit being one, consulting being another) but in general code is expensive and folks want to run it for a long time.

Often you’ll jump into code to fix a bug, investigate an issue or answer a question.

When you do so, improve it. This doesn’t mean you rewrite it, or upgrade all the libraries it depends on, or rename all the variables.

You don’t need to transform it.

But you should make it better. Just clean it up a bit. Doing so makes everyone’s lives just a bit better, helps the codebase in a sustainable way, and assists the business by making its supporting infrastructure more flexible.

What are some ways to improve the code when you are in it?

Document

Whether that is a comment that explains something tricky, a larger piece of documentation external to the code which explains how to interact with it, or fixing a typo, trustworthy documentation is key to interacting with code. This is a good way to start improving a codebase because it has minimal impact on the actual code. Therefore it is low risk. But if you’ve ever had a great comment explain a confusing bit of code, you’ll appreciate the time this effort can save.

You can also help documentation by removing old, crufty docs. If you see a comment that doesn’t apply, remove it. If there’s cut and paste documentation which doesn’t apply, get rid of it. That cleans up the code for the next person to come along (who might be you).

Write a test or improve a test

Tests help you write maintainable, extensible code that others can change fearlessly. If you run across code that isn’t tested and you have time and the supporting framework to write one, do so.

Even if it tests simple functionality such as “can I instantiate this object” or “how does this function react when I pass it two null values”, an additional test will help the robustness of the code.

Refactor it

This is one of the most flexible improvements. Refactoring code can range from renaming a variable to be more true to its nature to an overhaul of an entire module. Start small and don’t get wrapped up in perfection. Make the code clearer in intent.

It’s easy with refactoring to get wound around an axle and make too many changes and end up with broken things. Timeboxing is one technique I use to avoid, or at least minimize, my tendencies toward this when refactoring. If all I have is 30 minutes, I’ll make my changes smaller in scope.

A warning about refactoring. Don’t refactor what you don’t understand. Don’t drive by refactor. Discuss your plan with someone more familiar with the code; git blame is your friend. Especially if the code is not well tested, you want to make sure you don’t do more harm than good.

Upgrade a dependency

It’s sometimes a winding path, but upgrading your dependencies regularly is a good way to maintain the code. I remember working in a fork of struts. It was an important application for the company, but we didn’t spend the time upgrading the dependencies, because it was too painful. Eventually, parts of the code became harder to update. The entire application couldn’t benefit from newer technologies and paradigms because of the older dependencies holding it back.

It never feels good to spend time updating a dependency; to me this always feels like running in place. But if you don’t do so, eventually dependencies will end of life and you’ll be forced to update. That’ll be even less pleasant.

How to do it

Based on feedback (this comment, among others), I added this section on Nov 28.

When you are making these changes to improve the code, you’ll help out code reviewers and your future self by making changes that are improving the code separate from changes that add functionality. Whether you do this in separate pull requests, tickets, or commits depends on your team culture. Ask about that. But such separation will make it easier for people who aren’t familiar with the changes to understand them and give feedback on them, whether that is a code review this week or someone reviewing this component two years from now.

Why do it

All of these actions not only help others because they improve the quality of the code, they also provide examples to other developers on how to do so. For example, it is far easier to write the second test in a suite than the first. You can cut and paste a lot of the setup code and tweak only what is different. The first bit of documentation will inspire more.

Code isn’t everything, but it is an important work output. Whenever you touch it, you should strive to leave it in a better place that it was before you did so.

Sincerely,

Dan

The perfect is the enemy of the good (and the done)

Dear new developer,

When you are facing a problem, it can be very tempting to try to solve it perfectly. Handle all the possible edge cases, make it extensible, have it be configurable without code changes.

As with everything in life, there’s an opportunity cost to this perfection. If you’re spending days perfecting a single page of a webapp, for example, you won’t be able to move on to the next feature.

There are times for perfectionism, without a doubt. When data could be lost or security could be compromised, you want to move slowly and carefully.

For the rest of the time, good and done is better than perfect and undone.

Of course, the definition of ‘good’ is the crux of the above statement. Unfortunately, this is a judgement call, depending on context, team and risk. One thing that is non-negotiable is that the code meets the requirements. If it doesn’t work, then nothing else matters.

Some things to think about when you are considering what ‘good’ is:

  • How often will this be used? Once a second? Once a year? Once?
    • The more often code is used, the more it should be polished.
    • Note that usage often changes over time, so it’s worth keeping an eye on this and spend some time cleaning up code if it moves from the once a year category to the once a week category.
  • Is this a relatively static part of the system, or is it in flux?
    • If you’re touching this once and don’t expect to touch it again for a while, taking some extra time makes sense. Niggling bugs may live for years.
    • On the other hand, if this area of the codebase is churning, polish could be wasted because the code may be trashed.
  • What are the risk factors if this fails? Will someone be killed? Will money be lost? Will someone be inconvenienced?
    • Obviously, the higher the risk, the more care you should take.
  • What is the next thing on the list?
    • If you have other high priority issues, you may want to avoid polish.
  • What is the team quality ethic?
    • You should meet and improve that standard. For example, in the context of testing: if no one writes tests, set things up to write tests. If everyone writes unit tests, think about integration tests. If everyone writes integration tests, think about performance regression tests.
    • This is a great topic of conversation at an interview or team meeting so that everyone can be on the same page.
  • How permanent is this code?
    • The more permanent the code, the more care should be taken.
    • Some code lives on servers and is easy to change. Other code is shipped to mobile apps and is harder to change. Yet more is distributed to IoT devices and really hard to modify (when is the last time you updated your router’s firmware? I thought so.). Some is burned to read only memory and can’t be changed short of great expense.

One question that I’d avoid asking is “how will I feel about this code in 12 months”. Because I know the answer–you’ll cringe. At least, in all my discussions of running code, I’ve never met someone who revisited old code and didn’t find at least one thing to improve.

Two examples, rating the above factors on a scale of 1-5 (where 1 indicates you should just get it done, and 5 indicates you should get as close to perfection as possible).

  • Your website portfolio:
    • Usage: 3. Used maybe once a year or two (if you are an employee) or maybe every month (if a contractor), when someone is checking you out.
    • Static or churning: 4-5. relatively static. You probably build out an example app once.
    • Risk factor: 3. You’re looking for a job, and your portfolio is a powerful signal, but not the only one.
    • Next thing on the list: ?
    • Team ethic: N/A
    • Permanence: 1. This is a website, so you can ship new code whenever you have time.
  • Billing software on your large public site:
    • Usage: 5. Used many times a day.
    • Static or churning?: 4-5. Should be relatively static as payment processing doesn’t change that often.
    • Risk factor: 4-5. Billing mistakes are expensive.
    • Next thing on the list: ? Depends on the company.
    • Team ethic: ?
    • Permanence: 2. You have the code on the server, but you have a lot of history and state to maintain.

When you are working on code and struggling to get to ‘done’, I encourage you to consider the above questions to guide your decision. Finally, don’t forget that (most) code can be revisited. If something is good and done now, ship it. You can typically improve it next week or next year if the code needs it.

Sincerely,

Dan