$30 off During Our Annual Pro Sale. View Details »

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

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

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

Hodaka Suzuki

March 07, 2019
Tweet

More Decks by Hodaka Suzuki

Other Decks in Technology

Transcript

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

    View Slide

  2. 鈴木穂高(Hodaka Suzuki)
    Twitter @hoddy3190
    ● 2014年DeNA新卒入社
    ● アプリゲーム開発・運用(2014/08 〜 2018/10)
    ○ サーバー、クライアント、マスター管理ツール、インフラ
    整備、マネジメントなど
    ○ パフォチューが好き
    ● テスト技術チーム - SWET(2018/10 〜)
    ○ Android
    ○ 形式手法

    View Slide

  3. 目次
    ● 動機
    ● 形式手法とは
    ● モデル検査とは
    ● 形式手法適用のシミュレーション
    ● メリデメ等
    ● まとめ

    View Slide

  4. 動機
    なぜ形式手法を調べようと思ったのか?

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  9. 形式手法とは

    View Slide

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

    View Slide

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

    View Slide

  12. 代表的な手法
    ● 形式仕様記述
    ○ 矛盾がなく論理的に正しい仕様を作成する
    ● モデル検査
    ○ プログラムの状態をモデル化することで
    プログラムが正しいことを検証する
    ● 定理証明
    ○ 法則や説明に基づき、理論的に性質が
    成り立つことを示していく

    View Slide

  13. 代表的な記述言語/ツール
    ● 形式仕様記述
    ○ 例: VDM++/Event-B/Z etc.
    ● モデル検査
    ○ 例: Alloy/Promela/TLA+ etc.
    ● 定理証明
    ○ 例: Coq/Isabelle etc.

    View Slide

  14. モデル検査
    今回は

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  19. 変数a * 変数bの結果は常に9未満である
    1から3までの任意の値をとる
    変数a
    表明
    1から3までの任意の値をとる
    検査
    1. aとbの取りうるすべての組み合わせを探索する
    2. 表明に反する組み合わせ(反例)の有無をチェックする
    3. 反例があれば表示する
    変数b

    View Slide

  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)
    }

    View Slide

  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の値をセットする

    View Slide

  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つが非決定的に実行されるという意味

    View Slide

  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が満たすべき条件を記述する(表明)

    View Slide

  24. Promelaでの例 Spinで検査
    $ spin -a -o2 ./sample.pml
    $ gcc -o pan pan.c
    $ ./pan -E -c0 -e
    $ spin -p -t1 sample.pml # 結果出力
    Spinはモデル検査ツール。Homebrewでインストール可能。

    View Slide

  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)
    1 process created

    View Slide

  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)
    1 process created
    (a, b) = (1, 1), (1, 2) …, (3, 2), (3, 3) と
    すべての組み合わせを探索した上で、
    a = 3, b = 3 のときが反例であると表示される

    View Slide

  27. モデル検査の基本
    1. システムが取りうる状態・パスを自動で網羅的に探索する
    2. 反例があればトレースとともに表示する

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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;
    *)
    ====

    View Slide

  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;
    *)
    ====
    状態を変数で定義

    View Slide

  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;
    *)
    ====
    ボタンが押されているかいないか

    View Slide

  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固定にしてある)

    View Slide

  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;
    *)
    ====
    ボタンの初期状態は、押されている場合もあれば
    押されていない場合もある(ランダム)

    View Slide

  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;
    *)
    ====
    ログイン中であればボタンを押した状態にする

    View Slide

  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;
    *)
    ====
    表明を常に満たせないようにすることで、
    システムが取りうる全状態を反例として抽出できる

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  45. PlusCalでの例 TLCで検査
    $ pcal button.tla
    $ tlc button.tla -continue -dfid 100 -dump log
    $ cat log.dump
    結果出力

    View Slide

  46. PlusCalでの例 結果
    State 1:
    /\ logined = TRUE
    /\ pc = "Lbl_1"
    /\ pushed = FALSE
    State 2:
    /\ logined = TRUE
    /\ pc = "Lbl_1"
    /\ pushed = TRUE

    View Slide

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

    View Slide

  48. State 1:
    /\ logined = TRUE
    /\ pc = "Lbl_1"
    /\ pushed = FALSE
    State 2:
    /\ logined = TRUE
    /\ pc = "Lbl_1"
    /\ pushed = TRUE
    ログイン中かつすでにボタンは押された状態
    PlusCalでの例 結果

    View Slide

  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での例 結果

    View Slide

  50. ● いいねボタンを設置してください
    ○ 投稿にいいねは1回のみ押せます
    ○ ログインしているユーザーしかいいねを押せません
    ○ 押された状態のボタンを押すと
    仕様不備発見
    NEW

    View Slide

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

    View Slide

  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での例

    View Slide

  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での例
    ボタンが押せる状態になっているか否か

    View Slide

  54. ● いいねボタンを設置してください
    ○ 投稿にいいねは1回のみ押せます
    ○ ログインしているユーザーしかいいねを押せません
    ○ 押された状態のボタンを押すとボタンはもとに戻る
    ○ ログインしていない場合、ボタンは押せない状態になる
    仕様不備発見
    モデル化する過程を通して、
    「押せない状態」の存在に気づけた
    NEW

    View Slide

  55. その他、仕様はまだまだ明確化できるが、
    どこまでモデル化するのかという話もあるのでここで終える
    例: ボタンを押したらいいねがされたことになるのか etc.
    重要

    View Slide

  56. メリデメ等

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  62. まとめ

    View Slide

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

    View Slide

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

    View Slide