続・開発環境のツール周りをちょっと快適にした話

tools_eyecatch.jpg

こんにちは!パンダ大好きエンジニア、宮原です。

前回(開発環境のツール周りをちょっと快適にした話)の記事では、チームで導入した開発支援ツールについて、どんなツールがあるのか、どんな考えで導入したのか、というところをご紹介しました。

開発支援ツール導入後はコードの品質も上がって(潜在バグの低減、保守性の向上)、快適に開発ができるようになりました。現在はCIサーバによる監視も実現できており、もっと開発が快適になりました!

今回は、CodeIgniterというPHPフレームワークに各開発支援ツールをどのように導入し運用しているのか(CIサーバを活用したツール実行方法)をもっと具体的に紹介していきたいと思います。

目次

リポジトリ(ディレクトリ)の構成

各ツールの詳細なお話をする前に、まずはプロジェクトのリポジトリ(ディレクトリ)構成のお話をしたいと思います。

前回の記事で「ツール自体もリポジトリに格納するスタイルを取りました。」とお伝えしました。CodeIgniterに導入した具体例を紹介しますと、アプリケーションディレクトリの下、controllersやmodelsと同じ階層にtoolsというディレクトリを作り、その中にツール類を格納しています。

repository

このような構成にすることで、リポジトリをcloneすればプロジェクトに適用している開発支援ツールも併せて取得できるようにしています。
※ CodeIgniterのデフォルトのディレクトリ構成に加えて、tools以外にもtestsなどいくつかのディレクトリが追加されていますが、サービスの稼働に必要ないファイルはデプロイしないようにしています

各ツール用ディレクトリの詳細

各開発ツールのファイル構成と、運用スクリプトを紹介していきます。

PHP Coding Standards Fixer

PHP Coding Standards Fixerは、コードを自動整形してくれるツールです*1。このツールのためのディレクトリのファイル構成は以下のようになっています。

phpCsFixer
├── dry-runPhpCsFixer_jenkins.sh
├── dry-runPhpCsFixer.sh
├── .gitignore
├── .php_cs.dist
├── php-cs-fixer.phar
├── README.md
└── runPhpCsFixer.sh

ツール本体も php-cs-fixer.phar としてリポジトリに格納してあり、実行は実行時引数が付加されたシェルスクリプトを使います。 README.md 、デフォルト設定ファイル .php_cs.dist 、および各シェルスクリプトの中身は以下のようになっています。

前回の記事では、シェルスクリプトの中で引数として適用するルールを指定していましたが、ここでは .php_cs.dist というファイル内で設定するように変更されています。コード整形のルールや対象ディレクトリが実行時引数で指定されていない場合、 .php_cs.dist というファイルがあれば、デフォルトでその内容を適用してくれます

いずれもコードを整形したいファイルまたはディレクトリを引数に指定して、シェルスクリプトを実行します。dry-runとついているものは実際にはファイルは更新せず、diffコマンド形式でどの行がどんな風に変わります、というのを表示してくれます。

ファイルやディレクトリを指定せずに実行した場合は、 .php_cs.dist で指定された、対象とすべき全てのディレクトリが範囲に含まれます。

_jenkins とついているものは、後述するCIサーバ(Jenkins)から実行するためのもの。詳細はCIのセクションで説明します。

CodeIgniterでの実行例としては、例えば以下のコマンドでcontrollers配下のファイルを自動整形します。

./runPhpCsFixer.sh ../../controllers

なお、 .gitignore ではキャッシュファイルとJenkins用の出力ファイルを指定しています。

PHPMD

PHPMDは、複雑度や関数の長さなどを調べて警告してくれる、コードの静的解析ツールです*2

phpmd
├── .gitignore
├── phpmd.phar
├── README.md
├── ruleset.xml
└── runStaticAnalysis.sh

PHPMDのツール本体もphpmd.pharとしてリポジトリに格納してあり、実行は実行時引数が付加されたシェルスクリプトを使って行います。README.mdおよびシェルスクリプトの中身は以下のようになっています。

このシェルスクリプトでは対象とするディレクトリを全て指定し、静的解析を行うためのルールを記載したxmlファイルも指定しています。出力は、同階層にHTML形式でindex.htmlとして生成されます。シェルスクリプトの実行時には特に引数は必要ありません。

静的解析を行うためのルールは、以下のように設定しています。

PHPLOC

PHPLOCは、コードメトリクスを測定するためのツールです*3。前回の記事時点では触れていませんでしたが、後述するようにCIサーバを構築できるようになったので、プロジェクトの規模を可視化するために導入してみました。

