# The Six Code Decay Risks

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.

Most linters count lines and cyclomatic complexity. They measure the surface. 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.

Each risk below follows the same shape brooks-lint uses for every finding — the Iron Law: Symptom → Source → Consequence → Remedy. 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.

## 1 · Cognitive Overload R1

Diagnostic question: How much mental effort does a human need to understand this?

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.

### Signature symptoms

- Functions that mix multiple levels of abstraction in one body, or run past ~20 lines
- Nesting deeper than three levels; boolean conditions with three or more clauses
- Long parameter lists, flag arguments, and "train-wreck" chains like `a.getB().getC().doD()`
- Primitive obsession — domain concepts smuggled through as `String`/`int` instead of purpose-built types
- Shallow modules — an interface as complex as the functionality it hides

Sources: Fowler, Refactoring (Long Method, Long Parameter List, Message Chains, Primitive Obsession) · McConnell, Code Complete (high-quality routines, the power of variable names) · Ousterhout, A Philosophy of Software Design (deep modules) · Evans, Domain-Driven Design (ubiquitous language).

### Remedy

Extract methods until each one operates at a single level of abstraction. Replace primitives with value types. Make modules deep: a simple interface over substantial functionality, not a thin wrapper.

## 2 · Change Propagation R2

Diagnostic question: How many unrelated things break when you change one thing?

This is the most expensive decay risk because it compounds: every future edit inherits the blast radius of the last one.

### Signature symptoms

- Modifying one feature forces edits across more than three files in unrelated modules
- One class changes for several different business reasons (Single Responsibility violation)
- Shotgun surgery: a single conceptual change scattered across many small edits

Sources: Fowler, Refactoring (Divergent Change, Shotgun Surgery) · Martin, Clean Architecture (Single Responsibility) · Hunt & Thomas, The Pragmatic Programmer (orthogonality) · Winters et al., Software Engineering at Google.

### Remedy

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.

## 3 · Knowledge Duplication R3

Diagnostic question: Is the same decision expressed in more than one place?

DRY is about knowledge, 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.

### Signature symptoms

- The same logic copy-pasted across files or functions
- One concept named differently in different parts of the codebase
- Business rules (tax rates, validation limits) hard-coded in multiple spots

Sources: Hunt & Thomas, The Pragmatic Programmer (DRY) · Fowler, Refactoring (Duplicated Code) · Evans, Domain-Driven Design.

### Remedy

Give each decision a single authoritative home. Extract shared logic; unify vocabulary so one concept has one name everywhere.

## 4 · Accidental Complexity R4

Diagnostic question: Is the code more complex than the problem it solves?

Brooks distinguished essential complexity (inherent to the problem) from accidental complexity (introduced by our solution). The second kind is the only kind you can delete.

### Signature symptoms

- Abstractions built "for future use" with no current consumer (speculative generality)
- Classes that barely justify their existence — a wrapper around a single call
- Configuration, indirection, or patterns out of proportion to the actual requirement

Sources: Brooks, The Mythical Man-Month (essential vs. accidental complexity) · Fowler, Refactoring (Speculative Generality) · Ousterhout, A Philosophy of Software Design.

### Remedy

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.

## 5 · Dependency Disorder R5

Diagnostic question: Do dependencies flow in a consistent, predictable direction?

Architecture is mostly about the direction of the arrows. When high-level policy depends on low-level detail, the detail starts driving the design.

### Signature symptoms

- Circular dependencies between modules or packages
- High-level business logic importing directly from low-level infrastructure (DB drivers, HTTP clients)
- The domain layer reaching out to frameworks instead of the other way around

Sources: Martin, Clean Architecture (Dependency Inversion, the Dependency Rule) · Brooks, The Mythical Man-Month · Winters et al., Software Engineering at Google.

### Remedy

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.

## 6 · Domain Model Distortion R6

Diagnostic question: Does the code faithfully represent the problem it is solving?

When the model drifts from the domain, every reader has to translate in their head — and translation errors become bugs.

### Signature symptoms

- Anemic models: business logic scattered across services while domain objects hold only getters and setters
- Names that don't match what business stakeholders actually call the concept
- Logic bugs hiding in code that reads plausibly but doesn't model reality (e.g. comparing a value to itself after overwriting it)

Sources: Evans, Domain-Driven Design (rich models, ubiquitous language) · Fowler, Refactoring (Anemic Domain Model).

### Remedy

Move behaviour onto the objects that own the data. Align every name with the language the business uses. Make illegal states unrepresentable.

## And the test suite decays too

brooks-lint applies the same lens to tests, with six parallel risks (T1–T6) drawn from xUnit Test Patterns, The Art of Unit Testing, How Google Tests Software, and Working Effectively with Legacy Code:

Risk | The question it asks
--- | ---
Test Obscurity | Can you tell what a test verifies without reading its implementation?
Test Brittleness | Does a refactor that preserves behaviour still break the test?
Test Duplication | Is the same setup or assertion copied across many tests?
Mock Abuse | Does the test verify interactions instead of outcomes?
Coverage Illusion | Does high coverage hide tests that assert nothing meaningful?
Architecture Mismatch | Do the tests fight the code's seams instead of using them?

### See it run on your own code

brooks-lint is a free, open-source plugin for Claude Code, Gemini CLI, and Codex. Install it and review any file in one command:

`/plugin marketplace add hyhmrright/brooks-lint`
`/brooks-review`

⭐ Get it on GitHub