読みやすいコードのための規則
この記事は「リーダブルコード」の内容から必要箇所を抜粋し、自分用にまとめなおしたメモです。古い記法や変数定義に関する内容および現在は汎用的ではないと感じた情報は意図して省いています。
表面上の改善
名前に情報を詰め込む
変数や関数の名前は見ただけでその情報が読み取れるようにする。
-
具体的な動詞を選ぶ:
Get
などの広義の捉え方ができる単語は避け、Fetch
やDownload
などより明確な単語を選択する。 -
汎用的すぎる名前を避ける:
tmp
,m
などの汎用的すぎる名前や短すぎて意味が伝わらない名前も避ける。ただし、変数のスコープが短く、寿命が数行程度であれば使用可。 -
詳細な名前を使う: 具体的な名前を使ってより詳細に説明する名前をつける(
ServerCanStart()
よりもCanListenOnPort()
とするなど)。スコープが大きい変数・関数であればその情報を伝えるために長い名前であっても良い。 -
単位や属性を名前に含める: 変数名にはその単位やその他の重要な属性を付与する。
- 例: 開始時間をミリ秒で表す変数 →
start_ms
- 例: URL エンコードされた入力データ →
data_urlenc
- 例: 開始時間をミリ秒で表す変数 →
誤解されない名前
読者に誤解をされる恐れのある広義の捉え方のできる名前は避けるようにする。
-
限界値を含む範囲: 未満ではなく限界値を含める名前を命名する時は
max
,min
を使う。 -
包括的範囲: 包括的範囲を指定する時は
first
,last
を使用する。max
,min
でも可。- 包括的範囲とは、範囲の両端(始点と終点)を含む範囲のこと(例:1〜10 なら 1 と 10 も含む)
-
包括・排他的範囲: 慣習的に使用されている
begin
とend
を使用する(少し曖昧な意味合いとなっている)。- 包括・排他的範囲とは、始点は含むが終点は含まない範囲(例:
begin=0, end=5
は 0,1,2,3,4 を意味する)
- 包括・排他的範囲とは、始点は含むが終点は含まない範囲(例:
-
bool 値の命名: 真偽値を返す関数は true , false の意味が明確な名前をつける(
need_pasword
やuser_is_authenticated
など)。- 接頭辞に
is_
,has_
,can_
,should_
をつけることが多い。 -
disable_ssl
などの否定形の関数名は避ける。 - Ruby では接尾辞に
?
をつける慣習がある(is_authenticated?
など)。
- 接頭辞に
-
ユーザの期待に合わせる: 例えば
get()
という多くの場合軽量なデータ取得が期待されるような関数名で複雑な計算処理を含むような処理を実装しない。その場合は処理内容に合わせた名前をつける。
美しさ
優れたコードは「目に優しい」ものでなければならない。一貫性と意味のあるやり方でコードを「整形」すれば素早く簡単に読めるようになる。
-
一貫性のある改行位置: 複数のコードブロックで同じようなことをしていたら、その改行位置を揃えることでシルエットも同じようなものにする。
-
メソッドを使った整列: 複数のコードブロックで同じようなことしてるが、見た目に統一性がない場合、ヘルパーメソッドを作成してロジックを分離して整列させる。
- 副作用として、コードが DRY になり、コードの追加やメンテナンスも容易になる。
-
縦の線をまっすぐにする: 似ているコードの羅列のパラメータの頭位置などを揃えることで縦の線をまっすぐにする。
// 整列されていない例 addUser("John", 25, true); addUser("Elizabeth", 30, false); addUser("Bob", 19, true); // 整列された例 addUser("John", 25, true); addUser("Elizabeth", 30, false); addUser("Bob", 19, true);
-
一貫性と意味のある並び: コードの並びは一貫性と意味のある並びにする(対応するフォームの並び順、データベースのカラム順など)。
-
宣言をブロックにまとめる: クラスの内容を空行がない一つの大きなコードブロックとして記述せず、メソッドの処理内容によっていくつかにグループ分けして空行を入れる。
-
コードを段落に分割する: 長いコードを一つのコードブロックとして記述せず、処理内容によって段落分けし、段落ごとに要約コメントを挿入する。
-
個人的な好みより一貫性を優先する: 例え正しいスタイルのコードでも様々なスタイルが入り乱れれば可読性が低下する。間違ったスタイルのプロジェクトでも一貫性を優先してその規約に従う。
コメントすべきことを知る
コメントの目的は「コードの動作を説明する」のではなく、「書き手の意図を読み手に知らせる」ことである。
-
コメントすべきではないこと
- コードからすぐ分かることをコメントに書かない。
- ひどいコードの内容を補う補助的なコメントは書かない。コメントを書くよりもまず命名を改善することでコードの意図をわかりやすくすることを検討する。
-> 「優れたコード > ひどいコード + 優れたコメント」
-
自分の考えを記録する
- なぜコードが他のやり方ではなくこうなっているのかをコメントする(コードからは得られない情報)。
- コードの欠陥にコメントをつける(慣習的に使用されている以下の接頭文字を使用して問題点や改善点を明示する)。
接頭文字 | 意味 | 例 |
---|---|---|
TODO | 将来的に実装や改善が必要な項目 | // TODO: もっと高速なアルゴリズムを使う |
FIXME | 既知のバグや問題点で修正が必要 | // FIXME: 計算処理にバグがある |
HACK | 一時的な回避策や美しくない解決法 | // HACK: 解決済みだがリファクター推奨 |
XXX | 危険または重大な問題がある箇所 | // XXX: 重大なバグがあるので早急に対応が必要 |
- 定数を定義する際はその「背景」をコメントする(// 人間が読める合理的な限界値のため など)。
-
読み手の立場になって考える
- 質問されそうなことを予想してコメントする(なぜ where 句ではなく select 句を使ったのかなど)。
- 平均的な読み手が驚く動作(命名からの想定以上に時間がかかるなど)をコメントしておく。
- ファイルやクラスには高レベルに「全体像」をコメントする。
- 読み手が細部にとらわれないようにコードブロックに要約コメントをつけて、概要をまとめる。
コメントは正確で簡潔に
コメントは領域に対する情報の比率が高くなければいけない。
- 無駄な複数行・長文コメントは避ける: なるべく一行で簡潔・直接的にコメントする。
- 曖昧な代名詞を避ける: 「その」「これ」などの曖昧な代名詞は避ける。
- 関数の動作を正確に記述する: 正確かつ具体的な返却値(行数ではなく'\n'の数など)を記述する。
- 入出力の実例をつける: 関数の入出力の実例をエッジケースを考慮して慎重に選んでコメントする。
- コードの意図を書く: 技術的な動作よりもコードの意図を書く(「降順にソート」ではなく「値段の高い順」など)。
-
引数に名前コメントをつける: 関数の引数に名前コメントをつける。
- Ruby ではこの書き方は推奨されない。代わりにキーワード引数を用いる。
Connect(/* timeout = */ 10, /* use_encryption =*/ false);
- 情報密度の高い言葉を使う: 「キャッシュ層」や「正規化」などエンジニアにとって既知の多くの意味を含んだ言葉や表現を用いてコメントを簡潔に保つ。
ループとロジックの単純化
制御フローを読みやすくする
- 比較条件は変化する値を左、安定・固定値を右に配置する
if (bytes_received < bytes_expected) {
// ...
}
- if/else のブロックは適切に並び替える: 肯定系・単純・目立つものを先に処理する。
- 三項演算子は読みやすく簡潔になる時のみ使用する: 基本的には if/else を使う。コードを短くするより、読み手がコードを読む時間を短くする。
- ネストを浅くする: ガード節を用いて「失敗ケース」を先に早期リターンさせるなど、ネストをなるべく削除する。ループ文においては(Rubyでは)next を使う。
巨大な式を分割する
巨大で複雑な式を読み手が飲み込みやすい大きさに分割する。
- 説明変数を導入する
if line.split(':')[0].split() == "root"
// 導入後
user_name = line.split(':')[0]
if user_name == "root"
-
ド・モルガンの法則を用いて論理式を読みやすくする:
- notを分配してand/orを反転する(not(A or B) <=> not(A) and not(B))
- 逆方向はnotを括り出す(not(A) or not(B) <=> not(A and B))
-
過度な短絡評価の使用を避ける: 無理に一行で収めるよりも複数行で読みやすいコードを書く。
- 短絡評価とはAND演算子とOR演算子を用いた論理式の評価で、左辺の評価結果によって右辺の評価を省略すること(例:
if (user && user.isAdmin)
)。
- 短絡評価とはAND演算子とOR演算子を用いた論理式の評価で、左辺の評価結果によって右辺の評価を省略すること(例:
- 複雑な条件は小さなif条件に分割する: 問題を「否定」したり、「反対」のことを考えてみることで複雑な条件を簡潔にする。
変数と読みやすさ
プログラムの変数はすぐに増えるのでいずれすべてを追跡できなくなる。なるべく変数を減らし「軽量」に保つことでコードを読みやすくする。
- 邪魔な変数を削除する: 一次変数や中間変数など、重複コードを削除する役割を果たしていなかったり結果をすぐに使用すれば不要な変数を削除する。
- 変数のスコープをなるべく小さくする: 変数のスコープを小さくすることで、読む時に一度に考えなければいけない変数を減らせる。
- 変数は使用する直前に宣言する: 関数の最初にすべてを定義すると、後になって使用する変数を覚えていないといけない。
- 変数は一度だけ書き込む: 変数に一度だけ値を設定する、もしくはconstなどのイミュータブルにする方法を使えばコードが理解しやすくなる。
コードの再構成
-
無関係の下位問題を抽出する: プロジェクト固有のコードからプロジェクトに関係のない下位問題を汎用コードとして分離する。
- インターフェースが汚いコードはラッパー関数を作成してインターフェースを改善する。
- 小さい関数を作りすぎると逆に可読性が下がる。プロジェクトの他の部分で再利用できないような過度な分離は避ける。
-
コードは一度に一つのタスクを行うように分割する: 以下の手順で行う。
- コードが行なっているタスクを列挙する。
- タスクをできるだけ異なる関数・クラス、または少なくとも異なる領域(例えば保存・更新・削除処理をそれぞれの領域にまとめるなど)に分割する。
-
コードを簡単な言葉に変換してリファクタリングする: コードをより明確にする以下の簡単な手順を行う。
- コードの動作を同僚にも分かるように簡単な言葉で説明する。
- その説明の中で使用しているキーワードやフレーズに注目する。
- その説明に合わせてコードを書く。
-
短いコードを書く: できるだけコードを減らし、新しいコードを書かない方法を考える。
- 未使用のコードや不必要な機能は定期的に削除する。過剰な機能を持たせない。
- 課題と要求を分割し、もっとも簡単に課題を解決できる要求を考える。
- 標準ライブラリで提供されている機能をなるべく利用する。そのために定期的に公式ドキュメントを読んでそれらを把握しておく。
テストと読みやすさ
テストコードが読みやすければ、テスト対象のコードの理解もしやすくなる。「テストコードがそのコードの動作と使い方を示した仕様書」という考え方もある。
- 最小のテストを作る: テストのトップレベルは可能な限り簡潔になるようにする。入出力のテストはコード一行で記述できると良い。
- テストの入力値は有効かつもっとも単純な値を選ぶ
- テスト名は説明的な名前をつける: 他のコードから呼び出されないので長くなっても構わない。コメントのように考えても良い。