# Design folklore

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)[^ousterhout2018] 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)[^czaplicki2017] 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)[^mccabe1976], ABC
metric (Fitzpatrick 1997)[^fitzpatrick1997], Halstead measures, cognitive
complexity (Campbell 2018)[^campbell2018] (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)[^jay2009] [^herraiz2010]. 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)[^gamma1994] — "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)[^norvig1996] 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)[^marick2017] (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)[^wlaschin2014]
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)[^hunt1999]. 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)[^metz2016], 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](https://quality.stereobooster.com/testing-folklore.md):

1. Pick one surface property to optimize: *function size*;
   *cyclomatic complexity number*; *pattern membership*;
   *duplication count*.
2. Treat it as the universal quality proxy.
3. 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*.
4. 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](https://quality.stereobooster.com/refactoring-practice.md).

## Referenced by

- [Maintainability](https://quality.stereobooster.com/maintainability.md) · Quality dimensions
- [Git-history hotspots](https://quality.stereobooster.com/git-hotspots.md) · Methods
- [Refactoring practice](https://quality.stereobooster.com/refactoring-practice.md) · Methods
- [Conventional](https://quality.stereobooster.com/conventional.md) · Conventional
- [Test smells](https://quality.stereobooster.com/test-smells.md) · Conventional
- [Testing folklore](https://quality.stereobooster.com/testing-folklore.md) · Conventional
- [About this project and how it is checked](https://quality.stereobooster.com/about.md) · Overview
- [Glossary](https://quality.stereobooster.com/glossary.md) · Overview

## References

[^ousterhout2018]: Ousterhout, John. 2018. *[A Philosophy of Software Design](https://archive.org/details/philosophyofsoft0000john)*. Yaknyam Press. <https://archive.org/details/philosophyofsoft0000john>.
[^czaplicki2017]: Czaplicki, Evan. 2017. *[The Life of a File](https://www.youtube.com/watch?v=XpDsk374LDE)*. Talk, Elm Europe. <https://www.youtube.com/watch?v=XpDsk374LDE>.
[^mccabe1976]: McCabe, Thomas J. 1976. "[A Complexity Measure](http://literateprogramming.com/mccabe.pdf)." *IEEE Transactions on Software Engineering* SE-2 (4): 308–20. <https://doi.org/10.1109/TSE.1976.233837>.
[^fitzpatrick1997]: Fitzpatrick, Jerry. 1997. "[Applying the ABC Metric to C, C++, and Java](https://www.win.tue.nl/~wstomv/edu/2ip30/references/ABCmetric.pdf)." *C++ Report*. [https://www.win.tue.nl/\~wstomv/edu/2ip30/references/ABCmetric.pdf](https://www.win.tue.nl/~wstomv/edu/2ip30/references/ABCmetric.pdf).
[^campbell2018]: Campbell, G. Ann. 2018. "[Cognitive Complexity: An Overview and Evaluation](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)." *Proceedings of the 2018 International Conference on Technical Debt (TechDebt '18)*, 57–58. <https://doi.org/10.1145/3194164.3194186>.
[^jay2009]: 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](https://content.scirp.org/pdf/jsea20090300001_74742661.pdf)." *Journal of Software Engineering and Applications* 2 (3): 137–43. <https://doi.org/10.4236/jsea.2009.23020>.
[^herraiz2010]: 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.
[^gamma1994]: Gamma, Erich, Richard Helm, Ralph Johnson, and John Vlissides. 1994. *Design Patterns: Elements of Reusable Object-Oriented Software*. Addison-Wesley.
[^norvig1996]: Norvig, Peter. 1996. *[Design Patterns in Dynamic Languages](https://www.norvig.com/design-patterns/design-patterns.pdf)*. <https://www.norvig.com/design-patterns/design-patterns.pdf>.
[^marick2017]: Marick, Brian. 2017. *[Patterns Failed. Why? Should We Care?](https://www.deconstructconf.com/2017/brian-marick-patterns-failed-why-should-we-care)* <https://www.deconstructconf.com/2017/brian-marick-patterns-failed-why-should-we-care>.
[^wlaschin2014]: Wlaschin, Scott. 2014. *[Functional Programming Design Patterns](https://fsharpforfunandprofit.com/fppatterns/)*. <https://fsharpforfunandprofit.com/fppatterns/>.
[^hunt1999]: Hunt, Andrew, and David Thomas. 1999. *[The Pragmatic Programmer: From Journeyman to Master](https://archive.org/details/isbn_9780201616224)*. Addison-Wesley. [https://archive.org/details/isbn\\\_9780201616224](https://archive.org/details/isbn\_9780201616224).
[^metz2016]: Metz, Sandi. 2016. *[The Wrong Abstraction](https://sandimetz.com/blog/2016/1/20/the-wrong-abstraction)*. Sandi Metz's blog. <https://sandimetz.com/blog/2016/1/20/the-wrong-abstraction>.

## Acronyms

- ABC — Assignments, Branches, Conditions (a size metric)
- DRY — don't repeat yourself
- FP — functional programming
- KPI — key performance indicator
- OOP — object-oriented programming
