Defence Before Fix: Converting Bug Reports into Permanent Defences
When you find a bug, build a static analysis rule that catches the entire class before fixing the specific instance. The DBF methodology by Joseph Edmonds converts reactive debugging into permanent, automated prevention.
When your team finds a bug, the instinct is to fix it. That instinct is incomplete. Fixing the bug is the last step. The first step is defending against the entire class of bug that just revealed itself.
This approach — Defence Before Fix, abbreviated DBF — is a methodology I developed after years of watching the same bugs reappear in new guises, new files, and new projects. A null-coalescing-to-empty-string pattern fixed in one function reappears three months later written by a different developer. An empty catch block removed in one service reappears in the next feature added by an AI assistant. The fix never propagated because nothing enforced the lesson.
DBF changes that. Instead of fixing bugs one at a time, you encode the lesson into a static analysis rule — an automated check that catches every future instance of that pattern at the point of writing, not in production.
Fix the bug last. Build the defence first.
The Whack-a-Mole Trap
Consider a bug where a function returns null when it should return a username, and downstream code handles an empty string — not null — so the empty string passes all checks and processes silently as valid. You find the bug, fix the specific line, write a test for it, and move on.
Six weeks later, a different developer writes: const label = item.getLabel() ?? ''. Or an AI assistant autocompletes it. The same class of bug is back. Your fix addressed the symptom, not the cause.
This is the whack-a-mole trap: fixing individual bugs without addressing the structural vulnerability that permitted them. Each fix creates the impression of progress whilst leaving the underlying pattern intact and ready to reappear in the next sprint, the next feature, or the next developer's first week.
Each bug is evidence of a systemic vulnerability. Defend against the disease, not just the symptom.
The Four-Step DBF Process
When a bug is found, apply these steps in order. Resist the urge to jump straight to step four.
Step 1: Analyse the Pattern
Ask: does this bug represent a repeating pattern? Could a machine detect it automatically in source code? Common systemic patterns include null coalescing to falsy values, empty exception handlers, loose type comparisons, missing return types, and unchecked array access. If the pattern is detectable in source code before the code runs, it can be defended against statically.
Step 2: Defend Against the Class
Create a static analysis rule that flags every instance of the pattern across the entire codebase. For PHP codebases, PHPStan and Psalm both support custom rules. For TypeScript, ESLint custom rules handle this. The rule should provide a clear error message explaining why the pattern is banned — not just that it is.
Step 3: Write the Test
Write a test that documents the specific bug you found — a failing test before the fix. This proves the bug existed, proves the fix works, and prevents regression if someone reverts the change. The test and the rule together provide defence at two levels: static analysis at write time, automated tests at run time.
Step 4: Fix the Bug
Now fix the specific instance. With the static analysis rule in place, every existing instance is highlighted across the codebase. Fix them all. Every future instance — written by any developer, any AI assistant — will be caught at the point of writing.
Three Error-Hiding Patterns
Three structural patterns account for a disproportionate share of silent failures — bugs that do not crash the system immediately but corrupt its state over time.
The Silent Default
Null coalescing — the ?? operator in PHP and TypeScript — is useful when a genuine default value makes sense. It becomes dangerous when used to replace missing data with an empty value: const username = user.getName() ?? ''.
The function returned null because the data is absent or an error occurred. The null coalescing replaces that signal with an empty string, which passes all downstream validation and processes as if everything is normal. The bug is invisible until something downstream breaks — often far from the source.
The DBF defence is a static analysis rule that bans ?? '' and ?? 0 patterns, forcing explicit null handling instead.
The Empty Catch
An empty catch block is an exception graveyard. The code inside the try block throws because something went wrong — a payment failed, an external service returned an error, a file was not found. The empty catch discards that information entirely. The system carries on in an unknown or corrupt state.
Even a catch block that only logs the error before continuing is nearly as dangerous — it records the failure but does not stop the corrupt processing chain. Errors must be handled explicitly or re-thrown for callers to handle.
Implicit Type Coercion
Languages that silently convert between types allow logically invalid code to pass without complaint. PHP's loose comparison (== instead of ===) is a classic source: "1admin" == 1 evaluates to true in PHP because the string is coerced to an integer. TypeScript without strict mode permits implicit any, removing type safety entirely from affected code paths.
The DBF defence is strict compiler and analyser configuration applied across the entire codebase — not left as file-by-file opt-in.
Language Implementations
Each major language has mature static analysis tooling. The DBF approach starts with enabling the strictest available standard configuration, then adds custom rules for patterns the standard configuration misses.
PHP: PHPStan at Maximum Level
PHPStan is the leading static analyser for PHP. Setting level: max enables every available check. The strict-rules extension adds constraints that catch the silent-default and empty-catch patterns that base PHPStan misses.
Every PHP file must also declare strict types at the top: declare(strict_types=1);. Without this, PHP's type system is advisory rather than enforced, and implicit coercion can occur even with PHPStan passing cleanly.
TypeScript: Strict Mode Plus Additional Flags
TypeScript's strict flag enables a cluster of checks including strictNullChecks and noImplicitAny. Several additional flags beyond strict catch patterns that strict mode alone misses.
noUncheckedIndexedAccess is particularly powerful: array index access returns T | undefined instead of T, forcing explicit handling of the case where the index does not exist — a common source of silent runtime errors that strict mode alone does not catch.
Building Custom Rules
Standard configurations catch common patterns. Custom rules encode organisation-specific knowledge — lessons from your particular bug history that no generic tool would know to check. This is the heart of DBF: your codebase accumulates a permanent record of every significant bug class it has encountered.
Creating a custom ESLint rule for TypeScript takes roughly 30–60 minutes for a developer familiar with the tooling. The rule runs on every file, on every commit, for the lifetime of the project.
The rule above bans ?? '' and ?? 0 and provides a clear error message explaining the reasoning. Future developers — and AI assistants — see the error and understand why the pattern is rejected, not just that it is.
A custom rule is institutional knowledge that cannot be lost, forgotten, or overridden by a new hire.
The Ratchet Effect
Codebases with an active DBF practice undergo a structural change over time. Each rule added is permanent — a ratchet tooth that moves the quality floor upward and keeps it there. Rules represent lessons encoded into the build process; they are never removed.
In year one, rules surface violations as legacy code is addressed. In year two, new code is written clean from the start — developers have internalised the constraints. By year three, the codebase has structural memory of every significant bug class it has encountered. Code review focuses on logic rather than pattern policing. New developers are constrained to safe patterns from their first commit, without reading incident reports from years past.
The cumulative effect is a codebase where entire categories of bugs become structurally impossible to introduce — not because people are more careful, but because the build refuses them.
DBF and AI Coding Tools
DBF has always mattered. It matters considerably more in the age of AI-assisted development.
AI coding tools generate code at a pace that overwhelms traditional human review. They reproduce patterns from their training data, which includes vast quantities of code written before modern static analysis standards existed. They will happily autocomplete const username = user.getName() ?? '' because that pattern appears in millions of training examples.
DBF rules act as a quality floor that AI-generated code must clear before it can be committed. The static analyser does not care whether a pattern was written by a human or an AI — the rule fails either way. This makes AI coding tools substantially safer to use at scale, without proportionally increasing the review burden on your team.
DBF turns your static analyser into a quality filter for AI-generated code.
Starting Your DBF Practice
DBF does not require a major tooling overhaul. Most codebases already run some form of linting or static analysis — the question is whether that tooling is at its strictest available configuration, and whether custom rules are being written when bugs are found.
Three changes have the highest immediate impact:
- Enable the strictest standard configuration for your language: PHPStan level max, TypeScript strict mode with additional flags, mypy strict. Address violations incrementally — a codebase under analysis is better than none.
- Establish a team norm: when a bug is fixed, a static analysis rule is written first. Make it part of the definition of done for every bug fix.
- Write your first custom rule for the most recent significant bug your team found. This builds the habit and demonstrates the pattern to the rest of the team.
The full methodology — including PHPStan and ESLint rule examples, the bug-to-rule pipeline, and the quality priority hierarchy — is documented in the original article on ltscommerce.dev.
[src]Ready to eliminate your technical debt?
Transform unmaintainable legacy code into a clean, modern codebase that your team can confidently build upon.