@@ -160,6 +160,8 @@ The graph renders natively in GitHub, Notion, and other Markdown environments
The [Full Gallery](docs/gallery.md) has real brooks-lint output across Python, TypeScript, Go, and Java — including PR reviews, architecture audits with Mermaid dependency graphs, tech debt assessments, and test quality reviews.
New to the decay risks? The [**Decay Risk Field Guide**](https://hyhmrright.github.io/brooks-lint/guide.html) explains all six — diagnostic question, signature symptoms, source books, and remedy for each.
<title>The Six Code Decay Risks — A Field Guide Grounded in Twelve Engineering Classics | brooks-lint</title>
<metaname="description"content="A practical field guide to the six ways production code decays — cognitive overload, change propagation, knowledge duplication, accidental complexity, dependency disorder, and domain model distortion — each mapped to the classic software engineering books that named it, with diagnostic questions, signature symptoms, and concrete remedies.">
<metaproperty="og:title"content="The Six Code Decay Risks — A Field Guide Grounded in Twelve Engineering Classics">
<metaproperty="og:description"content="How code decays, what each pattern is called in the literature, and how to fix it — synthesized from Brooks, Fowler, Martin, McConnell, Evans, Ousterhout and more.">
<pclass="lede">A field guide to the ways production code rots — and the classic engineering books that named each one fifty years before your codebase did it again.</p>
<pclass="meta">Most linters count lines and cyclomatic complexity. They measure the <em>surface</em>. Decay happens underneath: in how responsibilities tangle, how knowledge duplicates, how dependencies invert. These six patterns are a synthesis of twelve classic software engineering books, applied to modern code review.</p>
<divclass="toc">
<h4>The six risks</h4>
<ol>
<li><ahref="#r1">Cognitive Overload</a> — how hard is this to understand?</li>
<li><ahref="#r2">Change Propagation</a> — what breaks when you touch one thing?</li>
<li><ahref="#r3">Knowledge Duplication</a> — is one decision expressed in many places?</li>
<li><ahref="#r4">Accidental Complexity</a> — is the code harder than the problem?</li>
<li><ahref="#r5">Dependency Disorder</a> — do dependencies flow one way?</li>
<li><ahref="#r6">Domain Model Distortion</a> — does the code mean what the business means?</li>
</ol>
</div>
<p>Each risk below follows the same shape brooks-lint uses for every finding — the <strong>Iron Law</strong>: <em>Symptom → Source → Consequence → Remedy</em>. A symptom you can see in the diff, a source you can cite to a book, a consequence that explains why it matters, and a remedy you can actually apply.</p>
<pclass="q">Diagnostic question: How much mental effort does a human need to understand this?</p>
<p>Working memory holds about four chunks at once. Code that exceeds that budget causes mistakes, invites avoidance, and blocks the very refactoring that would fix it.</p>
<h3>Signature symptoms</h3>
<ul>
<li>Functions that mix multiple levels of abstraction in one body, or run past ~20 lines</li>
<li>Nesting deeper than three levels; boolean conditions with three or more clauses</li>
<li>Long parameter lists, flag arguments, and "train-wreck" chains like <code>a.getB().getC().doD()</code></li>
<li><strong>Primitive obsession</strong> — domain concepts smuggled through as <code>String</code>/<code>int</code> instead of purpose-built types</li>
<li><strong>Shallow modules</strong> — an interface as complex as the functionality it hides</li>
</ul>
<pclass="src"><b>Sources:</b> Fowler, <em>Refactoring</em> (Long Method, Long Parameter List, Message Chains, Primitive Obsession) · McConnell, <em>Code Complete</em> (high-quality routines, the power of variable names) · Ousterhout, <em>A Philosophy of Software Design</em> (deep modules) · Evans, <em>Domain-Driven Design</em> (ubiquitous language).</p>
<h3>Remedy</h3>
<p>Extract methods until each one operates at a single level of abstraction. Replace primitives with value types. Make modules <em>deep</em>: a simple interface over substantial functionality, not a thin wrapper.</p>
<pclass="q">Diagnostic question: How many unrelated things break when you change one thing?</p>
<p>This is the most expensive decay risk because it compounds: every future edit inherits the blast radius of the last one.</p>
<h3>Signature symptoms</h3>
<ul>
<li>Modifying one feature forces edits across more than three files in unrelated modules</li>
<li>One class changes for several different business reasons (Single Responsibility violation)</li>
<li>Shotgun surgery: a single conceptual change scattered across many small edits</li>
</ul>
<pclass="src"><b>Sources:</b> Fowler, <em>Refactoring</em> (Divergent Change, Shotgun Surgery) · Martin, <em>Clean Architecture</em> (Single Responsibility) · Hunt & Thomas, <em>The Pragmatic Programmer</em> (orthogonality) · Winters et al., <em>Software Engineering at Google</em>.</p>
<h3>Remedy</h3>
<p>Separate responsibilities into focused units and let a thin orchestrator call them. Aim for orthogonality — changing the loyalty formula should never risk breaking email notifications.</p>
<pclass="q">Diagnostic question: Is the same decision expressed in more than one place?</p>
<p>DRY is about <em>knowledge</em>, not text. Two identical-looking blocks that encode different decisions are fine; one decision copied into three files is a latent bug waiting for the day someone updates two of them.</p>
<h3>Signature symptoms</h3>
<ul>
<li>The same logic copy-pasted across files or functions</li>
<li>One concept named differently in different parts of the codebase</li>
<li>Business rules (tax rates, validation limits) hard-coded in multiple spots</li>
<pclass="q">Diagnostic question: Is the code more complex than the problem it solves?</p>
<p>Brooks distinguished <em>essential</em> complexity (inherent to the problem) from <em>accidental</em> complexity (introduced by our solution). The second kind is the only kind you can delete.</p>
<h3>Signature symptoms</h3>
<ul>
<li>Abstractions built "for future use" with no current consumer (speculative generality)</li>
<li>Classes that barely justify their existence — a wrapper around a single call</li>
<li>Configuration, indirection, or patterns out of proportion to the actual requirement</li>
<p>Delete speculative abstractions until a second real consumer appears. Prefer the simplest design that solves today's problem — you can always add structure when the need is concrete.</p>
<pclass="q">Diagnostic question: Do dependencies flow in a consistent, predictable direction?</p>
<p>Architecture is mostly about the direction of the arrows. When high-level policy depends on low-level detail, the detail starts driving the design.</p>
<h3>Signature symptoms</h3>
<ul>
<li>Circular dependencies between modules or packages</li>
<li>High-level business logic importing directly from low-level infrastructure (DB drivers, HTTP clients)</li>
<li>The domain layer reaching out to frameworks instead of the other way around</li>
</ul>
<pclass="src"><b>Sources:</b> Martin, <em>Clean Architecture</em> (Dependency Inversion, the Dependency Rule) · Brooks, <em>The Mythical Man-Month</em> · Winters et al., <em>Software Engineering at Google</em>.</p>
<h3>Remedy</h3>
<p>Invert the dependency: define an interface the high-level code owns, and let infrastructure implement it. Break cycles by extracting the shared abstraction both sides can depend on.</p>
</div>
<h2id="r6">6 · Domain Model Distortion <spanclass="code">R6</span></h2>
<divclass="risk">
<pclass="q">Diagnostic question: Does the code faithfully represent the problem it is solving?</p>
<p>When the model drifts from the domain, every reader has to translate in their head — and translation errors become bugs.</p>
<h3>Signature symptoms</h3>
<ul>
<li>Anemic models: business logic scattered across services while domain objects hold only getters and setters</li>
<li>Names that don't match what business stakeholders actually call the concept</li>
<li>Logic bugs hiding in code that <em>reads</em> plausibly but doesn't model reality (e.g. comparing a value to itself after overwriting it)</li>
<p>Move behaviour onto the objects that own the data. Align every name with the language the business uses. Make illegal states unrepresentable.</p>
</div>
<h2>And the test suite decays too</h2>
<p>brooks-lint applies the same lens to tests, with six parallel risks (T1–T6) drawn from <em>xUnit Test Patterns</em>, <em>The Art of Unit Testing</em>, <em>How Google Tests Software</em>, and <em>Working Effectively with Legacy Code</em>:</p>
<table>
<tr><th>Risk</th><th>The question it asks</th></tr>
<tr><td>Test Obscurity</td><td>Can you tell what a test verifies without reading its implementation?</td></tr>
<tr><td>Test Brittleness</td><td>Does a refactor that preserves behaviour still break the test?</td></tr>
<tr><td>Test Duplication</td><td>Is the same setup or assertion copied across many tests?</td></tr>
<tr><td>Mock Abuse</td><td>Does the test verify interactions instead of outcomes?</td></tr>
<tr><td>Coverage Illusion</td><td>Does high coverage hide tests that assert nothing meaningful?</td></tr>
<tr><td>Architecture Mismatch</td><td>Do the tests fight the code's seams instead of using them?</td></tr>
</table>
<divclass="cta">
<h3>See it run on your own code</h3>
<p>brooks-lint is a free, open-source plugin for Claude Code, Gemini CLI, and Codex. Install it and review any file in one command:</p>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.