【読書メモ】リーダブルコード

読みやすさの基本定理

  • コードは他人が最短時間で理解できるように書かなければいけない。
  • 短ければいいってもんじゃない。

第1部 表面上の改善

2章:名前に情報を詰め込む

  • 明確な単語を選ぶ
    • 気取った言い回しよりも明確で正確な方がいい
  • 汎用的な名前を避ける

    • retvalという名前には情報がない。変数の値を表すような名前を使う
    • tmpという名前は、生存期間が短くて、一時的な保管が最も適切な変数にだけ使う
    • イテレータも説明的な名前にするとバグを見つけやすい
  • 抽象的な名前よりも具体的な名前を使う

    • DISALLOW_EVIL_CONSTRUCTORSや--run_locallyは抽象的
  • 接尾辞や接頭辞を使って情報を追加する
    • 16進のidならhex_idとする
    • 値の単位を入れる
      • start_ms
      • delay_sec
      • size_mb
      • max_kbps
      • degrees_cw
    • その他の重要な属性を追加する
      • plaintext_password
      • unescaped_comment
  • 名前の長さを決める
    • スコープが小さければ短い名前でもいい
    • 長い名前を入力するときは補完を使え
    • 頭文字と省略形
      • プロジェクト固有の省略形はダメ。
      • evaluationの代わりにeval, documentの代わりにdocはよい。
    • 不要な単語を投げ捨てる
      • ConvertToString()をToString()に
      • DoServeLoop()をServeLoop()
    • 名前のフォーマットで情報を伝える
  • 名前のフォーマットで情報を伝える

3章:誤解されない名前

名前が「他の意味と間違えられることはないだろうか?」と何度も自問自答する

  • filter()という名前は避ける
    • result = Database.all_objects.filter("year <= 2011")
      year <= 2011なオブジェクトを選択するのか排除するのかわからない。 選択するならselect()に。 排除するのであればexclude()にする
  • Clip(text, length)
    • これも「最後からlength文字を削除する」のか「最大length文字まで切り詰める」のかわからない
    • truncateもしくはremoveという名前に変えるべき。
    • lengthもmax_lengthに切り替える。そしてlengthからcharに切り替える。
  • 限界値を含めるときにはminとmaxを使う
  • 範囲を指定するときにはfirstとlastを使う
    • start,stopの代わりにfirst,lastを使う
  • 末尾を排他で指定するときはbegin、endを使う
  • ブール値の名前
  • ユーザの期待に合わせる
    • getなんとか()
      • 多くのプログラマはgetで始まるメソッドはメンバ値を返すだけの軽量アクセサと思い込んでいる。
      • コストの高い処理をその中で行ってはいけない。
    • list::size()
      • list::size()の計算量がO(n)であることがわかりづらい。
      • countSize()やcountElements()だったら、こんな問題は起きなかった。
  • 複数の名前を検討する

4章:美しさ

  • なぜ美しさが大切なのか
  • 一貫性のある簡潔な改行位置
    • コメントも整列させる
  • メソッドを使った整列
    • ヘルパーメソッドを使う
    • 重複も排除できる
  • 一貫性と意味のある並び
  • 宣言をブロックにまとめる
    • 大量のメソッドの宣言は論理的にグループ化する
  • コードを段落に分割する
    • コメントや改行を使ってコードを段落に分割する

一貫性のあるスタイルは「正しい」スタイルよりも大切だ

5章:コメントすべきことを知る

コメントの目的は、書き手の意図を読み手に知らせること

  • コメントすべきではないこと
    • コメントを読むとその分だけコードを読む時間がなくなる。 コメントにはその分だけ価値をもたせるべき。

コードからすぐにわかることをコメントに書かない

  • コメントのためのコメントをしない
  • ひどい名前はコメントをつけずに名前を変える
    • 優れたコード > ひどいコード + 優れたコメント
  • 自分の考えを記録する
  • コードの欠陥にコメントをつける
  • 読み手の立場になって考える
    • 質問されそうなことを想像する
  • 嵌りそうな罠を告知する
    • 「このコードを見てびっくりすることは何か?」
    • 「どんなふうに間違えて使う可能性があるだろう?」
  • 全体像のコメント
    • 新しいチームメンバにとって最も難しいのは全体像の理解
    • 短い適切な文章で構わない。ないよりはマシ
  • 要約コメント
  • ライターズブロックを乗り越える
    • コメントをうまく書くのは大変だ。その作業を3つに分解する
      • 頭の中にあるコメントをとにかく書き出す
      • コメントを呼んで改善が必要なものを見つける
      • 改善する

6章:コメントは性格で簡潔に

