社内で「良いコード/悪いコード」の輪読会を開催した話
こんにちは! koideです。
シェルフィーではチーム内の有志で輪読会を定期開催しており、今回は「良いコード/悪いコード」を読みました。
実際に開発したときに輪読会が役立ったエピソード
輪読会前後に担っていたIssueが外部サービスとの連携バッチのコード改善でした。
弊社にはDDDを取り入れたリポジトリがいくつかあり今回もその内のひとつでしたが、私はこの本を読むまではDDDが雰囲気でしか分かってなかったので、初めは理解するのが大変でした。
例えば、DDDの中でも「値オブジェクト(ValueObject)」といった概念がありますが、最初はこのクラスがある意味をちゃんと理解するのに苦労しました...
本書では値オブジェクトは以下のように説明されています。
値オブジェクト(Value Object)とは、値をクラス(型)として表現する設計パターンです。アプリケーションでは金額、日付、注文数、電話番号など、さまざまな値を扱います。こうした値をクラスとして表現することで、各値それぞれのロジックを高凝集にする効果があります。 たとえば金額を単なるint型のローカル変数や引数で制御していると、金額計算ロジックがあちこちに書かれて低凝集に陥ります。また、同じint型の「注文数」や「割引ポイント」が、金額用のint型変数に不注意で代入されてしまう可能性もあります。
仙塲 大也. 良いコード/悪いコードで学ぶ設計入門―保守しやすい 成長し続けるコードの書き方 (p.77). 株式会社技術評論社. Kindle 版.
ちょうど今回着手していたIssueが、新たな値オブジェクトを定義すべきか判断に迷うモノでした。
今回の実装で新たに△△支店IDという値を扱うようになり、当初は△△支店IDの値オブジェクトを作成する方針に決めました。
ですが、本書を通じて改めて考えてみると
- 業務上あり得ない値が入らないようにする
- 同じ値を指していることを明示的にわかるようにする
- 例:プロパティ名が同じでも実体は別、プロパティ名が異なっていても実体は一緒、がわかるようにする
と、名前が異なっていても同じ実体であれば同じ値オブジェクトを使うことが良さそうだと判断し、既存の支店IDの値オブジェクトを使う方針に変更することにしました。
設計効果を考えずに考えを取り入れるとどういう事が起きるか
以下は本書には書かれてなかったことですが、一方で値オブジェクト(エンティティ含む)をがむしゃらに使えばいいモノでもないことも学びました。
実際に困った話
現状のRepositoryクラスでは、以下のような制約があります。
- 引数は値オブジェクトのクラス
- 戻り値はオブジェクトを返却する場合、エンティティクラス
上記の制約に合わせると、Repositoryクラスにメソッドを生やすとき、Usecaseで使うデータ以外のデータをエンティティに詰める必要があり、Usecaseで使う以上の情報をRepositoryのメソッドで返却するのは必要過多に思えます。
値オブジェクトやエンティティに固執すればよい、という事でもないなと感じました。
サービスの相性
値オブジェクトを採用(≒DDDの採用)すべきかはサービスの相性に依ると思います。
今回対象のリポジトリは外部サービス連携用のバッチのコードだったため、DDDを採用するメリットがアプリケーションコードより薄かったと感じました。
基本的な流れとして、外部サービスに登録されている情報を取得しアプリケーション側のデータと突き合わせ、データ加工したものを外部連携する流れとなっているため、ビジネスロジックを持つメインのアプリケーションコードの方へ採用する方がより価値が高そうだと実際にコードに触れてみて感じました。
言語の性質
Python等の動的型付け言語だと、以下の例のようにクラス側で値オブジェクトで型を指定していても、インスタンス化のタイミングではプリミティブ型をいれることができてしまいます。
クラス設計
今回の改修対象のリポジトリはたくさんの値オブジェクトがありますが、フィールドはvalueだけを持ち、コンストラクタでのバリデーションも単純かつメソッドも特に持たないクラスが点在しています。
また他のクラスが他の値と一緒にバリデーションを行っていたりするので、二重でロジックが書かれていたりします。
しかしそれらの修正まで考慮し始めると、値オブジェクトの話だけでは収まらず、全体のクラス設計に発展していきます。
そして…値オブジェクトの定義は様々
値オブジェクトは人によって解釈が違うみたい、という話も本書をきっかけに知ることができました。
- マーチン・ファウラー
- あるvalueとvalueをオブジェクトとして組み合わせると初めて意味のあるものが生まれる
- xとyという値を {x: 1, y: 2}合わせることで、Pointという意味を持つ
引用元:https://kumagi.hatenablog.com/entry/value-object
業務の実態に合わせてテクニックを取り入れる
特に設計についての本は著者の経験や思考、背景が反映されやすいと思っています。
書かれていること全てが適している訳ではなく、その都度「本当に適切だろうか?」を考える必要があると感じました。
例えば今回の話でいうと、本書ではなるべく値オブジェクトを使ってプリミティブ型を使わない旨書かれていましたが、直近の私の業務のケースでは必ずしもそうとは限りませんでした。
他にも本書ではUtilsについて、他のクラスで共通して使用する業務ロジックをUtilsとしてまとめている例をアンチパターンとしてあげていました。
(中略)もうひとつの形として頻繁に見られるのが、共通処理の置き場所として用意されたクラス(共通処理クラス)です。Common、Utilなどと名付けられることが多いです。問題の性質はstaticメソッドと同じ低凝集構造です。
(中略) 同じような処理が多数書かれそうなとき、再利用できるよう共通処理を実装した共通クラスがつくられることがあります。このとき、共通処理用のメソッドはstaticメソッドとして実装されがちです。仙塲 大也. 良いコード/悪いコードで学ぶ設計入門―保守しやすい 成長し続けるコードの書き方 (p.123). 株式会社技術評論社. Kindle 版.
さまざまなロジックが雑多に置かれがち
仙塲 大也. 良いコード/悪いコードで学ぶ設計入門―保守しやすい 成長し続けるコードの書き方 (p.124). 株式会社技術評論社. Kindle 版.
本書のこの内容に共感できる点は多く、また『リファラジ』というPodcastでは「Utilを使う判断は、OSSに切り出せるかどうかが判断基準…」と話されていたりもしました。
#11 ユーティリティ① 「またユーティリティを作ってしまった...」 by リファクタリングとともに生きるラジオ
まとめると、何か考えを取り入れるときは「本当にプロジェクトにメリットがある設計か?」(=設計効果)を意識することが大事だなと再認識するきっかけになりました。
輪読会はいいぞ
もし一人で本を読んでいたとしたら、上記のようなことに気がつくことができなかったと思います。
輪読会をすることで以下のようなメリットが有るなと感じました。
- 様々な視点を持つメンバーとディスカッションできる
- メンバーとやるからこそ、業務とコードを紐づけることによって新たな発見がある
次回の輪読会は別の書籍を行うので、完走した際にはまた発見や知見などを記事にできたらなと思います!
Discussion