# リーダブルコード
# 表面上の改善
# 理解しやすいコード
- コードは他の人が最短時間で理解できるように書く
- そのためには少しくらい冗長な記載になっても構わない
# 名前に情報を詰め込む
# 明確な単語を選ぶ
- get => fetch, download
- size => height, numNodes, memoryBytes
- stop => kill, pause(resume できるなら)
# カラフルな単語を選ぶ
類語辞典を使ってより明確な意味の単語を使う
find => search, extract, locate など
# 汎用的な名前を避ける
- tmp など
- ループでの i,j,k など
生存期間がごく短いなど、理由があって使うならいいけど、ただの怠慢で上記を使うのはダメ。
# 抽象的でなく具体的な名前を使う
特定のポートが使えるかチェックする関数なら serverCanStart() => canListenOnPort()
# 名前に情報を追加する
// フォーマットを追加する
const id = "af84ef845cd8";
const hexId = "af84ef845cd8";
// 単位を追加する
const size = 10;
const sizeMb = 10;
// 文脈を追加する(セキュリティ被害やバグが出そうな場合)
const url = "http://danger.com";
const untrustedUrl = "http://danger.com";
# 名前の長さ
- スコープが小さければ短い名前でもよい(m など)
- スコープが大きいなら長くて具体的な名前をつける
- 新入りが即座に理解できないような省略形は使わない
- formatStr はオーケー
- BEManeger はだめ
- 意味のない単語は削る
- convertToString => toString
- doServeLoop => serverLoop
# 誤解されない名前
他の意味と間違えられることはないだろうか?と常に自問せよ
- 適切に名付ける
- 限界値 ---
max
,min
- 範囲 ---
first
,last
- 包含/排他(最後を含まない) ---
begin
,end
end
は包含的でもありうるものの、英語には「ちょうど最後の値を超えたところ」を表現する方法がないのでこれが最善
- 限界値 ---
- まぎらわしい単語を使わない
read
など
- ユーザの期待を裏切らない
getName()
に超ヘビーな処理をさせるなど(通常get***()
は軽量アクセサであることが期待される)
- 複数の名前を検討して一番いいのを選ぶ
# 美しさ
- 改行位置に一貫性をもたせる
- 多くの長ったらしい関数呼び出しは util 等に切り出して短くする
- コードを段落でまとめてコメントを付ける
- 「正しい」スタイルよりも一貫性を重視する
# コメントするべきことを知る
- コードからすぐ分かることをコメントしない
- ひどい名前はコメントでは救えない、良い名前にしろ
- コードを書いたときに頭にあった「大切な考え」をコメントする
- 映画監督のコメンタリーのように、なぜそうしたのか、を書く
- 既知の欠陥にコメントする
- 定数にコメントする(なぜその値なのか)
- 読み手の立場になる
- 質問されそうなこと
- ハマりそうな罠
- コードの全体像
- 要約(低レベルコードや、処理のまとまりごとに)
# コメントは正確で簡潔に
- 代名詞(こそあど言葉)を使わない。自明な場合を除き。
- 歯切れ良い文章にする
- ❌ ステータスによって優先度を変える
- ⭕ ステータスが 0 のものの優先度を上げる
- 正確に記述する
- ❌ ファイルの行数を数える
- ⭕
\n
の数を数える
- 実例、特にエッジケースをコメントで伝える
- 例:
Strip("aaba/a/ba", "ab")は"/a/"を返す
- 例:
- 低レベルではなく、高レベルの説明をする
- ❌ list を逆順にイテレートする
- ⭕ 値段の高い順に表示する
- 意図が伝わりやすい、間違いに気づきやすい
- 分かりにくい引数には名前付き引数を使う
- Java など名前付き引数が使えない場合はインラインコメントで
- 情報密度の高い言葉を使う
- 長くてくどいコメントになってしまったら情報密度の高い言葉がないか探す
- キャッシュ層、正規化、ヒューリスティック、ブルートフォース、など
# ループとロジックの単純化
複雑なコードは「精神的な荷物」を増やす。「精神的な荷物」が多いコードは、
- 理解しにくい
- バグが見つからない
- 変更しにくい
- 触れるのが楽しくなくなる
# 制御フローを読みやすくする
- 条件式の並べ方
- 左側に「調査対象」の式。変化する
- 右側に「比較対象」の式。あまり変化しない。
- ヨーダ記法は使うな
- if ブロック
- 非定形ではなく肯定形を使う
- 処理が単純な方の条件を先に書く
- 関心を引く条件や目立つ条件を先に書く
- これらの方針は矛盾することもあるので都度一番最適なものを選択する
- 三項演算子
- 読みやすくなる場合を除いて使うな。基本的に if 文を使え
- 行数を短くするよりも、他の人が理解するのにかかる時間を短くしろ
- do-while
- 使うな。while で書き直せ
- 関数から早く返す
- いわゆる「ガード節」
- クリーンアップコードを実行したいのなら、Python の
with
や JS のtry-finally
などの洗練された方法でやれ
- ネストを浅くする
- 早めに
return
する - 早めに
continue
する(ただし見通しが悪くならないよう注意) - 条件式の中に条件式を追加したくなったときは、条件式全体を眺めて再整理する癖をつけろ
- 変更するときにはコードを新鮮な目で見る。一歩下がって全体を見る。
- 早めに
- 処理の流れを追いやすいコード
- 上から下に向かって読めるように書く
- スレッド、シグナル、例外、関数ポインタ、無名関数、仮想メソッドなどをなるべく使わない
# 巨大な式を分割する
- 長くて非直感的な式は、説明変数や要約変数で読みやすくする
- ドモルガンの法則を使う
- not を(分配|くくりだし)して and/or を反転する
- not (a or b or c) <=> (not a) or (not b) or (not c)
- not (a and b and c) <=> (not a) or (not b) or (not c)
- 短絡評価を悪用しない。利用によりコードが簡潔になる場合に留める。
- 複雑なロジックになってしまった場合は「反対から考えてみる」ことでひらめきが得られることもある
- 配列を逆順に処理してみる
- データを後ろから挿入してみる
- 条件を逆にしてみる など
# 変数と読みやすさ
- 削除するべき変数
- 何の役にも立たない一次変数は削除する
- 中間結果の変数は削除できることが多い。「タスクをその場で完了させろ」
- 制御フロー変数は削除できることが多い。「使わずにすむコードに書きなおせ」
- 変数のスコープは短く
- 変数を使用する場所に最も近いところで変数を定義せよ
- ブロック(ネスト)の中で変数を定義するな
- C++や Java と違い、Python や ES5 ではブロック内の変数はその関数全体に「こぼれ出る」
- なので、読みやすさのために「最も近い共通の祖先(ネスト的な意味で)」で変数を定義すること
- 変数は一度だけ書き込む
- const や final 等を積極的に使う
# コードの再構成
# 無関係の下位問題を抽出する
無関係の下位問題を抽出するとは、プロジェクト固有のコードから汎用コードを分離するということ。
- 「このコードの高レベルの目標はなにか?」を自問する
- コードの各行が 1 に当てはまるかチェックする
- 当てはまらないコードが相当量あれば、util 等に切り出す(やりすぎると読みにくくなるので注意)
# メリット
- 高レベルの目標に集中できるようになる
- 再利用できる
- 機能追加やリファクタが容易になる
- 独立してテストできる
# 種類
- 完全にプロジェクトから切り離された下位問題を解決するコード
util
フォルダ等に配置するとよい- これらは複数のプロジェクトを横断して利用できる
- プロジェクトに特化した下位問題を解決するコード
- 使用する場所の近くにおいておく
# 具体例:既存のインターフェースを簡潔にする
理想とは程遠いインターフェースに妥協することはない。 たとえば cookie の標準 API はひどいので、ラッパー関数を用意するなど。
# 一度に一つのことを(分割)
- コードは 1 つずつタスクを行うようにしなければ行けない。HDD のデフラグのようなイメージ。
- 関数や領域を分けることで、その場所にいるときは他のことを考えなくてすむ
方法
- タスクを口語で全て列挙する(小さなことから大きなことまですべて)
- タスクを関数又は領域(書く場所、段落)に分割する
# コードに思いを込める(抽象から具体へ)
- プログラムのことを簡単な言葉で説明する
- ゴムのアヒルちゃんに話しかけるように、やりたいことを口語で説明してみる(ラバーダッキング)
- これにより自分の考えを明確にでき、他人にも理解してもらいやすくなる
方法
- やりたいことを簡単な自然言語で書き出す
- 1 をもとにしてコードを書く
# 短いコードを書く
最も読みやすいコードは、何も書かれていないコードだ
# その機能は必要ない
- プログラマはテンションが上がると必要のないクールな機能を実装しがち(オーバーエンジニアリング)
- プログラマは実装にかかる労力を過小評価しがち
これにより下記のような残念な結果になる
- 多くの機能が完成しない
- 多くの機能が全く使われない
- アプリケーションに複雑さをもたらす
# 要求を削ぎ落とす
- 全てのプログラムが高速で、100%正しくて、あらゆる入力をうまく処理できる必要はない
- 必要最低限の問題だけを最も簡単なやり方で解決する
- 可能な限り要件を削る
- そうすれば加速度的にコストを抑えられる
# コードを小さく保つ
- あらゆるシステムは成長する。複雑さはもっと速い速度で成長する。
- コードをできるだけ小さく軽量に維持する努力を怠れば、あっという間に破綻する
やるべきこと
- 無関係の下位問題の抽出
- 未使用のコードや無用の機能(無駄だったり必要性がなかったりする機能)を削除する
- プロジェクトをサブプロジェクトに分割する
- コードの重量を常に意識する
# 車輪の再発明をしない
既存コードを活用する。テコの原理を最大限活かせ。
- 標準ライブラリを使う
- たまには標準ライブラリの全てのドキュメントにざっと目を通してみよう。
- シェルスクリプトを使う
- 場合によってはコードを書かないようがよいこともある。
# その他
# 読みやすいテスト
他のプログラマが安心してテストの追加や変更ができるように、テストコードを読みやすくする
- 大切ではない詳細はユーザから隠す
- ヘルパー関数に切り出す
- 大切な詳細は目立つようにする
- テストの本質を 1 行で表現できればベスト
# エラーメッセージを読みやすくする
- よりよい
assert
を使う。例えば python ならassertEqual()
など。 - あるいは手作りでエラーを表示する
# 適切な入力値を選択する
- コードを完全にテストする最も単純な入力値の組み合わせを選択しなければいけない
- テストには最もきれいで単純な値を選ぶ
- 場合によってはPairwise (opens new window)法でテストケースを生成する
- 極端な値をテストしたいときは、ベタ書きではなくコードで書く
checkBeforeAfter([-5, 1, 4, -99998.7, 3], [4, 3, 1]);
// ではなく
checkBeforeAfter([1, 2, -1, 3], [3, 2, 1]);
# 一度に 1 つのことをテストする
- 1 つの機能をテストするとき、複数のテストに分けると分かりやすい
- ソートのテスト、マイナスの取り扱いのテスト、重複のテスト、など
# テスト関数に正しい名前をつける
- よそから呼ばれるわけじゃないので、名前が長くなっても構わない
# やりすぎない
- テストのために本物のコードの読みやすさを犠牲にするとか
- カバレッジを 100%にしないと気がすまないとか
- テストがプロダクト開発のじゃまになるとか