コメントは領域に対する情報の比率が高くなければならない.

  • コメントを簡潔にしておく
  • あいまいな代名詞を避ける
    • // データをキャッシュに入れる.ただし先にそのサイズを確認する
    • // データをキャッシュに入れる.ただし先にデータのサイズを確認する
  • 歯切れの悪い文章を磨く
    • // これまでにクロールしたURLかどうかによって優先度を変える
    • // これまでにクロールしていないURLの優先度を高くする
  • 関数の動作を正確に記述する
  • 入出力のコーナーケースに実例を使う
  • コードの意図を書く
    • コードの動作をそのまま書いているだけで,何の情報も追加されていないコメントが多い.
  • 「名前付き引数」コメント
    • pythonであれば,引数を名前付きで渡せる.
    • C++とかJavaであれば,インラインコメントで同じようなことができる.
      • 値の前に / / を置く.
      • 値の後ろに置いてはいけない.
  • 情報密度の高い言葉を使う

第2部 ループとロジックの単純化

7章:制御フローを読みやすくする

条件やループなどの制御フローはできるだけ自然にする.コードの読み手が立ち止まったり読み返したりしないように書く.

  • 条件式の引数の並び順
    • if(length > 10) の方が if(10 < length)よりも読みやすい
    • while(bytes_received < bytes_expected) の方が while(bytes_expected > bytes_received)より読みやすい.
    • 指針としては,左側:調査対象の式,右:比較対象の式とする.
  • if/elseブロックの並び順
if(a == b){

}else{

}

これは以下と同じ

if(a != b){

}else{

}
  • 並び順には優劣がある.
    • 条件は否定形よりも肯定形を使う.
    • 単純な条件を先に書く
    • 関心を引く条件や目立つ条件を先に書く
  • 三項演算子
    • 基本的にif/elseを使う.三項演算子はそれによって簡潔になるときだけ使う.
  • do/whileループを避ける.
  • 関数から早く返す
    • 関数で複数のreturnは積極的に使っていい.
  • 悪名高きgoto
    • 唯一ゆるされるのは,関数のexitと一緒に使うもの
  • ネストを浅くする
    • 早めに返してネストを削除する
    • ループ内のネストを削除する

8章:巨大な式を分割する

コードの塊が大きすぎると,周囲に悪影響を及ぼす. コードが大きくなると,人間の理解が及ばなくなる. 巨大な式は飲み込みやすい大きさに分割する

  • 説明変数
    • 式を簡単に分割するには,式を表す変数を使えば良い.
      これを説明変数と呼ぶ.式の意味を説明してくれる.
// 説明変数なし
if line.split(':')[0].strip() == "root":
    ...

// 説明変数あり
username = line.split(':').[0].strip()
if username == "root":
    ...
  • 要約変数
    • 式を説明する必要が無い場合でも,式を変数に代入しておくと便利.
      大きな塊を小さな名前に置き換えて,管理や把握を容易にする変数のことを要約変数とよぶ.
// 要約変数なし
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){

}
  • ド・モルガンの法則を使う
  • 短絡評価の悪用
// aがtrueなら,bは評価されない
if (a || b)
// 読みにくい
assert((!bucket = FindBucket(key))) || !bucket->IsOccupied());

// 読みやすい(短絡評価なし)
bucket = FindBucket(key);
if(bucket != NULL) assert(!bucket->IsOccupied());

「頭がいい」コードに気をつける.あとで他人がコードを読むときにわかりにくくなる 単に短絡評価を避けろというわけではない.

// 短絡評価だけど読みやすい
if(object && object->method())...
  • 複雑なロジックと格闘する
    • より優雅な方法,簡単な方法を見つける.
      そのためには,いつもと反対のことをやってみることだ.
  • 巨大な文を分割する
  • 式を簡潔にするもう一つの創造的な方法

9章:変数と読みやすさ

  • 変数を適当に扱うと,プログラムが読みにくくなる.
    具体的には以下.

    • 変数が多いと,変数を追跡するのが難しくなる
    • 変数のスコープが大きいとスコープを把握する時間が長くなる
    • 変数が頻繁に変更されると現在の値を把握するのが難しくなる
  • 変数を削除する

    • 役に立たない一時変数
now = datetime.datetime.now()
root_message.last_view_time = now
  • 意味がない
    • 複雑な式を分割していない.
    • より明確になっていない.datetime.datetime.now()の時点で十分に明確
      • 一度しか使っていないので,重複コードの削除になっていない.
    • 中間結果を削除する
      • タスクはできるだけ早く完了するほうがいい.
    • 制御フロー変数を削除する
  • 変数のスコープを縮める 変数のことが見えるコード行数をできるだけ減らす
    • なぜそうするのがいいとされているのか.
      • それは一度に考えなければいけない変数を減らせるから.
    • C++のif文のスコープ
  • 変数は一度だけ書き込む 変数を操作する場所が増えると,現在値の判断が難しくなる

第3部 コードの再構成

