システム開発で思うところ

JavaEEを主にシステム開発をしながら思うところをツラツラを綴る

【書評】現場で役立つシステム設計の原則

読みました。断片的に収集していた増田亨さん(@masuda220)の知識を理解するのに良い本だと思います。もしDDDについて調べていて増田さんのスライドなどを見て「もう少し詳しく知りたいかも」と思った人は読むことをお勧めします。

良かった

ロジックをenumを使って表現する

P60:
EnumStrategyを管理するという言い方になるのかな?確かにこういうのは分類と実装を意味ある塊に整理出来るので良さそうです。

調べたら、こういうのもありました。
【enum】メソッドの定義(3)−strategyパターンを使う方法 - THE HIRO Says

メソッドは必ずインスタンス変数を使う

P81:
インスタンス変数を使わない理由があるとしたら、そこには更なるクラス設計が出来るかもしれないと思って疑ってみる。

確かに引数だけを使ってインスタンス変数を使わないメソッドを作り始めると神Utilみたいなのを作りやすいです。また整理のやり方としては1つにまとめるべきかもしれないけど、往々にして「じゃぁ、これもこの概念にまとめておいた方が良いよね」となって設計の粒度が属人化することもあります。クラスは抽象的な概念で まとめるよりも関連するデータ(プロパティ)を中心に設計するという指針にしておけば「気がついたら巨大になっていた」「設計者によってクラスの粒度が大きく異なっている」というのは是正されるかもしれません。

P83:メソッドが全てのインスタンス変数を使うようになる
使うように「する」でも良いかもしれないです。環境変数の取得などもあるので「絶対」ではないですが、使っていない理由を整理していくと結果として「これは別クラスにしておこう」ということになるように思います。

スコープは可能な限りパッケージスコープに

P85:
IDEの補完においても余分な候補が出なくなるので公開窓口となるクラスやメソッド以外はパッケージスコープにしておくことについては賛成です。ただ、ドメインオブジェクトはフラットにどのレイヤーからでも参照することを前提に作ると考えると、パブリックが基本になりそうな気がしますが。

ドメインモデルとデータモデルは何が違うのか

P100:
カチッと分かったという感じではないけれど、何となく言わんとしようとしていることは分かったという感じです。1つ言えることは本書を読むまでの自分は どちらかというとデータモデルありきの発想だったので、何となくでも意識できるようになったのは良いことだと思います。

サービスクラスを分ける

P160:
参照系と登録系に分けるという発想は持っていませんでした。副作用のあるサービスと、副作用のないサービスはWebAPIのところでも触れられているように、ちゃんと分けておくと影響範囲調査などするときなど楽になるので、今後の実装で取り入れていきたいと思いました。

記録の変更を禁止する

P184:
以前、PostgreSqlの内部の仕組みの説明を聞いた記憶では、DBログとしては全て追記しておき それをサマリーする、というような話だったので 気が付いていないだけで低レベル実装としては更新では無くて追加をしています。また障害時もログ(履歴)+リカバリーポイントから復元します。DB自体がそういうアーキテクトであることから鑑みても変更というよりもコト履歴の赤黒追記でそれをサマリーするというのには賛成です。
ただ履歴のシーケンスの管理については別途考察が必要になる可能性は残るように思います。例えばサーバーを跨ったマイクロサービスなどの場合、順序性の保証をするためには何かしらの仕組みが必要で、その払い出し性能がネックになることは起こりえます。少なくとも全ての払い出しを1つのテーブルでやろうとしたら大量のリクエストでパンクします。接続ユーザーグループ毎にパーティションを作るとか そういう仕組みの検討が必要になる可能性があるかも、という話です。まぁそこまで非機能要件が厳しい場合、更新であっても別の検討が必要にはなっていると思いますが。

「記録の変更を禁止する」の一番の抵抗勢力は経験豊かな(?)DBAかもしれません。「マスタは頻繁に更新されないし、手で直すだけで対応できるんだから簡単でしょ?大丈夫大丈夫、更新日付のカラムを書き換えるべしっていうルールがあるから。ちょっとした修正なのにレコード追加とかシーケンス付番とか作業が増えるだけで事故の元だよ」と。当然そういう人は削除フラグ(もしくは削除日)も大好きです。

テーブル設計を優先して…