phploc
├── .gitignore
├── phploc.phar
└── runPhploc.sh

このツールは開発メンバーは特に使用せず、後述するCIサーバでコードメトリクスの推移を記録するために使っています。ツール本体もphploc.phar としてリポジトリに格納してあり、実行時引数が付加されたシェルスクリプトを使って実行します。

プロダクトコードだけのメトリクスを測定するため、toolsやtestsなどのディレクトリを除外。CIで結果を集計するためにcsv形式とxml形式の両方で出力しています。結果出力ファイルはリポジトリに格納しないように、.gitignoreを以下のように設定しています。

現時点では測定したコードメトリクスを何かに使用しているわけではないのですが、ゆくゆくはメンテコストやコード品質を考える指標に利用できたら良いな、と思っています。

PHPCPD

PHPCPD(PHP Copy/Paste Detector)は、コードの重複を検出してくれるツールです*4

phpcpd
├── .gitignore
├── phpcpd.phar
├── README.md
└── runPhpcpd.sh

ツール本体もphpcpd.pharとしてリポジトリに格納してあり、実行は実行時引数が付加されたシェルスクリプトを使って行います。シェルスクリプトの中身は以下のようになっています。

出力結果はテキスト形式で同階層に出力されます。出力結果はリポジトリには格納しないように、.gitignoreを以下のように設定しています。

意図的に重複させているケースもあるため「PHPCPDの指摘は必ず全て潰さなければならない」とは考えておらず、コードの保守性を上げる参考情報として使っています。そのため、CIでは特に実行しておらず、開発メンバーやレビュアーが気になったタイミングでツールを実行しています。

また現在の方法ではcontrollersとlibrariesの間など異なるディレクトリ間の重複が検出できないため、改善の余地があると考えています。

phpDocumentor

phpDocumentorは、コードコメントからドキュメントを生成してくれるツールです*5

phpdocs
├── generatePhpdocs.sh
├── .gitignore
├── phpDocumentor.phar
└── README.md

他のツールと同じように、generatePhpdocs.shというシェルスクリプトを実行すれば動くようにしてあるのですが、少々トリッキーなことをしています。

現在対象にしているプロジェクトではnamespaceを使用していないため、別ディレクトリにあっても同じクラス名の場合、phpDocumentorが生成したドキュメント上ではクリックするまで区別がつかない、という問題が発生します。以下は、それぞれ別のディレクトリにHogeクラスが格納されているプロジェクトに対して、phpDocumentorを実行した結果です。

without_namespace.png

各クラスの詳細を閲覧できるページ。ディレクトリ詳細を見ればクラス名が同じでも別物だと分かるものの、一覧表示の場合は別ディレクトリでも区別できない

そこで、generatePhpdocs.shでは、空間管理している(namespace相当)ディレクトリごとに phpDocumentor.phar を実行してそれぞれのhtmlファイルを生成し、最後に各htmlファイルへのリンクを持った目次ページを生成する、という処理を行っています。

generatePhpdocs.shの一部、あるnamespace相当に対する実行部分は、以下のように設定しています。

PHAR_DIR=`pwd`
OUTPUT_DIR=`pwd`"/html"
...
LIBRARIES_DIR=`pwd`"/../../libraries"
...
# libraries/hoge
TMP_OUTPUT_DIR=${OUTPUT_DIR}/libraries/hoge
mkdir -p ${TMP_OUTPUT_DIR}
php ${PHAR_DIR}/phpDocumentor.phar -d ${LIBRARIES_DIR}/hoge/ -t ${TMP_OUTPUT_DIR} --title "Library - hoge"
echo '<li><a href="libraries/hoge/index.html">libraries/hoge/</a>' >> ${OUTPUT_HTML}

この実行例では、libraries以下のhogeという空間に対してドキュメント生成を行い、生成されたファイルへのリンクを${OUTPUT_HTML} で定義された目次ファイルに追記しています。

このような処理を、ドキュメント生成したい空間の数だけ記載し、その前後で目次用HTMLファイルのヘッダ部分やフッタ部分を出力しています。いずれはもう少しスマートな方法へ移行しようと考え中です。

他のツールと同じように、出力結果をリポジトリへ格納しないように、 .gitignore は以下のように設定しています。

運用(CIサーバの構築)

