若くない何かの悩み

何かをdisっているときは、たいていツンデレですので大目に見てやってください。

画面仕様書への静的検査器を実装したらたくさんの欠陥を発見できた話

社のブログで最近の成果を公開しました。ぜひご覧ください。

swet.dena.com

画面仕様書への静的検査器を実装したらたくさんの欠陥を発見できた話 --- SWET第二グループの[Kuniwak](https://kuniwak.com/)です。本記事では画面仕様(後述)の仕様書に対する静的検査器を開発した事例について紹介します。 ## 伝えたいこと 1. 画面表示と画面遷移を記述する仕様書は機械可読にできる 2. 仕様書が機械可読であれば仕様の静的検査ができる 3. 静的検査によって自身の担当範囲の15%の画面から計40件弱の欠陥を発見した 4. 機械可読な仕様書にはさらなる応用が見込める ## おさらい:仕様とは 仕様の定義はいくつかあります。 ここでは仕様とは実装の正しい振る舞いを定める基準とします。 ある実装が正しいと判定されることを、実装が仕様を満たしたといいます。 誰による判定でも実装が仕様を満たしたかどうかの判定結果は一致すべきです。 さて実装の欠陥と同様に、仕様にも欠陥が生じえます。 本来正しいと意図した実装の振る舞いを誤っていると判断したり、その逆に誤っていると意図した実装を正しいと判断する仕様には欠陥があります。 仕様の欠陥の典型的な例の1つに矛盾した記述の存在があります。 矛盾した記述を含む仕様はどんな実装でも満たせません。 たとえば、ある箇所でログイン画面のボタンのテキストが「ログイン」と指示されているのに、別の箇所で「サインイン」と指示されている場合、どんな実装でもこの両方を同時に満たすことはできません。 すると、仕様を満たせる実装が1つもないということになります。 仕様を満たせる実装が1つもない仕様は無価値ですから、実際には正しいとしたかった実装はあったはずでしょう。 まさにこの例は正しいとしたかった実装を誤っていると判断する仕様になっています。 別の欠陥の例として、仕様が意図どおりだったとしても、その仕様を満たした実装が解決したかった問題を解決できなかった(e.g. 売り上げが目標に至らなかった)というものもあり得ます。 ただし本記事ではこの種類の欠陥はスコープ外としています。 ## 仕様に欠陥があるとどうなるか さて、仕様に欠陥があるとどうなるのでしょうか。 開発プロセスにおける仕様の利用者は主に2つです: * 実装者:実装者の役割は仕様を満たす実装を提供すること * 検証者:検証者の役割は実装が仕様を満たしていることを確認すること 仕様に欠陥があると、それぞれの利用者に次の悪影響があります: 1. 本来不要だったコミュニケーションコストの発生 - 実装者または検証者が仕様を不審に思えれば実装前に仕様への質問でこれを発見できます。ただし本来不要であったコミュニケーションです 2. 無駄な実装・検証コストの発生 - 検証者が不審に思えなければそのとおりに実装されてしまい、仕様策定者がその実装を触るまで意図どおりでないことに気づけません。運よく出荷前に気づけた場合でも一部または全部の実装が無駄になります 3. 信頼の失墜 - 運悪く出荷前に気付けなければ、エンドユーザーからのお問い合わせ等で発覚することになります。その場合は実装が無駄になるうえ、エンドユーザーからの信頼を損ないかねません ここまでのまとめです: 1. 仕様とはシステムの正しい振る舞いを定める基準です 2. 実装と同様に仕様にも欠陥を考えられます 3. 仕様に欠陥があると余計なコストの発生や信頼の失墜が発生しえます ## 画面仕様とは 本記事で扱う仕様は、GUIアプリケーションの画面の見た目と画面間の遷移にまつわるものです。 この仕様を**画面仕様**と呼び、今回は画面表示仕様(後述)と画面遷移仕様(後述)の2つの組であるとします。 ここで**画面**とは主にGUIの見た目によってグルーピングされたアプリケーションの状態の集合です[^1]。 たとえば一般的なログイン画面を例に画面について考えてみましょう。 ログインという操作には入力フォームの入力状態や認証サーバーとの通信状態が関与します。 これらのありえる組み合わせからなる状態の集合がログイン画面です。 [^1]: 実際には見た目が似ていても別の画面として扱った方がよいこともあります。たとえば一般ユーザーと特権ユーザーが別れているサービスがあったとして、それぞれのログイン画面の見た目を似せることはできますが、そこに到達するまでの経路およびそこから遷移する先の画面が大きく異なる場合、別の画面として扱った方がわかりやすくできるでしょう。 また画面内の状態遷移や画面間の遷移は、UI要素への操作(例:クリックやホバー、スクロール)やサーバーとの通信、時刻などを引き金として起こります。 この引き金のことを**イベント**と呼びます。 ### 画面表示仕様とは 多くの画面は入力フォームやボタンなどのUI要素を決まった位置に配置されています。 画面内のそれぞれの状態ごとに、UI要素をどんな見た目でどの位置に配置するかを指示する仕様が**画面表示仕様**です。 なお個々のUI要素のとりうる状態や見た目、受け付けるイベントについて画面表示仕様とは別にあらかじめ**UI要素仕様**として定めるのが一般的です。 そうすることで複数の画面で共通するUI要素の仕様をそれぞれの画面表示仕様内に重複して記述しなくともよくなります。 本記事ではUI要素の表示や状態遷移にまつわる仕様をUI要素仕様で別に定めているとし、画面表示仕様では(1)UI要素の配置と、(2)画面内のUI要素間の相互作用による振る舞いを記述することとします。 ここで**UI要素間の相互作用**とは、イベントによって複数のUI要素の状態が連動することをいいます[^2]。 たとえば画面に2つのUI要素として入力フォームとテキストが配置されているとします。 そして入力フォームへキーボード入力というUI操作をおこなった結果、その入力が入力規則に違反していればテキストにエラーメッセージが表示され、違反していなければテキストを非表示にされる、という振る舞いはUI要素間の相互作用による典型的な振る舞いです。 [^2]: CSPにおける並行合成を意図しています。 ここで画面表示仕様の例を、UI要素の配置とUI要素間の相互作用による振る舞いの順でみていきましょう。 まずUI要素の配置は、状態によってUI要素の配置が変わらなければ代表的な状態におけるUI要素の配置を示せば十分です。 たとえば次のログイン画面が状態ごとにUI要素の配置が変わらないとすると、このスクリーンショットで十分です。 !(https://cdn-ak.f.st-hatena.com/images/fotolife/s/swet-blog/20250416/20250416150013.png) 状態によってUI要素の配置が異なればそれらの配置の代表的な状態の配置を示せば十分です。 次にUI要素の間の相互作用は、UI要素の配置図に加えて何らかの表現でそれを記述する必要があります。 本事例ではこれを自然言語によって記述しました。 前述の入力フォームとテキストの振る舞いの説明のように記述しています。 なお実装がこの画面表示仕様を満たしたと判断する基準は、大雑把にいうと仕様と実装に同じイベントを与えた結果の見た目が一致することです[^3]。 この画面表示仕様の指示する見た目の集合はUI要素の配置とUI要素ごとの状態から計算できます。 [^3]: 仕様と実装の両方が決定的かつ内部イベントによる遷移を含まない場合です。非決定的な場合はトレース実行後の仕様と実装の両方の見た目は集合となります。このときは実装の見た目の集合が仕様の見た目の集合に包含されるか否かで判定します。内部イベントによる遷移を含む場合は不安定な状態を比較対象から取り除くとよいでしょう。このときクライアントとサーバー間の通信中の画面表示の指示もしたいことがほとんどですから通信によるイベントを隠蔽しない工夫が必要になるでしょう。 ### 画面遷移仕様とは ほとんどのGUIアプリケーションは、ユーザーが画面を遷移しながら操作していくことを意図されています。 このような画面内の状態を点とし、イベントを辺としたラベル付き有向グラフを本記事では**画面遷移仕様**と呼びます。 たとえばログイン画面内のユーザー名・パスワードが未入力な状態`S_0`に正しいユーザー名`Taro`とそのパスワードの組を入力すると`S_Taro`へと遷移する場合、`S_0`と`S_Taro`の間をユーザー`Taro`の正しい認証情報の入力という辺で結びます[^4]。 [^4]: この図はわかりやすさのために素朴に状態とイベントを描いています。実用的には状態変数やガード、事後条件を使ってより見通しのよい図にする方がよいでしょう。 !(https://cdn-ak.f.st-hatena.com/images/fotolife/s/swet-blog/20250414/20250414134448.png) この状態遷移グラフの表現方法はいくつかあります。 上に示した状態遷移図や状態遷移表がその代表的な候補です。 なお実装がこの画面遷移仕様を満たしたと判断する方法については説明が長くなるため割愛します。 本事例では[Communicating Sequential Process (CSP)知りたい方は](https://ja.wikipedia.org/wiki/Communicating_Sequential_Processes)という理論を背景にしているため、気になる方はCSPにおける「詳細化」という概念を調べてください。 CSPおよび詳細化については「[並行システムの検証と実装(磯部 祥尚 著、近代科学社)](https://www.kindaikagaku.co.jp/book_list/detail/9784764904354/)」がわかりやすいです。 ## 画面仕様を機械可読にする方法 今回の取り組みは[Confluence Wikiマークアップ](https://ja.confluence.atlassian.com/doc/confluence-wiki-markup-251003035.html)で記述された既存の仕様書が画面仕様として十分な情報を持っていないところからスタートしました。 そこで既存の仕様書が画面仕様として十分な情報を持てるように画面ごとにそれぞれを次のようなUI要素の配置図(画像左)とUI要素表(画像右)の組を書くようにしました。 ![](https://cdn-ak.f.st-hatena.com/images/fotolife/s/swet-blog/20250414/20250414134453.png) この`Scr.001`は画面IDで`ログイン画面`は画面名です。 本事例の仕様書ではすべての画面に画面名だけでなくIDをつけました。 画面はさまざまな箇所で言及され(例:UI要素表内の画面遷移)、その際に言及先の画面を一意に特定するためのIDが必要になるためです。 画像左はUI要素の配置図で画面表示仕様の一部です。 画像右はUI要素ごとにIDや種類、表示条件、表示内容、インタラクションを記述します。 表示条件や表示内容は画面表示仕様の一部です。 インタラクションは画面遷移仕様の一部です。 このようにUI要素表を組むことで、Confluence Wikiマークアップを解析すれば画面遷移仕様を読み取れるようになります。 また状態遷移図は画像ではなくPlantUMLマクロで描画することによって機械可読にしています。 ## 機械可読な画面仕様への静的検査 機械可読な画面仕様に対して静的な検査が可能です。 今回の事例ではGo言語で6000行弱の静的検査器を実装しました。 この静的検査器は23の検査ルールを持っています。 この検査ルールは、エンジニアの間で事前に洗い出しておいた仕様インスペクション観点がもとになっています。 この事前に洗い出された仕様インスペクション観点は20ほどあり、いくつかを抜粋します: 1. 画面遷移図とUI要素表が整合すること 2. インタラクション可能なUI要素(ボタン・チェックボックス等)にインタラクションの記載があること 3. UI要素の種類がリストのものについてはリスト内のUI要素の並び順の指示が明確であること 4. 動的画像は表示範囲に対して画像の大きさが異なるとき拡大縮小の指示が明確であること 5. ... 今回はこれらの観点のうち8つを自動化できました。 上の例であれば次のような基準で実装されています: 1. PlantUMLで記述された状態遷移図と各画面それぞれのUI要素表のインタラクション列に出現する画面IDを付き合わせた辺の整合性の検査[^5]。画面遷移図内のすべての遷移がUI要素表にあり、かつUI要素表にあるすべての遷移が画面遷移図にあればOK、それ以外はNGと判定 2. 種類がボタンなどの場合にインタラクションの列に記載があればOK、なければNGと判定 3. 種類が動的または静的リストの場合、「順」という文字が表示内容に出現すればOK、なければNGと判定 4. 種類が動的画像の場合、「拡大」「縮小」「見切れ」という文字列のいずれかが出現すればOK、それ以外はNGと判定 5. ... [^5]: 本来は画面間の大雑把な遷移関係ではなく、個々の状態間の遷移関係を特定できるように状態変数やガードや事後条件を機械可読な形式で記述すべきです。しかし機械可読な形式が人間にとって読みやすいとは限らないことから今回の事例でそこまでは踏み込めませんでした。自然言語での説明にとどまっています。 また記載ミスや記載漏れによって欠陥が見逃されないように、補助的な仕様インスペクション観点を追加で15設けています。 たとえば: 1. 重複したIDがなければOK、あればNGと判定 2. TODOという文字列を含まなければOK、含めばNGと判定 3. テキストや画像はそれが静的なのか動的なのかが指示されていればOK、なければNGと判定 4. ... 余談ですが、静的検査器の実装は検査ルール1つにつき、UI要素レイアウト、UI要素表、画面遷移図のいずれかを入力とし、発見された欠陥のリストを返す関数として実装しています。 このようにすることで検査ルール間の独立性が高まり、検査ルールの追加や削除、変更を容易にできます。 また検査対象外の要素は無視するようにした方がよいです。 こうすることで仕様に対して画一的な表現を強制されなくなり、より適した表現(例:画面内の複雑な状態遷移を状態遷移図で表現する、複雑な条件判定をフローチャートで表現する、など)を仕様書へ埋め込めるようになります。 その表現についても検査対象にしたければ、その表現だけを検査する追加のルールをあとで実装すればよいのです。 ## 画面仕様の静的検査を導入した結果 この静的検査器を使うことで、プログラマーに仕様が渡ってくる前に15%の画面から計40の欠陥を発見できました。 また検査器によって欠陥が摘出された後の仕様書への質問は、静的検査器を使っていない他事例の平均的な質問数より少ない傾向にあることがわかっています。 ## 機械可読な画面仕様の課題 非プログラマーによって機械可読な仕様書を保守する場合、機械可読に保つハードルが高いとわかりました。 そのため残念ながら今回実装した静的検査器は継続的な運用には至れませんでした。 プログラマーが仕様書を保守するように役割を変更すれば解決できるかもしれません。 そのように役割を変更する場合、仕様書の保守にかかるプログラマーの工数の捻出が課題になるでしょう。 後述するような機械可読な仕様書による実装の自動生成分でまかなえるかどうかがポイントになりそうです。 ## 機械可読な仕様の応用 画面仕様やその周辺を機械可読にすることで仕様策定を取り巻くプロセスの一部を自動化できることがわかっています。 たとえば本事例では[仕様策定プロセス](https://cdn-ak.f.st-hatena.com/images/fotolife/s/swet-blog/20250416/20250416171648.png)のP3とP4が自動化されました。 P3についてはUI要素の配置図はFigma上のオブジェクトと紐づけることでFigma APIを使い機械的な画像の更新を実現しています[^6]。 [^6]: ConfluenceにFigmaを埋め込めるFigma Widgetが利用可能でしたが動作が重すぎるため利用しませんでした。 また仕様を機械可読にすれば実装の自動生成や検証項目の自動生成をできるかもしれません。 完全な自動生成は難しいかもしれませんが、部分的な自動生成であれば実現しやすいと推測しています。 たとえば典型的なE2Eテストは画面遷移仕様に基づく検証です。 今回紹介した画面遷移仕様にいくつかUI要素のID等のヒントを与えればE2Eテストの自動生成が可能に思えます。 また機械可読な仕様の検索や解釈をLLM向けに支援するMCPサーバーを考えられます。 これは実装や検証項目、テストの自動生成や、それらのプロセスの支援の役に立つかもしれません。 ## まとめ 1. 画面表示と画面遷移を記述する仕様書は機械可読にできる 2. 仕様書が機械可読であれば仕様の静的検査ができる 3. 静的検査によって自身の担当範囲の15%の画面から計40件弱の欠陥を発見した 4. 機械可読な仕様書にはさらなる応用が見込める