10章:無関係の下位問題を抽出する

  • エンジニアリングとは,大きな問題を小さな問題に分割して,それぞれの解決策を組み立てることに他ならない.
  • そのためのステップは以下の3つ

    • 関数やコードブロックを見て「このコードの高レベルの目標は何か?」と自問する
    • コードの各行に対して「高レベルの目標に直接の効果があるのか?あるいは無関係の下位問題を解決しているのか?」と自問する.
    • 無関係の下位問題を解決しているコードが相当量あれば,それらを抽出して別の関数にする.
  • 入門的な例:findClosestLocation()

    • 無関係の下位問題は,完全に自己完結しているので,自分がアプリケーションにどのように使われるかを知らない.
  • 純粋なユーティリティコード

    • プログラムの核となる基本的なタスク(たとえば文字列の操作・ハッシュテーブルの使用・ファイルの読み書きなど)はプログラミング言語の組み込みライブラリとして実装されている.
  • その他の汎用コード

    • 思いもよらない恩恵
      • 呼び出し側のコードは簡潔になる.
      • 新たに作った関数は再利用できる.
      • コードが独立しているから,再利用する関数の改善が楽になる
  • 汎用コードをたくさん作る.
  • プロジェクトに特化した機能
  • 既存のインターフェースを簡潔にする
  • 必要に応じてインターフェースを整える
  • やりすぎ

11章:一度にひとつのことを

  • 本章はコードのデフラグについて説明をしている.
  • 「一度に一つのタスクをする」ためのステップ.

    • コードが行っているタスクをすべて列挙する
    • タスクをできだけ異なる関数に分割する.少なくとも異なる領域に分割する.
  • タスクは小さくできる

  • オブジェクトから値を抽出する
    • 「一度に一つのタスク」を適用する
    • その他の手法
  • もっと大きな例
  • さらなる改善

12章:コードに思いを込める

  • 複雑な考えを相手に伝えるときには,細かいことまで話さない.
    コードを読み手につ会えるときもこれと同じことを行う.
    できるだけ簡単な言葉で書くべき.

  • ロジックを明確に説明する

    • まずは説明するところから始めてみる.
    • できれば声に出してみるといい
    • 簡潔なコードを書くのに欠かせないのは,ライブラリが何を提供してくれるかを知ること
  • この手法を大きな問題に適用する
    • 解決策を言葉で説明する

13章:短いコードを書く

最も読みやすいコードは何も書かれていないコードだ

  • その機能の実装に悩まないで,きっと必要ないから
  • 質問と要求の分割
    • 店舗検索システム
    • キャッシュを追加する
  • コードを小さく保つ
    • 汎用的なユーティリティコードを作って,重複コードを差k除する.
    • 未使用のコードや無用の機能を削除する
    • プロジェクトをサブプロジェクトに分割する
    • コードの重量を意識する
  • 身近なライブラリに親しむ
    • ライブラリの再利用は何故いいことなのか

第4部 選抜テーマ

14章:テストと読みやすさ

  • スッキリと効果的なテストを書くための技法について学ぶ.
  • テストを読みやすくて保守しやすいものにする
    • テストを読みやすくするのは,テスト以外のコードを読みやすくするのと同じくらい大切

他のプログラマが安心してテストの追加や変更ができるように,テストコードを読みやすくする

  • テストコードが大きくて恐ろしいものの場合,以下のことが起きる

    • 本物のコードを修正するのを恐れる
    • 新しいコードを書いたときにテストを追加しなくなる
  • このテストのどこがだめなの?

  • テストを読みやすくする
    • 最小のテストを作る
      • テストの本質というのは,「こういう状況と入力からこういうふるまいと出力を期待する」のレベルまで要約できる
    • 独自のミニ言語を実装する
  • エラーメッセージを読みやすくする
    • もっといいassert()を使う
    • 手作りのエラーメッセージ
  • テストの適切な入力値を選択する

コードを完全にテストするもっとも単純な入力値の組み合わせを選択しれなければいけない

    • 入力値を単純化する テストには最もきれいで単純な値を選ぶ
    • 一つの機能に複数のテスト
      • コードを検証する完璧な入力値を一つつくるのではなく,小さなテストを複数作るほうがかんたんで効果的で,読みやすい.
  • テストの機能に名前をつける

  • このテストのどこが駄目だったのか?
  • テストに優しい開発
  • やりすぎ

15章:「分/時間カウンタ」を設計実装する

  • 問題点
  • クラスのインターフェースを定義する

    • 名前を改善する
      • getは軽量アクセサを意味する
    • コメントを改善する
  • 試案1:素朴な解決策

    • パフォーマンスの問題
  • 試案2:ベルトコンベヤー設計
    • これで終わり?
  • 試案3:時間バケツの設計
    • 時間バケツの実装