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

数年ぶりにリーダブルコードを読んだので自分用メモ。メモの内容は目次に対応。

表面上の改善

理解しやすいコード

  • わかりやすいコードは理解にかかる時間を指標にするのが良い

名前に情報を詰め込む

  • 多義的な語を避ける
  • 何を指しているかわからない語を避ける
    • size => height, bytes
  • tmp, retvalなどは最小限にする
  • ループイテレータも添字を工夫することができる
    • n重ループを書くときに、添字の対象を書く
      • foo_i, var_iみたいな形式
  • 一つの機能が複数のことを意味してないか考える
    • 実はそれぞれ独立した別の機能では?
  • 扱っているデータの形式・特性を変数の先頭・末尾に持ってくる
    • 16進数の場合: hex_foo
    • ミリ秒の場合: foo_msec
    • 平文: plaintext_foo
    • UTF: foo_utf
  • ハンガリアン記法(接頭辞):
    • p: pointer, S: string, c: count, ch: char, m: map
  • スコープが大きくないなら説明的な名前でなくても問題ない
  • 無くても成り立つ場合は極力シンプルに記述する
    • ConvertToString => ToString
  • フォーマットに工夫をする
    • クラス名: キャメルケース, 変数: ローワーケース, マクロ: 大文字のみ
    • アンダースコアでローカル変数であることを表現する
    • その他(JS)
      • コンストラクタ関数は大文字でスタート
      • $で始まる変数はjQuery

誤解されない名前

  • filter => filter_outなのか、filter_inなのかわからない
  • 限界値をふくめる場合 => max, min
  • 範囲を指定する場合 => first, last
  • 範囲の包含・排他の場合 => begin, end
  • 名前を否定系にしない
  • 実行する処理の計算量を意識させる書き方をする
  • 誤解される余地を検討しつつ名前を決める

美しさ

  • 一貫性のあるレイアウトを使う
    • 改行の箇所がズレて目立ってしまうものを避ける
    • カッコのスタイルを合わせる
  • 似ているコードは似ているように見せる
  • 関連するコードはブロックにする
  • 隠蔽用のメソッドを用意することでシンプルにすることができる
  • 行の縦方向を合わせることで、何をAssertしているかわかりやすくすることができる
  • 意味のある順番に並べる
    • アルファベット順
    • 重要度順
    • 実装されている他のものに合わせる
  • メソッド定義を種類によって分けて並べてあげたりする
  • コードを段落に分解する
    • まとまった処理で区切る

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

  • コード単体から読み取れることを書かない
    • ただし読み取るのに時間がかかるケースは有効
  • コード自体を変えるほうが良いケースがある
    • 変数名・メソッド名が分かりにくいなど
  • 試した結果や工夫・今改善すべき事項などはコメントとして残す
    • TODO, FIXME, HACK, XXX
  • 期待される値(max_threads)などを記録しておく
  • ざっくりとした全体像(クラス設計など)の説明を書く

コメントは正確で完結で

  • 短く書く
  • 代名詞を避ける
  • 具体的に書く
    • ~に応じて~を変える=>~に応じて~を増減させるなど
  • 入出力のコーナーケースを書く
  • コードのロジックの意図を書く
  • 引数付きコメントを上手に使う

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

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

  • 式を書くときは自然言語をなぞると良い
    • もし君が18歳以上ならば: ○
    • 18が君の年齢より大きければ:×
  • 条件は否定形よりも肯定形にする
  • 単純な条件もしくは目立つ条件を先に書く
  • 三行演算子は理解しやすい範囲に留める
  • do-while => while
    • 終了条件が後置的だと読みにくい
  • returnはコード中で返しても構わない
    • 返り値の整形はエラー処理の一部として書ける
    • returnを使うことでelse文が削減できることがある
  • gotoは処理の流れが読みにくくなるので避ける
  • continueを上手に使うとloopのelse処理が減らせることがある
  • 機能を追加した時は追加後の全体像を一度見る
  • スレッド、割り込みハンドラ、例外処理、関数ポインタ、無名関数
    • 処理全体の流れが見えなくなることがある

巨大な式を分割する

  • 説明変数・要約変数を使う
    • 文字列処理などをそのまま使わず、意味のわかる変数に代入する
    • 式の結果などを別に処理して代入しておくことで、分かりやすくなる
  • ド・モルガンの法則
    •  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
    •  notを分配してorとandを反転させる
      •  notをくくりだして、orとandを反転させる
  • 短絡評価を上手に使う
    • &&の前半の評価の結果で後半が読まれないように書くことができる
    • 式に||を使うと先頭から評価されていき、真と評価された値が代入される
  • マクロを上手に使うと代入をスムーズにすることができる
    • 類似している変数の同時代入など

変数と読みやすさ

  • 変数は必要がある程度に説明的でない限り削減する
  • 中間結果の保持も最小限に留める
  • 条件分岐を管理するための変数は可能な限り削除する
  • グローバル変数・メンバ変数は最小限に留める
    • 可能な限りローカル変数を使う
    • クラスそのものを小さく保つ
  • if文の中だけでしか使わないのであれば、代入式をそのまま評価することもできる
  • Closure, varを使う(JS)
  • ブロックスコープを上手に使う
  • 変数定義は変数が必要なタイミングで実施すれば良い
  • 変数の再代入は最小限に留めると挙動が読みやすくなる

コードの再構成

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

  • 直接無関係なロジックは外に切り出す
    • 幾何計算と最短距離の選択は独立したロジック
    • ファイルの読み書きを隠蔽する
    • JSONや文字列を整形する機能
  • イケてない標準ライブラリはwrapper関数を書くと良い
  • やりすぎると追わなきゃいけない全体の流れが多くなる

一度に一つのことを

  • パースと実際の処理は別々に記述することができる
  • 値がなかったら置き換える=>デフォルト値を設定して見つかったら上書きする
  • 複数のロジックがある場合分割することを考える

コードに思いを込める

  • 人間の考え方に沿った書き方をすると理解がしやすくなる
    • 思考の流れに近い記述がなされていると、プログラミング言語的には読みにくいものでも、読みやすくなることがある
  • 手順を書き出して本質的なロジックを見つけて実装し直していく

短いコードを書く

  • 汎用ロジックを切り出す
  • 未使用コードを削除する
  • 標準ライブラリの関数・モジュール・型にちゃんと目を通しておく

選抜テーマ

テストと読みやすさ

  • ちゃんとしたテストコードは、コード自体の修正やテストの拡張にもメリットがある
  • AssertするものをWrapすることで分かりやすくできることがある
  • 言語のAssertを上手に使うとよい
  • テストの入力値は、必要なことがテストできている限り、シンプルにするのがよい
  • テストのためにコードを書くと、ロジック分割などの点でメリットが大きい
  • やりすぎはよくない
    • テストのカバー範囲を不必要に広げる
    • テスト対象のロジックが読みにくくなってしまう
    • テスト自体が開発の邪魔になってしまう

分・時間カウンタを設計・実装する

  • インターフェースの設計
    • 名前の定義から機能が読み取れるか
    • getは裏でロジックが動くと想定されないことが多い
    • クラス名・メソッド名に単位などが含まれていると読みやすくなる
  • 冗長なコメントはいらない
  • 同じようなロジックを持つメソッドは共通関数をつくって処理する
  • ベルトコンベアー設計
    • 内部的なベルトコンベアを作るためのインターフェースを作る
  • 時間バケツ設計
    • ロジックが複雑なので割愛
    • 別のクラスを含めて設計することが必要になることがある