P192:
テーブル設計を優先してオブジェクトをそれに合わせるアプローチはロジックの整理に失敗する、というは、ずっとモヤモヤしていたところだったのでスッキリしました。

具体的には マッピング=形だけのDTO=あんまり良くないことでは?というのと、テーブルの関心事とドメインの関心事は違うから似て非なるものだから気にしすぎなくてよいのでは?というループです。考察の切欠はテーブル定義からドメインモデルを自動生成すると良いのでは?とボンヤリ考えていたところから。ただ自動生成をしても良いけれど、逆にその生成物があるためにドメインの検討の邪魔になるのでは?とも考え始めて…(ループ)。
私としては、違うものととらえるべきだということに整理が出来たので満足です*1

個人的な好み

書籍と私の好みの違いの整理。

状態遷移はStateパターン

P63:状態の遷移のルールをenumで管理

Stateパターンの方が個人的には好みです。

Enumだと状態遷移を全体として俯瞰して表現できるという考えもあると思うけど実際に見てみて私には分かりにくいものでした。状態遷移は状態遷移図とステートマシン図を使って検討・設計をしてテストで保証するというのが好みです。少なくとも、このEnumクラスでユーザーと仕様を共有するのはツライです *2。 このあたりは「状態を管理するクラス」を選択するのか「自分の次の状態は自分で管理する」のか、どちらを好ましいと思うのか、という違いかもしれません。前者は全体の俯瞰ができるメリットがあり、後者はクラスの責務が凝集しているけど全体の俯瞰は補助資料が必要です。

形式的な資料はかえって危険

P140:(P248にて補足あり)
形式的なドキュメントに「議事録」が入っているのは疑問です。意思決定や合意形成のアウトプットとして議事録は簡易的ではあるがエビデンスになりうるものと考えているからです。もちろん形式的なドキュメントの作成は私自身も必要最低限にしたいと思っているし、勘弁してほしいとすら思っています。ただし議事録・課題管理・QA表など経緯を把握できるドキュメントは後から参画したメンバーに仕様確定の経緯を深めてもらうために便利なので手抜きはしない方が良いです。とくにユーザーとの合意は特別扱いしておきたいです。ホワイトボードがそれにあたるというのであれば議事録相当の合意事項を箇条書きなりにして最終合意事項としてスナップを取っておかないと、打合せ非参加者が見た場合、重要なポイントが不明瞭になる可能性があります。 少なくとも 議事録の体裁にこだわる必要は無いとは思うけれど、ホワイトボードに書かれた検討メモや構成図をもって合意形成の確定とするのであれば、第三者が見ても誤認しないようなレベルの「まとめ」はしておいた方が良いでしょう。 *3

書籍の意図も、ホワイトボードがあるから議事録不要ではないとは思っています。ただ、「議事録作るの無駄」と整理すると事故が起きた時にシンドイですよ、ということを私は言いたかっただけです。ちなみに私は開発メンバーとの意思疎通をするときには、A3の紙か、ホワイトボードに書きながら話して、打合せが終わったら印刷して共有、というのを良くやっていましたので、その便利さは良く知っているつもりです。ただしその記載も責任の所在も全て「リーダーである私のみ」という前提でした。検討資料のインデックスはホワイトボード資料のファイル名か「私の記憶でいつくらいに、その話をしたか辿って探す」でした。開発チーム内であれば それでも良いですが、ユーザーとの合意事項を それと同等に捉えるのは個人的には抵抗があります*4

契約による設計

P165:
防御的プログラミングでのやり過ぎ懸念は分からなくはないけれど、そうではない契約による設計がイコールでシンプルになるというのは疑問です。ユースケース記述を例にしても、自分のユースケースを実行する事前条件の定義は、自分で一義的にするのが筋が良いと思いますし、仕様が漏れ出ていかないと思います。呼出側としても「何が返ってくるかわからないという前提でから様々な検証コードを書きます」とあるけれど、それは呼出側として後続処理を行うにあたって必要な「事前条件」の検証をしないといけないだけでは?と思います。
Nullの扱いについては、確かに課題ですが、それはプロジェクト全体で統一することであって、それがイコールで防御よりも契約に、とはイマイチつながらない気がする*5。個人的にはサービスの戻り値の型(つまりドメインオブジェクト)+例外+JavaDocというインターフェースを持って、基本的な約束事は満たされるようになると思っています。
ひょっとしたら私の理解が誤っている可能性がありますが そんな風に思いました。

