リーダブルコード
数年前(プログラミング初心者の時)に読んだので、もう一度振り返りでサクッとまとめてみました。
1. 理解しやすいコード
コードは理解しやすくなければならない。
読みやすさの定理
コードは他の人が最短時間で理解できるように書かなければならない。
2. 名前に情報を詰め込む
名前に情報を詰め込む
tmp, size, getなどはわかりにくいので❌
明確な単語を選ぶ
e.g.
getPage() → downloadPage()
size() → height(), numNodes, memoryBytes()
stop() → kill()(取り消しができないほど重い操作), Pause()(後から再開できる場合)
単語 | 代替案 |
---|---|
send | deliver, dispatch, announce, distribute, route |
find | search,extract, locate, recover |
start | launc, create, begin, open |
make | create, set up, build, generate, compose, add, new |
tempやretvalなどの汎用的な名前を避ける
retvalは「これは戻り値です」以外の情報はない。
ループイテレータは、汎用的だが、clubs[i].members[k] == users[j]
→ clubs[ci].members[mi] == users[ui]
のようにバグが分かりやすいようにする。
抽象的な名前よりも具体的な名前を使う
ServerCanStart() → CanListenOnPort()
DISALLOW_EVIL_CONSTRUCTORS → DISALLOW_COPY_AND_ASSIGN
--run_locally() → --extra_logging() / --use_local_database
名前に情報を追加する
- 単位
string id = "af84ef845cd8"
→string hex_id = "af84ef845cd8"
関数の仮引数 | 単位を追加した仮引数 |
---|---|
start(int delay) | start(int delay_secs) |
CreateCache(int size) | CreateCache(int size_mb) |
ThrottkeDownload(float limit) | ThrottkeDownload(float max_kbps) |
Rotate(float angle) | Rotate(float degrees_cw) |
- その他の重要な属性
関数の仮引数 | 単位を追加した仮引数 |
---|---|
プレインテキストのpassword | plaintext_password |
文字コードutf-8のhtml | htmx_utf8 |
入力されたdataをURLエンコードした | data_url_enc |
エスケープされていないコメント | unescaped_comment |
ハンガリアン記法 : 変数名の接頭辞に型をつける(e.g. pLast, psqBuffer, cch, mpcopx)
名前の長さを決める
- 長い名前を避ける(ダメではない)
- スコープが小さければ短くてok
- 頭文字と省略形を避ける(特にプロジェクト固有の省略形はダメ!BEManagerなど新メンバは理解できない)
- 不要な単語を捨てる(e.g. ConvertToString()→ToString())
名前のフォーマットで情報を伝える
- 定数 CONSTANT_NAME→kConstantName (#defineマクロと区別するため)
- メンバ変数
val_
(アンダースコアをつける) - jQueryのライブライ関数を代入した変数 $val ($をつける)
3. 誤解されない名前
名前が他の意味と間違えられることはないだろうか?と何度も自問自答する。
filter
results = data.filter("age <= 20")
とすると、resultsは20歳以下のオブジェクトか20歳以下ではないオブジェクトかわからない。→ select() にする。除外の場合は exclude() にする。
Clip(text, lenght)
段落の内容を切り抜く関数clip(text, lenght)は以下2通りの意味で捉えられる。
- 最後からlength文字を削除する → remove() にする
- 最大lenght文字まで切り詰める → truncate() にする
また、length → max_length → max_charsにする。
限界値の定義
CART_TOO_BIG_LIMIT
だと未満か以下か不明 → MAX_ITEMS_IN_CART
にする
ブール値の名前
bool read_password = true
は以下2つの解釈がある。
- パスワードをこれから読み取る必要がある
- パスワードをすでに読み取っている
→ need_password, user_is_authenticated
boolはis, has, can, shouldなどの接頭辞をつけると分かりやすい(e.g. spaceLeft()→ hasSpaceLeft())
また、名前は否定形より肯定形の方がいい(e.g. disable_ssl=false→ use_ssl=true)
ユーザの機体に合わせる
一般的にgetやsizeは軽量なイメージ
- get()はjavaでは、メンバの値を返すだけの軽量アクセサ。コストが多い場合等はcomputeXXX()を使う
- list.size()→O(1)ではない場合などcoutSize()やcoutElementsにする。size()はO(1)
複数の名前を検討する
e.g. 再利用したい実験のIDの名前
- template → 曖昧。これはテンプレートか、このテンプレートを使っているわからない。
- resuse → 100回再利用できると誤認識の可能性
- copy → 100回コピーできると誤認識の可能性、100回目のコピー
- inherit → 継承
4. 美しさ
- 読み手が慣れているパターンと一貫性のあるレイアウトを用いる
- 似ているコードは似ているように見せる
- 関連するコードをまとめてブロックにする
最近は、フォーマッターなどあるので不要。
5. コメントすべきことを知る
コメントの目的は、書き手の糸を読み手に知らせることである
コメントするべきではないこと
-
コードからすぐにわかることをコメントしない。
-
コメントのためのコメントをしない
-
酷い名前はコメントをつけずに名前を変える : コメントに頼らない。関数名などを修正する
通常は、補助的なコメント(コードの読みにくさを補うコメント)は不要である。
優れたコード > 酷いコード + 優れたコメント
自分の考えを記録する
- 監督のコメンタリーを入れる : e.g.
// このデータだとハッシュテーブルよりバイナリツリーの方が40%速かった
など。 - コードの欠陥にコメントをつける :
// TODO: もっと高速なアルゴリズムを使う
など - 定数にコメントをつける : e.g.
const int MAX_RSS_SUBSCRIPTIONS = 1000; // 合理的な限界値。人間はこんなに読めない
アノテーション | 意味 |
---|---|
TODO | あとで追加、修正するべき機能がある。 |
FIXME | 既知の不具合があるコード。修正が必要。 |
HACK | あまりきれいじゃないコード。リファクタリングが必要。 |
XXX | 危険!動くけどなぜうごくかわからない。 |
REVIEW | 意図した通りに動くか、見直す必要がある。 |
OPTIMIZE | 無駄が多く、ボトルネックになっている。 |
CHANGED | コードをどのように変更したか。 |
NOTE | なぜ、こうなったという情報を残す。 |
WARNING | 注意が必要。 |
読み手の立場になって考える
- 質問されそうなことを想像する
- ハマりそうな罠を告知する
- 全体像のコメント
- 要約コメント
コメントには、WHAT・HOWではなく、WHYをかくようにすることが多い。ただし、コードを理解するのに役立てば、どれを書いてもいい。
ライターズブロックを乗り越える
適切な言い回しにする。(e.g. やばい → 注意 )
6. コメントは正確で簡潔に
コメントは領域に対する情報の比率が高くなければいけない
- コメントを簡潔にしておく
- 曖昧な代名詞を避ける
- 歯切れの悪い文章を磨く
- 関数の動作を正確に記述する
- 入出力のコーナーケースに実例を使う
- コードの意図を書く : 詳細レベルではなく、高レベルで記述する
- 名前付き引数コメント : よく話からに引数にはインラインコメントを使う
- 情報密度の高い言葉を使う : 正規化、ナイーブソリューション、ヒューリスティックなど
7. 制御フローを読みやすくする
条件やループなどの制御フローはできるだけ「自然」にする。コードの読み手が立ち止まったり読み返したりしないように書く。
条件式の引数の並び順
- 左側 : 調査対象の式。変化する。
- 右側 : 比較対象の式。あまり変化しない。
if/elseブロックの並び順
- 条件は否定型よりも肯定型を使う。⭕️ if(debug) ❌ if(!debug)
- 単純な条件を先に書く。
- 関心を引く条件や目立つ条件を先に書く。
ただし、これは状況によって変える。
三項演算子
基本的にはif/elseを使う。
三項演算子は簡潔になるときだけ使う。
do/whileループを避ける
do-statementはエラーや混乱の原因になることが多いので、避けた方が良い。条件は前もって書いた方が良い。
関数から早く返す
関数で複数のreturnを使い早く返すことは良いこと。
悪名高きgoto
基本的にgotoは使わない方がいい。
goto exit;
だけ例外。
ネストを浅くする
- 早めに返してネストを削除する(ガード節)
- ループ内部のネストを削除する
実行の流れを追う
構成要素 | 高レベルの流れが不明瞭になる理由 |
---|---|
スレッド | どのコードがいつ実行されるかわからない |
シグナル・割り込みハンドラ | 他のコードが実行される可能性がある |
例外 | いろんな関数呼び出しが終了しようとする |
関数ポインタと無名関数 | コンパイル時に判別ができないので、どのコードが実行されるか不明 |
仮想メソッド | object.virtualMethod()は未知のサブクラスのコードを呼び出す可能性がある |
これらの構成要素を使うと、コードを追うのが難しくなる。
ただし、これらを使うことで、コードが読みやすくなったり、冗長性が低くなくなったりする可能性もあるので、うまく使う。
8.巨大な式を分割する
巨大な式は飲み込みやすい大きさに分割する。
説明変数
説明変数を使って式を分割する。
要約変数
要約変数 : 大きなコードの塊を小さな名前に置き換えて、管理や把握を簡単にする変数。
if (request.user.id == document.owner_id){
}
if (request.user.id != document.owner_id){
}
これを要約変数を使うと以下のようになる。
final boolean user_owns_document = (request.user.id == document.owner_id);
if (user_owns_document){
}
if (!user_owns_document){
}
ド・モルガンの法則を使う
not (a or b or c)
↔️ (not a) and (not b) and (not c)
not (a and b and c)
↔️ (not a) or (not b) or (not c)
短絡評価の悪用
頭がいいコードに気をつける。あとで他の人がコードを読みときに分かりにくくなる。
巨大な文を分割する
DRY原則など
指揮を簡潔にするもう1つの創造的な方法
マクロを利用すると良いときがある。
9.変数と読みやすさ
- 変数が多いと変数を追跡するのが難しくなる
- 変数のスコープが大きいとスコープを把握する時間が長くなる
- 変数が頻繁に変更されると現在の値を把握するのが難しくなる
変数を削除する
- 役に立たない一時変数
- 中間結果を削除する
- 制御フロー変数を削除する
変数のスコープを縮める
変数のことが見えるコード行数をできるだけ減らす
- 特にグローバル変数は避ける。
- メソッドをできるだけstaticにする
- 大きなクラスを小さなクラスに分割する
- 定義の位置を下げる
変数は一度だけ書き込む
変数を操作する場所が増えると、現在値の判断が難しくなる。
const
やfinal
を使い、イミュータブルにする。
10.無関係の下位問題を抽出する
無関係の下位問題を積極的に見つけて抽出する。
- 関数やコードブロックを見て、このコードの高レベルの目標は何か?と自問する
- コードの各行に対して、高レベルの目標に直接的に効果があるのか?あるいは、無関係の下位問題を解決しているのか?と自問する
- 無関係の下位問題を解決しているコードが相当量あれば、それらを抽出して別の問題にする
11. 一度に1つのことを
コードは1つずつタスクを行うようにしなければならない
- タスクは小さくする
- オブジェクトから値を抽出する
- 一度に1つのタスクを適用する
12. コードに思いを込める
おばあちゃんがわかるように説明できなければ、本当に理解したとは言えない by アルバート・アインシュタイン
- コードの動作を簡単な言葉で同僚にもわかるように説明する
- その説明の中で使っているキーワードやフレーズに注目する
- その説明に合わせてコードを書く
- ロジックを明確に説明する
- ライブラリを知る
- この手法を大きな問題に適用する
- 解決策を言葉で説明する
- 手法を再起的に適用する
ラバーダッキング : 問題を声に出して説明するだけで、解決策が見つかること
13. 短いコードを書く
最も読みやすいコードは何も書かれていないコードだ。
- 必要ない機能の実装について悩まない
- 質問と要求の分割
- コードを小さく保つ
- 汎用的なユーティリティコードを作って重複コードを削除する
- 未使用のコードや無用の機能を削除する
- プロジェクトをサブプロジェクトに分割する
- コードの重量を意識する。軽量で機敏にしておく
- 身近なライブラリに親しむ
- unixツールボックスを使う
14. テストと読みやすさ
- テストを読みやすくて保守しやすいものにする
- テストを読みやすくする : 大切ではない詳細はユーザから隠し、大切な詳細は目立つようにする
- 最小のテストを作る
- 独自のミニ言語を実装する
- エラーメッセージを読みやすくする
- もっといいassert()を使う
- エラーメッセージはできるだけ役に立つようにする
- テストの適切な入力値を選択する
- 入力値を単純化する
- 1つの機能に複数のテスト
- テストの機能に名前をつける
- テスト駆動開発(TDD)