Save 37% off PRO during our Black Friday Sale! »

形式手法について調べてみた

 形式手法について調べてみた

開発でよくある仕様不備についてなにか打ち手はないかと模索していたとき、形式手法に出会いました。形式手法とはなにか、形式手法に期待される効果などをまとめました。

693ed679c8dde3eccbc682ff44f357e1?s=128

Hodaka Suzuki

March 07, 2019
Tweet

Transcript

  1. @hoddy3190 Mar. 7th 2019 Android Test Night #6 形式手法について調べてみた Let’s

    Try Formal Methods
  2. 鈴木穂高(Hodaka Suzuki) Twitter @hoddy3190 • 2014年DeNA新卒入社 • アプリゲーム開発・運用(2014/08 〜 2018/10)

    ◦ サーバー、クライアント、マスター管理ツール、インフラ 整備、マネジメントなど ◦ パフォチューが好き • テスト技術チーム - SWET(2018/10 〜) ◦ Android ◦ 形式手法
  3. 目次 • 動機 • 形式手法とは • モデル検査とは • 形式手法適用のシミュレーション •

    メリデメ等 • まとめ
  4. 動機 なぜ形式手法を調べようと思ったのか?

  5. 動機 ゲーム開発をしているときに多かった 仕様の不備(考慮漏れ、記載漏れ、矛盾など)に 開発フェーズの早い段階で気づきたい。 筆者がSWETに異動した理由の1つです。

  6. 開発フロー 企画 実装 QA リリース 仕様作成 仕様不備があり、仕様を修正せざるを得なくなった場合、 仕様不備の発見が後になればなるほど、 手戻り工数は大きくなる傾向がある。

  7. 開発チームでも改善の取り組みは行われているが、 場当たり的な対策(チェックリスト、詳しい人に聞くなど)に なってしまったり、工数・スキル的に手に負えない感じもあった。

  8. 開発チームとは少し異なる立場にいる テスト技術チームだからこそできるアプローチは ないかと探していたところ、形式手法に出会った。

  9. 形式手法とは

  10. 形式手法 仕様を明確に記述したり、 記述された設計の性質を機械的に検証する手法の総称。 数学に基づく科学的な裏付けを持つ。 登場は20世紀中頃から。結構古い。

  11. 用途 • 記述に不具合がないことを数学的に証明し保証 • 厳密な言語を用いることで仕様を明確化 • 記述中に隠れている不具合を開発早期に発見 • 正しいシステムだけを系統的に開発 ※一番最後の用途は、Correctness

    by Constrution (CxC)と呼ばれ、 実現は不可能に近いと言われている
  12. 代表的な手法 • 形式仕様記述 ◦ 矛盾がなく論理的に正しい仕様を作成する • モデル検査 ◦ プログラムの状態をモデル化することで プログラムが正しいことを検証する

    • 定理証明 ◦ 法則や説明に基づき、理論的に性質が 成り立つことを示していく
  13. 代表的な記述言語/ツール • 形式仕様記述 ◦ 例: VDM++/Event-B/Z etc. • モデル検査 ◦

    例: Alloy/Promela/TLA+ etc. • 定理証明 ◦ 例: Coq/Isabelle etc.
  14. モデル検査 今回は

  15. モデル検査 システムを有限個の状態を持つモデルで表現し、 モデルが取りうるすべての状態を機械的かつ網羅的に検査することで、 システムが仕様を満たすことを確認する。 レアケースなど気付きにくいバグでも発見できるので、 品質の高いソフトウェアを開発するための有効な手段の1つに挙げられる。

  16. 手順 1. 検査したいもの(仕様書、ソースコードなど)から 専用言語でモデルを作成する 2. 検査対象が満たすべき性質から検査式を作成する 3. モデル検査ツールにかける

  17. 変数a * 変数bの結果は常に9未満である 変数a 表明 変数b

  18. 変数a * 変数bの結果は常に9未満である 1から3までの任意の値をとる 変数a 表明 1から3までの任意の値をとる 変数b

  19. 変数a * 変数bの結果は常に9未満である 1から3までの任意の値をとる 変数a 表明 1から3までの任意の値をとる 検査 1. aとbの取りうるすべての組み合わせを探索する

    2. 表明に反する組み合わせ(反例)の有無をチェックする 3. 反例があれば表示する 変数b
  20. Promelaでの例 inline Choose(n) { if :: n = 1 ::

    n = 2 :: n = 3 fi } active proctype P() { int a = 0, b = 0 Choose(a) Choose(b) assert(a * b < 9) }
  21. Promelaでの例 inline Choose(n) { if :: n = 1 ::

    n = 2 :: n = 3 fi } active proctype P() { int a = 0, b = 0 Choose(a) Choose(b) assert(a * b < 9) } a、bの値をセットする
  22. Promelaでの例 inline Choose(n) { if :: n = 1 ::

    n = 2 :: n = 3 fi } active proctype P() { int a = 0, b = 0 Choose(a) Choose(b) assert(a * b < 9) } 普通のif文とは異なり、::に続く3つの処理のうちの 1つが非決定的に実行されるという意味
  23. Promelaでの例 inline Choose(n) { if :: n = 1 ::

    n = 2 :: n = 3 fi } active proctype P() { int a = 0, b = 0 Choose(a) Choose(b) assert(a * b < 9) } a、bが満たすべき条件を記述する(表明)
  24. Promelaでの例 Spinで検査 $ spin -a -o2 ./sample.pml $ gcc -o pan

    pan.c $ ./pan -E -c0 -e $ spin -p -t1 sample.pml # 結果出力 Spinはモデル検査ツール。Homebrewでインストール可能。
  25. Promelaでの例 結果 using statement merging 1: proc 0 (P:1) a.pml:5 (state

    3) [a = 3] 2: proc 0 (P:1) a.pml:5 (state 9) [b = 3] spin: a.pml:13, Error: assertion violated spin: text of failed assertion: assert(((a*b)<9)) 2: proc 0 (P:1) a.pml:13 (state 13) [assert(((a*b)<9))] spin: trail ends after 2 steps #processes: 1 2: proc 0 (P:1) a.pml:14 (state 14) <valid end state> 1 process created
  26. Promelaでの例 結果 using statement merging 1: proc 0 (P:1) a.pml:5 (state

    3) [a = 3] 2: proc 0 (P:1) a.pml:5 (state 9) [b = 3] spin: a.pml:13, Error: assertion violated spin: text of failed assertion: assert(((a*b)<9)) 2: proc 0 (P:1) a.pml:13 (state 13) [assert(((a*b)<9))] spin: trail ends after 2 steps #processes: 1 2: proc 0 (P:1) a.pml:14 (state 14) <valid end state> 1 process created (a, b) = (1, 1), (1, 2) …, (3, 2), (3, 3) と すべての組み合わせを探索した上で、 a = 3, b = 3 のときが反例であると表示される
  27. モデル検査の基本 1. システムが取りうる状態・パスを自動で網羅的に探索する 2. 反例があればトレースとともに表示する

  28. 形式手法適用の シミュレーション

  29. ここからは模索中の話になります 筆者はまだ開発現場に形式手法を適用したことがありません。 形式手法のアプローチやコード、適用実現性等に疑問が残る場合が あるかもしれませんが、ご容赦ください。

  30. 前提 • 仕様作成者が作る一次仕様には不備がある ◦ 工数、スキルなど原因は様々

  31. モデル化するフェーズは? 企画 実装 QA リリース 仕様作成

  32. モデル化するフェーズは? 企画 実装 QA リリース 仕様作成 here!!

  33. サンプル仕様 • いいねボタンを設置してください ◦ 投稿にいいねは1回のみ押せます ◦ ログインしているユーザーしかいいねを押せません 仕様作成者が、実際に仕様をこのように記述し、 持ち込んできたとします

  34. 実際に形式手法を適用してみよう! 今回はPlusCalという言語で書いてみます。 ※先と同じくPromelaで書かない理由は、いろいろな言語を紹介したい という意図によるものです。

  35. PlusCalでの例 ---- MODULE button ---- EXTENDS TLC (* --algorithm button

    variables pushed \in BOOLEAN, logined = TRUE; begin if logined = TRUE then pushed := TRUE; end if; assert FALSE; end algorithm; *) ====
  36. PlusCalでの例 ---- MODULE button ---- EXTENDS TLC (* --algorithm button

    variables pushed \in BOOLEAN, logined = TRUE; begin if logined = TRUE then pushed := TRUE; end if; assert FALSE; end algorithm; *) ==== 状態を変数で定義
  37. PlusCalでの例 ---- MODULE button ---- EXTENDS TLC (* --algorithm button

    variables pushed \in BOOLEAN, logined = TRUE; begin if logined = TRUE then pushed := TRUE; end if; assert FALSE; end algorithm; *) ==== ボタンが押されているかいないか
  38. PlusCalでの例 ---- MODULE button ---- EXTENDS TLC (* --algorithm button

    variables pushed \in BOOLEAN, logined = TRUE; begin if logined = TRUE then pushed := TRUE; end if; assert FALSE; end algorithm; *) ==== ログイン中か否か(説明の便宜上、TRUE固定にしてある)
  39. PlusCalでの例 ---- MODULE button ---- EXTENDS TLC (* --algorithm button

    variables pushed \in BOOLEAN, logined = TRUE; begin if logined = TRUE then pushed := TRUE; end if; assert FALSE; end algorithm; *) ==== ボタンの初期状態は、押されている場合もあれば 押されていない場合もある(ランダム)
  40. PlusCalでの例 ---- MODULE button ---- EXTENDS TLC (* --algorithm button

    variables pushed \in BOOLEAN, logined = TRUE; begin if logined = TRUE then pushed := TRUE; end if; assert FALSE; end algorithm; *) ==== ログイン中であればボタンを押した状態にする
  41. PlusCalでの例 ---- MODULE button ---- EXTENDS TLC (* --algorithm button

    variables pushed \in BOOLEAN, logined = TRUE; begin if logined = TRUE then pushed := TRUE; end if; assert FALSE; end algorithm; *) ==== 表明を常に満たせないようにすることで、 システムが取りうる全状態を反例として抽出できる
  42. PlusCalでの例 TLCで検査 $ pcal button.tla $ tlc button.tla -continue -dfid 100

    -dump log $ cat log.dump
  43. PlusCalでの例 TLCで検査 $ pcal button.tla $ tlc button.tla -continue -dfid 100

    -dump log $ cat log.dump pcalコマンドでPlusCalをTLA+に変換
  44. PlusCalでの例 TLCで検査 $ pcal button.tla $ tlc button.tla -continue -dfid 100

    -dump log $ cat log.dump tlcコマンドで検査
  45. PlusCalでの例 TLCで検査 $ pcal button.tla $ tlc button.tla -continue -dfid 100

    -dump log $ cat log.dump 結果出力
  46. PlusCalでの例 結果 State 1: /\ logined = TRUE /\ pc =

    "Lbl_1" /\ pushed = FALSE State 2: /\ logined = TRUE /\ pc = "Lbl_1" /\ pushed = TRUE
  47. State 1: /\ logined = TRUE /\ pc = "Lbl_1"

    /\ pushed = FALSE State 2: /\ logined = TRUE /\ pc = "Lbl_1" /\ pushed = TRUE assert FALSEに至る直前の状態の全パターン PlusCalでの例 結果
  48. State 1: /\ logined = TRUE /\ pc = "Lbl_1"

    /\ pushed = FALSE State 2: /\ logined = TRUE /\ pc = "Lbl_1" /\ pushed = TRUE ログイン中かつすでにボタンは押された状態 PlusCalでの例 結果
  49. ---- MODULE button ---- EXTENDS TLC (* --algorithm button variables

    pushed \in BOOLEAN, logined = TRUE; begin if logined = TRUE then pushed := TRUE; end if; assert FALSE; end algorithm; *) ==== 押された状態のボタンを押す?? PlusCalでの例 結果
  50. • いいねボタンを設置してください ◦ 投稿にいいねは1回のみ押せます ◦ ログインしているユーザーしかいいねを押せません ◦ 押された状態のボタンを押すと 仕様不備発見 NEW

  51. • いいねボタンを設置してください ◦ 投稿にいいねは1回のみ押せます ◦ ログインしているユーザーしかいいねを押せません ◦ 押された状態のボタンを押すとボタンはもとに戻る 仕様不備発見 仕様作成者と相談して決めた

  52. ---- MODULE button ---- EXTENDS TLC (* --algorithm button variables

    pushed \in BOOLEAN, logined \in BOOLEAN, enabled \in BOOLEAN; begin NotEnded: either \* push await logined /\ ~pushed /\ ~enabled; pushed := TRUE; or \* unpush await logined /\ pushed /\ ~enabled; pushed := FALSE; or \* lock await ~enabled /\ ~logined; enabled := TRUE; or \* unlock await enabled /\ logined; enabled := FALSE; or \* login/logout logined := ~logined; end either; End: skip; end algorithm; *) ==== PlusCalでの例
  53. ---- MODULE button ---- EXTENDS TLC (* --algorithm button variables

    pushed \in BOOLEAN, logined \in BOOLEAN, enabled \in BOOLEAN; begin NotEnded: either \* push await logined /\ ~pushed /\ ~enabled; pushed := TRUE; or \* unpush await logined /\ pushed /\ ~enabled; pushed := FALSE; or \* lock await ~enabled /\ ~logined; enabled := TRUE; or \* unlock await enabled /\ logined; enabled := FALSE; or \* login/logout logined := ~logined; end either; End: skip; end algorithm; *) ==== PlusCalでの例 ボタンが押せる状態になっているか否か
  54. • いいねボタンを設置してください ◦ 投稿にいいねは1回のみ押せます ◦ ログインしているユーザーしかいいねを押せません ◦ 押された状態のボタンを押すとボタンはもとに戻る ◦ ログインしていない場合、ボタンは押せない状態になる

    仕様不備発見 モデル化する過程を通して、 「押せない状態」の存在に気づけた NEW
  55. その他、仕様はまだまだ明確化できるが、 どこまでモデル化するのかという話もあるのでここで終える 例: ボタンを押したらいいねがされたことになるのか etc. 重要

  56. メリデメ等

  57. 形式手法導入のメリット • 検査結果から、仕様の不備に気づける ◦ (注意)モデルが間違っていることも多々ある • モデル化する過程を経ることで、仕様の不備に気づけたり、 仕様に詳しくなれたりする • 書いたものは、より明確な仕様書として使える場合もある

    • etc.
  58. 形式手法導入のデメリット • 開発に組み込む際の導入コスト ◦ 学習コスト、開発フロー調整コストなど • モデル化自体が難しい ◦ 状態爆発しないようにうまく抽象化しない といけないなど

  59. 導入をする場合 • すべての仕様を形式仕様に落とすことはモデル化の容易さ、 工数などの理由から現実的ではないと言われている • 部分的に導入していくのがよい ◦ 部分的導入であれば、世の中に導入事例は観測できる

  60. モデル検査適用の勘所 • モデル化しやすいところ ◦ 状態遷移など • 考慮が複雑なところ ◦ 複数プロセス処理など

  61. モデル検査で解いてみよう • N-Queen • ハノイの塔 • 有向グラフ • 数独 モデル検査の入門としてこれらの題材が取り上げられることが多い。

    興味があれば是非。
  62. まとめ

  63. まとめ • 形式手法は、仕様にはらみがちな、あいまい、不正確、複雑すぎる などの問題の解決策として活用が期待できそう • 費用対効果は実際に試してみて確かめる必要がある • 形式手法によっても、得意不得意があるので、形式手法を適用するのか しないのか、現場にマッチした形式手法がどれなのか等はきちんと 吟味する必要がある

  64. さいごに • 現在、複雑な状態遷移をはらむAndroidのプロダクトに対し、 実際に適用することを考えています • Androidの状態遷移図などもモデル化と相性がよいはずなので、 この場を借りて発表させていただきました