What makes code hard to read: Visual patterns of complexity (2023)

Function Size, Decomposition, and “Irreducible” Complexity

  • Strong split between people who prefer one longer, linear function and those who want many small helpers.
  • Critics of the article’s function-level metrics say they incentivize “rabbit hole” code: endless 3‑line methods, cross‑file jumps, shared/global state, and hard‑to-follow call graphs (“lasagna”, “graphs not trees”).
  • Several argue some problems have irreducible complexity; splitting them just spreads it around and makes change harder.
  • Others say decomposition is good only when the extracted functions are real abstractions you can trust without re-reading their bodies.
  • Suggested pattern: one clearly “in control” function calling sub-functions in a mostly one‑directional, tree-like structure; keep call depth and sideways coupling low.

Readability vs. Complexity Metrics

  • General agreement that Halstead/cyclomatic/etc. are heuristics, not laws. They miss architectural complexity (relationships between modules) and focus too much on local structure.
  • Some see code complexity as roughly “size of the syntax tree”, making local micro‑optimizations in metrics largely irrelevant.
  • Others found the piece useful as a vocabulary for code review: metrics as prompts for “this might be hard to read”, not as absolute thresholds.
  • One thread notes “Cognitive Complexity” in psychology measures people, not code; avoiding all mental load is neither realistic nor desirable.

Abstractions, Chaining, and Functional Style

  • Heavy debate over long chains (map/filter/reduce, method or pipe chains).
  • Pro‑chain camp: linear, left‑to‑right/top‑to‑bottom pipelines are close to SQL/R/dplyr; fewer named temps means less to remember; easy to refactor; good abstractions hide incidental detail (pagination, etc.).
  • Anti‑chain camp: long or nested chains, especially with domain‑specific, “magical” methods, are hard to debug and to reason about; intermediate named variables or helper functions can document intent and improve testability.
  • Many prefer a hybrid: short chains are fine; for longer ones, either (a) break into named helpers or (b) keep the chain and annotate with comments instead of temps.

Readability as Social, Contextual, and “Literary”

  • Multiple comments stress that readability is learned and team‑specific: Rails style, ternary operators, FP idioms, etc. become readable with exposure.
  • Codebases develop a “voice”; good ones can feel very different yet both be “good”.
  • Strong push for consistent formatting enforced by tools (gofmt, black, dotnet format, .editorconfig) to avoid style wars.
  • Literate programming is discussed as almost the opposite extreme (code as secondary to prose); admired in small/academic settings, but many doubt its scalability across large teams.

Mutability, Architecture, and State

  • Several find mutable state more fatiguing than any specific syntax; immutability or short‑lived variables reduce the need to “restart” mental simulation.
  • Others counter that both mutable and immutable approaches can be clean or messy; overall architecture (where the state lives, how modules interact) matters more than per‑function style.
  • Widespread agreement that deep inheritance, pervasive DI, and thinly spread behavior across many files can ruin readability regardless of line-level cleanliness.

Types, Tools, and Visual Patterns

  • TypeScript example: deep chains of inferred/derived types make it hard to see what flows where; explicit function return types are valued as an anchor for readers and tooling.
  • Some want better tooling for variable liveness visualization and AST‑aware editors that can display alignment or highlights without baking spaces into files.
  • Visual preferences differ: some like aligning arguments/assignments into “tables”; others prioritize minimal diff noise and formatter simplicity.

Subjectivity and Limits of Rules

  • Every micro-style topic (ternary vs if/else, early returns, else usage, variable shadowing, extra temporaries, comment vs “self-documenting” code) provokes opposite, strongly held opinions.
  • One view: if you can’t grasp the high-level goal of a function in a few seconds it’s “bad”; counter‑examples from algorithms and systems code show this is not always realistic.
  • Common ground: prioritize clear, stable abstractions; avoid needless cleverness; choose a consistent team style; use metrics and “rules” as heuristics, not dogma.