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';
}
}
}
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';
}
}
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';
}
}
(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!
☜(:༎ຶ;益;༎ຶ;)☞
Keeping It Simple
By kid_icarus
Keeping It Simple
- 3,934