Write Code as if the Maintainer Knows Where You Live

In 1991, John Woods posted a phrase in the comp.lang.c++ newsgroup that would be quoted by self-important software bloggers for (at least) 33 years:

Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live.

John Woods

(I tried to find a link and failed miserably, sorry)

Obviously he’s kidding, but it’s still solid advice. At the time he was talking about the open source community, where often you don’t know the person who will maintain your contributions (unless you’re pretty active or it’s a small project), but outside the open source world, it’s much more likely that the people maintaining your code will be your coworkers (or future you!).

I think on the surface, we can pretty much all agree that writing clean, organized, readable code is important and A Good Thing To Do™ (and if you don’t, I doubt I could convince you). The devil’s in the details on this one.

The Basics, AKA “Just Write Clean Code”

Don’t take my implied air-quotes wrong, writing clean code is important, it’s just not nearly enough. But it’s still important, so let’s talk about it before we get to the good stuff. Here are the key tenets:

1. Writing for Clarity

I know this sounds obvious, but aim for code that is straightforward and easy to understand at a glance. That’s easier said than done for sure, but just remember that your code isn’t your opportunity to show how smart you are. Do you really need an intricate ternary statement there, or will a good old if-else work just as well? You’re not paying by the character, so you can afford a few extra to make things more obvious. Don’t go crazy, but keep it simple where you can.

2. Self-Documenting Code

This goes hand-in-hand with the above. Structure your code to make the logic flow intuitive, so that you don’t (often) need to explain the flow with comments. I’m not saying not to write comments to explain your flow, just not to make the code inexplicable without them. Also, name your functions and variables things that explain what/why they are. For example, prefer something like calculateTotalSalary over calcTS. No one knows what that means, especially future you looking at the code in a year when it’s broken and production is down and your boss is typing in all caps at you on Slack because the company is losing scrillions of dollars a minute. Or even worse, you’re trying to debug with your camera on in a Zoom call *shudder*.

3. Function / Method Size

Don’t write super-long functions; break them up keep them focused on one task. This one is easy, but still has to be said.

4. Comments

Even if you write the most self-documenting code in the history of computing (you don’t), you still need comments for a codebase of any substance. Use comments to explain why you made the decisions you made, especially if you did something non-obvious or weird. Whoever is reading your code a year from now isn’t in your head (even future you!), and won’t know why you did what you did. Comments should also not explain obvious things (e.g. /* Check if hasName is true */ over an if statement checking hasName’s value).

5. Code Reviews

Yes, OK, technically this isn’t part of “writing” clean code, but it’s still critical to making sure other people can understand your code. If you do a code review and your reviewer has a bunch of questions about what your code is doing, it probably isn’t very readable. And don’t just do rubber-stamp reviews either — reviewers should actually understand what the code is doing before they hit the approve button.

Now that we’ve covered the basics, let’s move on to the hard part.

Designing Software That Makes Sense

Following the above advice about writing “clean code” is pretty straightforward. The much harder (and often overlooked) part is designing your software in a way that actually makes sense. What do I mean by that? Luckily, Uncle Bob has us covered with the SOLID mnemonic:

Single Responsibility and Separation of Concerns

Simply put, your classes and functions should each exist for a singular purpose. Doing so inherently leads to more intuitive and manageable code.


For example, imagine you have a web application with a controller class responsible for all calls to your /users endpoint, and then independent functions for (for example) GET /users, GET /users/<id>, and POST /users calls.

This design, where each function is dedicated to handling a specific type of request, not only makes the code easier to navigate but also simplifies debugging and testing. Changes to the behavior of one endpoint, such as modifying the user creation process in POST /users, can be made without the risk of inadvertently affecting the logic of the other endpoints.

Furthermore, this separation allows for easier scaling and enhancement of individual functionalities. If you need to introduce additional logic or validation for a specific user operation, you can do so in the relevant function without cluttering or complicating the code handling other types of user requests. It’s also very clear to later maintainers where the functionality for each endpoint lives.

Let’s look at an example in Python that demonstrates this idea. Say we want to create a to-do list application. To ensure single responsibility and separation of concerns, we’ll make two classes: one to represent the whole to-do list, and one to represent an individual item in the list:

# todo_item.py
class TodoItem:
def __init__(self, description):
self.description = description
self.is_completed = False

def mark_completed(self):
self.is_completed = True

def __str__(self):
status = 'Completed' if self.is_completed else 'Pending'
return f"{self.description} [{status}]"
# todo_list.py
from todo_item import TodoItem

class TodoList:
def __init__(self):
self.items = []

def add_item(self, description):
self.items.append(TodoItem(description))

def complete_item(self, index):
if 0 <= index < len(self.items):
self.items[index].mark_completed()

def display_items(self):
for item in self.items:
print(item)
# app.py
from todo_list import TodoList

def main():
todo_list = TodoList()
todo_list.add_item("Buy groceries")
todo_list.add_item("Read a book")

