Originally published on phpdesignpatterns.dev — part of the PHP design patterns course. View this lesson on the original site.
Working code is the floor, not the ceiling
The discount calculator from the last lesson really does charge the right totals; your suite is not lying about the branches you exercised.
Because working code is the floor, not the ceiling. The calculator's cost shows up where tests rarely look: review time, onboarding, and the fear of touching branch four when branch five changes.
Before
As a reminder, this is the calculator from the previous lesson:
<?php
final class DiscountCalculator
{
public function calculate(string $customerType, int $totalInPence): int
{
if ($customerType === 'guest') {
return 0;
}
if ($customerType === 'standard') {
if ($totalInPence > 5000) {
return (int) round($totalInPence * 0.05);
}
return 0;
}
if ($customerType === 'loyalty') {
return (int) round($totalInPence * 0.10);
}
if ($customerType === 'loyalty_plus') {
$discount = (int) round($totalInPence * 0.15);
return min($discount, 2000);
}
if ($customerType === 'employee') {
return (int) round($totalInPence * 0.40);
}
if ($customerType === 'partner') {
$base = (int) round($totalInPence * 0.20);
return $base + 500;
}
return 0;
}
}
Keep that file in your head as we work through the costs below.
What this kind of code costs you
- Reading time on every change. Adding
studentdiscounts means re-reading the other six branches to make sure your new branch doesn't conflict, doesn't shadow them, and doesn't accidentally fall through. - Test setup that grows linearly with branches. One test class is now checking six rules. A bug in branch four can be hidden by a test that exercises branch one.
- Pull request reviews that take longer. A reviewer has to verify that the new branch is correct and that the existing branches still behave as before.
- Onboarding cost. A new starter sees a 60-line
calculatemethod and has to infer the domain rules from the structure ofifstatements. - Risk of accidental regression. Refactoring one branch is easy to get wrong because the branches share a method body.
The unit of change is a single rule
Look at the Before code: when the business changes the rule for loyalty_plus, you want to edit one place. Instead the file structure makes you reopen the calculator and scroll past every other branch. The unit of change (one customer's rule) does not match the unit of code (one big method).
That mismatch is the real cost. Strategy fixes it by giving each rule its own home — its own file, its own class, its own test, its own PR diff when it changes.
The next lesson shows the After.
From PHP design patterns — Strategy: replacing branching with swappable behaviour