はじめに
理解しやすいコードとは、普段自分が書いているコードはいいコードなのかを確認するために書籍にて振り返りしたく存じます
1章 理解しやすいコード
全ての原作はたった1つのテーマから生じている
鍵となる考え、コードは理解しやすくなければいけない
1.1「優れた」コードって何?
「簡潔」なのと「安心」なのは違う
1.2 読みやすさの基本定理
鍵となる考え、コードは他の人が最短時間で理解できるように書かなければならない
半年後の自分自身が見てもわかるようなコードでなければならない
1.3 小さいことは絶対にいいことか
コードは短くした方がいいが、「理解するまでにかかる時間」を短くする方が大切
1.4 「理解するまでにかかる時間」は競合するのか
理解するまでにかかる時間とその他の目標(コードの効率化、設計、テストのし易さ)とは競合しない
理解しやすいコードは、優れた設計やテストのし易さにつながる
第一部 表面上の改善
2章 名前に情報を詰め込む
変数であっても、関数であっても、クラスであっても、名前は短いコメントだと思えばいい。短くていい名前をつければそれだけ多くの情報を伝えられる
名前に情報を埋め込むこと
2.1 明確な単語を選ぶ
「名前には情報を詰め込む」には、明確な単語を選ばなければならない。「空虚な」単語は避けるべき
例)「get」はあまり明確な単語ではない
def GetPage(url)
...
end
「get」という単語からは何も伝わってこない
このメソッドはページをどこから取ってくるのか?
ローカルキャッシュ?データベース?インターネット?
インターネットから取ってくるなら、FetchPage()やDownloadPage()の方が明確
単語に合わせたカラフルな代替案
単語 | 代替案 |
---|---|
send | deliver, dispatch, announce, distribute, route |
find | search, extract, locate, recover |
start | laubch, create, begin, open |
make | create, set up, build, generate, compose, add, new |
気取った言い回しよりも明確で正確な方がいい
2.2 tmpやretvalなどの汎用的な名前を避ける
tmp,retval,fooのような名前は、「名前のことを考えていません」と言ってるようなもの
いい名前とは、変数の目的や値を表すもの。
アドバイス
retvalという名前には情報がない、変数の値を表すような名前を使おう
tmpという名前は、生存期間が短く、一時的な保管が最も大切な変数にだけ使う
ループイテレータ
i,j,k,iterなどの名前は、インデックスやループイテレータでよく使われている汎用的な名前だが、これだけで「僕はイテレータ」という意味になるので問題ない
しかし、それよりもいい名前がある
clubs, members, usersなどイデレータが複数ある時、説明的な名前(club_i, members_i, users_i)にする。もしくは頭文字だけ取って(ci,mi,ui)など、こうすればバグが目立ちやすくなる
tmp,it,retvalのような汎用的な名前を使う時は、それ相応の理由を用意する
2.3 抽象的な名前よりも具体的な名前を使う
2.4 名前に情報を追加する
文字絶対に知らせなきゃいけない大切な情報があれば、「単語」を変数名に追加すればいい。
その他の重要な属性を追加する場合、全ての変数名に属性を追加するのではなく、変数の意味を間違えてしまった時にバグになりそうなところだけ使うことが大切
基本的には、変数の意味を理解してもらわないと困るところに属性を追加する
2.5 名前の長さを決める
- スコープが小さければ短い名前でもいい
- 長い名前を入力するのは問題じゃない
- 頭文字と省略形
- 不要な単語を投げ捨てる
2.6 名前のフォーマットで情報を伝える
- フォーマット毎に名前を変える
2.7 まとめ
- 明確な単語を選ぶ
- tmpやretvalなどの汎用的な名前を避ける
- 具体的な名前を使って、物事を詳細に説明する
- 変数名に大切な情報を追加する
- スコープの大きな変数には長い名前をつける
- 大文字やアンダースコアなどに意味を求める
3章 誤解されない名前
名前が「他の意味と間違えられることはないだろうか?」と何度も自問自答する
3.1 filter
result = Database.all_objects.filter("year <= 2011")
例)filter
filterにすると「year <= 2011」のオブジェクトなのか「year <= 2011」ではないオブジェクトなのかどちらかわからない。
「選択する」のであれば、select、「除外する」のであれば、excludeにした方がいい
3.2 clip
段落の内容を切り抜く関数があるとする
clipの動作は2つ考えられる
- 最後からlength文字を削除(remove)
- 最大length文字まで切り詰める(truncate)
読み手に疑問を抱かせるより関数名をremoveやtruncateに変えた方が良い
※バイト数、文字数、単語数でも変わるのでlengthではなく文字数ならcharsなどにする
限界を含めるときはmix,maxを使う
「未満(限界値を含まない)」なのか「以下(限界値を含む)」なのかを明確にする
限界値を明確にするには、名前の前にmix_やmax_をつけること
範囲を指定するときはfirstとlast
包含/排他的範囲にはbeginとend
例えば10月16日に開催されたイベントを全て印字したい場合
例)PrintEventInRange("oct 16 12:00am", "oct 16 11 11:59:59.9999pm")
ブール値の名前
ブール値の変数やブール値を返す関数の名前を選ぶ時には、trueとfalseの意味を明確にしなければならない。
ユーザの期待に合わせる
複数の名前を検討する
4章 美しさ
- 読み手が慣れているパターンと一貫性のあるレイアウトを使う
- 似ているコードは似ているように見える
- 関連するコードをまとめてブロックにする
メソッドを使った整列
- 重複を削除したことでコードが簡潔になる
4.4 縦の線をまっすぐにする
4.5 一貫性と意味のある並び
- 対応するHTMLフォームのフィールドと同じ並び順にする
- 「最重要」なものから重要度順に並べる
- アルファベット順に並べる
4.6 宣言をブロックにまとめる
全てのメソッドを1つの大きなブロッックにまとめるのではなく、論理的なグループに分けてあげるといい
4.7 コードを「段落」に分割する
- 似ている考えをグループにまとめて、他の考えと分けるため
- 視覚的な「踏み石」を提供できるから、これがなければページの中で自分の場所を見失う
- 段落単位で移動できるようになる
4.8 個人的な好みと一貫性
一貫性のあるスタイルは「正しい」スタイルよりも大切だ
4.9 まとめ
- 複数のコードブロックで同じようなことをしていたら、シルエットも同じようなものにする
- コードの「列」を整列すれば、概要が把握しやすくなる
- ある場所でA・B・Cのように並んでいたものを、他の場所でB・C・Aのように並べてはいけない。意味のある順番を選んで、常にその順番を守る
- 空行を使って大きなブロックを論理的な「段落」に分ける
5章 コメントすべきことを知る
コメントの目的は、書き手の意図を読み手に知らせることである
- コメントするべきでは「ない」ことを知る
- コードを書いている時の自分の考えを記録する
- 読み手の立場になって何が必要になるかを考える
5.1 コメントするべきでは「ない」こと
コードからすぐにわかることをコメントに書かない
5.2 自分の考えを記録する
- コメントにはコードに対する大切な考えを記録しなければいけない
- コードの欠陥にコメントをつける(TODO)
- 定数にコメントをつける
5.3 読み手の立場になって考える
他の人にコードがどのように見えるかを想像するもの、「他の人」というのは、プロジェクトのことを君のように熟知していない人のこと
- 質問されそうなことを想像する
- ハマりそうな罠を告知する(コードを見て間違えそうなことを記載)
- 「全体像」のコメント
新しくチームに参加した人にコードを見せる時
「これはビジネスロジックとデータベースを繋ぐグルーコードです。アプリケーションから直接使ってはいけません」
「このクラスは複雑に見えますけど、単なるキャッシュです。システムのことは関知していません」 - 要約コメント
5.4 ライターズブロックを乗り越える
とにかく描き始める
例)
- 「やばい」は「注意:これには気をつけて」という意味
- 「これ」は「入力を処理するコード」という意味
- 「面倒なことになる」は「実装が難しくなる」ということ
コメントを書く作業は3つの手順に分解できる
- 頭の中にあるコメントをとにかく書き出す
- コメントを読んで(どちらかといえば)改善が必要なものを見つける
- 改善する
5.5 まとめ
コメントすべきでは「ない」こと
- コードからすぐに抽出できること
- ひどいコード(例えば、ひどい名前の関数)を補う「補助的なコメント」、コメントを書くのではなくコードを修正する
記録すべき自分の考え
- なぜコードが他のやり方ではなくこうなっっているのか
- コードの欠陥をTODOやXXXなどの記法を使って示す
- 定数の値にまつわる背景
読み手の立場になって考える
- コードを読んだ人が「えっ?」と思うところを予想してコメントをつける
- 平均的な読み手が驚くような動作は文書化する
- ファイルやクラスには「全体像」のコメントを書く
- 読み手が細部にとらわれないよう、コードブロックにコメントをつけて概要をまとめる
6章 コメントは正確で簡潔に
コメントは領域に対する情報の比率が高くなければいけない
6.1 コメントを簡潔にしておく
6.2 曖昧な代名詞を避ける
「それ」ではなく明確にしておく
6.3 歯切れの悪い文章を磨く
コメントを正確に簡潔にする
6.4 関数の動作を正確に記述する
6.5 入出力のコーナーケースに実例を使う
6.6 コードの意図を書く
6.7 「名前付き引数」コメント
6.8 情報密度の高い言葉を使う
6.9 まとめ
- 複数のものを指す可能性がある「それ」や「これ」などの代名詞を避ける
- 関数の動作はできるだけ正確に説明する
- コメントに含める入出力の実例を慎重に選ぶ
- コードの意図は、詳細レベルではなく、高レベルで記述する
- よくわからない引数にはインラインコメントを使う
- 多くの意味が詰められた言葉や表現を使ってコメントを簡潔に保つ
第二部 ループとロジックの単純化
7章 制御フローを読みやすくする
条件やループなどの制御フローはできるだけ「自然」にする。コードの読み手が立ち止まったり読み返したりしないように書く
7.1 条件式の引数の並び順
左側:「調査対象」の式。変化する
右側:「比較対象」の式。あまり変化しない
7.2 if/elseブロックの並び順
- 条件は否定系よりもこう提携を使う。例えばif(!debug)ではなくif(debug)を使う
- 単純な条件を先に書く。ifとelseが同じ画面に表示されるので見やすい
- 関心を引く条件や目立つ条件を先に書く
7.3 三項演算子
行数を短くするよりも、他の人が理解するのにかかる時間を短くする
基本的にはif/elseを使う。三項演算子はそれによって完結になるときだけ使う
7.4 do/whileループを避ける
7.5 関数から早く返す
7.6 悪名高きgoto
基本的にはgotoを使わない方がいい
7.7 ネストを浅くする
変更するときにはコードを新鮮な目で見る。一歩下がって全体を見る
早めに返してネストを削除する
ループ内部のネストを削除する
8章 巨大な式を分割する
巨大な式は飲み込みやすい大きさに分割する
8.1 説明関数
式を簡単に分割するには、式を表す変数を使えばいい。この変数を「説明変数」と呼ぶこともある
8.2 要約変数
8.3 ド・モルガンの法則を使う
8.4 短絡評価の悪用
「頭がいい」コードに気をつける。あとで他の人がコードを読むときにわかりにくくなる
8.5 複雑なロジックと格闘する
より優雅な手法を見つける
8.6 巨大な文を分割する
- タイプミスを減らすのに役立つ
- 横幅が縮まるのでコードが読みやすくなる
- クラス名を変更することになれば、一箇所を変更する
8.7 式を簡潔にするもう一つの創造的な方法
8.8 まとめ
- 巨大な式を分割する
- 簡潔な名前で式を説明することで、コードを文書化できる
- コードの主要な「概念」を読み手が認識しやすくなる
9章 変数と読みやすさ
変数を適当に使うとプログラムが理解しにくくなる
- 変数が多いと変数を追跡するのが難しくなる
- 変数のスコープが大きいとスコープを把握する時間が長くなる
- 変数が頻繁に変更されると現在の値を把握するのが難しくなる
9.1 変数を削除する
役に立たない一時変数
- 複雑な式を分割していない
- より明確になっていない、datetime.datetime.now()でも十分に明確
- 一度しか使っていないので、重複コードの削除になっていない
中間結果を削除する
制御フロー変数を削除する
9.2 変数のスコープを縮める
グローバル変数に限らず、全ての変数の「スコープを縮める」のはいい考え
変数のことが見えるコード業数をできるだけ減らす
- JavaScriptで「プライベート」変数を作る
- JavaScriptのグローバルスコープ
- PythonとJavaScriptのネストしないスコープ
- 定義の位置を下げる
- 変数を操作する場所が増えると、現在値の判断が難しくなる
9.5 まとめ
- 邪魔な変数を削除する:本章では、結果をすぐに使って、「中間結果」の変数を削除する例を示した
- 変数のスコープをできるだけ小さくする:変数を数行のコードからしか見えない位置に移動する
- 一度だけ書き込む変数を使う:変数に一度だけ値を設定すれば、コードが理解しやすくなる
第三部 コードの再構成
- プログラムの主目的と関係のない「無関係の下位問題」を抽出する
- コードを再構成して、一度に1つのことをやるようにする
- 最初にコードを言葉で説明する。その説明を元にして綺麗な解決策を作る
10章 無関係の下位問題を抽出する
- 関数やコードブロックを見て「このコードの高レベルの目標は何か?」と自問する
- コードの各行に対して「高レベルの目標に直接的に効果があるのか?あるいは、無関係の下位問題を解決しているのか?」と自問する
- 無関係の下位問題を解決しているコードが相当量あれば、それらを抽出して別の関数にする
11章 一度に1つのことを
コードは1つずつタスクを行うようにしなければならない
1度に1つのタスクをするには
- コードが行っている「タスク」を全て列挙する、「タスク」という言葉は緩く使っており、「オブジェクトが妥当かどうかを確認する」のように小さいこともあれば、「ツリーのすべてのノードをいてレートする」のように曖昧なこともある
- タスクをできるだけ異なる関数に分割する
12章 コードに想いを込める
自分よりも知識が少ない人が理解できるような「簡単な言葉」で説明する能力が大切
- コードの動作を簡単な言葉で同僚にもわかるように説明する
- その説明の中で使っているキーワードやフレーズに注目
- その説明に合わせてコードを書く
12.1 ロジックを明確に説明する
12.2 ライブラリを知る
12.3 この手法を大きな問題に適用する
解決策を言葉で説明する
13章 短いコードを書く
もっとも読みやすいコードは、何も書かれていないコードだ
13.1 その機能の実装について悩まないで、きっと必要ないから
13.2 質問と要求の分割
要求を詳しく調べれば、問題をもっと簡単にできることもある
13.3 コードを小さく保つ
プロジェクトが成長しても、コードをできるだけ小さく軽量に維持するしかない
- 汎用的な「ユーティリティ」コードを作って、重複コードを削除する
- 未使用のコードや無用の機能を削除する
- プロジェクトをサブプロジェクトに分割する
- コードの「重量」を意識する。軽量で機敏にしておく
13.4 身近なライブラリに親しむ
13.6 まとめ
- 不必要な機能をプロダクトから削除する。過剰な機能は持たせない
- もっとも簡単に問題を解決できるような要求を考える
- 定期的にすべてのAPIを読んで、標準ライブラリに親しんでおく
第四部 選抜テーマ
14章 テストと読みやすさ
14.1 テストを読みやすくて保守しやすいものにする
他のプログラマが安心してテストの追加や変更ができるように、テストコードを読みやすくする
テストコードが大きくて恐ろしいものだと以下が起きる
- 本物のコードを修正するのを忘れる
- 新しいコードを書いたときにテストを追加しなくなる
14.3 テストを読みやすくする
一般的な設計原則として、「大切ではない詳細はユーザーから隠し、大切な詳細は目立つようにする」べき
最小のテストを作る
14.4 エラーメッセージを読みやすくする
14.5 テストの適切な入力値を選択する
コードを完全にテストするもっとも単純な入力値の組み合わせを選択しなければいけない
入力値を単純化する
テストにはもっとも綺麗で単純な値を選ぶ
1つの機能に複数のテスト
14.6 テストの機能に名前をつける
テストの内容を表した名前をつけるべき、テストコードを読む人が以下のことをすぐに理解できるものがいい
- テストするクラス
- テストする関数
- テストする状況やバグ
14.10 まとめ
- テストのトップレベルはできるだけ簡潔にする、入出力のテストはコード1行で記述できるといい
- テストが失敗したらバグの発見や修正がしやすいようなエラーメッセージを表示する
- テストに有効なもっとも単純な入力値を使う
- テスト関数に説明的な名前をつけて、何をテストしているのかを明らかにする(Test1()ではなくTest_<関数名>_<状況>のような名前にする
15章 「分/時間カウンタ」を設計・実装する
終わりに
知ってはいるけれどなあなあにしていた部分をちゃんと学べてよかった。
リファクタリングするときにどのようなことを意識するか、開発をするにあたっていいコードがどんなものかを人に教えることに躊躇しなくなりそうではある。
参考
リーダブルコード -より良いコードを書くためのシンプルで実践的なテクニック-