Technik, Gothic und Anderes

Technik ist Spiel, Gothic ist ernst und Zeit hat man zuviel

  • Kategorien

  • Tags

  • Archiv

  • Links

    zu Bee5

    blog.oncode.info läuft bei Cyon und ich bin sehr glücklich damit.

PHP Code-Analyse mit Ant

Geschrieben von skaldrom am 2. März 2011


Warning: Cannot use a scalar value as an array in /home/oncodein/public_html/blogoncodeinfo/wp-content/plugins/organize-series/orgSeries-template-tags.php on line 46

Warning: Invalid argument supplied for foreach() in /home/oncodein/public_html/blogoncodeinfo/wp-content/plugins/organize-series/orgSeries-template-tags.php on line 55
Dieser Beitrag ist Teil 1 von 2 in der Serie Continuous Integration

    Graph LeadIch mag Code. Nicht jeden natürlich, “Douchebag” Code nervt, Code der aussieht wie ein schreiender Quasimodo oder Code der von einem Dr. Frankenstein in Ausbildung erstellt wurde sind bemitleidenswert und sehr viel Code stinkt. Code, der gefällt (wie beispielsweise der von Frigidor) ist clever ohne zu bluffen, kurz aber nicht kryptisch, tut etwas, ist lesbar und hat einige Wows drin.

    Diese Bewunderung sollte in Zahlen gefasst werden: mit Statistiken, Diagrammen, Balken und Graphen. Dies dient zur Vorbereitung der “Continuous Integration” mit Jenkins/Hudson, die in dieser Serie behandelt wird. Als Quellen haben vor allem jenkins-php und ein äusserst Lesbarer Artikel im Entwickler Magazin 01/11 gedient.

    Hier wird nun gezeigt, wie mit einem Befehl:

    • Die Unittests durchführt.
    • Qualitative Softwaremetriken mit PHP Depend misst: Hierarchietiefe, Komplexität, …
    • Unschöne Teile mit PHPMD identifiziert.
    • Copy-Paste-Verbrecher aufspührt mit phpcpd.
    • Den Coding-Style prüft mit dem PHP Code Sniffer.
    • Quantitative Softwaremetriken mit phploc misst.
    • Die PHPDoc-Doku erstellt.
    • Die Resultate mit dem PHP Code Browser schön darstellt.

    PEAR Tools installieren

    PEAR stellt viele Hilfsprogramme zur Analyse zur Verfügung. Darum muss zuerst pear und danach diese Tools installiert werden. Am Besten als Root, wenn man nicht ein riesen Chaos verursachen möchte:

    sudo pear upgrade PEAR
    sudo pear channel-discover pear.pdepend.org
    sudo pear channel-discover pear.phpmd.org
    sudo pear channel-discover pear.phpunit.de
    sudo pear channel-discover components.ez.no
    sudo pear channel-discover pear.symfony-project.com

    sudo pear install Text_Highlighter-0.7.1
    sudo pear install pdepend/PHP_Depend
    sudo pear install phpmd/PHP_PMD
    sudo pear install phpunit/phpcpd
    sudo pear install phpunit/phploc
    sudo pear install PHPDocumentor
    sudo pear install PHP_CodeSniffer
    sudo pear install --alldeps phpunit/PHP_CodeBrowser
    sudo pear install --alldeps phpunit/PHPUnit

    Ant build Files

    Um den Build zu steuern, eignet sich Ant sehr gut. Zwar ist es XML (Anwender sollen kein XML schreiben müssen!), stellt aber ein paar vernünftige Funktionen bereit. Für einen Build braucht es eine Properties- und eine XML-Datei, die am Besten in ein eigenes build Verzeichnis verfrachtet werden. In der Propertiesdatei können Variablen gesetzt und eventuell auf verschiedene Umgebungen reagiert werden. Bei mir heisst sie build.properties und sieht ziemlich einfach aus:

    # Source directory
    source=${basedir}/../htdocs/code
    # Target directory
    builddir=${basedir}/statistics
    # Where are the Unittests
    unittests=${basedir}/tests
    testsuite=suite.php
    # 10: Highest priority, 1: Lowest
    niceness=1
    # Set to a random String if you have no external libraries. PHPCPD accepts only ONE directory
    extlib=inc/external
    phpdocextlib=*${extlib}/*
    extlibs=${extlib},\\.svn
    # Files to consider
    suffixes=php
    # Ruleset for Code Sniffer
    csruleset=${basedir}/conf/syncic

    Hier sieht man auch schon eines der Hauptprobleme: Die Tools verwenden unterschiedliche Angaben um Verzeichnisse auszuschliessen: Pfade, RegExps, durch Komma getrennt, … Ziemlich mühsam.

    Die Build-Datei build.xml ist etwas lange, aber gut verständlich. Zuerst werden die abstrakten Ziele eingerichtet und dann gibt es für jedes Tool ein Target.

    <?xml version="1.0" encoding="UTF-8" ?>
    <project name="Easy-Form" basedir="." default="build">
      <property file="build.properties"/>
      <nice newpriority="${niceness}"/>
     
      <target name="build" depends="test, doc" />
     
      <target name="clean">
        <delete dir="${builddir}" />
      </target>
     
      <target name="prepare" depends="clean">
            <mkdir dir="${builddir}/logs" />
        <mkdir dir="${builddir}/logs/coverage" />
            <mkdir dir="${builddir}/html" />
            <mkdir dir="${builddir}/html/jdepend" />
            <mkdir dir="${builddir}/html/unittests" />
            <mkdir dir="${builddir}/html/experimental" />
            <mkdir dir="${builddir}/html/doc" />
            <mkdir dir="${builddir}/html/doc/api" />
            <mkdir dir="${builddir}/html/doc/codebrowser" />
      </target>
     
      <target name="test" depends="test-unit, test-static" />
     
      <target name="test-static" depends="prepare">
        <parallel threadCount="2">
          <sequential>
        <antcall target="test-static-jdepend"/>
        <antcall target="test-static-pmd"/>
          </sequential>
          <antcall target="test-static-cpd"/>
          <antcall target="test-static-checkstyle"/>
          <antcall target="test-static-phploc"/>
        </parallel>
      </target>
     
      <target name="doc" depends="prepare">
        <sequential>
          <antcall target="doc-phpdoc"/>
          <antcall target="doc-phpcb"/>
        </sequential>
      </target>
     
      <!-- PHPUnit Tests -->
      <target name="test-unit" depends="prepare">
        <exec executable="phpunit" failonerror="true" dir="${unittests}">
               <arg value="--log-junit" />
                <arg value="${builddir}/logs/junit.xml" />
                <arg value="--coverage-clover" />
            <arg value="${builddir}/logs/coverage/clover.xml" />
                <arg value="--coverage-html" />
            <arg value="${builddir}/logs/coverage" />
                <arg value="--testdox-html" />
                <arg value="${builddir}/html/unittests/index.html" />
                <arg value="--configuration" />
                <arg value="${phpunitconf}" />
                <arg value="${testsuite}" />
        </exec>
      </target>
         
      <!-- Static Code Analysis -->
      <target name="test-static-jdepend">
        <exec executable="pdepend" failonerror="false" dir="${source}">
          <arg value="--jdepend-xml=${builddir}/logs/jdepend.xml" />
          <arg value="--jdepend-chart=${builddir}/html/jdepend/dependencies.svg" />
          <arg value="--overview-pyramid=${builddir}/html/jdepend/pyramid-overview.svg" />
          <arg value="--summary-xml=${builddir}/logs/jdepend-summary.xml" />
          <arg value="--phpunit-xml=${builddir}/logs/jdepend-phpunit.xml" />
          <arg value="--ignore=${extlibs}" />
          <arg value="--suffix=${suffixes}" />
          <arg value="${source}" />
        </exec>
      </target>
     
      <target name="test-static-pmd">
        <exec executable="phpmd" failonerror="false" dir="${source}">
          <arg value="${source}" />
          <arg value="xml" />
          <arg value="codesize,design,naming,unusedcode" />
          <arg value="--reportfile" />
          <arg value="${builddir}/logs/pmd.xml" />
          <arg value="--suffixes" />
          <arg value="${suffixes}" />
          <arg value="--exclude" />
          <arg value="${extlibs}" />
        </exec>
      </target>
     
      <target name="test-static-cpd">
        <exec executable="phpcpd" failonerror="false" dir="${source}">
          <arg value="--log-pmd" />
          <arg value="${builddir}/logs/cpd.xml" />
          <arg value="--suffixes" />
          <arg value="${suffixes}" />
          <arg value="--exclude" />
          <arg value="${extlib}" />
          <arg value="${source}" />
        </exec>
      </target>
     
      <target name="test-static-checkstyle">
        <exec executable="phpcs" failonerror="false" dir="${source}">
          <arg value="--report=checkstyle" />
          <arg value="--report-file=${builddir}/logs/checkstyle-result.xml" />
          <arg value="--extensions=${suffixes}" />
          <arg value="--ignore=${extlibs}" />
          <arg value="--standard=${csruleset}" />
          <arg value="${source}" />
        </exec>
      </target>
     
      <target name="test-static-phploc">
        <exec executable="phploc" failonerror="false">
          <arg value="--log-csv" />
          <arg value="${builddir}/logs/loc.csv" />
          <arg value="--suffixes" />
          <arg value="php" />
          <arg value="--exclude" />
          <arg value="${extlib}" />
          <arg value="${source}" />
        </exec>
      </target>
     
      <!-- Documentation -->
      <target name="doc-phpdoc">
        <exec executable="phpdoc" failonerror="false" dir="${source}">
          <arg value="--directory"/>
          <arg value="${source}" />
          <arg value="--ignore" />
          <arg value="${phpdocextlib}" />
          <arg value="--target" />
          <arg value="${builddir}/html/doc/api" />
          <arg value="--output" />
          <arg value="HTML:frames:earthli" />
          <arg value="--title" />
          <arg value="${ant.project.name}" />
        </exec>
      </target>
     
      <target name="doc-phpcb">
        <exec executable="phpcb" failonerror="false">
          <arg value="--log"/>
          <arg value="${builddir}/logs" />
          <arg value="-i" />
          <arg value="${extlibs}" />
          <arg value="--output" />
          <arg value="${builddir}/html/doc/codebrowser" />
        </exec>
      </target>
    </project>

    Ist alles richtig eingerichtet, kann der Build mit dem Kommando ant gestartet werden.

    Hinweis: Das Coverage-XML und das Coverage-HTML müssen im gleichen Verzeichnis sein. Wenn der Clover-Report nicht funktioniert und ein 404 not found gibt, liegt es wahrscheinlich daran…

    Code Sniffer einrichten

    Der Stil des Codes (Einrücken, Benamsung, …) ist sehr persönlich. Sehrwahrscheinlich werden viele Warnungen gezeigt, mit denen man gar nicht einverstanden ist. Das ist aber kein Problem, denn der Codesniffer erlaubt es, eigene Codingstandards zu definieren. mit der neuen Version 1.3 (die mittels sudo pear install PHP_CodeSniffer-1.3.0RC2 installiert werden kann) geht das ziemlich einfach. Dafür brauch es in einem Grundverzeichnis eine ruleset.xml Datei und in einem Unterverzeichnis Sniff die eigenen Regeln, in PHP auscodiert. In ruleset.xml kann man sich frei an nestehenden Regeln bedienen. Meines sieht folgendermassen aus:

    <?xml version="1.0"?>
    <ruleset name="Syncic Standard">
    <rule ref="PEAR">
      <exclude name="Generic.WhiteSpace.DisallowTabIndent"/>
      <exclude name="PEAR.ControlStructures.ControlSignature"/>
    </rule>

    <rule ref="Generic.VersionControl.SubversionProperties"/>
    <rule ref="Generic.Classes.DuplicateClassName"/>
    <rule ref="Generic.Strings.UnnecessaryStringConcat"/>
    <rule ref="Generic.PHP.DeprecatedFunctions"/>
    <rule ref="Generic.PHP.ForbiddenFunctions"/>
    <rule ref="Generic.PHP.NoSilencedErrors"/>

    <rule ref="PEAR.WhiteSpace.ScopeIndent">
      <properties>
        <property name="indent" value="2"/>
      </properties>
    </rule>
    </ruleset>

    In dieser Datei übernehme ich vor allem den PEAR-Standard, konfiguriere etwas herum und mische ein paar allgemeine Regeln hinzu. Vorallem die Indentation mit Spaces wollte ich anpassen. Im Sniffs Verzeichnis habe ich meine eigenen Regeln. Beispielsweise ControlStructures/ControlSignatureSniff.php (angepasst vom PEAR Coding Standard:

    <?php
    /**
     * Verifies that control statements conform to their coding standards.
     */


    if (class_exists('PHP_CodeSniffer_Standards_AbstractPatternSniff', true) === false) {
        throw new PHP_CodeSniffer_Exception('Class PHP_CodeSniffer_Standards_AbstractPatternSniff not found');
    }

    /**
     * Verifies that control statements conform to their coding standards.
     */

    class syncic_Sniffs_ControlStructures_ControlSignatureSniff extends PHP_CodeSniffer_Standards_AbstractPatternSniff {
        /**
         * Constructs a PEAR_Sniffs_ControlStructures_ControlSignatureSniff.
         */

        public function __construct() {
            parent::__construct(true);

        }//end __construct()


        /**
         * Returns the patterns that this test wishes to verify.
         *
         * @return array(string)
         */

        protected function getPatterns() {
            return array(
                    'do {EOL...} while (...);EOL',
                    'while(...) {EOL',
                    'for(...) {EOL',
                    'if(...) {EOL',
                    'foreach(...) {EOL',
                    '} else if(...) {EOL',
                    '} elseif(...) {EOL',
                    '} else {EOL',
                    'do {EOL',
                   );

        }//end getPatterns()
    }//end class
    ?>

    Was gewinnen wir damit?

    Nach einem ant haben wir zum Einen ganz viele XML-Dateien. In einem späteren Beitrag werde ich zeigen, wie diese in ein “Continuous Integration” System integriert werden können. Für Menscen Lesbar haben wir Folgendes:
    Dokumentation unter statistics/html/doc/api/:

    PHPDoc Beispiel


    Eine Liste aller Style-Verstösse und Chaosattacken unter statistics/html/doc/codebrowser/:

    Codebrowser Output


    Ein Coverage-Report der Unittests unter statistics/html/unittests/coverage/:

    Coverage Report


    Zwei nette Diagramme von PHP-Depend (eine Abstraction Instability Chart und eine Overview Pyramid) unter statistics/html/jdepend:

    Fazit

    In einem nächsten Beitrag werde ich noch weitere Tools vorstellen und dann soll das alles ganz automatisch bei jeder Änderung geschehen… Stay tuned…


    3 Antworten zu “PHP Code-Analyse mit Ant”

    Kommentare

    1. Danilo Schreibt:

      Ich interessier mich momentan zwar nicht mehr für PHP, aber du hast mich mit dem Eintrag dazu gebracht, nach ähnlichen Tools für Python zu googlen. Und ich bin mit pylint fündig geworden, tolle Sache :) http://www.logilab.org/project/pylint Hab gleich mal ein kleines Projekt von mir refactored.

    2. skaldrom Schreibt:

      Python ist cool! Diese Sprache würde mich schon sehr lange reizen…

      Hey, schön dass es Dich inspiriert hat. Ich wünsche Dir weiterhin viel Spass beim Coden…

    3. Danilo Schreibt:

      Python ist wunderbar :) Bin schon fast zum Fanboy geworden… Mit Frameworks wie Django entwickelt man auch viel schneller als mit PHP (kenne jedenfalls kein PHP-Framework das daran rankommt).

    Lassen Sie eine Antwort hier...

    XHTML: Sie können folgende Tags verwenden: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>


    sieben + 5 =