形式手法
数学的な手法でソフトウェアの正しさを保証する技術の総称。
プログラマ向けの一言
テストは「バグがある」ことを示せるが、「バグがない」ことは示せない。形式手法は「バグがない」ことを数学的に証明する。
テストとの違い
// テスト: 有限個の入力を試す
test("add", () => {
expect(add(1, 2)).toBe(3);
expect(add(0, 0)).toBe(0);
expect(add(-1, 1)).toBe(0);
// ...でも全部の入力は試せない
});
// 形式手法: 「すべての入力に対して」正しいことを証明する
// ∀ x, y ∈ Int: add(x, y) === x + y
// これはテストでは不可能。数学的証明が必要。
3つのアプローチ
// 1. 定理証明 (Theorem Proving)
// 「この関数は仕様を満たす」ことを人間が証明を書いて示す
// → 詳細は [[theorem-proving|定理証明]] を参照
// 2. モデル検査 (Model Checking)
// 状態を全探索して「違反がないか」を自動で調べる
// → 詳細は [[model-checking|モデル検査]] を参照
// 3. 型による検証
// プログラマにとって最も身近な形式手法
// TypeScript の型システムも(限定的な)形式検証
function divide(a: number, b: number): number {
return a / b; // b === 0 のとき? → 型では防げない
}
// より強い型(篩型)があれば:
// function divide(a: number, b: NonZero<number>): number
// → コンパイル時に b !== 0 を保証
// → 詳細は [[refinement-type|篩型]] を参照
使い分け: テスト / モデル検査 / 定理証明
同じ「正しさの保証」でも、保証の強さ・自動化・適用場面が違う。カバレッジ(どこまで保証するか)と自動化(人手をどこまで使うか)のトレードオフで並ぶ:
| 手法 | 保証の強さ | 自動化 | コスト | 適用場面・強み | 限界 |
|---|---|---|---|---|---|
| テスト | 有限個の入力のみ(バグの存在は示せるが不在は示せない) | 完全自動(書けば走る) | 低 | 回帰検出・最も普及・どんなコードにも効く | 試した入力の外は何も言えない |
| Property-based testing | 多数のランダム入力 + 縮小。ほぼ全称だが確率的 | 自動(性質は人間が書く) | 低〜中 | テストと形式手法の中間。不変条件の発見に強い | 反例が「無い」ことは保証しない |
| 型による検証 | 型が表す範囲で全入力を静的保証 | 自動(篩型は SMT、依存型は要証明) | 低〜中 | 日常的・コンパイラに常駐。境界・整合性に強い | 型で表せる性質に限る |
| モデル検査 | 状態空間内の全パスを網羅(時相性質・並行性) | 完全自動(モデル化は人手) | 中 | 並行・分散プロトコル、デッドロック/レース。反例トレースが出る | 状態爆発。無限状態は抽象化が要る |
| 定理証明 | すべての入力に対し数学的に証明(無限状態も可) | 対話的(人間 + tactic) | 高 | 最強の保証。アルゴリズム正当性・数学・OS/コンパイラ核 | 証明労力が膨大。専門知識が要る |
選び方の指針:
- 既定はテスト + 型 — 費用対効果が最も高い。まずここを厚くする。
- 並行・分散で「設計レベルの」バグが怖い → モデル検査(TLA+ で仕様を検査。実装ではなく設計の論理矛盾を炙り出す)。
- その性質が崩れたら致命的(暗号・OS核・制御) → 定理証明。コストは高いが「全入力での証明」は他で代替できない。
- これらは排他でなく層: TLA+ で設計を検査 → 型で日常を守る → クリティカルなコアだけ定理証明、という積み方が現実的。「保証の強さ ∝ コスト」なので、失敗コストの高い部分にだけ強い手法を投じるのが原則。
実用されている場所
// 形式手法は「ミスが許されない」領域で使われている:
// - AWS: S3, DynamoDB の分散プロトコルを TLA+ でモデル検査
// - 航空: Airbus のフライト制御ソフトウェア
// - 暗号: TLS 1.3 のプロトコル検証
// - CPU: Intel の浮動小数点演算の検証(Pentium バグの再発防止)
// - 鉄道: パリ地下鉄の自動運転システム
// プログラマにとって身近な例:
// - TypeScript の型チェック
// - Rust の借用チェッカー
// - Property-based testing(fast-check 等)← テストと形式手法の中間
押さえどころ(カード化候補)
- 形式手法とは → 数学的手法でソフトの正しさを保証する技術の総称。テストは「バグの存在」を示せるが「不在」は示せない。形式手法は「すべての入力で正しい」を証明する。
- 3 つのアプローチ → 定理証明(人間が証明を書く・最強の保証・高コスト)、モデル検査(機械が全状態探索・並行/分散に強い・自動)、型による検証(コンパイラ常駐・最も身近)。
- 使い分けの軸 → 「保証の強さ ∝ コスト」。テスト+型を既定にし、並行/分散の設計バグはモデル検査、崩れたら致命的なコアだけ定理証明。排他でなく層として積む。
- モデル検査 vs 定理証明 → モデル検査は有限状態を全自動で網羅し反例トレースを出すが状態爆発する。定理証明は無限状態も全入力で証明できるが人手の証明労力が膨大。
- 現場での層 → TLA+ で設計を検査 → 型(篩型/依存型)で日常を守る → クリティカルなコアだけ定理証明。失敗コストの高い箇所にだけ強い手法を投じる。
関連
- 定理証明 — 人間が証明を書く最強保証・高コストのアプローチ。無限状態・任意の性質を扱える
- モデル検査 — 機械が状態空間を全探索する自動アプローチ。並行/分散・時相性質に強く反例トレースを返すが状態爆発する
- 依存型 — 型で任意の仕様を表現し証明をコードに内在させる。定理証明系の型基盤
- 篩型 — 値の制約を述語で型に載せ SMT で自動検証。テストより強く依存型より軽い日常的な層
- カリー=ハワード同型対応 — 型 = 命題、プログラム = 証明。型による検証が形式手法たりうる理論的根拠