いつも何かを実装するときの関心はどうすれば良いコードを書けるかでした。 同じような機能を作るときでも余裕があればもっと良いいコードにならないかということについて調べたりしています。 WEB+DB PRESS Vol.127 にはリファクタリングの特集があったので kindle 版で購入して読んでみました。
リファクタリングとは
リファクタリング とは、 外部 から 見 た 動作 を 変え ず に コード の 内部 構造 を 改善 し て いく こと です。
リファクタリングの意味は知っていますが、筆者の方はリファクタリングをどう捉えているのかを知るのが楽しみです。
この特集では何を紹介しているのか
リファクタリング 時 の 手法 の 根拠 として 凝集 度 と 結合 度 を 用い て コード を 改善 し て いく 方法 を 紹介 し ます。
根拠という根拠がなくコードを書き換えることもしばしばあったので、凝集度と結合度という指標を用いて改善できる というのはすごく興味がわきました。
忘れがちな観点
コード を 書く 時間 以上 に コード を 読む 時間 が 長い こと は 有名 な 話 です。 機能 追加 の 際 に、 コード を 読み解く 苦労 を ほか の メンバー や 将来 の 自分 に 与え ては いけ ませ ん。 もし 簡単 に 理解 できれ ば 開発 速度 は 上がり、 バグ 修正 も 容易 になり ます。
「コード を 書く 時間 以上 に コード を 読む 時間 が 長い こと 」 これはそうですね。でも、当たり前すぎていたのでハッとしました。
この特集での良いコードの定義
生産性 を 上げる ため には チーム メンバー と 協力 し て コード を 書い て いく 必要 が あり ます。 つまり 良い コード とは、 チーム として 最大 の 生産性 を 発揮 できる コード と 定義 する こと が でき ます。
本題に入る前に良いコードの定義がされているのがよかったです。ほとんどの場合、チームで開発しますし、サービスもスケールしていくと人も必然的に増えていくので生産性を発揮できるかどうかというのはスケールしていく上でとても重要だと思います。
凝集度
凝集度とは
関数の役割の少なさを表す尺度のこと → 凝集度が高いほど良い 凝集度 - Wikipedia
ひとつの関数の中で、A・B・C の機能の処理が混ざり合っているよりも、 機能と関数が 1 対 1 のほうが理解しやすく、修正の際に他の部分に影響を与えないため、機能と関数が 1 対 1 になっている方が凝集度が高くて保守しやすいコード と言えそうです。逆に、ひとつの関数の中で A・B・C の機能の処理が混ざり合っている方は A の修正をしたときに B・C に影響するため凝集度が低いコードと言えそうです。
7 つの凝集度
凝集度には、7 つの尺度が存在します。 下に行くにつれて凝集度が高い良いコードとなります。
- 偶発的凝集
- 論理的凝集
- 時間的凝集
- 手続き的凝集
- 通信的凝集
- 逐次的凝集
- 機能的凝集
偶発的凝集は、関数の役割とは関係ない処理が関数内にある関数 のこと。
- 問題点
- 関係ない処理が複数ある状態のため、可読性がとても低く。再利用できない。
- 改善方法
- 関係ない処理は関数から削除し、別途関数を作るなどする。
論理的凝集は、関連のない処理をフラグで切り替える関数 です。
- 問題点
- 関連のない処理をフラグで A のときは A を実行、B のときは B を実行するため、A と B の処理に必要な引数を受け取るが、フラグによっては使わない引数が存在する
- 改善方法
- ※後述の「時間的凝集と論理的凝集への向き合い方」に追記
時間的凝集は、機能的には関連はないが同じ時間に実行する処理をまとめた関数 です(例えば、initialize などの初期化)。
- 問題点
- 機能的には関連がないので initialize のときはたまたま同時実行しているけど、それ以外のときは同時に実行しないかもしれない。再利用性が低い。
- 改善方法
- 必要に応じて、関数に切り出す。
手続き的凝集は、機能的には関連はないが同じ時間で実行順序に意味がある処理をまとめた関数
- 問題点
- 時間的凝集に似ている。違いは実行順序に意味がある点。
- 順序に意味があるだけで、機能的には関連がない。必ず同時に実行するとは限らないため、再利用が難しい。
- 改善点
- 手続き的凝集の中に詳細な実装が複数行にわたって書かれているなら、関数として切り出しその関数を呼び出すだけにする
通信的凝集は、機能的には関連はないが同じ時間で同じ値に対して処理をする関数
- 問題点
- 時間的凝集に似ている。違いは同じ値に対して処理を行う。同じ値を操作するので、凝集度が高い。
- 機能的には関係ない関数が 1 つの関数にまとめられていること。
- 改善点
- 時間的凝集と同じ。
逐次的凝集は、機能的に関連はないが関連する値を受け渡して処理をする関数
- 問題点
- 時間的凝集に似ている。違いは手順間で値の受け渡しがあること。
- 機能的には関係ない関数が 1 つの関数にまとめられていること。
- 改善点
- 時間的凝集と同じ。
機能的凝集は、単一の機能を処理する関数。 これ以上の凝集度はないため、問題点や改善方法はない
凝集度まとめ
偶発的凝集は絶対に避けるべき、論理的凝集も極力避けるべき ということが分かりました。 時間的凝集、手続き的凝集、通信的凝集、逐次的凝集は類似しているため、この章以降は時間的凝集として紹介され、時間的凝集については詳細なロジックが書かれていないかを注意しようとありました。
結合度
結合度とは
関数の独立性を表す尺度のこと。 → 結合度が低いほど良い 結合度 - Wikipedia
結合度が高い状態とは、ある関数が持つデータを変更すると他の関数に影響を及ぼす状態 です。修正作業による副作用などを未然に防ぐため、結合度が低い状態を目指します。
7 つの結合度
結合度には、7 つの尺度が存在します。下に行くにつれて結合度が低い良いコードとなります。
- 内部結合
- 共通結合
- 外部結合
- 制御結合
- スタンプ結合
- データ結合
- メッセージ結合
内部結合は、宣言されていない変数での結合
- 問題点
- 直接メモリアドレスを指定してデータの受け渡しをしている状態
- 受け取る側ではどこで何が受け渡しされるかがコード上に表現されていないため、コードの挙動の予測が難しい
- 改善点
- メモリアドレスに保持していた値をローカル変数として定義して、変数を受け渡しする
共通結合は、共通の複数のグローバル変数で結合
- 問題点
- 共通の複数のグローバル変数で結合している状態。
- グローバル変数を使う側は、グローバル変数がどこでどのように変更されているかを知っていないといけない
- 改善点
- グローバル変数をローカル変数として定義して、変数を受け渡しする
外部結合は、共通の単一グローバル変数で結合
- 問題点
- 共通結合と同じ
- 改善点
- 共通結合と同じ
制御結合は、制御フラグで結合
- 問題点
- 関数にフラグを渡して処理を切り替える関数
- 呼び出し側の関数では、このフラグを渡したら A という処理をするというように呼び出す関数の中身を理解して実装する必要があるため結合度が高い
- 改善点
- フラグを受け取る関数そのものを削除する
これ以降は受け渡す値の種類に応じて結合度が決定される。構造体の方が受け渡す値が多いため結合度の強弱に差が出ている。これは良い悪いの差はない。
- スタンプ結合は、構造体やクラスで結合
- データ結合は、スカラ型の値で結合
- メッセージ結合は、引数のない関数の呼び出しで結合
結合度まとめ
内部結合、共通結合、外部結合はローカルのスコープで値が管理されていないため、関数間の見通しが悪いので絶対に避けるべき結合 ということがわかりました。 また、 制御結合もフラグを渡す関数は多くの場合不要なので極力避けるべき ということが具体的に分かったので気を付けようと思います。
まとめ
4 章の「時間的凝集と論理的凝集への対応」と 5 章の「DRY と凝集度」もかなり良いこと書いてあるのですが、力尽きたのでここまでにします。今まで感覚的にやっていたことが言語化されていてコード例もあったので明確に理解できました!とても良い特集だったので気になる人はぜひ買って読んでみてください。