/note/tech

PHPでやってみよう!テストだけじゃない、デシジョンテーブル(決定表)実装の勘所

要約:

■ 1. セッション概要

  • PHP カンファレンス関西 2025(2025年7月19日)での発表
  • 発表者: katzumi(かつみ)、LITALICO所属、レセプト業務基盤システムを開発
  • テーマ: デシジョンテーブルをテスト設計ではなく、実装パターンとして活用する手法

■ 2. デシジョンテーブルとは何か

  • 定義:
    • 複数の条件と結果の組み合わせを表形式で整理したもの
    • 条件部(Condition)と動作部(Action)で構成される
    • 各条件の状態を「Yes/No」「True/False」などで表現する
  • テスト設計での一般的な活用:
    • テストケースの漏れを防ぐ
    • 条件の組み合わせを網羅的に可視化する
    • テスト観点の明確化とチーム内での認識共有に有効
    • 複雑なビジネスルールや回帰テストへの応用
  • 活用例(住宅ローン審査基準):
    • 条件: 雇用形態(正社員/契約社員/無職)、勤続3年以上、年収500万以上
    • 動作: 承認、条件付承認(9パターン)

■ 3. 本セッションのテーマ設定

  • デシジョンテーブルを「書く」(仕様まとめ・テスト設計)ではなく「実装する」ことを主題とする
  • 発表者の背景: 障害福祉・介護サービスのレセプト業務の基盤システムを開発
    • 診療報酬明細書相当の「算定構造表」は1500頁超、3年ごとに大改定が発生する
    • 算定構造表は表形式でまとめられており、デシジョンテーブルと親和性が高い
    • 元の原文(報酬告示)は縦書き官報形式で非常に複雑

■ 4. 手続き的実装の問題点

  • 算定ルールを無策に実装すると巨大なif/switch文になりがち(深いネスト、通称「波動拳コード」)
  • 弊害:
    • コードのネストが深く可読性が低い
    • 図式化されたルールを手続きコードに変換することでインピーダンスミスマッチが発生し認知負荷が高まる
    • 同一規則のルールが複数箇所に散らばる
    • 事実・条件・動作が密結合し更に複雑化する
    • ルールは宣言的だが手続きとして書き直すと直感的でなくなり重複も発生する
  • 複雑さの分類:
    • 本質的な(Essential)複雑さ: ドメイン自体に由来するもの
    • 付随的な(Accidental)複雑さ: 実装手法の選択に由来するもの

■ 5. デシジョンテーブルが有効な場面

  • 有効な業務:
    • 審査・承認業務
    • 診断・レコメンデーション業務
    • リスク評価・不正検知
    • 自動応答・問い合わせルーティング
    • 業務フローの自動化・タスク割り当て
    • コンフィギュレーション・設定
  • 適している業務の特徴:
    • 判断基準が明確(複数の論理的なルールで意思決定を定義できる)
    • ルールの変更が発生しやすい(法改正・社内規定変更・新商品追加など)
    • 例外処理が多い(定型処理だけでなく多様な例外ケースへの対応が必要)
    • 属人化された知識の形式知化が求められる

■ 6. PHPでのデシジョンテーブル実装テクニック

  • Enumを活用した条件定義:
    • 条件は全てEnum(列挙型)の配列で定義し、マジックナンバーやクラス定数を排除する
    • Enumの区分値はドメイン用語(日本語)で定義し、開発者とビジネス担当者の共通言語として機能させる
      • 例: EmploymentStatus::社員, EmploymentStatus::契約社員
    • 数値条件もEnum化することで境界値を明示的に表現する
      • 例: AnnualIncome::300万未満, AnnualIncome::300-500万未満, AnnualIncome::500万以上
      • 変換ロジックも同Enum内に凝集させてテストしやすくする
  • 決定ルールをGenerator関数で定義:
    • イテレータとして条件の組み合わせを生成し、yield / yield from で柔軟にルールを宣言できる
    • 制度変更や期間限定ルールは日付による切り替えで明示的に表現する
    • ルールの実態クラスのオブジェクトは別途生成し、必要なケースだけを遅延評価で生成する(Generatorと相性が良い)
  • 特殊条件の型による制御:
    • 全ての条件に共通のマーカーインタフェース(Condition)を付与して型安全性を確保する
    • 複合条件の選択: サブインタフェース(例: DiscountCondition)でメソッドを追加し、複数の適用可能条件の中から最適な一つを選択させる
    • 動作への特殊制御: 別のサブインタフェース(例: RepeatableCondition)で、決定ルールには含まれないが動作に影響を与える特殊な型を定義する(例: 動作の繰り返し回数の指定)

■ 7. 責務分離による実装設計

  • 決定ルール・Enum変換・条件判定はそれぞれ独立したクラスとして設計(単一責任の原則: SRP)
  • 4層構造による責務分離:
    • 入力層: 生データのI/O処理(InputData
    • 変換層: 数値→Enum変換(ConditionConverter
    • ルール層: ビジネスルール定義(DecisionTable
    • 評価・実行層: ルール評価と特殊制御(RuleEvaluator
  • 責務分離のメリット:
    • 入力データに関連する変化があっても決定ルールには影響しない
    • I/O処理を分離することでテスト容易性が向上する
    • テストケースが最も多い決定ルールのテストサイズを小さくできる
    • 条件判定処理を共通化し決定ルールの差し替えが可能になる

■ 8. 品質確保のアプローチ

  • デシジョンテーブルの型を厳密にチェックする:
    • 決定ルール判定時に条件のEnumの過不足がないことをチェックする
    • Enum変換時の変換漏れや重複はバグとして検出する
    • Enumが存在しない場合は「条件を不問」とはしない
  • デシジョンテーブルから全因子テストを行う:
    • Enumのcases全ての組み合わせを生成して後続処理も含めて動作検証する
    • デシジョンテーブルに矛盾がないかをセルフチェックできる

■ 9. 導入のメリットと実践的な効果

  • デシジョンテーブル自体の特性によるメリット:
    • 条件分岐ロジックを構造化・可視化し、ネストの深いif文やswitch文を解消できる
    • ルールの追加・変更・削除が表形式で明示的に管理可能
    • 変更の影響が局所化され、条件ごとに独立して修正できる
    • 適用日や期間限定ルールを明示的に管理でき、バージョン管理や履歴保持が容易
  • アーキテクチャ(周辺構造)によるメリット:
    • ビジネスルールとデータ処理ロジックの明確な切り分け(関心事の分離)
    • ルールだけを独立してテスト可能で影響分析が簡単
    • 入力データ形式の変更に強い設計(基本ロジックが安定)
  • テスト面のメリット:
    • 条件判定結果の検証が容易(マスターデータとの照合や差異特定が迅速)
    • 全パターンテストによる品質向上(組み合わせテストの実装が標準化)
    • PICT(組み合わせテスト手法)との親和性が高く、テストケース数の効率的削減が可能