· 6 min read

The Aesthetics of Readable Code

This article was auto-translated from Chinese. Some nuances may be lost in translation.

Is Shorter Code Better?

Although reducing code size can improve readability, shortening the time it takes to comprehend the code is what really matters.

Surface-Level Improvements

  1. Clear naming for methods and variables
    • Methods don’t need words like do
    • Avoid ambiguous terms when choosing vocabulary, e.g., pop vs. popItem
    • Pack more information into method names
function getPage() {}
// 對方可能不知道 getPage 的實作方式?爬蟲? ajax?
function fetchPage() {}
// 可能比較清楚是用 ajax 的方式並且回傳 json。
  1. Find more specific and precise words
    • send => deliver, dispatch, announce, route
    • find => search, extract, locate, recover
    • start => launch, create, begin, open
    • make => create, setup, build, generate, add, new, compose

Clarity and precision are more important than being cute

  1. Even for tmp variables, you can provide a bit more context.

    • tmpNumber
    • tmpFile
    • tmpUsrData
  2. If loop iterators like i and j carry meaning, give them appropriate names, e.g., row, col, index.

  3. Choose concrete method names.

  4. If a variable represents a measurement with units, include the unit in the name.

    • startSec
    • delayMs
  5. Naming variables with important attributes

    • plainData
    • entryptedData
  6. When a variable’s scope is large, choosing a longer (or more informative) name is better. Conversely, if its scope spans only a few lines and anyone can instantly tell what it does, using a short alias is totally fine.

Names That Can’t Be Misunderstood

  1. Does filter filter things out, or keep them?
  2. Using min and max prefixes
  3. Booleans
  4. computeData => implies running a computationally expensive function

Consistent Formatting

  1. Maintain consistent formatting
  2. Make similar code look similar
  3. Group related code into blocks/paragraphs
  4. The aesthetics of comments

Why is formatting so important? First, it makes it easier (and more inviting) for others—or future you—to read the code. Second, you spend much less time trying to figure out what your code is doing. Win-win!

Code Quality Tools

  • eslint

  • stylelint

    ​

Eliminating Clutter with Methods?

Whenever you feel a task is getting messy or chaotic, it’s time to wrap it in a method.

Deceiving consumers with fancy packaging is human nature (just kidding). Whether you can fool others isn’t the point; what matters is whether you can fool yourself. If you don’t even respect your own code, no one else will.

For example:

assert(checkTime("12:00")) === "12:00"
assert(checkName("kalan", 20)) === { name: "kalan", age: 20 }
assert(checkPaid(20000, true)) === 20000

Looking closely at the code above, it’s not hard to see that they are all doing similar things, with several repeated strings. Moreover, it’s overly verbose—it takes a moment to decipher what these lines are actually doing.

This is when refactoring is needed; we can wrap it in a method.


function checkValue(type, value) {
  if (type === "time") {
		assert(checkTime(value));
  }

  if (type === "name") {
    assert(checkName(value) === value;
	}

	if (type === "paid") {
		assert(checkPaid(value)) === value;
	}
}

// checkValue(type,    value);
//  					[string] [depend]
checkValue("name", kalan);
checkValue("time", 12:00);
checkValue("paid", 20000);

This way, the code becomes much cleaner, and readability improves significantly! Once again, shorter code isn’t inherently good code; code that is easy to understand is good code. Beyond this, there are other benefits:

  • Clearly highlights what is being tested.
  • Makes it much easier to add new tests!

Break Down by Order and Paragraphs

Long, unbroken walls of code are something neither others nor you will want to look at. When declaring variables or writing statements, break them up based on their behavior or role. It’s the same reason we use paragraphs in writing.

function getUserInfo(userName, age) {}

getUserInfo("kalan", 20)

// getUserInfo(userName, age)
// 					  [string]  [number]
getUserInfo("kalan", 20)

If there are repeated function calls, aligning parameters can make them much easier to scan.


command = {
	{ "timeout"      ,   null,     cmd_spec_timeout},
	{ "timestamping" ,   bull,     cmd_adj_boolean},
f
}

When writing comments, don’t overthink it. Just write down your train of thought at the time and what the function is supposed to do. Sometimes, after a while, you’ll forget what that function was even for.

—

2. Comments

Comments exist to help others understand the developer’s intent—and to remind your future self of what you were thinking at the time.

  • How to write good comments, and what doesn’t need comments.
  • What not to comment on
  • Put yourself in the reader’s shoes

Don’t let comments substitute for what the code itself should convey

Avoid the resistance to writing comments. This usually takes some experience to appreciate, but writing comments early before the project architecture becomes complex is definitely a good habit. Otherwise, you’ll end up writing that guilty

// TODO: refactor

and never touch it again.

Summary

  • Reasons for choosing a specific implementation
  • Known flaws or shortcomings in the code
  • Parts that might confuse a reader

Keep Comments Concise

  • Describe your code precisely, and avoid using ambiguous pronouns for parameters.
  • If parameter behavior is complex, provide concrete examples to make it immediately clear.

3. Control Flow

The most common case is if/else branches. The book suggests a guideline: put positive conditions first, and handle the simpler cases first. If your function/method returns a value, return as early as possible!

  • Make good use of De Morgan’s laws: Anyone with a STEM background probably remembers this! It helps simplify complex logical expressions.

Battling Complex Logic

The book mentions an interesting approach that I’d like to note down. When implementing a range, we might have an overlapWith method to check whether two ranges overlap. Rather than directly checking if the two ranges overlap, it is often simpler to check if they do not overlap. This is because there are only two non-overlapping conditions: either other’s end comes before this range’s start, or other’s start comes after this range’s end.

Break Down Giant Expressions with Variables

$(".thumb_up").removeClass("highlighted")
$(".thumb_up").removeClass("highlighted")
$(".thumb_up").removeClass("highlighted")

// refactor

const $thumbUp = $(".thumb_up")
const highLight = "highlighted"

$(".thumb_up").removeClass("highlighted")
$(".thumb_up").removeClass("highlighted")
$(".thumb_up").removeClass("highlighted")

//

4. Variables

The longer a variable persists, the harder it is to debug

  • Eliminate unnecessary variable declarations.

What qualifies as an unnecessary variable?

  1. It doesn’t make the code’s meaning clearer or more concise.
  2. The underlying logic is already simple enough that a separate variable adds no value.
  3. It is only used once.
  • Prefer write-once variables

In functional programming, we prefer functions to be pure and data to be immutable. The same applies to variables—try to make them const or immutable. This not only makes the function easier to understand at a glance, but also makes it much easier to pinpoint bugs.

Turning Thoughts into Code

Describe the intended behavior in plain language first, then translate that behavior into code. This helps developers write more natural and intuitive code.

Avoid Writing Unnecessary Code

  • Understand the actual requirements
  • Re-examine and question requirements
  • Regularly read API documentation to stay familiar with standard libraries

Related Posts

Explore Other Topics