あと「例外を使うのは、通常の使い方ではあまり起きない場合に限ります」も同様に疑問です。例外は通常云々ではなく「事前条件不正」(事前条件だけではないですが)というような主たる関心事を満たせない場合のルートだと思っています。そうじゃないと古式ゆかしきリターンコードで対応するのかな?という印象しかないです*6。BeanValidation(入力値検証)も事前条件だと考えますが検証不正時は非チェック例外で表現します。それを「通常の使い方ではあまり起きない場合」というのは違うのではないでしょうか?

書籍の例として「ゼロ除算」を挙げていましたが、ゼロを格納した変数(クラス)が除算に使用されるかどうかを呼出元は知りません。知っている=仕様が漏れ出していることになるのではないでしょうか? もし 呼出先としてゼロがもし渡されたときに どうしたら良いか分からず、呼出元に対応を強制するというのであればチェック例外で呼出元に対処を明確に促すべきではないでしょうか*7。もしくは非チェック例外&JavaDocで表現するというやり方もあります。*8

2017/8/19 追記
増田さんの趣旨もしくは その一端が以下のtwitterで言っていることだとしたら、私の「契約による設計」の書評はズレているように思います。いずれにしても、このあたりは具体的な実装および実現技術を踏まえないとイメージも難しい気がします。ボンヤリ「こういうことかな?」というのはあるのですが、私は言語化できていないです。

2017/8/28 追記
入力値検証はBeanValidationにしてドメインロジックに混入しないことにするってことかな?

分析者が実装まで行う

P285:品質保証
「分析と設計が一体となった開発」についてのアンサーとして(かなり端折っていますが)分析者が実装まで行うというのは同意です*9。 それとは別アプローチとして、実験的にこういうのもあるのでは?と思うことを追記しておこうと思います。例えばペアプロ(どちらも実装ができる人+どちらかが仕様への理解が明るい)とかモブプロ(要件は分かるが実装は出来ない人も参加することを想定しているので厳密なモブプロとは違うけれど)とか、ユーザー自身も含めた分析者とドメインの理解が浅いけど手は動かせる実装者が一体になって開発するというような「仕組みの構築」も一案としてあるかなと思いました。
例えば、モブプロを通じてリファクタリングをしないと、開発スピードが だんだんと遅くなるという体験の共有も大切かもしれません。

だんだん開発スピードが遅くなっていくのをどうやってとめたら良かったんだろう? - Mitsuyuki.Shiiba

とはいえ、私が言っている案の方がSIerの現場だと非現実的な気がしていますが(ペアプロは出来るかな?)。

ちょっと残念?

勝手に私が期待していて物足りなかったところ。

画面とドメイン

P211:
「複数の関心事が混在している「何でも画面」を提供する場合は、ビュー専用のオブジェクトを…」について画面とドメインの連携で書かれていると思っていました。タスクベース画面はウィザード操作的であり業務習熟度が上がった担当者にとっては まどろっこしい入力になります。また高い習熟度は「関心事」として捉えられる範囲が広がるので「何でも画面」と一括りにもできないと思います。とはいえ全部のデザインについて1冊の書籍で網羅することを求めるのも違う気がするので、あくまで「個人的に物足りなかった」という感想です。

重箱の隅

本質に関係のないところです。

コーディング作法

Javaっぽくないかもと思ったりしたところ。この辺の作法は好みもあります。

if文の書き方

メソッドの行数を減らすために「{}」を使っていないような印象を受けました。スコープの範囲を明確にした方がバグが生まれにくいので個人的には好きではない書き方です。実際事故も起きています。行数を少なくすることは手段であって目的ではないと思います。

AppleがiOS7.0.6で修正したSSLバグの簡単な解説 - Qiita

3行は「{}」のあるif分岐すら実装できません。また「1行で書いてみました!」と過剰に行だけは少ないけれど、メソッドの仕事としては沢山の事をしている実装をされても困ります*10

私としては10行以内を目標に20行くらい、多くても50行というのが現実的なように思います。

参考:

