Jake Worth

Jake Worth

It's Harder to Read Code Than Write It

Published: April 25, 2022 3 min read

In Things You Should Never Do, Part I, Joel Spolsky narrates Netscape’s ruinous decision to rewrite their browser from scratch. This introduced the following concept to me:

“It’s harder to read code than to write it.”

I believe this. Today I’d like to explain why.

As an example, consider the “liking” feature of Today I Learned. This is a button under each Today I Learned post that you can click to announce: “I like this.”

The Code

When we explore TIL’s codebase, we see this method called when a visitor likes a post. I’m going to examine this method line-by-line and raise questions that I might raise while coding, as if I’d never seen it.

# app/models/posts.rb

def increment_likes
  self.max_likes += 1 if self.max_likes == self.likes
  self.likes += 1
  notify_slack_on_likes_threshold if likes_threshold?
  save
end

First line:

def increment_likes

This name suggests that likes is a column on the model. If true, why do we need a method? It’s not obvious.

Next:

  self.max_likes += 1 if self.max_likes == self.likes

Okay, what is max_likes? ‘Max’ as a prefix is pretty vague. Why are we comparing that to likes and only incrementing it when they’re equal? This comparison would benefit from abstraction to a method like new_max_likes?, or abstracting the whole line to maybe_increment_max_likes.

Then:

  self.likes += 1

Incrementing the likes; I get this.

Next:

  notify_slack_on_likes_threshold if likes_threshold?

So we’re notifying Slack about something called _likes_threshold, if likes_threshold? is true. This abstraction feels incomplete. Why can’t notify_slack_on_likes_threshold know if we’re in the right state? Once we refactor it, maybe we could give it name like maybe_notify_slack.

Last line:

  save

Why explicitly save? Could this be combined with the likes incrementing via .update?

That finishes the method. It’s short enough to appease Sandi Metz. But it’s doing a lot, with room to improve.

Context We Don’t Have

There’s a big piece of information missing from this analysis: this method was written to help us send an idempotent notification to Slack.

When we built TIL, we wanted to know which posts were popular, so we added liking. We chose to send messages to Slack when these likes crossed important thresholds (e.g. 100 likes).

max_likes came from this. Before it, if somebody clicked ‘like’ at nine likes, bumping the count to ten, then un-clicked it, and then clicked it again, our code would send two Slack notifications. We needed idempotency, hence max_likes, which tracks the highest amount of likes that has ever been for a post.

That feature was important, because it proved that our writing was having an impact. As we saw likes increment, we saw what resonated. It’s a little thing that helped made TIL a hit.

Someone reading this code would not have this context.

Impact

Practically, what happens when this context is absent?

If you’re rewriting an application from scratch, these questions hardly ever get asked. Given you even have the code, you can’t take the time to explore every method in this detail. You’re speed-running. Whoever wrote the feature is gone. If it is used, everyone takes it for granted.

In the unlikely event you do read this method, you’re going to say: “What’s happening here? I don’t get it. This isn’t MVP” and move on. This kind of omission is the rule in a rewrite. You’re only building the stuff that’s ‘important’, with bias, ignorance, and inattention coloring that definition.

A Path Forward

As gnarly as legacy code is, it works and people have to come to rely on it. That is worth our attention. When we read legacy code thoughtfully, we elevate our profession.

At AWS they almost never retire an API. Once it’s in production, it’s supported forever. Many companies can’t act this way, but the spirit prevents thoughtless mistakes like breaking the likes notification.

I think code reading is the highest programming skill you can have. Scanning a line, putting it in words, precisely jumping to a function definition in multiple codebases, returning without getting lost, and adding to the bigger story you’re telling yourself or those participating— few programmers do this well. I want to encourage us all to work on being better code readers.

“It’s harder to read code than to write it.” What does this statement mean to you?

Thanks to Josh Branchaud for this blog post idea.

What are your thoughts on this? Let me know!


Get better at programming by learning with me! Join my 100+ subscribers receiving weekly ideas, creations, and curated resources from across the world of programming.