--- title: "Clean Code Principles for 2026" description: "Documented, named critiques of Robert C. Martin's Clean Code lineage - the Muratori/Martin performance debate, the Ousterhout/Martin design debate on function length, comments, and TDD - with decision rules for when each side is actually right." language: null framework: null category: principles tags: - clean-code - code-smells - refactoring - critique - ousterhout - muratori - best-practices keywords: - clean code criticism - ousterhout vs martin debate - casey muratori clean code horrible performance - deep modules vs shallow functions - when comments are necessary - tdd design criticism last_updated: "2026-07-08" difficulty: intermediate related: - ./solid.md - ./dry_kiss_yagni.md - ../design_patterns/creational.md - ../design_patterns/structural.md - ../design_patterns/behavioral.md - ../architecture/clean_architecture.md - ../testing/unit_testing_principles.md - ../../languages/python/README.md - ../../languages/python/frameworks/fastapi/best_practices.md search_priority: high status: published --- # Clean Code Principles for 2026 Assume the reader already knows the textbook rules (small functions, intention-revealing names, few comments, one level of abstraction per function). This document is the part that isn't in the textbook: two documented, named, still-live disputes between Clean Code's author and two of its sharpest critics, and the decision rules that fall out of them. ## Dispute 1: Function length and "shallow" decomposition (Ousterhout vs Martin) John Ousterhout (*A Philosophy of Software Design*) and Robert Martin held a direct, public, written debate, archived at `github.com/johnousterhout/aposd-vs-clean-code`. It covers three concrete topics - method length, comments, TDD - and neither side backed down. Treat this as two named experts disagreeing, not as a settled question. **Ousterhout's claim**: Clean Code's push toward very short methods produces *shallow* functions - a function whose interface (name, parameters, the fact that a reader must now go find it) costs more attention than the small amount of logic it hides saves. Splitting a function does not reduce complexity, it relocates and multiplies interfaces. His rule, stated directly against Martin's: **"first make functions deep, then try to make them short enough to be easily read - don't sacrifice depth for length."** A deep function (small interface, substantial functionality behind it, e.g. a well-designed `read()` syscall) beats five shallow ones that force the reader to jump around to reconstruct one coherent operation. **Decision rule**: Before extracting a function, ask whether the extracted piece is independently meaningful (has its own name-worthy concept, will be reused, or is independently testable) or whether it's just relocating five lines behind a new name to satisfy a line-count target. Only the former is a net win. A 25-line function that reads top-to-bottom as one coherent paragraph beats five 5-line functions with names like `validateAndPrepare`, `doInnerCalc`, `finalizeStep` that only ever call each other once, in order. ## Dispute 2: Comments (Ousterhout vs Martin) Same debate thread, unresolved: Clean Code's position is that a comment is a failure to name something well enough - good naming should make most comments unnecessary. Ousterhout's APOSD position is that *structured* comments carry information code cannot express at all (why a value was chosen, what invariant a caller must uphold, what the interface promises independent of the current implementation) and that a codebase relying purely on naming under-documents its own contracts. **Decision rule**: Naming solves "what does this do." It cannot solve "why does this exist," "what did I already rule out," or "what must remain true across all future implementations of this interface." If the information is the second kind, write the comment - do not keep renaming things trying to make the name carry it. ## Dispute 3: TDD as a design method (Ousterhout vs Martin) Ousterhout's argument, from the same debate: TDD's natural unit of work is one test, which is smaller than a natural design unit (a class, a module boundary). Writing the next test that makes you write the next few lines of code optimizes locally and can produce a design nobody actually thought through end-to-end, because no step in the loop asks "does this whole class hang together." This is a critique of TDD *as a design technique*, not of having tests. **Decision rule**: Use TDD for well-understood, narrow units (a pure function, a parser, a bug-fix regression). For anything where the module boundary itself is undecided (a new subsystem, a new tool's data model), sketch the interface first, then backfill tests - do not let the red-green-refactor loop be the mechanism that decides the shape of the boundary. ## Dispute 4: Performance (Muratori vs Martin) Casey Muratori's "Clean Code, Horrible Performance" (`computerenhance.com/p/clean-code-horrible-performance`, 2023) benchmarked a shape-area calculation two ways: Clean-Code-style polymorphic dispatch (virtual functions / vtable calls) versus a flat switch/union approach. Result: roughly **1.5x slower** (24 cycles/shape flat vs 35 cycles/shape polymorphic) in his base case, and he argues the real cost isn't the vtable indirection itself but that **the compiler cannot optimize across a virtual call boundary because it doesn't know the concrete type** - the loss compounds with every optimization the compiler is blocked from making, not just the dispatch overhead. Robert Martin responded directly and at length on GitHub (`github.com/unclebob/cmuratori-discussion/blob/main/cleancodeqa.md`), conceding the specific Clean Code examples "are not efficient at the nanosecond level," and drew this explicit boundary: **"If you are trying to squeeze every nanosecond from a battery of GPUs, then clean code may not be for you... if you are trying to squeeze every man-hour of productivity from a software development team, then clean code can be an effective strategy."** A third party, Evan Teran (`blog.codef00.com/2023/04/13/casey-muratori-is-wrong-about-clean-code`), adds the load-bearing nuance both sides under-state: Muratori's own benchmark code already follows most Clean Code conventions (small functions, clear naming) - the actual variable is specifically **virtual-dispatch-based polymorphism**, not "clean code" as a whole. Teran's own benchmarks with `std::variant` show the direction of the effect is compiler- and pattern-dependent (Clang and GCC disagree on which approach wins), which argues for profiling over ideology in either direction. **Decision rule**: | Context | Guidance | |---|---| | Hot loop, profiler shows time in a virtual/interface call | Flatten it: switch/tagged-union/`std::variant`-style dispatch, monomorphize, or devirtualize. Isolate the fast path behind a clearly named boundary. | | Everything else (the large majority of business logic) | Default to Martin's boundary: optimize for the team's throughput, not for cycles nobody is measuring. Do not pre-flatten abstractions "for speed" without a profiler run backing it up. | | Reviewing a PR that removes an abstraction "for performance" | Demand the benchmark. Muratori's own point-of-agreement with Martin is that this is an empirical question, not a stylistic one. | ## Code Smell Reference A smell is cheap to fix the day it appears, expensive once other code depends on its shape. | Smell | Symptom | Usual Fix | |---|---|---| | Shallow function | Extracted piece has no independent meaning, is called once, only exists to hit a line-count target | Inline it back; see Dispute 1 | | Long parameter list | 5+ positional parameters | Parameter object / [Builder](../design_patterns/creational.md#builder) | | Shotgun surgery | One conceptual change touches many files | Consolidate responsibility - see [SOLID](./solid.md) | | Feature envy | Method uses another object's data more than its own | Move method to the object it envies | | Primitive obsession | Business concepts represented as raw `str`/`int` | Value objects / typed wrappers | | Comment restating code | `# increment i` above `i += 1` | Delete it; if it needed explaining, rename instead | | Naming-only documentation of a contract | A public interface with no comment on preconditions/invariants, name alone is expected to carry the contract | Add a structured comment per Dispute 2 - this is not "clutter," it's the part naming cannot do | ## When *Not* to Apply These Rules | Situation | Recommendation | |---|---| | Hot numerical loop with a measured (not assumed) performance requirement | Prioritize the profiler's verdict; isolate and comment the exception | | Auto-generated code | Do not hand-clean it; fix the generator | | A module boundary that is still genuinely unknown | Sketch the interface first; don't let TDD's next-test loop decide the architecture (Dispute 3) | | A public interface whose contract can't be inferred from its signature | Write the structured comment; don't keep renaming to avoid it (Dispute 2) | ## Related Documentation - [SOLID Principles](./solid.md) - [DRY, KISS, and YAGNI](./dry_kiss_yagni.md) - [Clean Architecture](../architecture/clean_architecture.md) - [Unit Testing Principles](../testing/unit_testing_principles.md) ## Sources - Ousterhout/Martin debate transcript - [`github.com/johnousterhout/aposd-vs-clean-code`](https://github.com/johnousterhout/aposd-vs-clean-code) - used for Disputes 1, 2, 3 (method length, comments, TDD), including the direct "deep vs shallow" quote. - Casey Muratori, "'Clean' Code, Horrible Performance" - [`computerenhance.com/p/clean-code-horrible-performance`](https://www.computerenhance.com/p/clean-code-horrible-performance) - benchmark numbers and the compiler-optimization-boundary argument (Dispute 4). - Robert Martin, response to Muratori - [`github.com/unclebob/cmuratori-discussion/blob/main/cleancodeqa.md`](https://github.com/unclebob/cmuratori-discussion/blob/main/cleancodeqa.md) - the "nanoseconds vs man-hours" quote and Martin's concession. - Evan Teran, "Casey Muratori is wrong about clean code (but he's also right)" - [`blog.codef00.com/2023/04/13/casey-muratori-is-wrong-about-clean-code`](https://blog.codef00.com/2023/04/13/casey-muratori-is-wrong-about-clean-code) - nuance that the villain is virtual dispatch specifically, and compiler-dependent `std::variant` benchmark numbers.