オブジェクト指向設計を強制するコーディング規則で手続き型設計からの脱却を図る
概要
設計について興味が湧き,「現場で役立つシステム設計の原則」を読みました.
Railsをよく利用する私にとって陥りがちだな...と思う課題がたくさん紹介されており,学びがありました.
特に,ドメインモデルではなく手続き型の設計(トランザクションスクリプト)で書いていると,次第に業務ロジックの見通しが悪くなる点については,今までの経験で思い当たる節がありました.
この課題を改善する手法の一つとして,オブジェクト指向設計を強制するコーディング規則が紹介されており,個人的にとても良い規則だと思ったのでまとめていきます.
この記事の結論
下記9つのルールを守りながらコードを書いていきます.
- 1つのメソッドにつき,インデントは1つまでにすること
- else句を使用しないこと
- 全てのプリミティブ型と文字列型をラップすること
- 1行につきドットは1つまでにすること
- 名前を省略しないこと
- 全てのエンティティを小さくすること
- 1つのクラスにつきインスタンス変数は2つまでにすること
- ファーストクラスコレクションを使用すること
- getter, setterプロパティを使用しないこと
解決したい課題
MVCでアプリケーションを開発していくと,次第に業務ロジックがプレゼンテーション層・ビジネスロジック層・データアクセス層の3層に漏れ出します.
そうすると,ロジックが複数箇所で重複し変更がしづらくなったり,どこにロジックが書いてあるか判別が難しくなっていきます.
課題の原因
MVCを採用するフレームワークを利用していると,手続き型の設計(トランザクションスクリプト)の書き方になってしまいがちだからです.
Java, Rubyはオプジェクト指向の言語ですが,設計の仕方が手続き型のままなのです.
手続き型の設計の場合,下記2つのクラスを分けて利用します.
- データクラス → データを格納
- 機能クラス(ロジッククラス) → ロジックを記述
この場合,データクラスはプレゼンテーション層・ビジネスロジック層・データアクセス層の3層から呼び出しが可能になり,3層に機能クラスが記述されていきます.
その結果,同じようなロジックがあちこちに記載されていき,次第にコードの変更が難しくなります.
解決方法
解決方法としては,ドメインモデルを採用することです.
ドメインモデルの各ドメインオブジェクトにデータとロジックの両方を管理することで,プレゼンテーション層・ビジネスロジック層・データアクセス層の3層は,ドメインモデルからロジックを利用する形になり,ロジックが漏れ出さなくなります.
Railsの場合,Controllerにロジックは書かず,Modelにロジックを書け!ということです.
Fat Modelになることを恐れず,Fat Controllerを阻止します.
ですが,書いてあることは何となく理解できたけど,どうやってドメインモデルを採用したコードを書いていけば良いの?という状態に陥っていたところ,オブジェクト指向のやり方と考え方が学べる「古い習慣から抜け出すためのちょっと過激なコーディング規則」がありました.
再掲しますが,下記9つのルールを守りながらコードを書いていきます.
- 1つのメソッドにつき,インデントは1つまでにすること
- else句を使用しないこと
- 全てのプリミティブ型と文字列型をラップすること
- 1行につきドットは1つまでにすること
- 名前を省略しないこと
- 全てのエンティティを小さくすること
- 1つのクラスにつきインスタンス変数は2つまでにすること
- ファーストクラスコレクションを使用すること
- getter, setterプロパティを使用しないこと
1. 1つのメソッドにつき,インデントは1つまでにすること
このルールを用いて解決したい課題
if文の繰り返しなどで階層が深くなり読みにくくなること
実践方法
インデント2つ目以降は,小さなロジックとして,メソッドで別で定義(=メソッド抽出)する
メリット
単体テストがやりやすくなる
メソッドが再利用できる
2. else句を使用しないこと
このルールを用いて解決したい課題
if文が複文構造になり理解しにくくなる
実践方法
ガード節,早期リターンを用いる
メリット
単文になり読みやすくなる
(「1つのメソッドにつき,インデントは1段階までにすること」で記載の,メソッド抽出にも繋がる)
3. 全てのプリミティブ型と文字列型をラップすること
このルールを用いて解決したい課題
そのサービスのドメイン上,ありえない数値の設定が可能な状態になり,バグの原因になる
似たようなロジックが複数クラスで重複し,コードの変更が難しくなる
実践方法
値オブジェクトを利用する(数値・文字列を加工するロジックを持つクラスを作成する)
- Railsのデザインパターン: Valueオブジェクト(Railsのサンプルコードでとても参考になりました!)
メリット
コードの重複を抑え,変更しやすくなる
(例: 金額計算に関するクラスを作り,各クラスから利用できるようにする.金額計算の変更はそのクラスの変更のみになる)
4. 1行につきドットは1つまでにすること
このルールを用いて解決したい課題
同じような処理が重複することを避けたい
メソッドが連なると意図が分かりにくい
実践方法
ドット1つ毎に変数に代入して,別の文に分ける
メリット
意図が分かりやすい
メソッド抽出にも繋がり,再利用できる
5. 名前を省略しないこと
このルールを用いて解決したい課題
1文字に省略された変数を使うことで,その変数が表す内容が分からない状況を避けたい
(Goの思想とは少し異なると思います.ただGoの場合は短い名前の変数を利用する = 前後数行でその変数の内容が分かるようにコーディングするといった考え方)
実践方法
名前を省略しない = 利用者の関心事に名前を一致させる
メリット
利用者の関心事とプログラムの対応が取れ,分かりやすく変更が楽で安全に
6. 全てのエンティティを小さくすること
このルールを用いて解決したい課題
1~5のルールを守ると,メソッドが短くなりシンプルになるが,クラス(Railsの場合Modelファイル)が大きくなる
(Fat Modelになること自体は良い兆候と私は考えています.)
実践方法
パッケージでグルーピングして,クラスを小分けにする
下記ルールを参考にすると良さそうです.
エンティティを小さく保つガイドライン
- メソッドの行数 → 3行を目標,1行でも可
- クラスの行数 → 50行を目標,100行以上は不可
- パッケージのファイル数 → 10ファイル以内
メリット
どこに何が書いてあるか分かりやすくなる
7. 1つのクラスにつきインスタンス変数は2つまでにすること
このルールを用いて解決したい課題
インスタンス変数が増えてくると,クラスの意図・範囲が大きくなり,「巨大なクラス」が出来上がる
実践方法
インスタンス変数3つ以上になる場合は,「6. 全てのエンティティを小さくすること」と同様,小分けにする
メリット
インスタンス変数とメソッドが密接に結びついたクラスになり,目的が単純明快になる
8. ファーストクラスコレクションを使用すること
このルールを用いて解決したい課題
配列やコレクションをそのまま利用すると,コードが複雑になりがち&思わぬ変更がされるようなバグが混入しやすい
実践方法
コレクションオブジェクトを利用して,対象の配列・コレクションを1つだけ持つ独立したクラスを定義する
- Rubyで作るファーストクラスコレクション(Rubyで実装する場合の便利なモジュールの紹介もあり,とても参考になりました!)
- ファーストクラスコレクション(Goのサンプルコードがあり,とても参考になりました!)
メリット
配列・コレクションを操作するロジックが1箇所になり,変更がしやすい
9. getter, setterプロパティを使用しないこと
このルールを用いて解決したい課題
データクラスの設計パターン(ロジックを持たない)に意識していないといつの間にか陥ってしまう
実践方法
インスタンス変数をそのまま返す・変更するのではなく,ロジックを持ったメソッドのみを記述する
値オブジェクト等を利用して,不変にする
メリット
インスタンス変数を書き換えるsetterによるバグを抑える
データクラスの設計パターンを阻止する
留意点
既にある程度の規模のアプリケーションでこのルールを全て適用し始めるのは難しいと考えています.
そのため,最初は採用するルールを少なくすべきだと思います.
私が関わっているアプリケーションでも,まずは適用しやすいルールから利用していこうと考えています.
さいごに
設計についてさらに興味が湧いたと同時に,すごく難しいな...というのが正直な感想です.
とはいえ,少しでもアウトプットした方が良いなという気持ちになり,今回記事にしました.
今後,「ドメイン駆動設計入門」や「クリーンアーキテクチャ」を読みたいと思っており,読了後にこの記事をもう一度ブラッシュアップできたらと思います!
Discussion