障害要因としてあるあるの課題を解決するためにLarastanを導入しました!

こんにちは、エンジニアの栄山 (@yamii_qq) です。 今日はLarastanをプロジェクトに導入した話を書いていこうと思います!

発端

最近こんな障害理由がありました!

本来nullで返ってくる可能性のあるメソッドを、そうとは知らずに使ってしまった。
動作確認ではnullではない返り値が返却されていたので、バグに気付けず、本番にリリースしてバグが判明した!

日々開発業務をされているエンジニアの皆さんならわかってくれると思うんですけど、結構あるあるのミスですよね。

確かに使用するメソッドを確認して使うようにすれば防げるけど、確認が漏れてしまうことだって時にはある!そう人間だもの!

そこで仕組みとして改善しようと考えたので、Larastanを導入しました。

Larastanとは?

公式リポジトリを和訳して、要約すると下記のようなことが書いてあります。

・PHPStanのLaravelラッパー
・コード内のエラーを見つけることに焦点を当てている
・コードに対するテストを書く前に、さまざまなバグのクラス全体を検出することができる

github.com

Larastanとはソースコードを静的解析して、コード内のエラーを自動で検知してくれる便利なライブラリです。

どんなものが検知できるのか?

Larastanにはレベルが設定でき、レベルごとに検知できる内容が変わってきます。

レベル 検知内容
0 基本的なチェック、未知のクラス、未知の関数、$this に対して呼び出された未知のメソッド、メソッドや関数に渡された引数の数が間違っている、常に未定義の変数
1 可能性のある未定義の変数、call や get を持つクラスでの未知のマジックメソッドやプロパティ
2 すべての式で確認される未知のメソッド($this だけでなく)、PHPDocs の検証
3 返り値の型、プロパティに割り当てられる型
4 基本的な未使用コードのチェック - 常に false の instanceof およびその他の型チェック、使用されない else ブランチ、return の後に到達不可能なコードなど
5 メソッドや関数に渡される引数の型のチェック
6 型ヒントの不足を報告
7 部分的に間違った共用型の報告 - 共用型内の一部の型のみで存在するメソッドを呼び出す場合、レベル 7 で報告が開始されます。その他の可能性のある不正確な状況も報告されます
8 null 許容型に対してメソッドの呼び出しやプロパティへのアクセスを報告
9 mixed 型について厳密にチェック - 許容される操作は、別の mixed に渡すことだけです

(上記テーブルの内容はRule Levels | PHPStanの内容を和訳したものです)

導入してみた

Larastan導入の発端となった障害理由を検知したかったので、レベル8まで検知できる設定で解析してみました!

すると,,,2271errorsという数の大量の解析エラーが!!!

既存プロジェクトにLarastanを導入した結果

そうですよね...。という話なのですが、この量は多すぎてびっくりしちゃいますね。 (特にコード品質が悪いというわけではなく、Larastanレベル8のチェックが厳しいのでこれは仕方がないものなのです。)

既存のエラーを全て修正するのは、時間もかかるしリスクもあるのでなかなか難しいです。

障害要因となった部分は検知できたのか?

障害原因のコードの検知

画像だと分かりづらいのですが、無事nullで返ってくる可能性のあるメソッドが、nullable対応なしで呼び出されている場合に検知できました。

Cannot call method id() on Domain\SellingTarget\AbstractSellingTarget|null

これで、我々エンジニアがたまにミスして頭を抱えてしまっていた障害要因を仕組みとして取り除けそうです!

新しく開発する部分のみ導入しよう

LarastanにはBaselineオプションがあり、このオプションを使用することで、新規に追加されたコードのみを解析対象とできます。

GitHub - nunomaduro/larastan: ⚗️ Adds code analysis to Laravel improving developer productivity and code quality.

オプションをつけて解析コマンドを実行すると

./vendor/bin/phpstan analyse --generate-baseline

phpstan-baseline.neonというファイルが生成されて、既存のエラーを全て無視する設定が追加されるというわけです。

parameters:
    ignoreErrors:
        -
            message: "#^Property App\\\\Aspect\\\\Interceptor\\\\AdminAdminOperationLoggingTransfer\\:\\:\\$className has no type specified\\.$#"
            count: 1
            path: app/Aspect/Interceptor/AdminAdminOperationLoggingTransfer.php

これでなんとか導入できそうです!

既存のエラーは直さなくても良い?

ここまで読んでくれた読者の皆さんは、「え!?、結局errorは残したままにするの?」のような疑問を持つかもしれません。

既存のコードはこれまで大きな障害もなく実行されてきた実績があるので、正しく動作している可能性が高いです。

無理に修正してしまうと、意図せぬ挙動の変更につながる恐れもあるのでそのままにしておくことを選択しました。

下記記事などとても参考になります。(もちろんうちのプロジェクトのコードはクソコードではないですが笑)

qiita.com

実際の運用

Larastanを実際に運用していくに当たり、下記のようなことを満たしている必要があります。

  1. プロダクトを開発するに当たって、チームメンバーがストレス少なく静的解析のチェック・修正ができること
  2. 障害原因となったnullableなメソッドの使用についての検知ができ、検知されたコードに関して本番リリースされないこと

そこで下記のような取り組みを行うことで、無理なく運用できるようにしました!

CircleCIに組み込む

弊社ではコミット時に定義されたJobがCircleCI上で自動実行する仕組みがすでに存在します。

そこでLarastanのコマンドも一緒に実行してしまうようにしました。

php ./vendor/bin/phpstan analyse --memory-limit=2G --configuration=phpstan.neon

commit時に常時上記コマンドが実行され、ソースコードの静的解析を行います。

errorがあると本番ブランチにマージされない仕組みとしたので、安全にerrorを検知でき修正することができます!

makeコマンドを作る

ローカル環境でよく使うコマンドに関しては、makeコマンド化することで開発メンバー誰でも使用できます!

makeコマンド

makeコマンドとは、例えば、make larastanというコマンドをターミナル上で実行すると、makeファイルのlarastan: と書かれている部分のコマンドを実行してくれるものです。

開発チームに共有可能なエイリアスというのが、しっくりくるような便利機能です。

結果

この仕組みを導入して3週間ほど経ったのですが、すでにDevOpsが社内に浸透していることもあり、新しくphpstanチェックが入っても特に変わらず開発が進んでいます。

まとめ

今回開発現場あるあるの課題を、仕組み化することで解消することができました!

Laravelを使用して日々業務している皆さん!ぜひLarastanを導入してより良い開発環境を作りましょう!!