Monday,
22nd February 2010
Article in Spanish

Refactoring php code with too much indentation


Too much indentation When programming, it has a lot of advantages to write short and concise lines:

  • We can see the line at once without having to scroll.
  • We are forced to simplify our solution.
  • To use with other programs like diff We can see two or more documents side by side.

Example

This example function expects an element of type array or var and an optional boolean argument. It returns the value multiplied by 2. If it is an array it doubles the value of each of its elements. If the optional argument is true it returns the original value and the doubled one in an array.

function duplicate($elem, $return_both= false) {
    if(is_array($elem)) {
        $ret= array();
        foreach($elem as $val) {
            if($return_both) {
                $ret[]= array($val*2,$val);
            } else {
                $ret[]= $val*2;
            }
        }
        return $ret;
    } else {
        if($return_both) {
            return array($elem*2,$elem);
        } else {
            return $elem*2;
        }
    }
}

We can write this code in many different ways to make it simple and easier to understand.

Types

We can see that part of the complexity of the code is due to the fact that $elem is an array or another type of variable. It could be always an array.

function duplicate($elem, $return_both= false) {
    if(!is_array($elem)) {
        $elem= array($elem);
    }

    $ret= array();
    foreach($elem as $val) {
        if($return_both) {
            $ret[]= array($val*2,$val);
        } else {
            $ret[]= $val*2;
        }
    }
    return $ret;
}

In this example we changed the function, if we pass a non-array element we get an array. We could change it so that it returns an element in this situation:

function duplicate($elem, $return_both= false) {
    if(!is_array($elem)) {
        $elem= array($elem);
    }

    $ret= array();
    foreach($elem as $val) {
        if($return_both) {
            $ret[]= array($val*2,$val);
        } else {
            $ret[]= $val*2;
        }
    }

    if(count($ret) > 1) {
        return $ret;
    } else {
        return $ret[0];
    }
}

This way the element is returned only if there is one element.

Multiple returns

One technique to avoid too much indentation is to use multiple returns. Even if using a single point of return has its advantages, sometimes it is more convenient to use more than one.

function duplicate($elem, $return_both= false) {
    if(!is_array($elem)) {
        $elem= array($elem);
    }

    $ret= array();
    foreach($elem as $val) {
        if($return_both) {
            $ret[]= array($val*2,$val);
        } else {
            $ret[]= $val*2;
        }
    }

    if(count($ret) == 1) {
        return $ret[0];
    }

    return $ret;
}

Conditional logic

Reorganising conditions is a good way to change our perspective of a problem. We can also shorten some conditions very clearly using the ternary operator

function duplicate($elem, $return_both= false) {
    $elem= is_array($elem)? $elem : array($elem);

    $ret= array();
    foreach($elem as $val) {
        if($return_both) {
            $ret[]= array($val*2,$val);
        } else {
            $ret[]= $val*2;
        }
    }

    $ret= count($ret) == 1? $ret[0]: $ret;
    return $ret;
}

Functions on arrays

PHP has a lot of functions to work with arrays and they are qutie useful. We can use them instead of some for loops and friends. This time we will use array_map to process the array elements.

function dup($v) {
    return array($v*2, $v);
}
function get_first($v) {
    return $v[0];
}
function duplicate($elem, $return_both= false) {
    $elem= is_array($elem)? $elem : array($elem);

    $ret= array();
    $b = array_map('dup', $elem);
    //Now $b is an array of arrays

    if($return_both) {
        $ret= $b;
    } else {
        $ret= array_map('get_first',$b);
    }

    $ret= count($ret) == 1? $ret[0]: $ret;
    return $ret;
}

To avoid traversing the array multiple times we calculate everything first and later we filter it.

Anonymous functions (or lambdas)

We created a dup function to use in duplicate function. What we were looking for was a disposable function not accessable from other code. From 5.3 version it is possible to use lambda functions in PHP. This way we won't pollute the namespace.

function duplicate($elem, $return_both= false) {
    $elem= is_array($elem)? $elem : array($elem);
    $ret= array();

    $dup= function($v) { return array($v*2, $v); };
    $get_first= function($v) { return $v[0]; };

    $b= array_map($dup, $elem);

    if($return_both) {
        $ret= $b;
    } else {
        $ret= array_map($get_first,$b);
    }

    $ret= count($ret) == 1? $ret[0]: $ret;
    return $ret;
}

If we check conditions again, we can improve them with:

function duplicate($elem, $return_both= false) {
    $elem= is_array($elem)? $elem : array($elem);
    $ret= array();

    $dup= function($v) { return array($v*2, $v); };
    $get_first= function($v) { return $v[0]; };

    $ret= array_map($dup, $elem);
    if(!$return_both) {
        $ret= array_map($get_first,$b);
    }

    $ret= count($ret) == 1? $ret[0]: $ret;
    return $ret;
}

Alternative options

These options are just suggestions and depend on each one style and the kind of problem to tackle. Variety is the spice of life. Another option, f.i. is this hack:

function duplicate($elem, $return_both= false) {
    //this way we can pass multiple values
    $c= array($elem,$return_both);

    switch($c) {
        case (is_array($c[0])):
        case ($c[1]):
            foreach($elem as $val) {
                $ret[]= array($val*2,$val);
            }
            return $ret;
        case (!$c[1]):
            foreach($elem as $val) {
                $ret[]= $val*2;
            }
            return $ret;
        break;
        case (!is_array($c[0])):
        case ($c[1]):
            return array($elem*2,$elem);
        case (!$c[1]):
            return $elem*2;
        break;
    }
}

This time we take advantage of the cascade execution without break and also of passing multiple parameters to switch. We have to consider too that in some months ahead we or another person have to be able to understand the code we wrote easily.

blog comments powered by Disqus
Bookmark and Share