Java読書会「現場で役立つシステム設計の原則」を読む会 第1回に参加
現場で役立つシステム設計の原則 〜変更を楽で安全にするオブジェクト指向の実践技法
- 作者: 増田亨
- 出版社/メーカー: 技術評論社
- 発売日: 2017/07/05
- メディア: Kindle版
- この商品を含むブログ (4件) を見る
本の概要
投票の結果こちらの本になりました。 ペース的に4回くらいになりそうです。 前回の投票でもKotlinの本に続き2位だったそうです。かなり人気な本だと思います。
正誤表:
サポートページ:現場で役立つシステム設計の原則 〜変更を楽で安全にするオブジェクト指向の実践技法:|技術評論社
賛否
賛否のある本だと思いますが、思っていたより批判が激しい感じでした。
というのも「現場で役立つシステム設計の原則」というタイトルですが、現場として次のものが暗黙的に想定されていると思います
- WEBシステムである
- モダンなJavaでSpringのようなフレームワークが使える
- バックエンドはRDBを使う
- 国内向けのシステムで内製ができる(アジャイルが可能な現場)
- 超大規模ではなく、国際化対応などはしなくて良い
著者が想定しきれていないほど多様な「現場」の方が参加されているので、業界が違う技術者に「原則」として著者の経験や考えが読み上げられてしまうと異論が湧き出るだろうなという印象です。
全体的にこのアマゾンレビューのスタンスな方が多い印象です。
この業界に溢れた、「オレのやり方がナンバーワンだ」の本の域からは出ていないように思います。「変更が容易」の判断基準や根拠が極めて定性的なまま、著者の考えにそぐわない既存の設計手法を「誤り」と断じる論調にはだんだん首をかしげてしまいました。 著者は『ドメインモデルのコードの中に、すべてのビジネスルールを凝集させてしまいたい』派なのだと思います。オブジェクトを第一に考えている、という点で確かにオブジェクト指向には違いないです。が、DBやUIとのミスマッチをどう埋めるか?という話に関しては論理に雑な印象を受けました。ストアドやリッチクライアントなど、ビジネスロジックを散在させる要素はそこかしこにあるわけで、「あくまでドメインモデルのコード内に閉じ込めるのが正しく、そうできないのはやりかたが悪いから」なスタンスは、現実から目をそらした排他主義に見えます。著者が「正しい」とする方法が、果たして本当にどこまで経済的にメリットのあるやり方なのかは疑問に感じました。 本書は「こういう考え方もあるのか」の視点で読める人には良書と思います。しかしながら、初心者が最初に読むには全くお勧めしません。著者の考えに強く賛同する人であったとしても、この本に「正しい」と書かれていることを妄信すべきではない、と思いました。
一方的に批判がでる箇所もあり、主催の高橋さんがバランスを取って何度かフォローされていますが、DDD界隈の方が参加されていい感じの反論が聞けたら面白いのになと思っています。
感想
オブジェクト指向やJavaを極めるとDDDに行き着くと思っていましたが、そうでも無いようです。実装が好きな人、モデルが好きな人、ドメインが好きな人、という感じに(もっと多いかも)枝分かれするようです。
ベテランの方が多いので、全員「ドメイン駆動設計」のファンだと思い込んで参加したのですが、そういう方がいなかったので衝撃的でした。*1
私自身はDDDの「プログラミングの対象領域を大事にする」という考え方は結構好きです。設計はあまりわかっていないので、勉強になるところも多かったです。
以下読書会ノートです。
オブジェクト指向とは何か
設計とプログラムどっちのことを言っているのかわからない
モデルとプログラムは一致するか?
- 最近のシステム開発は紙ベースの業務を置き換えるだけではない
- まだ世の中にないビジネス。どうやってITで作る?
文字数制限の話
文字数たくさん使ってわかりやすい単語で作れ的な本だが、 長い命名を使うことにも問題がある
- パス名が255文字を超えるとzipなど解凍できない不具合がある
- ミドルウェアやツールが持っている文字数制限に引っかかる
ShipingCostの設計について
class ShippingCost { static final int minimumForFree = 3000; static final int cost = 500; int basePrice ; //メソッドの実装略 ShippingCost(int basePrice); int amount(); } int shippingCost(int basePrice) { ShippingCost cost = new ShippingCost(basePrice); return cost.amount() ; }
- 重複をなくすだけならstatic methodにくくり出すだけで良い
- このクラス設計はいけてない。最近だとロジックとしてラムダ式とかを渡す?
計算の対象になる値が異なれば、別のオブジェクトに分けるのがオブジェクト指向らしい設計です
- この意見は局面による
- 直接newしたものを使うべきではない。(interfaceを使う例が後に出てくる)
- 結果を知りたい側(amountが欲しい呼び出し側)がbasePriceを知らない場合は、こんな設計もあり
- ライフサイクルが長いから送料クラスが意味を持つ
- 好意的に解釈するとプライムな変数ではなく、独自定義型を使うというのはOK
- intは使うな
- int型の最大値は21億だが、その規模の額を扱わないシステムでもカードなどで端数の扱いの違いがあるのでintは危険
- 国境を超えるシステムでは送料計算はめちゃくちゃ大変。
- エリックエヴァンスの本に貨物を輸送する例が出てくる
- DDD界隈の人達は貨物を輸送する例を好む傾向がある
- 著者はDDD界隈の人
- このへんからドメイン駆動設計への誘いを感じる。
エリック・エヴァンスのドメイン駆動設計 (IT Architects’Archive ソフトウェア開発の実践)
- 作者: エリック・エヴァンス,今関剛,和智右桂,牧野祐子
- 出版社/メーカー: 翔泳社
- 発売日: 2011/04/09
- メディア: 大型本
- 購入: 19人 クリック: 1,360回
- この商品を含むブログ (131件) を見る
業務構造とプログラムを一致させる
業務の構造とプログラムの構造が一致していれば
int itemPrice = (basePrice + shippingCost) * taxRate();
- 例えば上記は軽減税率などが入ると、税金計算の際に、料金だけでなく、明細が必要になってくる
- そうなると破綻する
- 一致させるのは大変難しい
DBとタイムゾーン
- PostgreSQL
- (Localの情報をデフォルトで持たない)
- タイムゾーン情報を持つ必要がある
- MySQL
- (2038年問題がある)
ロケールを明記していないと、タイムゾーン情報が消える(必ずUTCになるわけでない) この辺はサマータイム対応が難しい、ということになる。
- タイムゾーン対応のため+-9などを入れているコードは書くな
可視性について
class Customers { List<Customer> customers; ... List<Customer> getList() { return customers; } }
List<Customer> customers;
はprivateスコープにすべきでは?とはという話が出た- DDDをする上で可視性などは些細な問題なのでは?
List
への参照をそのまま外部に渡すと、要素の追加や削除がCustomersクラスの外部からできてしまいます。`
- だとしてもこのような前提があるならば、可視性はこだわってサンプルコードを作成するべき
- 可能な限りpackageスコープにする。という記述がある。その方針に沿っているのでは?
“関連性の強いクラスは同じパッケージに集めます。そして、クラスやメソッドのスコープ(参照範囲)は、可能な限りパッケージスコープにします。”
- デフォルトで可視性をpublicにする風潮は如何なものかと思う*2
- Java9で入ったモジュールは難しい
- それだけを扱っている本が3冊くらい出ている
この著者は防衛プログラミング反対派
まだこの章に到達していませんが、懇親会などで結構話題に上がりました。
ゼロ除算を防ぐのは叩く側の責任!!!!って書いてあって草も生えない。なんだこの本。
— Yoichiro Shimizu (@budougumi0617) January 21, 2018
確かに「契約による設計」と「防御的プログラミング」の両方を勘違いしている説明になっていますね。不正な値や不正な呼び出し順序に対する振る舞いもきちんと「契約として設計」し、その契約を守った呼び出しであるかを防御的にプログラミングしないと、無駄なデバッグ工数が発生するだけですからね。
— Yoshiki Shibata/柴田芳樹 (@yoshiki_shibata) January 21, 2018
メイヤーは、呼び出す側が必ず契約を守ることを前提にしてコードを書くスタイルを重視して、それを契約による設計と名付けたと思います。契約の表明は実行用のコードとは別にすべきだと。
— 増田 亨. (@masuda220) January 21, 2018
私の理解はこの回答に近いですね。https://t.co/XEKToVvMcb
本でうまく説明できていないかもしれませんが。
もちろん、すべてがすっきりうまくいっているわけではなく、契約による設計を重視した設計スタイルの実践で、いろいろ試行錯誤をしていることも事実です。また、本の中で、入力値の検証の扱いとかもきちんと記述できていないのは、私の力不足ですね。(ほんとうは、もう少し書きたかった)
— 増田 亨. (@masuda220) 2018年1月22日
実践の観点ではmeyerがどう言及しているかではなくて、防御的プログラミングが役に立つか否かで判断すればいいわけだけど、私の見解は「役に立つ」です #javareading
— quiver2k (@quiver2k) August 18, 2018
Javaの互換性
Java Day TokyoでJava 11に向けて大きなクラス削除などが予定されていることなどが発表された
- https://qiita.com/neriudon/items/44d4d3a2b8da4d09c0f8
- corbaはSEではなく最初からEEに入っておくべきだった
- https://ja.wikipedia.org/wiki/RMI-IIOP
- java applet
- java web start
- https://qiita.com/neriudon/items/44d4d3a2b8da4d09c0f8
外部ライブラリとして持ってこれば良い。
コレクションオブジェクト
class Customers { List<Customer> customers; ... Customers add(Customer customer) { List<Customer> result = new ArrayList<>(customers); result.add(customer); return new Customers(result); //return new Customers(result.add(customer));👈誤植間違い } }
- コピー先のCustomersでも参照先のcustomerオブジェクトは変更できるがいいのか?(シャローコピー、ディープコピーの話)
- ここまでの文脈的にCustomerはimmutableなオブジェクトなのでは?
- 少し後ろに説明が書いてある
“unmodifiableなListでも、個々の要素のオブジェクトの内容は変更できます。コレクションの要素を値オブジェクトにすればこの変更も防げます。”
- コンストラクタの説明がない
- list
行間の下の...に書かれているのでは
Listオブジェクトのファクトリメソッドについて
- List.of() 変更不可能なクラスを返す。可変長引数のみ。コレクションを引数を取ることはできない
- Arrays.aslist() 固定サイズのlistを返す
- Collections.unmodifiableList 変更不可能なクラスを返す
- Collectors.toList() ストリームとかで使う?
技術者の人種
モデル屋さん
- モデルを作るのが目標
- 自動生成とかやりたがる
- プログラミング言語が追いついていない
- 別の設計関係の本の読書会で意見が合わないことがあった
ドメイン屋さん
- 実装屋さん
- この読書会の大半の人?
https://ja.wikipedia.org/wiki/モデル駆動型アーキテクチャ
- プログラマ不要説は度々流行るが、結局流行らない
- if文やfor文を違う表現で出してるだけ。
- caseツール
※モデル屋さんとドメイン屋さんが来ておらず、偏った議論になっているのでは?と感じた。
自インスタンスを返すメソッドの意味は?
以下のコード。なんの意味があるのか?という質問が出た
class Customers { List<Customer> customers; ... //👇これ Customers add(Customer customer) { List<Customer> result = new ArrayList<>(customers); return new Customers(result.add(customer)); } }
- メソッドチェーンができる
- 標準ライブラリのimmutableなオブジェクトも大体この仕様
サブストリングにおけるStringPoolの扱い
最近はやってない
Strng.Classのソースを確認したところ、確かに
substring(int beginIndex, int endIndex)
StringLatin1.newString(value, beginIndex, subLen)
new String(Arrays.copyOfRange(val, index, index + len), LATIN1)
こんな感じで新たなインスタンスが作られるようになっていました。
public String substring(int beginIndex, int endIndex) { int length = length(); checkBoundsBeginEnd(beginIndex, endIndex, length); int subLen = endIndex - beginIndex; if (beginIndex == 0 && endIndex == length) { return this; } return isLatin1() ? StringLatin1.newString(value, beginIndex, subLen) : StringUTF16.newString(value, beginIndex, subLen); }
なぜラッパークラスを使っているのか
- Booleanではなくbooleanを使うべき
private Boolean isChild() { return customerType.equals("child"); }
- Object.equalsはboolean型を返すが?
- 無駄なBooleanクラスを使ってる
- Booleanを使っている意図がわからない
- 執筆にワードを使って書いてる説
- customerTypeの値はメソッド内に持たなくて良いのか?
- ちょっと変えてストラテジーパターンに落ち着くいつものパターンでは?
Reservationクラスは不変クラスにしなくていいのか
- addFeeでオブジェクトの状態を変えられるけどそれはいいのか?
class Reservation { List<Fee> fees; //大人と子供の内訳は不明 Reservation() { fees = new ArrayList<Fee>() } void addFee(Fee fee) { //大人でも子供でも追加できる fees.add(fee); } Yen feeTotal() { Yen total = new Yen() // 合計ゼロ円 for( Fee each : fees ) { total.add( each.yen() ); } return total; } }
- 現実的には徹底的にやる必要はない。どこかで線引きが必要
- 徹底的にやると目指せhaskellになってしまう。
enum
enum FeeType { adult( new AdultFee() ), child( new ChildFee() ), senior( new SeniorFee() ); private Fee fee; // Feeインターフェースを実装したどれかのクラスのオブジェクト private FeeType(Fee fee) { this.fee = fee; // 料金区分ごとのオブジェクトを設定する } Yen yen() { return fee.yen(); } String label() { return fee.label(); } }
型引数
Set nextStates = EnumSet.of(承認済,差し戻し中); // 状態のグルーピング
- Setの宣言時に型引数を使うべき
日本語の定義名
- Enumの定義名が日本語でわかりやすくて良い
enum State { 審査中, 承認済, 実施中, 終了, 差し戻し中, 中断中 }
EnumMapを使うべきか?
class StateTransitions { Map<State, Set<State>> allowed; { allowed = new HashMap<>(); allowed.put(審査中, EnumSet.of(承認済, 差し戻し中)); allowed.put(差し戻し中, EnumSet.of(審査中, 終了)); allowed.put(承認済, EnumSet.of(実施中, 終了)); allowed.put(実施中, EnumSet.of(中断中, 終了)); allowed.put(中断中, EnumSet.of(実施中, 終了)); } boolean canTransit(State from, State to) { Set<State> allowedStates = allowed.get(from); return allowedStates.contains(to); } }
- HashMapではなく、EnumMapを使うべき
- 「終了」をセットしていないので、終了で呼ぶとぬるぽで落ちる
- バグでは?
- EnumMapを使った場合もぬるぽが発生する
- EnumMapを使うメリットは?
データクラスと機能クラス
UtilとかCommonな便利クラスを作るなという恒例のアレ
- 悪い共通クラス
- RXJavaが共通ライブラリの最たる例。
- メソッドが多すぎる。それでも使うけど。
Javaのルーツ(ちょっとざわついた)
オブジェクト指向設計の基本
ロジックの置き場所やクラス名/メソッド名の改善を続け、より良い設計を見つけていくのが、オブジェクト指向設計の基本です。
これはリファクタリングだと思う。
- 「オブジェクト指向設計の基本」とは言えない
- 多態性を説明していた箇所らへんはオブジェクト指向設計っぽい
- 「オブジェクト指向設計の基本」とは言えない
- このほんに書かれている「設計」は設計ではなくプログラミングだと思う
- OODではなくOOP
凝集度
“このように、関連性の強いデータとロジックだけを集めたクラスを凝集度が高いと言います。”
- モジュール設計のことを書いて凝集度を説明するのは、少し説明不足な気がする
- そもそも凝集度なんかは本を読んでもよくわからない。
- 実際に手を動かして作ってみるべき。
- そもそも凝集度なんかは本を読んでもよくわからない。
データと処理を同じクラスに置く(分離させない)
“データとロジックを一体にして業務ロジックを整理する”
時間の流れ?
時間軸に沿った業務の基本の流れを軸に、業務ロジックの関係を整理します。
- この手法は初めて聞いた
- 注文と出荷は非同期。同期する必要はない(溜め込めばいい)
パッケージ間の参照
図3-3
出荷する時に顧客は見ないのか?
- has a関係のようなものでは?
- クラス単位の参照関係のようなものでは?
パッケージの関連では?
モジュラー屋さんはよくこのようなUMLを書く
- 矢印がないからと言って、本当に参照しないという意味ではないのではないか。
技術選定などのフェーズが抜けている
Spring,Javaありきで技術選定などのフェーズが抜けている
- グランドデザインができてしまっている
- どうしてもWEBアプリケーションで成り立つ理論になる
- 大企業の調達システムなどでは通用しない考え方
動き(設計)を想定しながら分析する
- 最初に習ったオブジェクト思考設計は確かにこんな感じ
分析と設計と実装を一人の人間がやるのは小規模でないと難しい
この本は実装者に結構寄り添っている
- モジュラー屋さんほどコードから離れていない
- モジュラー屋さんはプログラミング実現性みたいなものを全く考えない。
- ものごとは綺麗なツリーにはならないので、多重継承*3の形で分析することもある
- 以下の本がそんな感じらしい。
- 作者: 児玉公信
- 出版社/メーカー: 日経BP社
- 発売日: 2011/05/26
- メディア: 単行本
- 購入: 6人 クリック: 23回
- この商品を含むブログ (6件) を見る
- 以下の本がそんな感じらしい。
次回Chapter 4の“なぜドメインモデルだと複雑な業務ロジックを整理しやすいのか”から