PHPのバージョンアップ時にも役立つAST(抽象構文木)

この記事は、CYBOZU SUMMER BLOG FES '24 (Garoon Stage) DAY 5の記事です。

cybozu.github.io

こんにちは、Garoon開発チームの松尾です。中堅・大規模組織向けのグループウェアであるGaroonはバックエンドにPHPを使って開発されています。本記事では、Garoonで使用しているPHPのバージョンを8.1から8.2に更新した作業の一部において、プログラムの本質的構造を示すAST(抽象構文木、Abstract Syntax Treeの略)に差分がないことを確認することにより大幅にテスト工数を削減できたケースを紹介したいと思います。

PHPのリリースサイクルとバージョンアップの必要性

Garoon開発チームにはいくつかのサブチームがあり、私が所属しているサブチームであるYukimiチームは、より安全なGaroonをユーザーに届けることを目標とし、将来に渡って継続的にGaroonを安定提供できるように開発および保守を行っています。Yukimiチームの詳細については次の記事をご参照ください。

blog.cybozu.io

PHPは年に1回新しいバージョンがリリースされます。リリースされてから不具合やセキュリティの問題が修正される「アクティブサポート」期間が2年間あり、さらにその後の2年間、重要なセキュリティ問題のみが修正される「セキュリティサポート」期間が設けられるようになっています。

もしもバージョンアップを行わずに、PHPのセキュリティサポート期間の終了を迎えてしまうと、深刻なセキュリティ脆弱性がPHPに見つかって製品に重大な影響を与えてしまうような問題が出てきた場合に、多くのユーザーを危険に晒すことになってしまいます。

Garoonはさまざまな組織を支えるグループウェアであり、お客さまの業務にまつわるデータを預かっていることから、セキュリティの確保はとても重要です。多くのユーザーが危険な状況に陥ることを最大限回避し、最新のセキュリティ更新をすぐに取り込める状態を保つために、開発チーム全体で常日頃からGaroonで使用しているPHPのアップデートおよびバージョンアップ作業に取り組んでいます。

バージョンアップ前の調査で推奨されなくなる箇所を大量に発見

PHPのバージョンアップ時に参照することになる移行マニュアルには、たくさんの新機能だけでなく、互換性のない変更や推奨されなくなる機能も書かれています。実運用環境で使っているPHPのバージョンを上げる前に、移行マニュアルに記載されている変更内容についてテストすることが求められています。

Garoonでは今年6月に製品で使用しているPHPのバージョンを8.1系統から8.2系統に更新したのですが、PHP 8.2で推奨されなくなった「動的なプロパティの利用」と「"${var}" / "${expr}" 形式の、文字列への値の埋め込み」の2点が特に製品に影響を与える範囲が広いことが事前の調査で判明していました。今回取り上げる後者についてその概要を紹介すると、"${var}"や"${expr}"といった形式での書き方が推奨されなくなり、"$var"、"{$var}"および"{${expr}}"といった形式で今後は書いていく必要があるという内容です。

GaroonはPHPスクリプトのソースコードだけでも120万行以上あり、本記事で取り上げる「"${var}" / "${expr}" 形式の、文字列への値の埋め込み」だけでも434箇所変更すべき箇所が見つかっていました。大量に見つかった箇所のソースコードを変更した場合、挙動が変わらないように品質をどう担保するか、テストをどうするかなどといったことを考えなければなりません。変更する箇所すべてに対して1箇所ずつ細かくテストするのは手間や期間を考えると現実的に無理があると感じ、何とかテスト工数を抑えられるような良い方法はないか思案していました。

文字列への値の埋め込みについてはASTのハッシュ値を活用できることが判明

何か方法はないか考えていたときに、昨年11月に開催されたPHP勉強会@東京で金城さんのASTに関する発表を聞いたことがきっかけで、PHP 8.1からPHP 8.2にバージョンアップする際に文字列への値の埋め込みについてはASTのハッシュ値を活用できるのではないかということを思いつくことができました。金城さんには改めてお礼を申し上げたいと思います。

daisuki.nichiyoubi.land

