Skip to content

Chapter 18: Code Should be Obvious

Obvious code can be read quickly, without much thought, and the reader’s first guesses about it are correct. Obviousness is judged by readers, not writers, so code reviews are the best test. Make code obvious by reducing the information a reader needs, reusing what they already know, and presenting the rest through names and comments.

  • Things that make code more obvious:
    • Good names (ch14) and consistency (ch17), the two most important.
    • Judicious use of white space: separate parameters in documentation; put blank lines between the major blocks of a method, ideally with a comment as the first line after each blank line; and use spaces inside statements.
    • Comments: when nonobvious code can’t be avoided, put yourself in the reader’s position, find what will confuse them, and supply that information.
  • Things that make code less obvious:
    • Event-driven programming: handlers are called indirectly, chosen at runtime, so control flow is hard to follow. Compensate with an interface comment saying when each handler is invoked.
    • Generic containers (Pair in Java, std::pair in C++): fields such as getKey() and getValue() hide their meaning. Define a specific class or struct with meaningful names and its own documentation.
    • Different types for declaration and allocation: List<Message> declared but ArrayList<Message> allocated. The actual type affects performance and thread safety, so match them.
    • Code that violates reader expectations: document the surprise where the reader will be surprised.
  • Three ways to give readers the information they need (best first):
    1. Reduce the information needed, through abstraction and eliminating special cases.
    2. Reuse information readers already have, by following conventions and conforming to expectations.
    3. Present the information in the code, through good names and strategic comments.
  • Obvious code: “someone can read the code quickly, without much thought, and their first guesses about the behavior or meaning of the code will be correct.”
  • Red Flag: Nonobvious Code: the meaning and behavior can’t be understood with a quick reading, usually because important information isn’t immediately clear.
  • Design for ease of reading, not ease of writing: a general rule shown by generic containers, which save the writer a few minutes and confuse every later reader.
  • Obscurity: one of the two causes of complexity (ch02); obviousness is its cure.
  • “If someone reading your code says it’s not obvious, then it’s not obvious, no matter how clear it may seem to you.”
  • Think of nonobviousness as missing information. Ask what the reader doesn’t know: that a constructor starts threads, or that result.getKey() is the current term number.
  • Obvious code needs fewer comments than nonobvious code.
  • Squeezed-out whitespace in parameter documentation, which hides where one parameter ends and the next begins.
  • for(int pass=1;pass>=0&&!empty;pass--) { instead of for (int pass = 1; pass >= 0 && !empty; pass--) {.
  • return new Pair<Integer, Boolean>(currentTerm, false); forcing callers to use getKey() and getValue().
  • Undocumented event handlers.

Separated parameter documentation:

/**
* @param numThreads
* The number of threads that this manager should spin up in
* order to manage ongoing connections. The MessageManager spins
* up at least one thread for every open connection, so this
* should be at least equal to the number of connections you
* expect to be open at once. This should be a multiple of that
* number if you expect to send a lot of messages in a short
* amount of time.
* @param handler
* Used as a callback in order to handle incoming messages on
* this MessageManager's open connections. See
* {@code MessageHandler} and {@code handleMessage} for details.
*/

A handler comment that says when it runs:

/**
* This method is invoked in the dispatch thread by a transport if a
* transport-level error prevents an RPC from completing.
*/
void
Transport::RpcNotifier::failed() {
...
}

The book’s Buffer::allocAux example shows the blank-line technique: four blocks, each starting with a comment. They round the size up to a multiple of 8 bytes for alignment; use memory at firstAvailable, working down from the top because that memory is guaranteed to be aligned; otherwise use extra space at the end of the last chunk; otherwise create a new allocation.

A main that doesn’t exit (a Raft client).

public static void main(String[] args) {
...
new RaftClient(myAddress, serverAddresses);
}
  1. Reader expectation. Most applications exit when main returns, so a reader assumes this one does.
  2. Reality. The RaftClient constructor starts additional threads that keep running after the main thread finishes.
  3. Fix. Document the behavior in the interface comment of the RaftClient constructor. Because it is so nonobvious, also add a short comment at the end of main saying the application continues executing in other threads.
  4. Principle. “Code is most obvious if it conforms to the conventions that readers will be expecting.” When it can’t, document the difference where the reader will look.

The Pair return value. Replace new Pair<Integer, Boolean>(currentTerm, false) with a small class whose fields have meaningful names (for example, the current term and a flag named for what it means), documented in its declaration. The writer spends a few extra minutes, and every caller’s code becomes obvious.

  1. Use code reviews to find nonobvious code, and don’t argue with the reviewer.
  2. Use white space to show structure in comments, methods, and statements.
  3. Replace generic containers with specific, named types.
  4. Match declared types to allocated types.
  5. When code must surprise the reader, say so in a comment at the point of surprise.
  6. Reduce needed information first, reuse known information second, and present information explicitly third.
  • Ch 2: obscurity and the goal of an obvious system.
  • Ch 10: eliminating special cases reduces what readers need to know.
  • Ch 13: comments that supply missing information.
  • Ch 14: names.
  • Ch 17: consistency and conventions.