Measuring PHP code complexity
What makes good code and why is some code better than others? One quantitative approach to answer this is the use of software metrics. These metrics try to capture the size and complexity of code in numbers (e.g. ‘lines of code‘, ‘cyclomatic complexity‘) and can be useful indicators for maintainability and simplicity (or more often the lack thereof).
I found three ways to get complexity measures for PHP code.
The most advanced technique is to use a continuous integration server. One good example is the server at test.pear.php.net which uses CruiseControl (a Java CI server) with phpUnderControl for the PHP specific parts. Certainly a nice solution for big projects, but nothing to use on your own laptop to improve a single package.
Often the easiest way is to use PHP_CodeSniffer, which includes checks for cyclomatic complexity and nesting level (causing warnings and errors above some thresholds). If you already use it to maintain a uniform coding style (e.g. PEAR, Zend) then you just have to add these checks to your standard definition. These commands add metrics to the PEAR standard:
cd /usr/local/share/pear/PHP/CodeSniffer/Standards sudo cp -R Generic/Sniffs/Metrics PEAR/Sniffs sudo sed -I .bck -e 's/Squiz_/PEAR_/' PEAR/Sniffs/Metrics/* |
The program PDepend creates a more detailed analysis of methods, classes, and projects (see this release note for details). Because it is intended as a component within a CI server its primary output is in XML. So I wrote myself a small wrapper (pdepend_summary.php) to format the data as an ASCII table. This allows for quick edit-check-cycles and now I run it along with phpcs and phpunit before I commit PEAR code.
Example output:
Package Class LoC %Comment Variables Methods Class_SiZe WMC ------- ----- --- -------- --------- ------- ---------- --- Payment_DTA DTA 1084 36.3 3 15 114 111 Payment_DTA DTAZV 757 37.1 1 12 72 71 Payment_DTA DTABase 563 33.0 6 19 33 27 Payment_DTA Payment_DTA_ChecksumException 3 0.0 0 0 0 0 Payment_DTA Payment_DTA_FatalParseException 3 0.0 0 0 0 0 Payment_DTA Payment_DTA_Exception 3 0.0 0 0 0 0 Payment_DTA Payment_DTA_ParseException 3 0.0 0 0 0 0 Package/Class Method LoC %Comment CCN NPath ------------- ------ --- -------- --- ----- Payment_DTA/DTA _generateCrecord 158 25.9 13 1600 Payment_DTA/DTA _parseCextension 81 30.9 13 93 Payment_DTA/DTA _processCextension 47 2.1 8 8 Payment_DTA/DTA parse 59 35.6 7 36 Payment_DTA/DTAZV _exchangeFillSender 33 0.0 7 64 Payment_DTA/DTAZV parse 41 17.1 7 36 Payment_DTA/DTAZV _exchangeFillReceiver 26 7.7 6 24 Payment_DTA/DTA _exchangeFillArrays 31 0.0 6 32 Payment_DTA/DTA _parseCrecord 109 27.5 6 72 Payment_DTA/DTAZV setAccountFileSender 41 0.0 5 15 Payment_DTA/DTA _parseErecord 49 30.6 5 16 Payment_DTA/DTABase getStr 18 0.0 4 8 Payment_DTA/DTAZV __construct 19 10.5 4 4 ... |