私達のチームでは、git flowをベースにしたブランチ戦略を採用しています。ツール実施の運用としては、各開発メンバーが自身の変更をFeatureブランチにプッシュ(マージ)する前に、ツールを実行して指摘が出ないことを確認することにしています。

branch_img

まだ未完成のコードだけどバックアップも兼ねてプッシュしておきたい!というようなときには、subfeatureブランチを切ってリモートにプッシュしており、ここではまだツールが適用できていなくてもOKとしています

Jenkinsでツールのかけ忘れや監視ブランチの切り替え漏れを検知する

ツールの掛け忘れはどうしても発生してしまうもの。今まではレビュアーがコードを確認するタイミング等に随時ツールを実行してチェックしていましたが、現在はCIサーバ(Jenkins)にてツールの掛け忘れを検知するようにしています。

CIサーバでの対象ブランチは、そのときホットなfeatureブランチとしています。開発が終了し、他に適当なfeatureブランチがない場合は、developブランチを指定。featureブランチを切ったのにCIサーバの監視対象を切り替え忘れた場合でも、developブランチにマージされた際に何か指摘があれば検知できます。

一方で、リモートでfeatureブランチを削除したのにCIのブランチを切り替え忘れてずっと古いローカルのfeatureブランチを監視していた、ということがないように、Jenkinsのソースコード管理の設定で、GitのAdditional Behaviours として Wipe out repository & force clone を有効にしています。もしリモートに存在しないブランチを指定したままにしていた場合、ジョブの実行に失敗してブランチの切り替え忘れが検知できます。

jenkins_git_setting

ジョブ実行タイミングとSlackへの通知

ジョブは平日の深夜帯に定期実行するよう設定しており、具体的には以下のスケジュールを指定しています。

H 1 * * 1-5

2番めの 1 は、プロジェクトAに関するジョブは1、プロジェクトBに関するジョブは2、というように、プロジェクトごとに同じ時間帯で実行するようまとめています。

実行結果はSlackへ通知するようにしています。ジョブを設定してすぐの頃は、結果をすべて通知するようにしていましたが、安定して稼働していることが確認できたので、現在はジョブが失敗した(ブランチ変更忘れや指摘があった)場合のみSlackへ通知が来るようにしています。

それでは、各ツールに対して、CIサーバ(Jenkins)のジョブをどのように設定しているかをご紹介します。

PHP Coding Standards Fixer

ビルド

PHP Coding Standards Fixer用ディレクトリに、Jenkins用シェルスクリプトが用意してあります。Jenkins用シェルスクリプトは以下のような内容になっています。

Jenkins上で結果を見やすくするためにJunit XML形式で結果を出力しています。

Jenkinsのビルド部分はこのファイルを利用して、以下のシェルスクリプトを実行するように設定しています。

cd [リポジトリ上のCodeIgniterのディレクトリへのパス]/application/tools/phpCsFixer

./dry-runPhpCsFixer_jenkins.sh > result.xml

このシェルスクリプトは、戻り値として指摘が何もなければ0、指摘があれば非0を返してくれます。上記のようにシェルスクリプトを実行するだけで、Jenkinsジョブとしても指摘がなければビルド成功、指摘があればビルド失敗、として結果が得られます。

ビルド後の処理

検出された結果は、result.xmlというファイル名でJUnit XML形式で出力しているので、「JUnitテスト結果の集計」にてテスト結果XMLに以下を指定しています。

[リポジトリ上のCodeIgniterのディレクトリへのパス]/application/tools/phpCsFixer/result.xml

PHPMD

ビルド

PHPMD用ディレクトリに用意したシェルスクリプトさえ実行すれば良いので、Jenkinsのビルド部分は、以下のシェルスクリプトを実行するように設定しています。

cd [リポジトリ上のCodeIgniterのディレクトリへのパス]/application/tools/phpmd

./runStaticAnalysis.sh

このシェルスクリプトも、戻り値として指摘が何もなければ0、指摘があれば非0を返してくれます。

ビルド後の処理

ビルド(ツールの実行)が終わったあとは、実行結果を保存するようにしています。ツールと同じディレクトリに index.html として結果を出力しているので「成果物を保存」にて以下のパスを保存するファイルとして指定しています。

[リポジトリ上のCodeIgniterのディレクトリへのパス]/application/tools/phpmd/index.html

こうすることで、Jenkinsで実行されたジョブ番号をクリックすればそのときの結果が見られます。Slackへの通知にもジョブ実行結果へのリンクが含まれているので、何か指摘があってSlackに通知が来たら、そこから2クリックで指摘内容を確認することができます。

