Detect Code Smells During Refactoring: The 4-Parameter Rule

📖 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:

  1. Find all methods with 4+ parameters
  2. Check if parameters are related (they usually are)
  3. Group related parameters into value objects
  4. 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.

Daryle De Silva

VP of Technology

11+ years building and scaling web applications. Writing about what I learn in the trenches.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *