Skip to content

Instantly share code, notes, and snippets.

@thangchung
Last active June 19, 2025 10:35
Show Gist options
  • Save thangchung/c5aee405103869a79e159ed1dd496efe to your computer and use it in GitHub Desktop.
Save thangchung/c5aee405103869a79e159ed1dd496efe to your computer and use it in GitHub Desktop.
Code Readability Rules and Examples

Original article: https://seeinglogic.com/posts/visual-readability-patterns/

This article discusses various visual patterns that contribute to code complexity and make it difficult to read. It examines metrics like Halstead Complexity and Cognitive Complexity, highlighting their strengths and weaknesses in measuring readability. The author concludes that while no single metric is perfect, they provide a framework for understanding and discussing code quality. The main takeaway is that writing readable code involves using simple, familiar patterns, creating smaller functions, and managing variables effectively.

Here are the main rules for making code easy to read, with examples:

1. Line/Operator/Operand Count Make smaller functions with fewer variables and operators.

  • Hard to Read (More operators and operands):

    function getOddness(n) {
      const evenOdd = ['Odd', 'Even'];
      const index = Number(n % 2 === 0);
      return evenOdd[index];
    }
  • Easy to Read (Fewer operators and operands):

    function getOddness(n) {
      if (n % 2) {
        return 'Odd';
      }
      return 'Even';
    }

2. Novelty Avoid using novel or language-specific syntactic sugars and stick to common, familiar patterns.

  • Hard to Read (Novel/uncommon operator):

    // Using a less common operator that might not be familiar to all developers
    const result = value ?? "default";
  • Easy to Read (Common and widely understood):

    const result = (value !== null && value !== undefined) ? value : "default";

3. Grouping Break up long function chains into smaller, more manageable parts using intermediate variables.

  • Hard to Read (Long chain):

    function getVisibleNeighborNames(graph) {
      return graph.nodes(`node[name = ${name}]`).connected().nodes().not('.hidden').data('name');
    }
  • Easy to Read (Broken into logical groups):

    function getVisibleNeighborNames(graph) {
      const targetNode = graph.nodes(`node[name = ${name}]`);
      const neighborNodes = targetNode.connected().nodes();
      const visibleNames = neighborNodes.not('.hidden').data('name');
      return visibleNames;
    }

4. Conditional Simplicity Keep conditional tests as short as possible and avoid mixing logical operators.

  • Hard to Read (Mixed logical operators):

    if (debug || (verbose && consoleMode)) {
      // ...
    }
  • Easy to Read (Single type of logical operator):

    if (debug || verbose || consoleMode) {
      // ...
    }

5. Gotos Avoid using goto statements as they create non-linear and hard-to-follow control flow.

  • Hard to Read (Using goto):

    #include <stdio.h>
    
    void print_numbers() {
        int i = 0;
    loop_start:
        if (i < 5) {
            printf("%d\n", i);
            i++;
            goto loop_start;
        }
    }
  • Easy to Read (Using a standard loop):

    #include <stdio.h>
    
    void print_numbers() {
        for (int i = 0; i < 5; i++) {
            printf("%d\n", i);
        }
    }

6. Nesting Minimize nested logic. If deep nesting is required, isolate it in a separate function.

  • Hard to Read (Deeply nested):

    function logIntegerDiv(x, y) {
      if (debug) {
        if (x != 0) {
          if (y != 0) {
            console.log(Math.floor(x/y));
          }
        }
      }
    }
  • Easy to Read (Flat structure):

    function logIntegerDiv(x, y) {
      if (debug === false) {
        return;
      }
      if ((x == 0) || (y == 0)) {
        return;
      }
      console.log(Math.floor(x/y));
    }

7. Variable Distinction Use descriptive and visually distinct variable names, and avoid variable shadowing.

  • Hard to Read (Confusingly similar names):

    function processNodes(nodes) {
      let node = nodes[0];
      // ... more code
      if (someCondition) {
        let node = getNewNode(); // variable shadowing
        // ...
      }
    }
  • Easy to Read (Distinct names):

    function processNodes(allNodes) {
      let currentNode = allNodes[0];
      // ... more code
      if (someCondition) {
        let newNode = getNewNode();
        // ...
      }
    }

8. Variable Liveness Prefer shorter liveness durations for variables.

  • Hard to Read (Variables declared at the top of the function):

    function fibonacci(n) {
      let fibN = n;
      let fibNMinus1 = 1;
      let fibNMinus2 = 0;
    
      if (n === 0) return 0;
      if (n === 1) return 1;
    
      for (let i = 2; i <= n; i++) {
        fibN = fibNMinus1 + fibNMinus2;
        fibNMinus2 = fibNMinus1;
        fibNMinus1 = fibN;
      }
      return fibN;
    }
  • Easy to Read (Variables declared just before use):

    function fibonacci(n) {
      if (n === 0) return 0;
      if (n === 1) return 1;
    
      let fibN = n;
      let fibNMinus1 = 1;
      let fibNMinus2 = 0;
      for (let i = 2; i <= n; i++) {
        fibN = fibNMinus1 + fibNMinus2;
        fibNMinus2 = fibNMinus1;
        fibNMinus1 = fibN;
      }
      return fibN;
    }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment