凝集度を意識したコーディングとは
凝集度(cohision)とは
凝集度(ぎょうしゅうど、コヒージョン、cohesion)とは、情報工学においてモジュール内のソースコードが特定の機能を提供すべく如何に協調しているかを表す度合いである。IPAが実施する情報処理技術者試験では、強度(きょうど、ストレングス、strength)という言葉が使われる。凝集度は順序尺度の一種であり、「凝集度が高い」とか「凝集度が低い」といった言い方で使われる。凝集度の高いモジュールは、堅牢性、信頼性、再利用性、読みやすさなどの点で好ましく、凝集度の低いモジュールは保守/評価/再利用/読解が難しいため好ましくないとされる。
簡単に言うと、役割が明確な関数、クラスの実装を心がけましょうということです。
1つの関数にいろんな機能を詰め込みすぎたり、1つのクラスに大量のメソッドを定義していくと、保守性が低下するだけでなく、拡張性も失われてしまうので困ります。
そうしないためにも、凝集度の高いコードを作ることを心がけて置くことが大事です。
凝集度の種類
- 偶発的凝集
- 論理的凝集
- 時間的凝集
- 手続き的凝集
- 通信的凝集
- 逐次的凝集
- 機能的凝集
上記のように7つに分類され、
低 1 .... 7 高
このような順位付けです。
機能的凝集に向けた実装をすることが好まれるということになりますね。
では、1つずつどういった凝集度を指しているのかクリーンアーキテクチャの観点も踏まえて見ていきましょう。
偶発的凝集
function execute(): void
{
申込書を作成する();
ユーザー情報を作成する();
ログを保存する();
管理者情報を更新する();
メールを送信する();
}
いわば、何でも有りの無法地帯を指しています。
これはどの層で合っても避けるべき実装となります。機能やAPIを分割することを考えるべきです。
とある機能を実装しようとなると、1つの処理で多岐に渡る機能を実装する必要が出てくる場合もあります。もちろん分割できるのがベストですが、なかなかそうはいかないケースもありますね。
その場合はできるだけ低レイヤーで処理するなどして、1つの処理に対して複数の処理が依存しないように実装することが望まれます。
論理的凝集
function validation(array $data): bool
{
if (empty($data['name'])) {
return false;
} else if (empty($data['name']) && empty($data['user_name'])) {
return false;
} else if (empty($data['user_name'])) {
return false;
} else {
if($data['name'] !== 'a') {
return false;
}
return true;
}
...
}
1つの関数に似た処理が含まれている状態を指します。
上記であれば、バリデーションの処理が羅列されていて一見機能的にはまとまりが良いようにも思います。
しかし、これらはそれぞれの機能として分割することが望ましいです。
特に、1つの関数内で複数のif文が発生していれば論理的凝集に該当する可能性が高いので気をつけておきましょう。
修正後
function validation(array $data): bool
{
validateName($data['name'], $data['user_name']);
validateUserName($data['name']);
...
return true;
}
時間的凝集
function before_bedtime(): void
{
本を読む();
テレビを観る();
勉強する();
}
順番にとらわれない機能が集約された状態を指します。
高レイヤーな部分ではこういった実装をせざる負えないケースも多いですが、中核部分に入ってきた際には分割することが望まれます。
MVCであればController
層で、この実装がされることは許容されますが、Model
層では好まれません。
クリーンアーキテクチャであれば、Interface
層では許容されても、Enterprise
やApplication
層では避けておきたいところですね。
手続き的凝集
function before_goto_school() void
{
起きる();
朝食を食べる();
学校へ行く();
}
順番に依存する機能が集約された状態を指します。
時間的凝集になっている部分は、このレベルまで凝集度を高めることができると良いですね。
こちらもできるだけ高レイヤーの部分での実装が望まれます。
通信的凝集
function search(Query $query, string $name, string $email): array
{
$query->名前で検索する($name);
$query->メールアドレスで検索する($email);
}
機能としては同一であるが、対象が異なる処理が集約されている状態を指します。
1つの処理が膨大になれば上手く集約して分割実装することが望まれますが、ここまでくれば比較的凝集度が高い実装になってきています。
逐次的凝集
function uploadFile(File $file, string $name): bool
{
if (exist_file($name)) {
return false;
}
return upload($file, $name);
}
複数の機能が組み合わされているが、それをセットで行うことで処理として成立する集まりです。
ファイルの存在を確認して、保存するなどは、それぞれが別機能であるが1つの処理であることが求められます。
逐次的凝集になっているか手続き型凝集であるかなど、その処理自体を機能として見たときにどちらに属しているか(凝集度が高くなっているか)という観点でみることが大事です。
機能的凝集
function add(int $lsv, int $rsv): int
{
return $lsv + $rsv;
}
単一機能、機能としての最小単位になっている状態です。
ドメイン層などはこの状態により近づくよう実装することで、凝集度かつメンテナンス性の高いコードとなります。
まとめ
言葉で覚えるのは難しい凝集度ですが、実際に実装する上で気をつけることでテストが書きやすくなったり、使いまわしのし易いコードへと変わっていきます。
「どの凝集度に該当するか」は実際のところ重要ではなく、レイヤーに適した実装がなされているかが重要です。
変更が容易であるか、テストがし易いか、という観点で確認しつつ実装していくことで、自然と凝集度の高いコードを作成することができるようになるので、意識的に取り組んでいきましょう(言い聞かせ)。
Discussion