発表の内容は、プログラムの本質的構造を示すASTのハッシュ値が同じであればプログラムの変更前と変更後の間に差分がないと判断できるというものでした。当該発表の元ネタとなったモノタロウさんの記事( https://tech-blog.monotaro.com/entry/2018/09/26/142451 )でも、ASTが変化しない場合はテスト不要という基準を作って本番リリースできた事例が示されています。

tech-blog.monotaro.com

当時Yukimiチームが直面していた課題の1つであった「"${var}" / "${expr}" 形式の、文字列への値の埋め込み」の件については、ASTのハッシュ値を比較する手法が使えるかもしれないと感じて、後日検証してみると実際に適用できそうだということが分かりました。後日、チームメンバーに当該方法を使ってみることを提案し、チームでASTのハッシュ値に関する信憑性を確かめることにしました。

「ASTが変化しない=プログラムの差分はない」とみなせるのでテスト不要

ASTのハッシュ値については、複数のソフトウェアを使って確認しました。使用したソフトウェアは、php-astPHP-Parserの2種類です。実際に検証してみたところ、PHP 8.1でこれらのソフトウェアを使い、ソースコードの変更前と変更後のハッシュ値を比較したところ、ASTのハッシュ値は変わらないことを確認できました。

PHP 8.2で各ソフトウェアを実行した場合は、PHP 8.2からは非推奨を示すエラーメッセージが出力されるようにPHPの内部で変更されているため、ASTおよびASTのハッシュ値が変わっていました。ASTのハッシュ値を確認する際には、ASTが変わる前のPHP 8.1で実行する必要があり、使っているPHPのバージョンやASTが変わるタイミングを改めて意識および確認することが大事であることも再確認できました。

なお、一部ハッシュ値が変わるケースがありました。PHP CS FixerやRectorによる置換ではなく、テキストエディターやIDEを使って単に「${」を「{$」に一括置換した場合、「/** */」のコメント内にある"${var}"および"${expr}"形式で書かれている変数を置換した場合はハッシュ値が変わることをチームメンバーが発見してくれました。最終的には、コメント内部での置き換えであればプログラムへの影響はないと判断し、ASTのハッシュ値が変化しない場合はプログラムの差分はないとみなせるのでテスト不要という結論になりました。

変更障害率を下げるための取り組みの一環として小さくリリース

ASTのハッシュ値に関する信憑性を確認しつつ、QAエンジニアを交えてチームでテスト範囲やリリース方法を相談しながら実際のリリース作業を進めました。以前はビッグバンリリースが常態化していたGaroonでしたが、最近は変更障害率を下げるための取り組みの一環として可能な限り小さく早くリリースする方針をとっています。リリースプロセス改善の取り組みについては次の記事をご参照ください。

blog.cybozu.io

PHP 8.2にバージョンアップした際も、PHPのバージョンを実際に変更するメンテナンスを実施する前に、後方互換性のある内容を順次適用する形で小さくリリースする方針をとることにしました。上述の置換作業を反映したリリースを事前に順次行っていき、実際にPHPのバージョンを上げる時にはソースコードの書き換えはほぼない状態でリリースを迎えました。

今回紹介した内容については、結果として434箇所を個別にテストしなくても問題がないことを確認でき、大幅にテスト工数を削減できました。リリース後も特に問題はなく、将来の負担になるような技術的負債を残すことなく解決できました。チームメンバーからも感嘆の声があがったほどで、個人的にも非常に面白いほど上手くいったなと感じた経験でした。

まとめ

昨年参加した勉強会でプログラムの本質的構造を示すASTのハッシュ値が同じであればプログラムの変更前と変更後の間に差分がないと判断できるという発表を聞いたことがきっかけで、実際のプロダクト開発においてPHP 8.1からPHP 8.2にバージョンアップする作業の一部をスムーズに進められることに気づき、結果として大幅にテスト工数を削減できました。

ソースコードを変更した際にASTのハッシュ値が変化しなければ、プログラムの変更前と変更後の間に差分がないと見なすことができ、技術的に自信を持ってテスト不要と判断できることを今回改めて確認できました。

関心を持ち続けながら勉強会に参加していると、ふとしたことがきっかけでさまざまな情報がつながり、点から線に変わることを実感することがたまにあります。勉強会で得られる情報や知識は毎回関心を持っていることと関連することばかりではありませんが、勉強会で学んだ情報は後になって業務で役立つことがあります。機会があれば、皆さんも勉強会やカンファレンスに参加してみてはいかがでしょうか。