Escape from Parameter Hell

I’ve been doing a lot of interface design, of late. When first designing a system, it’s pretty easy to keep interfaces simple. It’s easy enough to write functions and methods such that they only need a few things:

function do_action(Person $person$action)
{} 

But as design progresses, or as you use a system, interfaces change. You need to pass more information. Parameters previously required become optional. Eventually, you end up with function signatures like this:

function do_action(
    
Person $person_doing
    
$action
    
Person $person_done_to NULL
    
$at_time 'now'
    
$with_implement 'voice',
    
$with_coconuts FALSE,
    
$are_repressing_the_peasantry TRUE)
{} 

Welcome to Parameter Hell. It gets even more obfuscated when you run across the function call:

do_action($king_arthur'run away'NULL'now''legs'TRUEFALSE); 

In plotting your escape, the very first thing to consider is that your interface might be trying to do too much. It may need to be broken down into multiple methods, or possibly moved on to an object (or a different object). In our example, this is clearly the case. The first thing I would consider doing is moving the function to the Person class:

class Person {

    
// -- SNIP --

    
public function doAction($action
        
$person_done_to NULL
        
$at_time 'now'
        
$with_implement 'voice, 
        $with_coconuts = FALSE, 
        $are_repressing_the_peasantry = TRUE)
    {}

So now the call would look like this:

$king_arthur->doAction('run away'NULL'now''legs'TRUEFALSE); 

A little more clear. The next step might be to split this method into two methods:

public function doAction(
    
$action
    
$at_time='now'
    
$with_implement='voice'
    
$with_coconuts FALSE
    
$are_repressing_the_peasantry TRUE)
{}

public function doActionTo(
    
Person $person
    
$action$at_time='now'
    
$with_implement='voice'
    
$with_coconuts FALSE
    
$are_repressing_the_peasantry TRUE)
{} 

Giving us:

$king_arthur->doAction('ride''now''legs'TRUEFALSE);
$guardsman->doActionTo($king_arthur'question''now''voice'FALSEFALSE); 

With these sorts of small refactors, you can gradually move your way out of Parameter Hell and back to reasonable interfaces.

Sometimes a function or method can’t be easily split. In that case, the next step is to examine the information it is taking. Does it stick together throughout a request? Does it (or some subset of it) hold together conceptually? If the answer to either of those questions is “yes”, then the next move is to take that subset and make a new class.

In our example case, if we find that $action tends to be associated with a time and an implement in our code, then it might make sense to make a new Action class:

class Action {
    
public $what;
    public 
$when 'now';
    public 
$with 'voice';

Now our method signatures look like this:

public function doAction(Action $action$with_coconuts FALSE$are_repressing_the_peasantry TRUE)
{}

public function doActionTo(Person $personAction $action$with_coconuts FALSE$are_repressing_the_peasantry TRUE)
{} 

Which gives us the following calls:

$the_french->doActionTo($king_arthur$insultFALSEFALSE);
$king_arthur->doAction($run_awayTRUEFALSE); 

If there isn’t a new class that can be easily created from the parameters, or if you still have the odd configurational parameter hanging around (like we do), then we still have one more trick up our sleeves. The options array. We put the remaining parameters into a single associative array, usually called $options or $config. We name them this way intentionally, things that end up in $options arrays should be just that, optional configuration parameters.

Using an associative array lets you ignore the parameters you don’t need at the moment. You also don’t have to worry about parameter order. Each parameter is associated with its name. And if you need defaults, you can always compare the options array to a $defaults array inside the method. It’s a substantial improvement.

The method signatures become this:

public function doAction(Action $action, array $options=array())
{
    $defaults 
= array(
        
'with_coconuts' => FALSE,
        
'are_repressing_the_peasantry' => TRUE
    
);   
    
// Later values (the ones in options) will overwrite earlier values
    // (the ones in $defaults).
    
$options array_merge($defaults$options); 

    
// --- SNIP ---
}

public function doActionTo(Person $personAction $action, array $options=array())
{
    $defaults 
= array(
        
'with_coconuts' => FALSE,
        
'are_repressing_the_peasantry' => TRUE
    
);   
    
// Later values (the ones in options) will overwrite earlier values
    // (the ones in $defaults).
    
$options array_merge($defaults$options); 

    
// --- SNIP ---

And the calls look like this:

$options = array(
    
'with_coconuts' => TRUE,
    
'are_repressing_the_peasantry' => FALSE,
);
$king_arthur->doAction($run_away$options);

$options = array(
    
'with_coconuts' => FALSE,
    
'are_repressing_the_peasantry' => TRUE
);
$king_arthur->doActionTo($dennis$repress$options); 

You do have to be careful with options arrays. Storing data in associative arrays can be dangerous in large or long running projects. Associative arrays are not strictly defined. They’re fluid. In a long running project, the definition of the array can become separated from its use. It gets defined and populated. Then more stuff gets added to it. It gets passed to a method, which removes some stuff and adds more, before passing it to another method and so on.

When the new developer goes to fix a bug in a method and looks at the array coming in, he has no idea what is in that array. The data being passed around has been effectively obfuscated. He has to go digging back through code to find the structure of the array before he can fix the bug.

There are a number of things you can do to mitigate this problem.

The defaults array we mentioned helps. It can define the structure, provided you have a default for every possible value.

You can also try to create and populate the options array as close to the method call as possible:

$options = array(
    
'with_coconuts' => TRUE,
    
'are_repressing_the_peasantry' => FALSE,
);
$king_arthur->doAction($run_away$options); 

It’s not always possible to create it right next to the method call, sometimes you have to do work to populate it. But try to keep it close.

Once inside, don’t let it live beyond the method. Don’t return it and don’t pass it on. If you need to send some of those options on to another method, build a new array.

The final thing you can do is make sure to thoroughly document every array passed to any method. Don’t do this:

* @param  mixed[] $options  (OptionalAn array of options

Do this:

* @param  mixed[] $options  An array of options for the actionwith the
*   following possible fields:
*     
'with_coconuts' - (OptionalIs the action being performed using coconuts
*     to mimic the sound of a horse hoof?  Do coconuts migrate?
*     
'are_repressing_the_peasantry' -  (OptionalIs a peasant being repressed
*     through this action

That way a developer coming into the method doesn’t have to go farther than the docblock to know exactly what to expect to be in that array. The catch, of course, being that you have to keep the docblock up to date.

Finding yourself in Parameter Hell is a good indication that it’s time to stop and refactor. Sometimes it’s just that one function signature that got out of hand, but often you’ll find many interfaces that need to be rearranged. You can do it gradually. In bits and pieces, one change at time. When you’re finished the result will be a set of interfaces that are much more readable, much more maintainable and should make life much easier on you. At least, until the next time you find yourself in the depths of (in a meanacing, echoy voice) Parameter Hell.

.(JavaScript must be enabled to view this email address) or share your feedback on this entry with @ellislab on Twitter.