Table of Contents
📖 2 minutes read
Detect Code Smells During Refactoring: The 4-Parameter Rule
When refactoring legacy code, it’s easy to get lost in the weeds. One simple heuristic I use: methods with 4+ parameters are a code smell.
Why 4+ Parameters is a Red Flag
Methods with many parameters suffer from:
- Poor readability: Hard to remember parameter order
- High coupling: Too many dependencies
- Testing difficulty: Combinatorial explosion of test cases
- Maintenance burden: Every new requirement = another parameter
Example: Before Refactoring
class InvoiceHandler
{
public function processInvoice(
$invoiceId,
$customerId,
$amount,
$currency,
$taxRate,
$discountCode,
$paymentMethod
) {
// 7 parameters = code smell!
// Logic here...
}
}
This is hard to call:
$handler->processInvoice(
123, // invoiceId
456, // customerId
99.99, // amount
'USD', // currency
0.08, // taxRate
'SAVE10', // discountCode
'credit_card' // paymentMethod
);
Positional parameters force you to count and remember order. Miss one? Runtime error.
Solution 1: Introduce a Value Object
class InvoiceData
{
public function __construct(
public readonly int $invoiceId,
public readonly int $customerId,
public readonly float $amount,
public readonly string $currency,
public readonly float $taxRate,
public readonly ?string $discountCode,
public readonly string $paymentMethod
) {}
}
class InvoiceHandler
{
public function processInvoice(InvoiceData $data)
{
// Single parameter!
// Access via $data->amount, $data->currency, etc.
}
}
Now the call site is self-documenting:
$data = new InvoiceData(
invoiceId: 123,
customerId: 456,
amount: 99.99,
currency: 'USD',
taxRate: 0.08,
discountCode: 'SAVE10',
paymentMethod: 'credit_card'
);
$handler->processInvoice($data);
Solution 2: Builder Pattern (for complex construction)
class InvoiceBuilder
{
private int $invoiceId;
private int $customerId;
private float $amount;
private string $currency = 'USD';
private float $taxRate = 0.0;
private ?string $discountCode = null;
private string $paymentMethod = 'credit_card';
public function forInvoice(int $id): self
{
$this->invoiceId = $id;
return $this;
}
public function forCustomer(int $id): self
{
$this->customerId = $id;
return $this;
}
public function withAmount(float $amount, string $currency = 'USD'): self
{
$this->amount = $amount;
$this->currency = $currency;
return $this;
}
public function withDiscount(string $code): self
{
$this->discountCode = $code;
return $this;
}
public function build(): InvoiceData
{
return new InvoiceData(
$this->invoiceId,
$this->customerId,
$this->amount,
$this->currency,
$this->taxRate,
$this->discountCode,
$this->paymentMethod
);
}
}
// Usage
$data = (new InvoiceBuilder())
->forInvoice(123)
->forCustomer(456)
->withAmount(99.99)
->withDiscount('SAVE10')
->build();
$handler->processInvoice($data);
The Refactoring Audit
During any refactoring session, run this audit:
- Find all methods with 4+ parameters
- Check if parameters are related (they usually are)
- Group related parameters into value objects
- Update call sites to use named parameters or builders
This simple rule catches bloated methods early and guides you toward cleaner abstractions.
The Rule
4+ parameters = time to introduce a value object or builder.
Your future self will thank you.
Leave a Reply