Born Too Late

Yuya's old tech blog.

PHP のコードパスを解析する CodePathAnalyzer を作った

2012-11-11 17:55:50

レガシーコードと戦っていると、「このコードのどこをどう通ってこういう結果になってしまっているのか」がわからなくなることがあります。

初見でコードを理解する能力は、コードを読んできた経験が多ければ多いほど、向上するものだと思います。
とは言っても、構造化やモジュール化が適切でなく、スコープの長大なコードなどは、人間の限界を超えているものもあるでしょう。

ステップ実行のできる IDE などを使う、という選択もあると思いますが、僕は重厚な IDE を好みません。
もっと楽にできる方法で、コードパスを解析する方法があれば、ということで作ってみました。

yuya-takeyama/code_path_analyzer

元になっているのは、仕事の時にコード中にベタ書きした関数です。
Gist に公開したところはてブが 10 ぐらい付いたのと、次必要になったときにすぐ使えるようにしておきたかったので、ライブラリ化しました。

使い方

Composer でインストールできます。

Composer を使っている場合は気にする必要が無いのですが、例えば PHP 5.2 などの環境で Composer やそのオートローディングの仕組みが使えない、という場合を想定して、ファイルツリーを適当に配置したら、./src/Yuyat/CodePathAnalyzer/Registrar.php 1 ファイルを読み込むだけで、他に必要な全てのファイルを読み込むようになっています。
パフォーマンスを気にするのであればオートローダを使いたいところですが、デバッグ時しか使われないので、それぐらいの妥協はいいんじゃないでしょうか。

解析対象のできるだけ先頭に以下のように記述すれば、そのファイルを解析することができます。

解析結果は以下のようなテキストファイルとして出力されます。

先頭に + (プラス) が付いているのが、実際に実行された行です。
これを見ることで、どのようにこのプログラムが実行されたのか、を把握しやすくなります。

仕組み

XDebug のコードカバレッジ解析の仕組みを利用しています。

Yuyat_CodePathAnalyzer_Registrar::registerDefault() の実行時に xdebug_start_code_coverage() を実行することで XDebug のカバレッジ解析を有効にするのと同時に、register_shutdown_function() で終了時に解析処理をフックするようにしています。
解析処理の中では xdebug_get_code_coverage() を実行することで収集した解析データを XDebug から取得し、CodePathAnalyzer の各コンポーネントが協調して結果の出力を行います。

出力形式の変更について

現在はテキストファイルへの出力のみに対応していますが、HTML 形式などに出力できたほうがより見やすいでしょう。

AnalysisHandlerInterface を実装したクラスを用意することでそういった拡張にも行えるようにしていますが、個人的にはテキストファイルだけで充分だったので、詳細は割愛します。
要望があればその辺の情報もちゃんとドキュメント化するかもしれません。 (めんどくさくてやらないかもしれません)

そもそもコードパスって...

完成してから気づきましたが、これを持ってコードパス解析、というのはどうかなー、という気がしています。
コードパスというのは「コードの経路」ですから、「この行は実行されたか」といった点の情報ではなく、「どのような順番で実行されたか」という線の情報だと思うので、これだとコードパスとは言えないんじゃないかなーと。

XDebug には IDE でのステップ実行のためのインターフェイスもあったと思うので、それを使って、例えば Socket.IO なんかでブラウザ上に表現できればよりかっこいい!なんていう気はするのですが、今のところそこまでのものは必要としていないので、多分作らないと思います。

まとめ

気軽にコードパス (?) を解析できる CodePathAnalyzer について紹介しました。
そんなに気の利いたツールではないですが、この程度情報があればちょっと楽になる、ぐらいのときに使っていただければ幸いです。