書籍「データモデリングでドメインを駆動する──分散/疎結合な基幹系システムに向けて」の第7章「ソフトウェア設計とデータモデル――用途から道具への転換」でご説明している「テーブル駆動方式」のコードサンプルです。
「納品後7日以内」「月末締め翌月末払」といった支払条件に基づいて支払日を決定する処理が題材です。
同書では、支払条件のバリエーションごとに条件分岐する、ある意味素直なアプローチと、支払条件のバリエーションを属性の組からなる表に展開して、その属性にもとづいて処理を行う「テーブル駆動方式」を対比し、それぞれについて部分的なコード例を示しています。このリポジトリは、それらのコードの全貌及びユニットテストを収容しています。
テーブル駆動方式を用いない「素直な」アプローチの例です。クラス「支払処理」の「支払期日」メソッド内で、支払条件に応じて処理を分岐しています。業務上の処理パターンごとにコードを記述するので業務パターン駆動方式と呼んでもよいかもしれません。
テーブル駆動方式の例です。前の例と同じく、クラス「支払処理」の「支払期日」メソッドが、支払日決定のメインルーチンですが、支払条件(すなわち、業務上の処理パターン)に応じて処理を分岐するのではなく、処理条件ごとに設定された属性値に従って処理を行っています。
前掲書の関心の焦点は、業務パターン駆動方式とテーブル駆動方式を対比することですから、他の論点が混入しないよう、これまでの2例のコードは、いずれも、単純な static メソッドを用いています。
これに対して、当リポジトリには、テーブル駆動方式のコードをオブジェクト指向的に記述した例も含めました。これは、業務パターン駆動方式とテーブル駆動方式の比較には関係ありません。オマケです。
テーブル駆動方式の適用については、table_driven_exampleと変わりありません。以下のような点を変更しています。
-
各メソッドを最も深く関連するクラスに振り分けるとともに、staticメソッドからインスタンスメソッドに変更する。
- 前日や翌日の取得など日付計算は「日付」クラスにまとめた
- 適用締日の決定ロジックは期日指定方法クラスに移した
-
ポリモーフィズム(多態)を適用した方がスッキリする箇所はそうする
- 適用締日の決定ロジックは期日指定方法(即日/月末日/指定日付)に依存するので、期日指定方法にポリモーフィズムを適用し、switchを無くした
ちなみに、table_driven_example_oopは、table_driven_exampleをもとに、小刻みなリファクタリングを重ねて作成しました。その過程は、プルリクエストをご覧ください。これをご覧いただくと、手続き的なプログラミングとオブジェクト指向プログラミングは決して対立概念ではなく、二者択一ということでもなく、ひとつのスペクトラム上の位置の違いでしかないことがご理解頂けると思います。
テーブル駆動方式(table_driven_example)のコード中、支払処理クラスの「n期間後」メソッドで、指定された日付のn日後やn月後を求めています。この処理では、起点となる日付の翌日をまず求め、そのn期間後を算出し、その前日を、起点となる日付のn期間後としています。
これは、起点が例えば4月30日だった場合、1か月後を、5月30日ではなく同じく月末日である5月31日としたいためです。すなわち、4月30日をまず翌日である5月1日に変換し、その1か月後である6月1日の前日として5月31日を得る、という流れです。もちろん、そもそもこういう処理が適切かは、要件に依存します。
月末日であるかを if 文で判定して処理を分けることも可能ですが、私は、条件分岐なしで済ませられるなら済ませたい派なので、このようにしています。もちろんこれは、条件判定を自前で行わずに LocalDate に託しているだけですが、私は、コードの品質に関して、自分よりライブラリ作者の方を高く買っているわけです。
なお、こうした「なぜ、こんなやり方をしているの?」という疑問に対する答えは、コード中にコメントとして含めた方がよいと思います。今回は、書籍のコードサンプルですから、本筋の論点に関係ない点を割愛するという趣旨で、そうしませんでした。
業務パターン駆動方式は、ユーザーが意図しているパターンごとにコードを書くので、直観的なわかりやすさという点でメリットがあります。
支払条件は6つしかないので、この例では、コードの長さという点でも業務パターン駆動方式に問題はないでしょう。一方で、業務パターンが多い場合には、テーブル駆動方式の方がコンパクトなコードで済むようになります。
条件分岐を出来るだけ少なくすることは良いコードを書く上で重要です。その観点からは、テーブル駆動方式に分があります。業務パターン駆動方式では業務上のパターンごとに条件分岐しますが、テーブル駆動方式では、それはありません。処理が少数の独立の要素に分解され、その組み合わせで業務上の多数のパターンが表現されます。条件分岐は前者に関連して発生するので、少なくて済む傾向があります。
条件分岐が少ないと言うことは、テストすべきケースが少なくて済み、不具合の混入可能性を減らせるということでもあります。業務パターン駆動方式の場合、パターンごとに処理が記述されているので、それぞれをテストする必要があります。テーブル駆動方式の場合、本質的には、業務パターンごとではなく、属性によって指定される個々の処理条件を網羅すればよいので、テスト容易性の点でメリットがあります。
要件変更への適応という点で、テーブル駆動方式にメリットがあることはご理解頂けるでしょう。新しい支払条件が追加された場合、テーブル駆動方式では、当該支払条件に関する属性値を決めて登録すればよいだけで、ロジック本体は修正不要です。
コードの質以外の観点からの比較については、前掲書をご覧いただければ幸甚です。
オブジェクト指向のサンプルも作成したので、手続き的プログラミングとオブジェクト指向プログラミングを対比して考察したいと考えています。ブログに掲載する予定ですので少々お待ちください。
杉本 啓
「データモデリングでドメインを駆動する──分散/疎結合な基幹系システムに向けて」著者