リーダブルコードを読み、自分なりに解釈してまとめた覚書です。
表面上の改善
名付け
- 誤解されにくい明確な名前を付ける
- ✕
send
→ 〇deliver
,dispatch
,announce
,distribute
,route
- ✕
find
→ 〇search
,extract
,locate
,recover
- ✕
filter
→ 〇select
/exclude
- ✕
- 汎用的な名前や短い名前(例:
tmp
,retval
)を避ける- 具体的な名前の方がバグに気づきやすい
- 例:
sum_squares
(意味:2乗の合計)
- 例:
- スコープが短い場合は使ってOK
- コードを簡単に追えて用途を理解できるから
- コードを簡単に追えて用途を理解できるから
- 具体的な名前の方がバグに気づきやすい
- 重要な属性を付与する
- 例:値の単位を付ける
- ✕
start
→ 〇start_ms
(意味:ミリ秒) - ✕
size
→ 〇size_mb
(意味:メガバイト)
- ✕
- 例:注意喚起する ※ バグになりそうな箇所だけ付与でOK
- ✕
password
→ 〇plaintext_password
(意味:暗号化が必要) - ✕
comment
→ 〇unescaped_comment
,raw_coment
(意味:エスケープが必要) - ✕
html
→ 〇html_utf8
(意味:文字コードをUTF-8に変えた)
- ✕
- 例:値の単位を付ける
- 限界値を決めるときは
min
/max
を使う- ✕
CART_TO_BIG_LIMIT
- 「未満」なのか「以下」なのか判断できない
- 〇
MAX_ITEM_IN_CART
- ✕
- 範囲を指定するときは
first
/last
を使う- ✕
print range(start=2, stop=4);
- 出力は「2, 3」?それとも「2, 3, 4」?
- 〇
print range(first=2, last=4);
-
last
を使えば、4を包含していることが明確
-
- ✕
- 「包含/排他的範囲」(= 開始は含み、終了は含まない範囲)を表現するときは、慣習的に
begin
/end
を使う
- Bool値だと分かる名前にする
- 例:
is
,has
,can
,should
,use
,need
- 例:
- 先入観のある単語に気をつける
- △
getResult()
- メンバ値を返すだけの軽量アクセサだと思われがち
- 〇
computeResult()
- コストの高い計算の場合は
compute
などの名前にすべき
- コストの高い計算の場合は
- △
レイアウトを整える
- 改行のタイミングや変数の並びなどに一貫性と意味を持たせる
- 縦の線をまっすぐにする
- 例:変数への代入を複数行に記載する際、
=
の位置を縦方向で揃える- ただ、メンテナンス箇所が増えるので、この項目については好みが分かれる
- ただ、メンテナンス箇所が増えるので、この項目については好みが分かれる
- 例:変数への代入を複数行に記載する際、
- 宣言をブロックにまとめる
- 段落に分割する
コメント
- 読み手の立場になって考える
- コードを読んだ人が「えっ?」と思うところを予想してコメント
- 利用時にハマりそうな罠を注意喚起
- ファイルやクラスには「概要」を記載
- あいまいな(複数の意味を示す)言葉を使わない
- 例:代名詞(「それ」「これ」)など
- 例:代名詞(「それ」「これ」)など
- 入出力の実例を書く
- 例:
// src の先頭や末尾にある chars を除去する ↑「chars は順序のない文字集合?」「src 末尾に複数の chars があったら?」など疑問が生じる // 実例:Strip("abba/a/ba", "ab") は "/a/" を返す ↑ このように実例を書いておけば明確 String Strip(String src`, `String chars) { ... }```
- 例:
- コードの「動作」ではなく「意図」や「なぜこの実装をしたのか」を書く
- 良い例1:
// 価格の高い順に表示する
- 良い例2:
// このデータだとハッシュテーブルよりバイナリツリーの方が40%速かった
- 良い例3:
const MAX_RSS_SUBSCRIPTIONS = 1000; // 合理的な限界値。人間はこんなに読めない
- 良い例1:
- 多くの意味が詰め込まれた言葉や表現(例:ブルートフォース、ヒューリスティックなど)を使って、簡潔にする
- 悪い例:
// この関数は、問題を解決するための一番単純な方法で書かれている // もっと速いアルゴリズムがあるが、とりあえず動くことを優先
- 良い例:
// TODO: まずはナイーブソリューションで実装したので、要改善
- 悪い例:
ループとロジックの改善
制御フロー
- 比較を書くときは、変化する「調査対象」の値を左に、あまり変化しない「比較対象」の値を右に配置する
- 例:
if(bytes_received > bytes_expected)
- 例:
- 可能ならガード節を使う(特殊なケースやエラーは冒頭で処理し、すぐに処理を抜ける)
- if/else文のブロックの並びは、基本的には以下のものを先に処理することで、読む人の認知的な負担を減らす
- 単純な条件:コードの長さ的に
if
とelse
が同じ画面内に収まりやすいので見やすい - 正常系、主要なケース:「このコードが主に行なっている処理は何か?」を先に伝えたい
- 肯定系の条件:肯定系の方が直感的。ただし、否定系でも目立つ条件の場合は先に書く
- 単純な条件:コードの長さ的に
- 以下の構文は、あまり使わない方がいい
- 三項演算子:単純な条件式の場合なら使ってOK
- do/while文:ループの終了条件が最後まで分からないため
- goto文:プログラムの実行順序を理解しにくい
- ネストを浅くする
-
return
やcontinue
を利用し、なるべくネストを削除する
-
変数
- 複雑な式は理解するのに時間がかかるため、適切な名前の変数に代入する(= 説明変数)
- ただし、変数が多いと追跡するのが難しくなるため注意
- 良い例:
username = line.split(':')[0].strip() if username == "root" { ... }
- 悪い例:
now = datetime.datetime.now() root_message.last_view_time = now()
- 良い例:
- 変数のスコープはなるべく小さくする
- 変数の定義は、変数を使う直前に行う。先頭にまとめて定義すると覚えておくのが大変
- 一度だけしか書き込まない変数を使う(あるいは、
const
やfinal
でイミュータブルにする)とコードを理解しやすい。
コードを再構成するときに大事な視点
- プログラムの主目的と関係のない「無関係な下位問題」を抽出する
- 一度に1つのことをやるようにする。メソッドを分けるか、ブロックにまとめる
- なるべくライブラリでロジックを実現する
- ライブラリやAPI、組み込みモジュールの一覧に目を通しておき、「引き出し」を作っておく