はじめに
Laravel Advent Calendar 2021 2日目の投稿です。
PHPのソースコードの品質管理はどのように実施していますでしょうか。
チームで開発している場合はコーディング規約に従ってコードレビューを実施している場合もあるかと思います。
しかし、コードレビューを人の手で実施している場合は以下のような問題があるかと思います。
人力によるコードレビューのつらみ
-
レビューに時間がかかる
- 修正コード量が多いと修正差分を見ただけで「うっ・・・」っとなります
-
ルール違反していても見逃してしまう場合がある
- ルールが増えれば増えるほどチェック漏れのリスクが増加します
-
コードレビューしてくれる人を探さなければならない
- コードレビューできる人が限られている場合、修正したコードがマージされるまでにかなり時間差が生まれることも
-
コードレビューできない人をできるように育てないといけない
- 特定のスーパープログラマがレビュアーになっている場合は新しいメンバーに後任を務めてもらえるようにするのは簡単ではありません
-
差し戻し時にコードレビューする人の主観が入ってしまう場合がある
- 意見が半々に分かれるような指摘は受け入れられない場合があります
そもそものやりたいことって?
コードレビュー時は 型がかけているか
とか PHPDocがちゃんと書けているか
などを1つ1つ血眼でチェックしたいわけではないと思います。
基本的なルールは機械的にチェックしてしまい、コードレビューでは可読性や保守性を高めるにはどうしたらよいかといったコードと向き合う時間をできるだけ多く確保したいですね。
静的解析
静的解析ではプログラムを実行せずともソースコードを解析することで構文のチェックができます。つまり、プログラムを実行する前に、エラーとなりうる実装箇所や非推奨となっている実装箇所を発見することができます。
PHP はスクリプト言語であるため、プログラムを実行するまでそのソースコードが文法上問題ないかがわかりません。場合によっては特殊な条件でしか処理されないような分岐があったとして、その分岐内の処理がテストされないままリリースされてしまった結果、リリース後に不具合が発覚するなどもありえます。そのため、静的解析ツールでソースコード全体を機械的に構文チェックすることが有効です。
PHPStan
PHPStan はPHPで有名な静的解析ツールの1つです。
Composerでインストール可能であったり公式のDockerコンテナが存在するなど、手軽に導入できる点が嬉しいです。
また、解析の厳密さを示すレベルを9段階で指定できることから、いきなりすべてのエラーに対応するのが大変な巨大なプロジェクトにも比較的導入しやすいかと思います。
各レベル別の解析内容は公式のドキュメントを参照していただけますと幸いです。
Larastan
前置きが長くなりましたが本題のLarastanについて記述していきます。
Larastan は PHPStanの拡張であり、Laravel プロジェクトの静的解析が可能です。
Larastan では Laravel でアプリケーションを作成する上でパフォーマンスを劣化させる可能性がある実装箇所や実行時エラーになりえる箇所をチェックすることができます。
導入方法
公式のREADMEにもしっかりと記載されていますが改めて解説します。
composer 経由での取得
Laravelプロジェクトのルートに移動して以下コマンドを実行
composer require nunomaduro/larastan --dev
設定ファイルの作成
Laravelプロジェクトのルートにて phpstan.neon
または phpstan.neon.dist
というファイル名のファイルを作成して以下の内容を記載します。
includes:
- ./vendor/nunomaduro/larastan/extension.neon
parameters:
paths:
- app
# The level 9 is the highest level
level: 5
ignoreErrors:
- '#Unsafe usage of new static#'
excludePaths:
- ./*/*/FileToBeExcluded.php
checkMissingIterableValueType: false
各パラメータの詳しい内容は以下をご参照ください。
Larastan による静的解析の実行
以下のコマンドを実行することで Larastan による静的解析が実行できます。
./vendor/bin/phpstan analyse
解析結果はPHPStanによる実行結果に加えてLarastanで独自拡張された解析ルールで解析された結果が表示されます。
Larastanの解析ルール
Larastanによって解析できる特別な解析ルールをいくつかご紹介いたします。
解析ルールの詳細はGitHub上のドキュメントより参照させていただきます。
NoModelMake
Modelクラスの静的メソッドmake()
を利用している箇所を検知してくれます。
User::make();
ソースコード上、上記ような書き方をしてもエラーにはなりませんが、インスタンスが無駄に2つ生成されてしまいます。
モデルクラスを利用する場合は単純に new でインスタンスを生成した方が効率的という理由から解析対象となっております。
内部実装を知っていなければそれがパフォーマンスを悪化させる原因になっていることに気づけないのでとてもありがたいですね。
NoUnnecessaryCollectionCall
Illuminate\Support\Collection
とそのサブクラスの組み合わせにおいて、無駄に重いSQLが生成される可能性がある実装箇所を検知してくれます。
// 改善前
User::all()->count();
$user->roles()->pluck('name')->contains('a role name');
// 改善後
User::count();
$user->roles()->where('name', 'a role name')->exists();
こういうことも知っていないとうっかりやってしまいそうですね。
RelationExistenceRule
Eloquent 経由でDBアクセスする際、存在しないカラムに対してデータ取得しようとしている場合に検知することができます。
// Userにfooというカラムが存在しない場合にエラーを検知
\App\User::query()->has('foo');
// 複数カラムに対するデータ取得の場合でもすべてのカラムが存在しているかが検知可能
\App\Post::query()->has('users.transactions.foo');
マイグレーションを行い、DB構造が変わった場合などに特定処理が壊れてしまっていないかなどを簡単に検知できそうです。
CheckDispatchArgumentTypesCompatibleWithClassConstructorRule
ジョブのディスパッチ引数の型がジョブクラスのコンストラクターと互換性があるかをチェックします。
class ExampleJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/** @var int */
protected $foo;
/** @var string */
protected $bar;
public function __construct(int $foo, string $bar)
{
$this->foo = $foo;
$this->bar = $bar;
}
// Rest of the job class
}
// 引数が足りないパターン
ExampleJob::dispatch(1);
// 引数の型が一致していないパターン
ExampleJob::dispatch('bar', 1);
これも嬉しいですね。
バッチ処理は通常の画面操作テストでは確認しづらい部分かと思いますので、単純ミスなどが早期に見つかる仕組みがあるのは心強いです。
おわりに
Larastan は比較的簡単に導入できますが、きっちり有益なエラーを検知してくれます。実行時にようやく見つかるような不具合をより早い段階で気づけるようにしておくことで実装時の手戻りも減らせると思います。また、どういうコードが不適切であるかは知らなければ気付けません。コード品質を高めるための前提知識を静的解析経由で知ることができるのも導入のメリットかも知れません。