print("To-Do List after adding items:")
todo_list.display_items()

todo_list.complete_item(0)

print("\nTo-Do List after completing an item:")
todo_list.display_items()

if __name__ == "__main__":
main()

In this setup, TodoItem manages the state and behavior of individual to-do items, while TodoList manages a collection of these items. The app.py script orchestrates the creation and manipulation of the to-do list. Each class focuses on a specific responsibility, with TodoItem handling individual item data and TodoList managing the overall list operations.

Open/Closed Principle

The OCP states that “software entities entities […] should be open for extension but closed for modification”. What does that mean? Basically, an object’s behavior should be extensible without needing to modify the class.

For example, right now our to-do list app can only do basic display of the list items in STDOUT. What if we want to do more? We’d like to have the ability to add new display functionality without modifying the existing classes.

To achieve that, we’ll introduce an abstract base class for displaying the to-do list, which can then be extended for different display formats (e.g., simple list, detailed list, etc.) without modifying the existing TodoList class.

Let’s start with an abstract base class to represent a generic display strategy. Actual display strategy classes will inherit this class.

# display_strategy.py
from abc import ABC, abstractmethod

class DisplayStrategy(ABC):

@abstractmethod
def display(self, todo_list):
pass

Next, we can create a child class that implements the simple display strategy we used previously. Later, we’ll go back to our TodoList class to take advantage of one of the strategies. For now let’s just make our new class:

# simple_display_strategy.py
from display_strategy import DisplayStrategy

class SimpleDisplayStrategy(DisplayStrategy):
def display(self, todo_list):
for item in todo_list.items:
print(item)

And let’s make another child class that gives a more detailed display option. These examples are kinda contrived, but I think you can see what I’m going for here.

# detailed_display_strategy.py
from display_strategy import DisplayStrategy

class DetailedDisplayStrategy(DisplayStrategy):
def display(self, todo_list):
for index, item in enumerate(todo_list.items):
print(f"{index + 1}. {item}")

Now we can go back and update the TodoList class to take advantage of the new functionality:

# todo_list.py
from display_strategy import DisplayStrategy

class TodoList:
def __init__(self, display_strategy):
self.items = []
self.display_strategy = display_strategy

# ... other methods ...

def display_items(self):
self.display_strategy.display(self)

With this new structure, later people can add as many new display strategy classes as they want, and as long as it’s a subclass of DisplayStrategy and implements display_items() it’ll work just fine. Extensible!

3. Liskov Substitution

Oh boy. It’s a fancy name, but it’s not that complicated, I promise. All it means is that any subclass should be substitutable for it’s parent class. Or put more directly: you should be able to use any child of a class instead of the parent anywhere, and your code should work. Child classes can have functionality that the parent doesn’t have, but not the other way around. Child classes also shouldn’t modify or break how the parent class works.

4. Interface Segregation

Another fancy name for a pretty simple idea: clients should only have to implement the interfaces and depend on the methods they’re actually going to use. If you have 300 different DisplayStrategies, to use our above example, applications using the TodoList class should only have to care about the one(s) they’re actually going to use. If clients have to implement/worry about/know about the others, your design is probably bad and you should feel bad.

I don’t think we need a code example for this one. Email me if you disagree, and I’ll add one, probably.

5. Dependency Inversion

I’m really feeling a pattern with these names. The Dependency Inversion Principle (DIP) has two key rules: (1) high-level modules should not depend on low-level modules and (2) abstractions should not depend on details, but details should depend on abstractions. I think in practical terms those are basically the same rule, but good to know anyways.

In terms of an example, our TodoList code already obeys this! In case you forgot what it looks like:

# todo_list.py
from display_strategy import DisplayStrategy

class TodoList:
def __init__(self, display_strategy):
self.items = []
self.display_strategy = display_strategy

# ... other methods ...

def display_items(self):
self.display_strategy.display(self)

Notice that TodoList depends on the DisplayStrategy abstract class, and not a specific implementation class like SimpleDisplayStrategy. This ensures that any subclass of DisplayStrategy can be used seamlessly by TodoList without changing its code. This is the crux of the DIP.

The point of the Dependency Inversion Principle is to reduce direct coupling between different or loosely-related parts of your code, which ultimately leads to improved modularity and ease of maintenance. It also makes your system much easier to understand, because you can think concretely about the implementation details of whichever area of the code your working on, while abstracting away the details of other parts.

The Point™

I said a lot of stuff in this post, but hopefully I’ve convinced you that writing maintainable software is harder than it sounds, but also that it’s worth doing. I doubt there’s a senior engineer in the world who hasn’t looked back at code they wrote 6mo+ ago and wondered “what the heck was I trying to do?”. It’s even harder trying to do that for other people’s code, especially if they’ve left the organization or are otherwise unavailable to ask them why they are they way they are and why they hate you.

Be the change, and all that.

If you have thoughts, or you think I’m wrong and just have to tell me, email me!