はじめに
「良いコード・悪いコードで学ぶ設計入門」という本を読みました。
前から図書館で予約をしていましたが、予約待ち人数が非常に多く、約半年後の 9 月に本を借りられました。
この記事では、最初に本書の感想をまとめます。
その後、本書を読んで自分に刺さった点(新しく学びとなった点)をまとめます。
感想
他の設計本と比較して「悪いコード」が題材となっているため、抽象で終わらず具体的な改善方法が載っている点が非常に良いなと思いました。
多くの設計本の場合、「なぜだめなのか?」という過程は載っておらず、「こうしましょう」という最適な設計方法・コードが載っていることが多いです。
そのため、どう改善していいか?どう実装するべきか?の具体化が難しい点があります。
このような問題は初心者における for loop
の問題に近いかなと思います。最初から for loop
を学んでもよくわからないですが、実際に同じコードを 10 回コピペした経験をしてから for loop
を学んだほうがループ処理のメリットがわかる、ようなことです。
「悪いコード」が題材となっているため、それをどう直していくかが直感的にわかりやすいためよいと思いました。
自分はモダンな会社で現在働かせていただいているため、本書にあるような「Super legacy…」なコードは見かけることがほぼないです。
そのため、こういうコードあるのかなぁ…と思う部分が多いのですが、世に出るとそういうコードでプロダクトが動いていることも多いのか…と改めて気付かされました(怖いですね)。
3. クラス設計
ここからは急に本書で良かった内容まとめになります。
3 章ではクラス設計の基本が書かれています。
が、内容を読み解くとドメイン駆動設計における ValueObject Entity をきちんとドメインを理解したうえで設計しましょう、と同じ内容であると感じました。
int amountPrice
や int magicPointPower
など、型のないスカラー変数は他の int になんでも打ち込めてしまいます。
ドメインにおける「目的」ごとにクラス(ValueObject Entity)を設計し、これら Entity にはそのドメイン知識を表現するメソッドとプロパティを用意する。
目的を表現するクラス設計にし、そのクラスの型によって開発効率が上がる、に近い章でした。
4. Immutable
4 章は「Immutable / 不変性」についての内容でした。
オブジェクト指向で設計していると、オブジェクトに副作用をきたすようなメソッドを実装したくなるときがあります。
しかし、できるだけこれは避けて副作用のない実装にしましょう(新しいインスタンスを作るなど)というものです。
メモリ的な観点から Mutable なインスタンスを参照付きで引き回す、ということもあります。
しかし、メモリが潤沢になっておりクラウドインスタンスをドカドカ借りられる世の中になっています。
そのため、できるだけ Immutable な設計にして、自由にインスタンスを編集できる設計にしたいですね。
6. 条件分岐
6 章は条件分岐を減らそう、という章でした。
早期 return を筆頭にネストをできるだけ減らす、というのは当たり前です。
くわえて、 interface & strategy pattern を利用して、コード上における分岐を減らそうということも載っていました。
interface ならびに strategy pattern など、 共通 API Interface を用意して「プログラミング言語」側で切り替えてもらう、というのはよくやる手法です。
開発においても意識していきたいですね(複数人で開発するときに、実装と設計も分離できてうれしい)。
7. Collection
ファーストクラスコレクションを使っていこうね、という章です。
各プログラミング言語における list/array をそのまま使って、複数の要素からなる集合を表現しようとすると各地にロジックが広がってしまいます。
また、 X.Y.Z()
のように、法則・原則名を忘れてしまいましたが、自分のクラスの責務でない他クラスインスタンスのメソッドもいじることになりえます。
そのため、集合自体を表現する Collection を作成し、コレクションを操作するメソッドを用意しましょう。
8. 密結合
単一責任の原則
クラスはある関心事 1 つについてのみ責任を負う、ようにクラス設計をするのが単一責任の原則です。
適当にコードを書くと God Class… が生まれてしまうため、プロダクト・ドメインの観点からそのクラスは本当に 1 つの責任のみ負っているか?を問うようにしましょう。
ここで、注意すべき点として「DRY 原則」があります。
似たようなコードをなんでもかんでも共通化して、 Don’t Repeat Yourself! というのは異なります。
似たようなコードであっても、担っている責任が異なるのであれば別々のままとし共通化しなくてよいです。
ビジネス・プロダクトの知識を表現するうえで、異なる概念なのであれば異なるクラスのままとどめておき、同じコードが存在しても問題ないです。
継承より composition
できるだけ is-a ではなく、 has-a で考えるようにしましょう。
つまり、継承ではなく composition (合成) を利用します。
似た概念 X と Y があったときに「機能として共通している Z」を切り出して API にするのが composition です。
9. 悪魔たち
設計をするうえで良くない悪魔たちを紹介する、つまり悪いコーディング集をまとめた章です。
YAGNI 原則
必要なタイミングになるまで、できるだけ機能は追加しないようにしましょう。
が、機能を追加するときに拡張に openness な設計であることが大事です。
開放閉鎖の原則を満たしたまま、 YAGNI 原則も大事にしましょう。
null をできるだけやめよう
これは自分が null を使ってしまうことが多いため注意したいです。
null を許容するコードを書いてしまうとメモリエラーになってしまう可能性が高いです。
そのため
- できるだけ最初からインスタンスを初期化して用意する
- 「存在していない」という概念を Enum やインスタンスで表現できるようにする
- EMPTY というインスタンスを用意するなど
メタプログラミング
表現力が増してコード記述料を減らせることが多いですが
- 可読性が悪い
- 型を弱めてしまう事が多い
ため、できるだけやめたほうがよいですね。
逆にいえば「コード自動生成」など、プロダクトコードに直接乗らない「開発お助けツール」ではどんどん使ってよいと考えています。
技術駆動パッケージング
今職場で書いているコードが技術駆動パッケージなので、なかなか厳しいですね…(これはこれでよいところがあるので)
UseCase / Entity などなど、技術的な観点からフォルダ・パッケージを構築することが技術駆動パッケージです。
一方で、 User フォルダのなかに、 User 関係の UseCase Repository などを実装することもできます。
技術駆動ではなく、 Entity の概念ごとにフォルダを作ったほうが Entity に関する内容が 1 箇所に集まるため、ビジネス的な観点でいうとよいですね…
ただ、コードを自動生成する場合などにおいては技術駆動のフォルダ構成のほうがやりやすいことが多く、難しいなぁ…という感じです。
10. 名前設計
関心の分離
Product
という抽象的すぎるクラスを用意するのはやめましょう。
Product
といっても在庫的な観点から見た「商品」なのか、予約的な観点から見た「商品」なのか、発送に関する「商品」なのか
で利用するプロパティやメソッドも異なります。
できるだけそれぞれの関心を明確に表現するようなクラスを設計するようにしましょう。
予約品・注文品・在庫品・発送品など、ドメインを表現するようなユビキタスな名前をプロダクト全体で考えて、そのうえでコードのクラス名に落とし込むことがベストです。
目的ベースで名前を設計する
「存在」ではく「目的」ベースで名前を設計します。
たとえば、「名前」という存在ではなく
- 個人名
- ハンドルネーム
- 法人名
- 認証アカウント名(認証用)
など、それぞれの用途・目的で名前を具体化します。
命名で違和感がある場合は別の場所へ移動させる
ゲームにおいて addItemToParty
のように、「動詞+目的語」として別の Entity に影響を及ぼそうとしていることがあります。
この場合、対象となる「目的語」にメソッドを生やすことを考えたほうがよいです。
できるのであれば、メソッド名は動詞 1 語で表せるほうがクラスの責務が最小になっている、といえます。
そのため、 PartyItems のようなパーティの所持品を管理するコレクションクラスを用意して acquire というメソッドを用意することがリファクタとして考えられます。
11. comment
意図や仕様変更の注意点を読み手に伝える
コメントで処理や命名を記述しているのは無駄です。
そのようなことを行っている場合、変数名やメソッド名の設計が不十分なためコメントを書かざるをえなくなっています。
そのような場合はコメントを書くのではなく、名前を再設計しましょう。
コメントは「どうしてこのような処理を行っているのか?」や「仕様が変わった」のように、コードだけでは読み取れない裏側の背景や意図を伝えるようにします。
12. method
getter/setter はやめよう
オブジェクトの変数はすべて private とし、できるだけ public な getter / setter は意味がないため削除しましょう。
(プロダクトにおいてはコードを自動生成しているため、すべて public になっていますね…)
getter setter を用意してしまうと、他オブジェクトからプロパティを勝手に編集される可能性が高まります。
意味のある単位の「メソッド」を設計することが大事です。
コマンド・クエリ分離
「状態を変更すること」と「状態を取得すること」はそれぞれ異なるメソッドに分離しましょう。
コマンド(状態を変更する)とクエリ(状態を返す)をできるだけ利用し、モディファイア(コマンド+クエリ)はできるだけさけましょう。
ただ、 Immutable な設計で、 object を copy して編集して返す場合はどれに値するのだろうか…
「状態を変更すること」に該当する気がする…
となると、 Immutable な設計においては「コマンド」=「copy したうえで状態を変更して返す」ことといえる。
純粋なコマンド、つまり copy せず直接オブジェクトを編集することは基本ないかも…?
13. modeling
目的ごとにモデリングする
命名でもそうでしたが、目的ごとにモデルを構築します。
現実においては 1 つの名前しかなかったとしても、システムモデルの上では複数の用途や名前が割り当てられることが多いです。
アカウント名・法人名・宛先名などなど…
システムを表現するうえのアクターをすべて洗い出し、各 UseCase ごとに目的を割り出して、それにあったモデリングならびに命名をしましょう。
16. 設計を様違える開発プロセス
コミュニケーション
コミュニケーションを増やすことで、設計品質が向上します。
プロダクト・クライアント・サーバエンジニア全員で仕様を相談し、名前や設計を考えることによって変更に強い設計になります。
ここでコミュニケーション不足になると、仕様の理解や認識がずれて、誤った設計になり、あとから修正したりそもそも修正されないことが起こり得ます。
とにかく、「コミュニケーションが一番大事」です。
早く終わらせることは悪である
「タスクを早く終わらせたい」という欲に駆られることがあります。
とにかくコードを動かしたい、このタスクは面倒なので別の面白いタスクをやりたい、となります。
そうすると設計が甘くなり、結果としてコード実装もガバリティが高くなります。
そのため、「設計」「コード実装」それぞれにおいて自分の中におけるリファクタリング、そして他の人の Review をもらうことによってよりよい品質にしましょう。
早くてまずいよりは、遅くてうまい、ほうがよいですね。
割れ窓理論
割れ窓理論ということがあるように、何かしら割れた窓があるとそこから治安が悪くなっていきます。
X でコピペしているから、 Y でもコピペしちゃうか〜と、納期を理由に甘えたコードを書きやすくなってしまいます。
そのため、「ボーイスカウトの規則」のように、今のコードをよりきれいにして実装を終える、ということを意識しましょう。
そうすればリファクタ絶賛強化週間、というフェーズも用意することもありません。
17. 設計技術への理解の深め方
本を読む
アウトプットする
パレートの法則とあるように
- インプット 2 割
- アウトプット 8 割
がベストです。
ブランチを切って、実際にリファクタをしながら設計をより良くするなど、本を読むだけでなく必ずアウトプットするようにしましょう。