Defensive Programming
debt(d5/e5/b5/t5)
Closest to 'specialist tool catches' (d5). PHPStan and Psalm (from detection_hints.tools) can detect missing type declarations, overly broad catch blocks, and some validation gaps through static analysis. However, the pattern 'external data used without validation' requires careful configuration and won't catch all cases — many defensive programming violations only surface in code review or runtime testing.
Closest to 'touches multiple files / significant refactor in one component' (e5). The quick_fix mentions validating at system boundaries and using guard clauses — implementing this properly means adding validation layers at HTTP controllers, queue handlers, and file readers. It's not a single-line fix but a systematic approach across input entry points, though typically contained within boundary code rather than architectural rework.
Closest to 'persistent productivity tax' (b5). Applies to web, cli, and queue-worker contexts per applies_to. Defensive programming is a cross-cutting concern that shapes how every boundary interaction is written. Done well, it becomes habit; done poorly (validating at every layer per common_mistakes), it creates noise that slows down many work streams. The choice of where to trust vs. validate becomes load-bearing.
Closest to 'notable trap' (t5). The misconception field explicitly states the trap: developers assume defensive programming means 'validating every input with the same intensity' when it actually means 'calibrating defences to trust level.' This is a documented gotcha that most devs eventually learn — external input needs maximum validation while internal module calls rely on contracts, not redundant checks.
Also Known As
TL;DR
Explanation
Defensive programming assumes that callers will pass invalid data, external services will fail, and unexpected states will occur. Defences include: validating all input at trust boundaries, checking postconditions of external calls, handling all branches of switch/match, using guard clauses to reject invalid state early, and writing tests for edge cases. Taken too far, defensive programming produces code cluttered with redundant checks — balance it with Design by Contract, which makes assumptions explicit rather than defending against everything everywhere.
Common Misconception
Why It Matters
Common Mistakes
- Not validating method arguments — passing null or out-of-range values causes failures far from the source.
- Catching Exception too broadly and suppressing errors instead of handling them specifically.
- Defensive checks at every layer instead of trusting validated input from the boundary — creates noise.
- Not using PHP type declarations and strict_types=1 which provide free defensive checks at the language level.
Code Examples
function divide(int $a, int $b): float {
return $a / $b; // crashes with division by zero, no guard
}
function divide(int $a, int $b): float {
if ($b === 0) {
throw new \DivisionByZeroError('Divisor cannot be zero');
}
return $a / $b;
}
// For value objects, validate in constructor so callers can't create invalid state:
readonly class Divisor {
public function __construct(public int $value) {
if ($value === 0) throw new \DivisionByZeroError();
}
}