Keeping it Simple 


 An overview of static code analysis 








Ryan Kois
@kid_ic4rus
http://ryankois.com

The Problem With Complex Code

  • Easier to write,  far more difficult to maintain.
  • Requires more time to read and comprehend.
  • Inhibits code re-use.
  • More difficult to test.
  • Positive correlation to defective code.

What is Complexity?

Many factors, let's focus on
  • Cyclomatic Complexity
  • Npath Complexity

Cyclomatic Complexity



                                     For loop ->


                            

                                 Condition ->



Number of independent linear paths through code.


Example


// 1 point for the function declaration
function foo($list) {
  // 1 point for any for, foreach, or while
  foreach ($list as $item) {
    // 1 point for any if, elseif, case
    if ($item == 'foo') {
      return 'bar';
    }
    else if ($item == 'bar') {
      return 'baz';
    }
    //no points given for else, this represents default execution path
    else {
      return 'default';
    }
  }
}
Cyclomatic Complexity: 4

What is the cyclomatic complexity of this function?

function complex_form_alter(&$form, &$form_state, $form_id) {
  switch ($form_id) {
    case 'search_form':
      if (user_is_logged_in()) {
        array_unshift($form['#submit'], 'complex_submit');
      }
      break;

    case 'article_node_form':
      drupal_set_title('This is an article form!');
      break;

    default:
      watchdog('complex', 'someone accessed a form!');
      break;
  }
}

Npath complexity


The number of acyclic execution paths through a function...





Wat?

Example

function foo($foo, $bar) {
    // First conditional, two possible outcomes  
    if ($foo > 100) {
        echo 'path 1';
    } else {
        echo 'path 2';
    }
    // Second conditional, two possible outcomes
    if ($foo > $bar) {
        echo 'path 3';
    } else {
        echo 'path 4';
    }
}
(1 conditional * 2 outcomes) * (1 conditional * 2 outcomes) = 4

Example 2

function foo($foo, $bar) {
    // First conditional, two possible outcomes  
    if ($foo > 100) {
        echo 'path 1';
    } 
    else {
        echo 'path 2';
    }
    // Second conditional, two possible outcomes  
    if ($foo > 200) {
      echo 'path 3';
    } 
    else {
      echo 'path 4';
    }
    // Third conditional, two possible outcomes
    if ($foo > $bar) {
        echo 'path 5';
    } else {
        echo 'path 6';
    }
}
3 conditionals with 2 outcomes each:
 (2 * 2 * 2) = 8
NOT 6! NPath complexity grows exponentially :O

Why should i care? 

all computers suck anyway.


I know they do, but we can ease the pain a bit through the power of refactoring.

But first, lets add a valuable tool to our toolbox.

phpmd

  • Written by By Manuel Pichler
  • FOSS
  • Requires PHP >= 5.2.3

Installation
 composer global require 'phpmd/phpmd'

    Usage

     phpmd [filename|directory] [report format] [ruleset file]

    Report formats
    • xml, which formats the report as XML.
    • text, simple textual format.
    • html, single HTML file with possible problems.

    Rulesets
    http://phpmd.org/rules/index.html for more info
     php foo.php text codesize


    Example output

    Cyclomatic complexity too high

    foo.php:2 The function foo() has a Cyclomatic Complexity of 17. The configured cyclomatic complexity threshold is 10.           



    NPath complexity too high

    foo.php:20  The function bar() has an NPath complexity of 226. The configured NPath complexity threshold is 200.

    TL;DR

    Don't create the pyramid of doom in your code.

    The Pyramid of doom

    if ($something) {
      foreach ($list as $item) {
        if ($item == 'foo') {
          foreach ($other_list as $other_item) {
            while ($yet_another = get_another_thing()) {
              if ($yet_another == 'bar') {
                print 'AAAAAAAAHHHHH!!!!!!';
              }
            }
          }
        }
      }
    }
    

    Functional Programming to the rescue!

    Use the Extract Method refactoring pattern  in order to extract new functions where conditionals and loops once existed.

     We can save the code and our minds! 

    EXAMPLE

    function foo($words) {
      $contains_long_word = false;
      foreach ($words as $word) {
        if (strlen($item) >= 20) {
          $contains_long_word = true;
        }
      }
      if ($contains_long_word) {
        foreach ($words as $key => $word) {
          $words[$key] = strtolower($words[$key);
        }
      }
    }
    

    refactored

    function array_some($arr, $fn) {
      foreach ($arr as $item) {
        if (!$fn($item)) {
          return false;
        }
        return true;
      }
    }
    function is_long_word($word) {
      return (strlen($word) >= 20);
    }
    
    function foo($words) {
      $has_long_word = array_some($words, 'is_long_word');
      if ($has_long_word) {
        return array_map('strtolower', $words);
      }
      return $words;
    }
    

    Practical drupal examples

    • If, else if, switch statements in hook_form_alter()
      • Use hook_form_FORM_ID_alter() instead
    • Conditionals inside theme overrides or templates
      • Use theme hook and template suggestions
        • block__module
        • block__module__delta
        • example.com/page/something
          • page--something.tpl.php

    go forth and create beautiful code!



    Thank you for listening!

    ☜(:༎ຶ;益;༎ຶ;)☞