GitHub ActionsにPHPMDを導入して循環的複雑度を計測・警告してみた

ソフトウェアエンジニアの荻島です。

弊社では現在レガシー化してしまったLaravelアプリケーションのフレームワークのアップデートとリファクタリングが絶賛進行中です。

今日はその活動の一環でPHPMD(Mess Detector)という老舗の静的解析ツールを導入した話を記事にしたいと思います。

静的解析ツールって?

静的解析ツールとは、コンピュータプログラムに誤りがないかや、規約などに照らして不適切な個所がないかなどを解析・検証する専門的なソフトウェア。*1

PHPUnitなどで実施する自動テストは動的(コードを実際に実行して)検証をする仕組みですが、静的解析ツールでは記述されたコードをツール側で解釈し、構文エラーや特定の規約に基づきチェックできるツールです。 (PHPStanPsalmなどもこの分類になります)

PHPMDって何ができるの?

今回導入したPHPMDは老舗の解析ツールでプログラムの複雑さや未使用変数など、古めのシステムでありがちなものを検出できるツールです。 既存のルールセットをXMLで記述することで柔軟に適用でき、また独自のルールセットをPHPで記述することでカスタムルールの定義をすることもできます。

なお、デフォルトで用意されている各ルールセットで検出できる内容は公式ドキュメントで確認できます (英語苦手な方はこちらの記事が一通り日本語でまとめてくださっています)

PHPMDを導入してCLIで実行してみる

ということで早速PJに導入してみます。 導入方法でいちばん簡単なのはcomposer経由でのインストールです。 (composerがない場合は.phar形式でDLすることもできます)

ライブラリをインストール

composer require --dev phpmd/phpmd

設定ファイル(phpmd.xml)を作成

<?xml version="1.0"?>
<ruleset name="PHPMD rule set"
         xmlns="http://pmd.sf.net/ruleset/1.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://pmd.sf.net/ruleset/1.0.0
                     http://pmd.sf.net/ruleset_xml_schema.xsd"
         xsi:noNamespaceSchemaLocation="
                     http://pmd.sf.net/ruleset_xml_schema.xsd">
    <!-- コードの循環的複雑度を検出 -->
    <rule ref="rulesets/codesize.xml/CyclomaticComplexity">
        <properties>
            <!-- 閾値を15に変更(デフォルトは10) -->
            <property name="reportLevel" value="20"/>
        </properties>
    </rule>
</ruleset>

(参考:PHPMDのルールセットの作り方 - kikukawa's diary

【お好みで】composer.jsonのscriptsに追記

"scripts": {
    "phpmd": "phpmd app text phpmd.xml"
}

のようにしておくとcomposer phpmdのように呼び出せて便利です。

phpmdの呼び出し方

基本的には./vendor/bin/phpmd <解析したいディレクトリ> <レポート出力形式> <使用するルールセット>という形式です。 (今回はcomposer script経由で呼び出しするのでcomposer phpmdとします)

なお、特定ディレクトリを除外したい場合は--exclude <ディレクトリ名(カンマ区切り)>で解析しないようにできます。 (例:--exclude vendor/*,tests/*

PHPMDをGitHub ActionsのCIに組み込む

PHPMDが真価を発揮するのは個人的にはCIだと思っているので是非組込みましょう!

GitHub/GitLabに関しては公式ドキュメントにも書いてあるとおり、PHPMDはそのままレポート出力形式に渡す文字列を変更するだけでいい感じに出力してくれます。

ただ、問題を検出した際にCIで落としてしまうと、既存PJに導入する際のハードルが上がってしまいます...なので基本的にCIに組み込む際は--ignore-violations-on-exitオプションをつけることをおすすめします。 (上記をつけることで「違反は報告するけど、CIとしては通す」という形にできます)

今回の導入ではcomposer scriptを下記のように記述して、手元で実行する際とCIで実行するコマンドを分けるようにしました

"scripts": {
    "phpmd": "phpmd app text phpmd.xml",
    "phpmd:ci": "phpmd app github phpmd.xml --ignore-violations-on-exit"
}

ここまで書いたらあとは公式ドキュメントにあるような感じでWorkflowファイルを書けばGitHub ActionsのCIに導入することができます。

name: CI

on: push

jobs:
  phpmd:
    name: PHPMD
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v2

      - name: Setup PHP environment
        uses: shivammathur/setup-php@v2
        with:
          coverage: none
          tools: phpmd

      - name: Run PHPMD
        run: composer phpmd:ci

まとめ

自分は少し設定周りでハマってしまったため、半日ほどかかってしまいましたが、設定値や引数などを理解していれば小一時間もあれば簡単にPHPMDを導入することができることが分かりました。

また、今回は既存コードの問題点を洗い出したい意図もあったので導入しませんでしたが、既存コードは一旦解析の対象外とするbaselineという仕組みもあります。

上記に加えてPHPMDはPHPのバージョン5.3.9から対応していたり、composerがなくてもpharで固めたファイルを置けば導入できたりとレガシーなPJにも導入しやすいツールですので、他のPJにも適宜導入して開発者体験を良くしていければと思っています。

少しでも参考になれば幸いです。

*1:出典:e-words