pex_jenkins_slack

PHPLOC

ビルド

PHPLOC用ディレクトリに用意したシェルスクリプトさえ実行すれば良いので、Jenkinsのビルド部分は以下のシェルスクリプトを実行するように設定しています。

cd [リポジトリ上のCodeIgniterのディレクトリへのパス]/application/tools/phploc

./runPhploc.sh

ビルド後の処理

ビルド後の処理では、コードメトリクスの遷移を追うため、「ビルドデータをプロット」を利用して各種データのグラフを作成しています。

loc-sample

測定したい項目ごとに、詳細な設定内容を紹介します。

ファイル数・ディレクトリ数
  • プロットグループ:コードメトリクス
  • プロットタイトル:Number of files and directories
  • 使用するビルドの数:100
  • Y軸のラベル:個数
  • プロットスタイル:線グラフ
  • データ系列
    • データ系列ファイル:[リポジトリ上のCodeIgniterのディレクトリへのパス]/application/tools/phploc/phploc.csv
    • CSVファイルからデータを読み込む
      • 使用するカラム名を指定
      • 使用/除外するカラム:Directories,Files
コード行数
  • プロットグループ:コードメトリクス
  • プロットタイトル:LOC
  • 使用するビルドの数:100
  • Y軸のラベル:Lines
  • プロットスタイル:線グラフ
  • データ系列
    • データ系列ファイル:[リポジトリ上のCodeIgniterのディレクトリへのパス]/application/tools/phploc/phploc.csv
    • CSVファイルからデータを読み込む
      • 使用するカラム名を指定
      • 使用/除外するカラム:Lines of Code (LOC),Comment Lines of Code (CLOC),Non-Comment Lines of Code (NCLOC),Logical Lines of Code (LLOC),LLOC outside functions or classes
複雑度
  • プロットグループ:コードメトリクス
  • プロットタイトル:Complexity
  • 使用するビルドの数:100
  • プロットスタイル:線グラフ
  • データ系列(1)
    • データ系列ファイル:[リポジトリ上のCodeIgniterのディレクトリへのパス]/application/tools/phploc/phploc.xml
    • XPath式を用いてXMLファイルからデータを読み込む
      • XPathResult型:nodeset
      • XPath式://phploc/classCcnMin
  • データ系列(2)
    • データ系列ファイル:[リポジトリ上のCodeIgniterのディレクトリへのパス]/application/tools/phploc/phploc.xml
    • XPath式を用いてXMLファイルからデータを読み込む
      • XPathResult型:nodeset
      • XPath式://phploc/classCcnAvg
  • データ系列(3)
    • データ系列ファイル:[リポジトリ上のCodeIgniterのディレクトリへのパス]/application/tools/phploc/phploc.xml
    • XPath式を用いてXMLファイルからデータを読み込む
      • XPathResult型:nodeset
      • XPath式://phploc/classCcnMax
  • データ系列(4)
    • データ系列ファイル:[リポジトリ上のCodeIgniterのディレクトリへのパス]/application/tools/phploc/phploc.xml
    • XPath式を用いてXMLファイルからデータを読み込む
      • XPathResult型:nodeset
      • XPath式://phploc/methodCcnMin
  • データ系列(5)
    • データ系列ファイル:[リポジトリ上のCodeIgniterのディレクトリへのパス]/application/tools/phploc/phploc.xml
    • XPath式を用いてXMLファイルからデータを読み込む
      • XPathResult型:nodeset
      • XPath式://phploc/methodCcnAvg
  • データ系列(6)
    • データ系列ファイル:[リポジトリ上のCodeIgniterのディレクトリへのパス]/application/tools/phploc/phploc.xml
    • XPath式を用いてXMLファイルからデータを読み込む
      • XPathResult型:nodeset
      • XPath式://phploc/methodCcnMax

以上の設定により、ジョブの「プロット」リンクから、対象プロジェクトのコードメトリクスの遷移を見ることができます。実行は平日のみとしているので、使用するビルドの数100=20週=半年弱分の遷移を追うことができます。

ジョブの実行結果は、ブランチ切り替え忘れなどを除けば基本的に常に成功となります。

実現方法がちょっとトリッキーになってしまっているので、他に良い方法があれば切り替えたいと思っています。

phpDocumentor

ビルド

実行自体は、phpDocumentor用ディレクトリに用意したシェルスクリプトさえ実行すれば良いので、Jenkinsのビルド部分は、以下のシェルスクリプトを実行するように設定しています。

