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,elseusage, 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.