4 Strategies to Write Clear Code, Not Clever Code

Software tends to live a lot longer than we ever imagine. Remember that one-off tool you built that’s still in use…3 years later? Over the course of your software development career, you’ll spend more time reading code than writing it. If you do nothing else, write clear code, not clever code. Making your code easy to read will go a long way towards making your software that much better.

We can sometimes be tempted to write clever code that isn’t very clear. Perhaps you’ve been tempted to use a bit-shift operation because you learned that it’s faster when multiplying by two. Maybe you come up with a clever scheme to process some data. But even you, the person who wrote the program, can’t understand what it’s doing when you return to it a month or two later, when a bug is discovered.

Sometimes we invent these clever solutions to show off. “Look how smart I am,” we seem to say, if only subconsciously. Other times, we read about some new technique or technology and every new problem we work on looks like a nail to hammer with our new-found knowledge.

Readable code naturally is more maintainable. If I can’t understand what my code is doing quickly or without reading a page of comments, something is very wrong.

Readable code can mean a few different things. It doesn’t really contribute to the overall design of the software, but it definitely help with maintenance. Here are four things that I think contribute to the readability of code.

Descriptive Names

In this day and age, there’s very little reason to use many abbreviations. Give things a name that’s obvious and describes what it does or what role is serves. Consider this example:

1
public void submit(int id, int rt, String text) { … }

Just looking at this method, is it obvious what this method will do? What is being submitted, is the id for the thing to submit or the user doing the submitting? What does the text relate to? Is rt a rate, route, or something else? Let’s see what happens if the method is rewritten to use more descriptive names

1
public void submitReview(int businessId, int starRating, String reviewText) { … }

Now is it clearer what is happening here? We’re obviously submitting a review for a business with an ID defined by businessId. We’re giving it a star rating and some text about the business. All of that without a single comment! Longer, more descriptive names might take longer to type when you need to reference them, but that’s what your development environment’s autocomplete feature is for!

Whitespace

There’s a reason books don’t print text all the way to the edge of the page, it makes it too hard to read. What do most of the modern websites have? Lots of blank space. It has been shown that good use of whitespace between paragraphs and in the margins increases comprehension by nearly 20%!

When it comes to software, add blank lines to separate the “paragraphs” of your code. By grouping logical chunks together, it is easier to absorb and understand before moving on to the next chunk.

Some even argue that something as simple as putting the opening brace of a code block on a new line aids in comprehension since it adds whitespace and lines up with the closing brace. I’ve used both styles at various points in my career and don’t find that much difference between the two, as long as additional whitespace is added where necessary.

Don’t get hung up on any particular code style for whitespace. Follow your organization’s coding conventions, but sprinkle in whitespace liberally.

Shorter Lines

Back in the days of FORTRAN 77, the compiler would ignore anything after the 72nd character on each line. Terminals used to be only 80 characters wide, and many organizations still respect this in their coding standards. Today, editors can easily display much longer line lengths. But just you can, doesn’t mean you should.

Shorter lines are easier to follow with the eyes. Websites, books, and newspapers all limit the width of a block of text because it’s harder to read beyond a certain limit. Some sources suggest that line lengths between 45 to 75 characters are ideal. Code is no different.

Would you rather read this?

1
int value = (doStuff(50) && theThing != theOtherThing) ? doSomething(30) + ", " + suffix : anotherThing + ", " + foo;

Or this?

1
2
3
4
5
int value; if (doStuff(50) && theThing != theOtherThing) {
value = doSomething(30) + ", " + suffix;
} else {
value = anotherThing + ", " + foo;
}

The first one might seem more compact, but it takes a lot more effort to read and understand. The second example is very clear: if the condition is satisfied, assign one value, otherwise assign a different value.

I am definitely guilty of writing code like the first example. It feels satisfying to be able to express something so compactly. But when it comes time to read it several months later, you’ll be glad you wrote it the second way.

Minimum Nesting

Each time you add a condition or looping block inside another, the line length effectively gets longer. Even if the actual, non-whitespace code is relatively short, the eye still has to traverse all that distance and the code becomes harder to read.

At each level of nesting, consider putting that logic into a separate method. Not only does this make the code easier to read, but it helps to enforce the single responsibility principle. Each method does one thing: process the contents of an “if” block or run a loop. In addition, simply having a method name in place of the nested block provides some documentation around what is happening in the code, without a single comment!

Here’s another (albeit silly) example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void doSomething(String input) {
if (input != null && input.length() > 0) {
for (String color : this.getColors()) {
if (color.equalsIgnoreCase(input)) {
for (Day day : this.getDays()) {
if (color.equals(day.getColor()) {
System.out.println("Matches daily color for " + day.getName());
return;
}
}
}
}
}
}

This is a fairly trivial example, but consider what happens if each of the code blocks was multiple lines long. If this code starts to exceed the height of the page, it becomes much harder, even with the IDE’s help, to follow where one block starts and the next ends.

Compare this to the following code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public void doSomething(String input) {
if (StringUtils.isEmpty(input)) {
return;
}
Optional<Day> day = Optional.absent();
if (isValidColor(input)) {
day = getDayForColor(input);
}
day.ifPresent(day -> System.out.println("Matches daily color for " + day.getName()));
}

private boolean isValidColor(String value) {
for (String color : this.getColors()) {
if (color.equalsIgnoreCase(input)) {
return true;
}
}
}

private Optional<Day> getDayForColor(String color) {
for (Day day : this.getDays()) {
if (color.equals(day.getColor()) {
return Optional.of(day);
}
}
return Optional.empty();
}

While the second code example is longer, it’s much clearer what is going on. The methods names help document the steps that are taken and the reduced nesting make it easier to follow what’s going on., rather than having to spend time working out what each loop and if condition does.

Conclusion

Writing code that is clear, instead of clever doesn’t require that much effort. It just requires some attention to how you name things, keeping your lines short, using enough whitespace to keep things grouped appropriately, and refactoring to avoid deeply nested code blocks. These might seem like overly simplistic things, but sometimes it’s the simple things that make all the difference in the world.

Question: What techniques do you use to make your code clear, not clever?