はじめに
「理解容易性」は「保守性」の観点の1つとして重視され、多くの原則や技法が紹介されているが、断片的かつ多様であり、全体像を理解することは難しい。
抽象度は高いが、体系的に観点を整理する事で、その理解の助けとなれば幸いである。
定義
「理解容易性」を簡単に言えば、「理解のしやすさ」であるが、その意味から掘り下げると、「思考する量」と言い換えることができる。
本記事では理解容易性を「思考量の少なさ」と定義し、7つの観点に整理した。
先に要約およびチェックリストを記載し、概略を記載した。
後に詳細で理解のため、各観点毎の説明と個別の原則や技法へのリンクを記載した。
要約
7つの観点の要約を先に示す。
- (変数や関数の)名称は分かりやすくする
- (変数や関数の)役割は1つにする
- (変数や関数の)参照は狭くする
- (変数や関数の)状態は変えられなくする
- (関数やクラスの)面積は小さくする
- (関数やクラスの)階層は浅くする
- (関数やクラスの)秩序は整える
チェックリスト
質問の形に変え、一部を詳細化した。
- (変数や関数の)名称は分かりやすいか?
- (変数や関数の)役割は複数ないか?
- (変数や関数の)参照は広くないか?
- 色々な所から参照されてないか?
- 色々な所を参照してないか?
- (変数や関数の)状態は変えられないか?
- (関数やクラスの)面積は大きくないか?
- 縦の行数は長くないか?
- 横の文字数は多くないか?
- (関数やクラスの)階層は深くないか?
- (関数やクラスの)秩序は整っているか?
実装を終えたタイミングで確認してみて下さい。
詳細
前提
整理の為、観点を2つに大分した。
-
識別子(Identifier)
- 変数や関数などを他と区別するための名称や記号
-
区画(Block)
- 関数やクラスなど、コードのまとまり
識別子(Identifier)
名称:曖昧 -> 明瞭
識別子の名称は、曖昧ではなく、明瞭とする。
曖昧である場合、確認範囲が広くなるため、思考量が増えてしまう。
-
命名規則の準拠
- リポジトリで採用された命名規則に準拠する
- 規則に準拠しない場合、規則の理解に時間を要する
ref. 命名規則について
-
適切な英語の使用
- プログラムで一般的に使われる英語を使用する
- 独自な英語表現の場合、意図の理解に時間を要する
ref. Qiita: プログラミングでよく使う英単語のまとめ
-
省略名の不使用
- 名称は省略せず記載する
- 経験や文化に依存した名称の場合、意図の理解に時間を要する
ref. Qiita: 変数名を冗長にしたら変数名で悩まなくなった
-
マジックナンバーの不使用
- 意味を持った値には適切な名称をつける
- 無名である場合、その値の理解に時間を要する
ref. Wikipedia: マジックナンバー (プログラム)
-
説明変数/関数の利用
- 自明ではない値の場合、変数や関数を定義し、名称を付ける
- 無名である場合、その値の理解に時間を要する
ref. Qiita: 説明変数(要約変数)のメリット・デメリットと、発展例(メソッドの抽出)
-
列挙型の利用
- 定数をグループ化し、用途を明確にする
- グループ化しない場合、用途の確認に時間を要する
ref. Qiita: 列挙型(enum)の基本的な使い方とコード例
-
驚き最小の原則に準拠
- 名称と内容を一致させる
- 不一致である場合には、内容を読むことを強制する
ref. Wikipedia: 驚き最小の原則
-
アノテーションコメントの利用
- 意図を表現しきれない時はコメントで残すが、コメントの意味も明示する
- コメントがない場合、その意図の理解に時間を要する
ref. Qiita: アノテーションコメント
役割:複数 -> 単一
識別子の役割は、複数ではなく、単一とする。
複数である場合、複雑性への対処のため、思考量が増えてしまう。
-
単一責任の原則に準拠(SOLIDの1つ)
- 識別子が持つ責務は1つにする
- 複数ある場合、意図の理解に時間を要する
ref. Wikipedia: SOLID.単一責任の原則
-
モジュール分割
- 責務が複数ある場合、分割をする
- 分割をしない場合、責務の理解に時間を要する
ref. Qiita:モジュールの分割
-
関心の分離
- 関心(役割)により、分割をする
- 分割しない場合、複雑さへの対処に時間を要する
ref. Wikipedia: 関心の分離
ref. Qiita: 関心の分離を意識した名前設計で巨大クラスを爆殺する
-
値オブジェクトの利用
- 役割の分離方法として、値の特性ごとに分割する
- 分割しない場合、複雑さへの対処に時間を要する
ref. Zenn:【DDD入門】TypeScript × ドメイン駆動設計ハンズオン ‐ Chapter 08 値オブジェクト (Value Object)
-
モジュールの凝集度
- 同じ役割で纏まるように、分割する
- 纏まっていない場合、影響範囲の調査に時間を要する
ref. Qiita: 凝集度について
-
インターフェース分離の原則(SOLIDの1つ)
- 同じ役割ではないインタフェースは分割する
- 分割しない場合、不要な部分が増え、理解に時間を要する
ref. Qiita: 【SOLID】インターフェース分離の原則を完全に理解したい
-
コマンド・クエリ分離の原則
- コマンドとクエリは別の関数に分割する
- 分割しない場合、複雑性の理解に時間を要する
ref. コマンド・クエリという考え方
状態:可変 -> 不変
識別子の状態は、可変ではなく、不変とする。
可変である場合、確認範囲が広くなるため、思考量が増えてしまう。
-
定数の利用
- 変更されない値は変更できないようにする
- 変更できる場合、変更された可能性の確認に時間を要する
- 言語によっては
const
だけでなく、readonly
なども利用可能である
ref. Wikipedia: 定数 (プログラミング)
-
不変オブジェクトの利用
- オブジェクトが持つ値を変更できないようする
- 変更できる場合、変更された可能性の確認に時間を要する
ref. Qiita: 不変オブジェクトって何?【作り方も解説】
-
値オブジェクトの利用
- 不変オブジェクト化する際に関連する機能群を集約する
- 集約しない場合、不変オブジェクトの扱いに多くの時間を要する
ref. Zenn:【DDD入門】TypeScript × ドメイン駆動設計ハンズオン ‐ Chapter 08 値オブジェクト (Value Object)
-
継承の禁止
- 必要のないクラスの継承を禁止する
- 禁止しない場合、変更時の影響確認に時間を要する
ref. Qiita: 【Java】final修飾子 ( 定数化・継承禁止、オーバーライド禁止 )-クラスにつける
-
オーバーライドの禁止
- 必要のないオーバーライドを禁止する
- 禁止しない場合、呼び出し内容の確認に時間を要する
ref. Qiita: 【Java】final修飾子 ( 定数化・継承禁止、オーバーライド禁止 )-メソッドにつける-オーバーライド
参照:広域 -> 局所
識別子の参照は、広域ではなく、局所とする。
広域である場合、確認範囲が広くなるため、思考量が増えてしまう。
-
モジュールの疎結度
- モジュール間の結合度は低くする
- 低くない場合、影響範囲の確認に時間を要する
ref. Qiita: 結合度について
-
値のスコープを狭くする
- グローバル、フィールド、ローカルとできるだけ狭くする
- 狭くない場合、影響範囲の確認に時間を要する
ref. Qiita: 良いコードの書き方-変数のスコープを小さくする
参照
-
純粋関数への転換
- 外部との副作用を持たない関数やクラスに転換する
- ex. グローバルや環境変数を直接参照せず、引数として受け取る
- 転換しない場合、参照先の確認に時間を要する
ref. Qiita: 純粋関数
- 外部との副作用を持たない関数やクラスに転換する
被参照
-
データ隠蔽
- クラスが持つフィールドなどを直接参照させない
- 参照できる場合、影響範囲の確認に時間を要する
ref. Qiita:データ隠蔽・情報隠蔽とは-データ隠蔽
-
情報隠蔽
- 値を公開せず、必要な処理のみを公開する
- 値を公開した場合、影響範囲の確認に時間を要する
ref. Zenn:ドメイン知識が漏れるとは何なのか
区画(Block)
面積:広大 -> 狭小
区画の面積は、広大ではなく、狭小とする。
広大である場合、確認範囲が広くなるため、思考量が増えてしまう。
-
重複の除去(DRY原則)
- 同じ意味で重複したものは1つにする
- 重複した場合、変更時の対応に時間を要する
ref. Wikipedia: Don't repeat yourself
-
関数の抽出
- まとまりごとに関数として抽出する
- 抽出しない場合、確認する量の多さに時間を要する
ref. Qiita: リファクタリング入門-関数の抽出
縦:行数
主処理部分の行数は5~10行程度、全体としても20行程度に収める。
-
デッドコードの除去
- 利用されることのなくなった、できないコードは削除する
- 削除しない場合、別の人も同じ確認に時間を要する
ref. Qiita: CleanCode読書メモ-デッドコード
-
不使用コードの除去(YAGNI)
- 現時点で使わないコードは記述しない
- 記述した場合、意味のないコードの確認に時間を要する
ref. Wikipedia: YAGNI(You ain't gonna need it)
-
nullの不使用
- 必要な時以外はnullを利用しない
- 利用した場合、nullを考慮した実装の冗長さにより多くの時間を要する
ref. Javaでnullを安全に扱う − Optionalの使い方
-
値オブジェクトの利用
- 直接値を利用せず、値オブジェクトにする
- 値オブジェクトにしない場合、値の判定や関連処理の冗長さにより多くの時間を要する
ref. Zenn:【DDD入門】TypeScript × ドメイン駆動設計ハンズオン ‐ Chapter 08 値オブジェクト (Value Object)
-
3項演算子の利用
- 複雑ではない場合、3項演算子を利用する
- 利用しない場合、記述の冗長さにより多くの時間を要する
ref. Qiita: 三項演算子の適切な使い方(条件演算子)
コメント
-
退化コメントの除去
- 古くなったと分かったコメントは除外する
- 除外しない場合、別の人も同様の確認に時間を要する
ref. Qiita: CleanCode読書メモ-退化コメント
-
冗長コメントの除去
- 翻訳コメントなど意味のないコメントは除外する
- 除外しない場合、情報密度が低くなり、把握に多くの時間を要する
ref. Qiita: CleanCode読書メモ-冗長コメント
-
コメントアウトの除去
- バージョン管理で確認できる内容を残さない
- 残した場合、情報密度が低くなり、把握に多くの時間を要する
ref. Qiita: CleanCode読書メモ-コメントアウトされたコード
横:字数
単純な行ではなく、一文として80字程度に収める。
-
パラメーターオブジェクトの利用
- 引数の数が多い場合、専用のオブジェクトを作成する
- 作成しない場合、宣言部および利用箇所の記述の冗長さにより、把握に多くの時間を要する
ref. Hatena: パラメータオブジェクトの導入【リファクタリング】
-
説明変数/関数の利用
- 冗長な条件判定は別途変数や関数に分割する
- 分割しない場合、記述の冗長さにより、把握に多くの時間を要する
ref. Qiita: 説明変数(要約変数)のメリット・デメリットと、発展例(メソッドの抽出)
階層:多層 -> 単層
区画の階層は、多層ではなく、単層とする。
多層である場合、複雑性への対処のため、思考量が増えてしまう。
-
早期リターン
- 主処理の必要性ない条件は先に
return
やexit
させてしまう - させない場合、ネストの深さの理解に時間を要する
- ガード節・アーリーリターンとも呼ばれる
- 類型として、アーリー・コンティニュー もある
ref. Zenn: 早期リターンを書こう
- 主処理の必要性ない条件は先に
-
関数の抽出
- ネスト内の処理が多く、更にネストしている場合、別関数に分割する
- 分解しない場合、冗長さや複雑さの理解に時間を要する
ref. Qiita: 良いコードの書き方 > ロジックの切り出し
-
配列・リスト操作関数への転換
- ネスト自体を必要としない記述方式に転換する
- 転換しない場合、複雑さの理解に時間を要する
ref.Qiita: 深いネストを減らすには?-LINQ を使う
秩序:雑然 -> 整然
区画の秩序は、雑然ではなく、整然とする。
雑然としている場合、複雑性への対処のため、思考量が増えてしまう。
-
簡潔に単純にする(KISS)
- おおよそ簡潔かつ単純にする
- 簡潔かつ単純ではない場合、理解に時間を要する
ref. Wikipedia: KISS(Keep it simple stupid)の原則
-
コーディングスタイルの準拠
- リポジトリルールがある場合はそれに準拠する
- 準拠しない場合、ルールの理解に時間を要する
ref. Google Style Guides
-
段落分け
- 意図の纏まりごとに段落を分ける
- 分けない場合、意図の理解に時間を要する
ref. 【リーダブルコード】良いコードを書くための原則【入門編】 > 5コードを段落に分ける
-
関数の抽出
- 意図の纏まりの数が多い場合、それぞれを別の関数に分割する
- 分割しない場合、内容の理解に時間を要する
ref. Qiita: 良いコードの書き方 > ロジックの切り出し
-
高凝集化
- 意図が同じ内容を同じところに纏める
- 纏めない場合、関連する箇所の把握に時間を要する
ref. Qiita: 凝集度について
-
カプセル化
- データとロジックを同じところに纏める
- 纏めない場合、関連する箇所の把握に時間を要する
ref. Wikipedia: カプセル化
-
対称性
- 大きな意図が同じ名称やブロックでは、対称性や相似性を高くする
- 低い場合、大きな意図が同じである事の理解に時間を要する
ref. Qiita: コードレビューに使える「7つの設計原則」 > 3. 対称原理
-
インフラ・ドメインの分離
- ドメインとインフラ層のコードを混ぜない
- 混ぜた場合、複雑さの理解に時間を要する
ref. HP: 私がコピペしたいからドメイン層とインフラストラクチャ層を分離してくれ
-
粒度の統一
- ロジック内の粒度は統一して記載する
- バラバラの場合、複雑さの理解に時間を要する
ref. Hatena: 脱・読みづらいコード!今日から一段上のプログラマーになる方法 5 選 > 2. 粒度を揃える
終わりに
分かり辛い点が多いですが、良いコーディングの参考になれば幸いです。
参考
書籍
- リーダブルコード
- リファクタリング 既存のコードを安全に改善する(第2版)
- 良いコード/悪いコードで学ぶ設計入門―保守しやすい 成長し続けるコードの書き方
- Clean Architecture 達人に学ぶソフトウェアの構造と設計 (アスキードワンゴ)
- エリック・エヴァンスのドメイン駆動設計
- 脳に収まるコードの書き方 ―複雑さを避け持続可能にするための経験則とテクニック