もちろん行数を増やしたところで、上述の懸念は残ります。私は集中できる時間帯というのを「息を止めていられる時間」と考えています。なので後輩たちにメソッド実装をしてもらうときの基準として、上述の行数とあわせて「メソッドは軽く息を止めて実装の意図がすんなり分かるくらいの規模で」というようにしていました。コードが複雑でも短ければ許容できるし、長くても単純なボイラーだったら無理に分割しなくても良いかな、と。当然、個人差はありますが「コードは読み物」という意識は持ってもらえるかなと思っています。

2017/8/19 追記

増田さんのブログ引用
if文のブロックは必ずメソッドに抽出して(省略)
else句は原則使わないようにしているので、ほとんどのif文は、このような一行形式で記述でき(省略)
手ごたえを感じている書き方です。

条件ロジックは分割することで複雑度を減らしている&3行くらい、というこで私が事例に挙げたようなバグは混入し辛いということになるでしょうか。私としては本件は「重箱の隅」よりも「個人的な好み」に分類した方が良いところだったかな、と読み返して思いました。

ブロックの書き方

P58:
クラスのブロックはJava風だけど、static初期化ブロックが.Net風というか、初期化ブロック風。static初期化ブロックと初期化ブロックでは初期化の順番が違うので紛らわしいコーディングはしない方が良いと思います。*11

参考: 【Java】初期化ブロックについて - TASK NOTES

Enumの列挙子が小文字

P60:
Enumの列挙子が小文字始りになっていてpublicプロパティを直接参照している印象を受けます。列挙子は定数的な扱いなので全て大文字の方が好みです。

参考: Java列挙型メモ(Hishidama's Java enum Memo)

誤記?

P111
判断/加工/処理

P144
判断/加工/計算処理

おそらく「判断/加工/計算」の誤記だと思います。

ドメインを参照するレイヤー図

P150: ドメインロジックはアプリケーション層だけから使う、という整理だったかな? 少なくとも増田さんの以前のスライドでは、そうなっていなかったように思います。

www.slideshare.net

2017/8/19 追記

増田さんのブログ引用
処理の流れのイメージとしてこっちのほうがわかりやすいと思って、ドメインモデルへの矢印を、アプリケーション層だけに絞ってみました。

個人的には3層から使用できるということがドメインモデルのポイントだと思っていたのと、増田さんの3層からの矢印スライドを見て「ドメインってそういうことなのか~」と理解が深まったということもあって妙に拘ってしまいました。

さいごに

批判的なことも書きましたが総じて良い本だと思います。OJTの名のもとに師事する人(メンター)が現場の人だけだったという人は 一度 目を通しておくと良いと思います。また、なんとなくですが 3年くらい経験を積んだ人が読むと良いと思います。

増田さん自身による書評のまとめ
たくさんの書評、ありがとうございます | システム設計日記

*1:ということで自動生成ツールの作成は当面しない

*2:とはいえ本書籍はEmunの使い方集ではなく、こういう整理をしましょう、という本だと思うので あまり突っ込むのも野暮な気もする。

*3:少なくとも、私の経験では 最終アウトプットで合意を取っているからOKでしょ、というプロジェクトは経緯や考察への配慮が不足していて、結果として炎上プロジェクトになっていました。また、議事録・メールなどのエビデンスにより相手方の承認履歴があったことで資金回収が出来たという個人的な経験によるところも大きいです。

*4:別にお客様は神様です などと思いませんが

*5:lombokアノテーションもあるし

*6:であったとしても、本質的に満たそうとしている事前条件不正を呼出元に通知するという目的は同じです

*7:チェック例外の良し悪しは横に置いておきます

*8:私は非チェック例外派なので後者を選びますが

*9:というか、設計から実装まで出来るメンバーが集まっているのであればオブジェクト指向云々に関係なく、それなりに高い品質を担保できるとは思います。実際、初見メンバーで各社から集まってもらったチーム(自社で開発技術がある要員は実質私だけ)での開発をしましたが、設計力と実装力が一番ある人をプロト開発に、実装力はあるけれど経験年数が若い人をコアドメインの設計者に、そして私は方式検討と仕様課題と実装課題の検討にのみ注力することで、超短期間の開発であったけれども どうにか乗り切ることが出来た経験はあります。

*10:MSXの1画面プログラムみたいな。凄いとは思うけど写経してゲームはしたけど、ちっとも意味は分からなかった。Cのポインタ使いも同様。複雑な正規表現も同じく。ただ正規表現については、自分がもっと勉強をしないといけないと思います。

*11:実際は整形についてはIDE任せなので問題はないけれど