Design folklore is a prescription about what good code looks like, repeated as settled practice until the doctrine outruns the evidence. Four recur: the small-files heuristic, complexity metrics as quality targets, GoF design patterns as universal design vocabulary, DRY taken to mean "no duplication anywhere". Each has a defensible original meaning, and the critique targets the doctrine, not the underlying technique.
Small files / small functions¶
The doctrine. Functions and files should be small. Some formulations cite specific limits (Robert C. Martin's "20 lines maximum per function" is the most-quoted); others stay vague ("if it doesn't fit on a screen it's too long"). The underlying claim: smaller units are easier to read, easier to test, easier to maintain.
Strongest case. Reading a 10-line function is faster than reading a 200-line function. Smaller units extract cleanly, rename cleanly, move cleanly. They surface in stack traces with informative names. The IDE's Extract Function move makes the operation mechanical.
What the doctrine misses. Readability is not equivalent to size. A 200-line function with one obvious narrative arc can be easier to follow than the same logic split across twenty 10-line functions where the reader has to jump between callers, callees, shared state, and the file structure to reconstruct the flow. The cost of fragmentation — split-thinking, indirection, naming overhead, hunting for where the bug lives — is not captured by "lines of code per function" as a metric.
John Ousterhout's A Philosophy of Software Design (Ousterhout 2018)1 names this trap shallow modules: small functions that are mostly interface, contributing complexity rather than abstracting it.
Within the Elm community, Evan Czaplicki's The Life of a File (Czaplicki 2017)2 makes a related argument about files: the temptation to split files early based on a-priori categories ("this looks like a User module") produces module boundaries that fight the language's design. Czaplicki recommends letting files grow with the program and splitting only for a real reason — non-coincidental duplication, multiple actual consumers. The point generalizes beyond Elm: structure-first splitting tends to optimize for the prescription, not for the reader.
The corrective. Optimize for one obvious thing per unit of code that fits in the reader's head, not small. Sometimes that is 10 lines. Sometimes that is 80. The metric that discriminates is whether the reader had to jump around to understand the unit.
Complexity metrics as quality targets¶
The doctrine. Cyclomatic complexity (McCabe 1976)3, ABC metric (Fitzpatrick 1997)4, Halstead measures, cognitive complexity (Campbell 2018)5 (SonarSource) — these quantify "how complex is this function?". A cyclomatic complexity over 10 is a common rule-of-thumb threshold; over 15 triggers warnings in most static-analysis tools.
Strongest case. Functions with high cyclomatic complexity do correlate with defects. Thresholds make "this function is harder than its neighbors" discoverable without reading the code. A trending metric (complexity rose 40% on this PR) is a useful diff-time signal.
What the doctrine misses. Most of the correlation between cyclomatic complexity and defects is mediated by size — larger functions tend to be more complex and tend to have more defects, but the residual signal once size is controlled for is small (Jay et al. 2009; Herraiz and Hassan 2010)6 7. Cyclomatic complexity also penalizes flat control flow with many branches more harshly than nested control flow that may actually be harder to follow; ABC over-weights assignments in language families that treat assignment as routine.
The deeper failure mode is Goodhart's law: when a measure becomes a target, it ceases to be a good measure. A team graded on cyclomatic complexity will refactor to lower the number, not to lower the difficulty. The most common "complexity reduction" is to extract sub-functions that just move branches around the file — the cyclomatic complexity of the original function drops, the cyclomatic complexity of the extracted ones is low by construction, and the cognitive load of understanding the whole thing is higher than before.
The corrective. Treat complexity metrics as diff-time signals ("this PR added 80 to the cyclomatic complexity of one file — should it have?") rather than as fixed thresholds or KPIs, paired with reviewer judgment on whether the function is actually hard to follow. A number alone is not a failure.
GoF design patterns¶
The doctrine. The Design Patterns book (Gamma et al. 1994)8 — "the Gang of Four" — cataloged 23 patterns for object-oriented design: Singleton, Factory, Observer, Visitor, Iterator, Strategy, Decorator, Adapter, Composite, and others. The patterns became foundational vocabulary for OO design education and job interviews; knowing the patterns by name is treated as evidence of design literacy.
Strongest case. A shared vocabulary lets engineers communicate design intent compactly. "Use a visitor" is shorter than ten lines of explanation. The catalog codifies recurring solutions that a sufficiently-experienced OO programmer would arrive at anyway.
What the doctrine misses. The critiques of GoF differ in what they indict.
Design Patterns in Dynamic Languages (Norvig 1996)9 is the canonical "patterns describe language deficiencies" argument. Norvig examined the 23 GoF patterns under Lisp and Dylan and showed that many become invisible, trivial, or substantially simplified once the language has first-class functions, sum types and pattern matching, multimethods, or macros.
- The Strategy pattern is a workaround for missing first-class functions. Where a function can be passed as an argument, "strategy" is a function literal.
- The Command pattern is a workaround for missing closures.
- The Iterator pattern is a workaround for missing first-class collections / generators.
- The Visitor pattern is a workaround for missing pattern
matching over sum types. In a language with
match, it reduces to a single function with one branch per variant. - The Observer pattern is a workaround for missing first-class events / signals / channels.
By Norvig's reading, the patterns are at minimum language-specific idioms, not transferable design wisdom.
Patterns Failed. Why? Should We Care? (Marick 2017)10 (Deconstruct conference) indicts the level of abstraction instead: Marick argues GoF failed because the patterns relied only on "functions or methods" and "polymorphism, based on inheritance". The GoF book invoked Christopher Alexander's A Pattern Language as its inspiration, but Alexander worked with rich, interconnected concepts. The software patterns book gave the mechanism without Alexander's generative philosophy, and what spread was template-copying rather than design thinking. Marick recommends reviving pattern thinking through Domain-Driven Design, which captures more of Alexander's intent.
Functional Programming Design Patterns (Wlaschin 2014)11 acknowledges that the FP community has its own patterns — functor, monad, applicative, lens — and disclaims the "OOP patterns vanish in FP" caricature Wlaschin is associated with: his viral slide satirized ivory-tower FP advocates.
There are no controlled outcome studies showing that pattern-using codebases ship higher-quality software than pattern-naïve ones in the same language.
The corrective. Pick the critique that matches the situation. For language choice, Norvig's argument is the operative one — in a language with first-class functions, sum types, and pattern matching, several GoF patterns vanish or become idioms instead of named patterns. For design discipline, Marick's argument points the other way: patterns as templates is the failure mode; patterns as Alexander-style generative design is what would have worked. Wlaschin holds that both paradigms have patterns worth knowing. In all three readings, naming a pattern is not evidence of good design.
DRY taken as universal¶
The doctrine. Don't Repeat Yourself, from The Pragmatic Programmer: any duplication in the codebase is a smell, so refactor to a shared abstraction.
Strongest case. Hunt and Thomas's original formulation is about knowledge: "every piece of knowledge must have a single, unambiguous, authoritative representation within a system" (Hunt and Thomas 1999)12. Restricted to knowledge, that formulation is hard to argue with — two copies of a tax-calculation rule drift, and the drift is the bug.
What the doctrine misses. The common reading drops the "knowledge" qualifier and treats any surface-level duplication as a smell. Two functions that happen to look similar get collapsed into one parameterized function with three configuration flags. The shared function then has to be extended every time either caller's needs diverge, and the flags multiply until the abstraction is unreadable and every caller is fighting it.
Sandi Metz, in The Wrong Abstraction (Metz 2016)13, gives the canonical corrective: "duplication is far cheaper than the wrong abstraction". Metz recommends leaving duplicate code alone until the third or fourth occurrence makes the shared knowledge obvious. Where a previously-extracted abstraction turns out to have been premature, the repair is to inline it back and start over.
The corrective. Apply DRY to knowledge, not to surface code patterns — the Rule of Three (wait for three occurrences before extracting) is the practical heuristic.
What design folklore has in common¶
These prescriptions share one shape, and share it with testing folklore:
- Pick one surface property to optimize: function size; cyclomatic complexity number; pattern membership; duplication count.
- Treat it as the universal quality proxy.
- Stay silent on everything else — what the reader needs to keep in their head; what the metric is actually measuring; what problem the pattern was a workaround for; whether surface similarity reflects shared knowledge.
- Travel widely because the metric is easy to teach and easy to grade on.
The corrective in each case is to name the failure mode being prevented rather than optimize the surface metric. Long opaque functions, runaway complexity, undisciplined OO, and knowledge drift across copies are genuine failure modes, and the metrics are bad proxies for them.
For refactoring as a practice — mechanical moves, tests as a safety net, the Feathers playbook for legacy code — see Refactoring practice.
Referenced by¶
- Maintainability · Quality dimensions
- Git-history hotspots · Methods
- Refactoring practice · Methods
- Conventional · Conventional
- Test smells · Conventional
- Testing folklore · Conventional
- About this project and how it is checked · Overview
- Glossary · Overview
References¶
-
Ousterhout, John. 2018. A Philosophy of Software Design. Yaknyam Press. https://archive.org/details/philosophyofsoft0000john. ↩
-
Czaplicki, Evan. 2017. The Life of a File. Talk, Elm Europe. https://www.youtube.com/watch?v=XpDsk374LDE. ↩
-
McCabe, Thomas J. 1976. "A Complexity Measure." IEEE Transactions on Software Engineering SE-2 (4): 308–20. https://doi.org/10.1109/TSE.1976.233837. ↩
-
Fitzpatrick, Jerry. 1997. "Applying the ABC Metric to C, C++, and Java." C++ Report. https://www.win.tue.nl/~wstomv/edu/2ip30/references/ABCmetric.pdf. ↩
-
Campbell, G. Ann. 2018. "Cognitive Complexity: An Overview and Evaluation." Proceedings of the 2018 International Conference on Technical Debt (TechDebt '18), 57–58. https://doi.org/10.1145/3194164.3194186. ↩
-
Jay, Graylin, Joanne E. Hale, Randy K. Smith, David Hale, Nicholas A. Kraft, and Charles Ward. 2009. "Cyclomatic Complexity and Lines of Code: Empirical Evidence of a Stable Linear Relationship." Journal of Software Engineering and Applications 2 (3): 137–43. https://doi.org/10.4236/jsea.2009.23020. ↩
-
Herraiz, Israel, and Ahmed E. Hassan. 2010. "Beyond Lines of Code: Do We Need More Complexity Metrics?" In Making Software: What Really Works, and Why We Believe It, edited by Andy Oram and Greg Wilson. O'Reilly. ↩
-
Gamma, Erich, Richard Helm, Ralph Johnson, and John Vlissides. 1994. Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley. ↩
-
Norvig, Peter. 1996. Design Patterns in Dynamic Languages. https://www.norvig.com/design-patterns/design-patterns.pdf. ↩
-
Marick, Brian. 2017. Patterns Failed. Why? Should We Care? https://www.deconstructconf.com/2017/brian-marick-patterns-failed-why-should-we-care. ↩
-
Wlaschin, Scott. 2014. Functional Programming Design Patterns. https://fsharpforfunandprofit.com/fppatterns/. ↩
-
Hunt, Andrew, and David Thomas. 1999. The Pragmatic Programmer: From Journeyman to Master. Addison-Wesley. https://archive.org/details/isbn_9780201616224. ↩
-
Metz, Sandi. 2016. The Wrong Abstraction. Sandi Metz's blog. https://sandimetz.com/blog/2016/1/20/the-wrong-abstraction. ↩