Auditing Your Own Code
The last session, and the one that runs in both directions. Finding where a pattern would help is half of it; finding where one is already costing you and buying nothing is the half nobody does.
Direction 1: smells that have a pattern answer
Each row is a query you can actually run, not a feeling.
| Smell | How to find it | Suspect |
|---|---|---|
| One type branched on in 3+ files | grep the variant names, count distinct files | Strategy or State – ask who owns the transition |
| A class with N independent booleans | count var .*Boolean properties |
State – N booleans is 2^N states, most illegal |
| A function with 2+ boolean parameters | grep fun .*Boolean.*Boolean |
a sealed type; the flags are probably mutually exclusive |
| Nullable fields meaningful only in some statuses | a status field plus T? siblings |
State – make illegal states unconstructible |
| A foreign type’s vocabulary inside your domain | grep the third-party type name | Adapter – it should appear in one file |
| The same 4-step sequence in several callers | grep any distinctive middle step | Facade |
| A class with 6+ constructor parameters used together | count parameters, then read the body | Facade, or the parameters are unrelated and it is doing two jobs |
| Recursion re-derived per operation | grep for branching on node type | Composite + one function per operation |
| Config branching that must outlive the build | rules in code that users ask to change | Interpreter |
| A class name made of two vocabularies | PushShiftNotifier, JsonHttpClient |
Bridge – if both halves grow |
| A feature users would undo and cannot | ask support, not the code | Command + Memento |
Right-size the prescription. The most common audit error is not missing a smell – it is answering a small one with a large pattern. A when repeated in seven files might need one extension function, not a Strategy hierarchy. Gate 3 from Foundations is the check: does the code get shorter, or at least easier to follow?
Direction 2: patterns that should come out
This is the pass almost nobody runs, and it finds as much as the first.
Interfaces with one implementation and no test double. The rule from Foundations, made into a query: list every interface, count implementations, count test fakes. One and zero means you built a seam and never used it.
Two legitimate exceptions, and you must check both before deleting anything:
- The interface crosses a module boundary in the inverting direction – declared in the module that uses it, implemented in the one that provides it. That is dependency inversion; the single implementation is the point.
- It exists for a fake you are about to write. Fine – write the fake now, or drop the interface until you do.
object declarations with mutable state or I/O. The Singleton audit: list every object, and for each ask whether it holds mutable state or touches time, randomness, or the network. A stateless namespace of pure functions is fine and common. Anything else is a hidden dependency that no signature mentions.
Decorator stacks nobody can order. If a wrapper chain exists and no document says what each layer should observe, the order is accidental. Write down the intent per layer; where you cannot, the layer is probably in the wrong place.
Abstract factories with one family. A Environment/Provider interface with a single implementation is the first item again, wearing a creational hat.
Patterns applied to closed sets. A Strategy over an enum that has had the same three values for two years is a when that grew a hierarchy.
The one-page decision guide
Keep this; it is the actual output of the whole series.
Before adding any pattern, three gates – all must pass.
- Has the change actually arrived twice? Once is a coincidence.
- Is there exactly one clear axis of variation? Two means consider Bridge. Unclear means it is too early.
- Does the code get shorter, or at least easier to follow, afterward?
Then pick by the question you are answering:
| The question | The answer |
|---|---|
| Something varies, and the caller knows which | Strategy – a function type first |
| Something varies, and the object itself decides when | State – sealed, with the data per state |
| The order is fixed, the steps vary | a higher-order function; a class only past 3 hooks |
| I need a different interface | Adapter |
| I need the same interface, plus behavior | Decorator – by delegation |
| I need the same interface, with access controlled | Proxy |
| I need a simpler interface over many things | Facade – or a module boundary, which is stronger |
| These objects must come from the same world | Abstract Factory – or your DI component, which already is one |
| The structure is recursive | Composite, sealed, one function per operation |
| The rules must outlive the build | Interpreter |
| Several unrelated things react to one event | Observer – Flow |
| Several related things have interlocking rules | Mediator – and keep the domain rules out of it |
| Steps run in order and one may stop the rest | Chain of Responsibility |
| I need undo, queueing, or a replayable log | Command, with Memento for the irreversible parts |
| Two independent axes, both growing | Bridge |
| Thousands of near-identical objects, measured | Flyweight – or a value class |
And the Kotlin overlay. Five patterns survive intact: Abstract Factory, Facade, Mediator, Flyweight, Bridge. Everything else has a language form that should be your first attempt, and the class form is what you fall back to when the language form stops fitting – more than one operation, state to carry, a name that has to appear in a log.
Doing the audit
An hour, in this order:
- Run the queries from direction 1. Write down file and line, not impressions.
- Run direction 2. Expect it to find more than you think.
- Sort by the three gates. Most findings fail gate 1, and that is a result.
- Take the top one. Only the top one.
Then stop. An audit that produces a twelve-item backlog produces nothing; one that produces a single change you make this week has paid for itself.
What the series was actually for
Not the twenty-three names. Three things:
A vocabulary for design conversations. “That is a Decorator, and the order matters” is faster than three paragraphs, and it is precise.
The ability to read libraries. The previous session is the transferable skill: six passes, and the question of what each pattern bought.
A calibrated sense of when not to. Every page here had a Don’t use it when section for a reason. The failure mode of learning patterns is applying them, and the gates exist because the most common outcome of a design-patterns course is worse code for about six months.
If the series worked, the test is not that you can name Bridge. It is that you notice PushShiftNotifier and pause.