cd [リポジトリ上のCodeIgniterのディレクトリへのパス]/application/tools/phpdocs

./generatePhpdocs.sh

ビルド後の処理

ビルド(ツールの実行)が終わったあとの処理としては、実行結果を保存するようにしています。ツールと同じディレクトリに html として結果を出力しているので「成果物を保存」にて以下のパスを保存するファイルとして指定しています。

[リポジトリ上のCodeIgniterのディレクトリへのパス]/application/tools/phpdocs/html/**

保存された成果物の中から、 html/index.html をクリックすれば、生成されたドキュメントを見ることができます。

PHPLOCと同じく、ジョブの実行結果は基本的に常に成功となります。

CI構築後

CIによりツールの掛け忘れが防止できるようになった

メンバーもツールに慣れてきて、普段から掛け忘れは減ってきていたのですが、バグ修正で急ぎで対応したときなど、どうしてもツールの掛け漏れが発生することがあります。しかし、CIが常に監視してくれているので、ツールを掛け忘れてしまってもちゃんと検知できるようになりました。

レビュアーも、CIから指摘が上がってきていない=ツールがちゃんと適用されていると認識でき、安心してレビューができるようになったと思います。

いつか担当を離れても品質が保たれる

開発支援ツールなどは有識者がいなくなった途端に、使い方がわからなくなったりして使用されなくなるケースもあるかと思います。CIを構築しておけば、CIの設定を見るだけで実行方法が分かりますし、常に監視してくれています。そのため、チームに新しく入ったメンバーがいたり、プロジェクトを他のチームに移管したりなど、ツールを使った運用のことを知らない人でも引き継いで利用できると思います。

今後の展望

今後実現したい機能や取り組みがまだまだあります。

検知のタイムラグを減らす

CIサーバを構築し、ツールの掛け忘れを検知できるようになりましたが、どうしてもタイムラグが発生しています。ドイツのカンファレンスに参加した際に知ったCaptainHookを使えば、git hookが簡単に扱えそうなので、こちらを活用して各開発者のコミット時やプッシュ時に検知する仕組みを検討してみたいな、と考えています。

環境構築の自動化

本記事では触れていませんが、toolsディレクトリには setup.sh というシェルスクリプトも格納してあり、ここにプロジェクトの開発に必要なソフトウェアをインストールするコマンドが列挙してあります。開発者は開発に必要なOSを用意し、Gitさえインストールすれば、リポジトリをcloneして setup.sh を実行すれば環境が整うようになっています。

現時点でも環境構築は半自動化できていると思うのですが、ChefAnsibleを活用してより柔軟でスマートな環境構築を実現してみたいです。

他プロジェクト・他チームへの横展開

実は、環境構築当初から横展開の容易さも頭の片隅に置いていました。そのために、ツール実行時のパス指定を相対パスにし、いくつかソフトをインストールすれば、後はリポジトリをcloneしてきてシェルスクリプトを実行するだけでツールが掛けられるようになっています。したがって、本記事で紹介した tools ディレクトリをコピーして相対パス部分を書き換えたり、Jenkinsのジョブ設定のパスを変えたりすれば、他プロジェクトに適用できると思います。

すでにチーム内の別プロジェクトや他チームへの展開を少しずつ進めていますが、今のところ大きな障壁はなさそうです。異なるフレームワークのプロジェクトへもすんなり適用できています。

自分が担当するプロジェクトに導入し、品質の維持・および維持するためのコスト削減という点で実績は積めてきていると感じており、引き続きぐいぐいと横展開を進めていきたいと思っています。


お知らせ
新しい開発手法やツールの導入などなど、新しい風をぐるなびに吹き込みたいエンジニアWanted!!


‘宮原良介’

2010年に大学院を卒業し、新卒で某大手精密機器メーカに就職。2015年の秋、食への熱い思いを持って、ぐるなびへ転職。ぐるなびでは単に開発を行うだけではなく、様々な業務改善を推進するとともに、アジャイル(スクラム)の普及にも尽力している。
パンダ好きのフルスタック気味エンジニア。

*1:Copyright (c) 2012-2017 Fabien Potencier Dariusz Rumiński

*2:By Manuel Pichler

*3:Copyright (c) 2009-2017, Sebastian Bergmann sebastian@phpunit.de. All rights reserved.

*4:Copyright (c) 2009-2017, Sebastian Bergmann sebastian@phpunit.de. All rights reserved.

*5:Copyright (c) 2004-2011 Fabien Potencier