「リーダブルコード」まとめやで


書籍「リーダブルコード」には何が書かれているの?

「美しいコードを見ると感動する。優れたコードは見た瞬間に何をしているかが伝わってくる。そういうコードは使うのが楽しいし、自分のコードもそうあるべきだと思わせてくれる。本書の目的は、君のコードを良くすることだ」(本書「はじめに」より)。
コードは理解しやすくなければならない。本書はこの原則を日々のコーディングの様々な場面に当てはめる方法を紹介します。名前の付け方、コメントの書き方など表面上の改善について。コードを動かすための制御フロー、論理式、変数などループとロジックについて。
またコードを再構成するための方法。さらにテストの書き方などについて、楽しいイラストと共に説明しています。

1章 理解しやすいコード

原則

  • コードは理解しやすくなければならない
  • コードは他の人が最短期間で理解できるように書かなければならない
  • コードは短くしたほうがいい。だけど「理解するまでにかかる時間」を短くするほうが大切

▶︎常に一歩下がって「このコードは理解しやすいだろうか」と自問自答しよう!

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

「名前は短いコメントだと思えばいい。短くてもいい名前をつければ、それだけ多くの情報を伝えることができる。」
情報を詰め込んだ名前の付け方は以下の6点を意識しよう

①明確な単語を選ぶ

「get」-> この単語からは何も伝わってこない。もっと明確な単語で表現しよう
(インターネットからページを取ってくるなら「FetchPage」など)

「stop」-> 動作に合わせてもっと明確にしよう
(取り消しができない重い操作なら「kill」、 あとから解除できるものなら「pause」の方が適切かもしれない)
▶︎類語辞典などを使って名前を考えてもいいかも
stop / break / holdOn / intercept / halt / block / quit / stay

②汎用的な名前は避ける

  • tml / retval / foo などの空虚な名前を使ってはならない。エンティティの値や目的を表した名前を選択しよう (エンティティ = 「実体」E-R図で出てくる箱のことやで) retvalという名前には「戻り値です」以外の情報はない。戻り値であることは構文から明白なので、変数の目的や値を表すものにしよう。
  • ループイテレータはそれ自体が「この変数はループイテレータです」と意味を持つのでそのような例外はある

▶︎汎用的な名前をつけるときは其れ相応の理由があるか考えよう。怠惰で名前をつけないこと。

③抽象的な名前よりも具体的な名前を使う

任意のTCP/IPポートをサーバリッスンできるかを確認するメソッドに「ServerCanStart()」と名前をつけるのは少し抽象的
→ 「CanListenOnPort()」の名前の方が具体的にメソッドの動作を表現できている
▶︎名前を考えることを諦めて、抽象化してないか振り返ろう

④接尾語や接頭語を使って情報を追加する

値の単位やその他の重要な属性を追加しよう

1 2
改善前 改善後
delay delay_secs
size size_mb
limit max_limit
angle degrees_cw
password plaintext_password
comment unescaped_comment
html html_utf-8

⑤名前の長さを決める

  • スコープが小さければ短い名前でも良い(スコープ内の情報全てが見えるので)
  • 長い名前を入力するのは問題ではない(補完機能を使うと時間はかからない)
  • 不要な単語は含めなくて良い(ConvertToString を ToStringにしても必要な情報は損なわれない)

⑥名前のフォーマットで情報を伝える

HTMLタグのidの区切り文字にはアンダースコアを、classの区切り文字にはハイフンを使う規約などがある。

<div id = "middle_column" class = "main-content">

その他にもJSではコンストラクタ関数は大文字から始め、通常の関数は小文字から始めるなど名前のフォーマットを揃えてあげることで理解しやすくなる。
▶︎規約を使うかどうかはチームで決めて、一貫性を持たせよう

3章 誤解されない名前

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

  • 「filter」選択するか除外するか分からずに誤解を招くから避ける
  • 「clip」は削除するか切り詰めるか分からないから避ける
  • 限界値を示すときは「min」、「max」を使う
  • 範囲を指定するときは「start」、「stop」ではなく、「first」、「last」を使う
  • 包含/排他的範囲には「begin」、「end」を使う

4章 美しさ

美しさの原則

  • 読み手が慣れているパターンと一貫性のあるレイアウトを使う
  • 似ているコードは似ているように見せる
  • 関連するコードをまとめてブロックにする

以下のことに注意を払おう。
コードを美しくすることで「視覚的な手すり」になりエラーも減らすことができる

  • 一貫性のある簡潔な改行位置になっているか
  • メソッドを使ってうまく整列できているか
  • 縦のラインは揃っているか
  • 宣言はブロックに纏まっているか
  • 順番に意味があり、一貫性が保てているか
  • 段落ごとに分割できているか(段落ごとに要約コメントがあると良い)

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

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

コメントするべきでは「ない」こと

コメントを読むとその分だけコードを読む時間がなくなる。
コメントは画面を占領してしまうので、コメントにはそれだけの価値を持たせるべき。
コードからすぐわかることをコメントに書かない
ひどい名前はコメントをつけずに名前を変える。「CleanReply」 -> 「EnforceLimitsFromRequest」

コメントするべきこと

優れたコメントは「考えを記録する」ためのものである。

「監督のコメンタリー」を入れるつもりでコメントを書く。

//このデータだとハッシュテーブルよりもバイナリーツリーの方が40%速かった。
//左右の比較よりもハッシュの計算コストの方が高いようだ。
//このクラスは汚くなってきている。
//サブクラス'ResourceNode'を使って整理した方がいいかもしれない。

コメントから情報を得ることができるので、下手に最適化して無駄に時間を使うこともなくなる。
コードが汚いことを認めるコメントがあれば、修正箇所を確認しやすくなる。
コードの欠陥にもコメントをつけて、改善の必要度も記入しておくと良い。

定数にコメントをつける

定数を定義する場合は、なぜその値を持っているのかという「背景」を持っている場合が多い。

const int MAX_RSS_SUBSCRIPTIONS = 1000;

上記のコードをみてもなぜ値の上限が1000であるのかが理解できないかもしれない。そのため「//合理的な限界値。人間はこんなに読めない」などとコメントしておく方が良い。

読み手の立場になって考える

  • 質問されそうなことを想像する。プロジェクトを始めてみる人でも理解できるように。
  • ハマりそうな罠を告知する。関数やクラスを文章化するときには、「このコードを見てビックリすることは何だろう?どんなふうに間違えて使う可能性があるだろう?」と自分に問いかける。
//実行時間は(タグの数*タグの深さの平均)なのでネストの深さに気をつける
def FixBrokenHtml(html):....
  • 全体像のコメントをつける 新しくチームに参加した人でも分かるように、ソースコードに目を通しただけでは得られない高レベルのコメントを記載する
//このファイルには、ファイルシステムに関する 便利なインターフェースを提供するヘルパー関数が含まれています。
//ファイルのパーミッションを扱います。

▶︎コードを理解するのに役立つものならとにかく書こう!

<コメントを書く作業>
❶頭の中にあるコメントをとにかく書き出す
❷コメントを読んで開演が必要なものを見つける
❸改善する 

6章 コメントは正確で簡潔に

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

  • コメントは簡潔にしておく
  • 曖昧な代名詞は避ける
//データをキャッシュに入れる。ただし、先にそのサイズをチェックする。

上記のコメントだと「その」という代名詞が「データ」か「キャッシュ」のどちらを指しているかが分からない。
- 歯切れの悪い文章は使わない。
- 関数の動作を正確に記述する。
- 入出力のコーナーケースに実例を使う。実際に動かすと何が返ってくるのか。
- コードの意図を書く。コードの内容ではなく作者のプログラムした意図を書く。
- 情報密度の高い言葉を使う
- 専門用語や共通理解されている言葉で端的に書く。

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

条件式の引数の並び順

比較を用いた式を使い菜には以下のようにする。

左側 右側
「調査対象」の式。変化する。 「比較対象」の式。あまり変化しない。

if ($user_age >= 18)
日本語でも「もし18年が君の年齢以下ならば」ではなく「もし君が18歳以上ならば」の方がスマートだろう。

if/elseブロックの並び順

❶条件は非定型よりも肯定系を使う。
❷単純な条件を先に書く。
❸関心を引く条件や目立つ条件を先に書く。

参考演算子はそれによって完結になる場合だけ使う

if (hour >= 12){
  time_str += "pm";
}else{
  time_str += "am";
}

上記のif/elseで表した表現よりも下記の参考演算子を用いた式の方が簡潔で読みやすい。

time_str += (hour >= 12) ? "pm" : "am";

do/whileループは避ける

コードブロックを再実行する条件が下にあると不自然で分かりにくい。
コードブロックを読む前に繰り返しの条件が分かるwhileループを使おう。

ネストを浅くする

ネストが深くなると条件を思い出せなくなってしまう。

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) {
  // ユーザーはこの文章を編集できない
}

上記でも理解することは出来るが、変数が5つも入っているので少し理解に時間がかかる。
「ユーザーは文書を所持しているか」を表す要約変数を追加すると下記のようになり理解しやすい。

final boolean user_owns_document = (request.user.id != document.owner_id)

if(!user_owns_document) {
 // ユーザーはこの文章を編集できない 
}

if(user_owns_document) {
 // ユーザーはこの文章を編集できる
}

ド・モルガンの法則を使う

if(!(file_exists && !is_protected)) Error("すみません。ファイルを読み込めません。")

notを分割してand/orを反転する = notをくくり出す

if(!file_exists || is_protected)) Error("すみません。ファイルを読み込めません。")

短絡評価に気を付ける

assert((!(bucket = FindBucket(key))) || !bucket->IsOccupied());

上記のコードよりも下記のコードの方が読みやすい。短略評価が必ずしも読みにくいわけではないので「これは理解しやすいか」と再確認しよう。
「頭がいい」コードに気を付ける。あとで他の人がコードを読むときに分かりにくくなるので。

bucket = FindBucket(key);
if(bucket != NULL) assert(!bucket->IsOccpuied());

DRY原則

var update_highlight = function (message_num) {
  if ($("#vote_value" + message_num).html() === "Up") {
    $("#thumbs_up" + message_num).addClass("highlighted");
    $("#thumbs_down" + message_num).removeClass("highlighted");
  } else if ($("#vote_value" + message_num).html() === "down") {
    $("#thumbs_up" + message_num).removeClass("highlighted");
    $("#thumbs_down" + message_num).addClass("highlighted");
  } else {
    $("#thumbs_up" + message_num).removeClass("highlighted");
    $("#thumbs_down" + message_num).removeClass("highlighted");
  }
}

上記のコードは一箇所に巨大な文が集まっていてかつ、同じ式がいくつかあって冗長になっているので、要約変数などを用いて下記のようにした方が良い。

var update_highlight = function (message_num) {
  var thumbs_up = $("#thumbs_up" + message_num);
  var thumbs_down = $("#humbs_down" + message_num);
  var vote_value = $("#vote_value" + message_num).html();
  var hi = "highlighted";

  if (vote_value === "Up") {
    thumbs_up.addClass(hi);
    thumbs_down.removeClass(hi);
  } else if (vote_value === "Down") {
    thumbs_up.removeClass(hi);
    thumbs_down.addClass(hi);
  } else {
    thumbs_up.removeClass(hi);
    thumbs_down.removeClass(hi);
  }
}

var hi = "highlighted" としているのは以下のような効果がある。
❶タイプミスを減らす
❷横幅が縮まるのでコードが読みやすくなる
❸クラス名を変更することになれば、一箇所を変更すればよい

9章 変数と読みやすさ

変数の問題点

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

コードが読みやすくならない変数を削除する

now = datetime.datetime.now()
root_message.last_view_time = now

上記のコードの「now」変数は必要だろうか?
・複雑な指揮を分割していない / より明確になっていない、datetime.datetime.now()のままでも十分に明確である / 一度しか使っていないので、重複コードの削除になっていない。
以上の観点から役に立たない一時変数といえるだろう。下記の方が分かりやすい。

root_message.last_view_time = datetime.datetime.now()

変数のスコープを縮める

グローバル変数を避けるルールを守る。なぜならグローバル変数はどこでどのように使われているかを追跡することが難しい。また名前空間を汚染する(ローカル変数と衝突する可能性がある)ことから、ローカル変数を使っているつもりでグローバル変数を修正したり、逆もまたしかりだ。グローバル変数に限らず、全ての変数のスコープを縮めるのはいい考えである。→変数のことが見えるコード行数をなるべく減らす。
一度に考えなければならない変数を減らそう

class LargeClass {
   string str_;
   void Method1() {
     str_ = ...;
     Method2();
   }

   void Method2() {
    // str_を使ってる
   }
 //strを使っていないメソッドが沢山ある
}

上記のメンバ変数はクラスの中でいわば「ミニグローバル」になっているともいえる。全てのメソッドが「str_」を使っていないのであれば、下記のようにメンバ変数からローカル変数に格下げした方が良い。

class LargeClass {
   void Method1() {
     string str_ = ...;
     Method2();
   }

  void Method2(string str) {
    // str_を使ってる
  }

 //その他のメソッドはstrが見えない
}

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

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

純粋なユーティリティコード

文字列の操作やハッシュテーブルの使用・ファイルの読み込みなどのプログラムの基本となるタスク「基本的なユーティリティ」がなけれ、自分で作ることになると思うが、その部分は新しい関数として置き換え、ユーティリティコード(汎用コード)のように使おう。

トップダウンプログラミングとは先に高レベルのモジュールや関数を設計してから、それらをサポートする低レベルの関数を実装していく方式
ボトムアッププログラミングとは先に全ての下位問題を解決してから、それらを利用する高レベルのコンポーネントを実装していく方式
どちらを採用するかはプロジェクトによって異なるが、大切なのはプログラミングとは両方の側面をもっており、下位問題を分離して処理できるかが鍵になる。

11章 一度に1つのことを

var vote_changed = function (old_vote, new_vote) {
  var score = get_score();

  if (new_vote !== old_vote) {
    if (new_vote === 'Up') {
      score += (old_vote === 'Down' ? 2 : 1);
    } else if (new_vote === 'Down') {
      score -= (old_vote === 'Up' ? 2 : 1);
    } else if (new_vote === '') {
      score += (old_vote === 'Up' ? -1 : 1)
    }
  }
  get_score(score);
};

上記のコードは一度に2つのタスクを実行している。
❶old_voteとnew_voteを数値に「パース」する。
❷scoreを更新する
この2つのタスクを下記のように別々に解決すれば読みやすいコードになる。

var vote_value = function (vote) {
  if (vote === 'Up') {
    return +1;
  }
  if (vote === 'Down') {
    return -1;
  }
  return 0;
}

var vote_changed = function (old_vote, new_vote) {
  var score = get_score();
  score -= vote_value(old_vote); //古い値を削除する
  score += vote_value(new_vote); //新しい値を追加する

  set_score(score);
}

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

問題や設計をうまく言葉にできないのであれば、何かを見落としているか、詳細が明確になっていないということである。プログラム(あるいは自分の考え)を言葉にすることで明確な形になる。
デバッグに悩んだらテディベアなどに向かってプログラムの内容を説明してみよう。

var show_next_tip = function () {
  var num_tips = $('.tip').size();
  var show_tip = $('.tip:visible');

  var show_tip_num = Number(show_tip.attr('id').slice(4))
  if (show_tip_num === num_tips) {
    $('#tip-1').show();
  } else {
    $('#tip-' + (show_tip_num + 1)).show();
  }
  show_tip.hide();
}

上記でも特段問題ないコードではあるが、もう少しよくできる。
まずは説明するところから始める。
「今見えているヒントを見つけて隠す
次のヒントを見つけて表示する
ヒントがなくなったら、最初のヒントに戻る」

var show_next_tip = function () {
  var cur_tip = $('.tip:visible').hide();
  var next_tip = cur_tip.next('.tip');
  if (next_tip.size() === 0) {
    next_tip = $('.tip:first');
  }
  next_tip.show();
}

13章 短いコードを書く

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

  • 汎用的な「ユーティリティ」コードを作って、重複コードを削除する
  • 未使用のコードや無用の機能を削除する
  • プロジェクトやサブプロジェクトに分割する
  • コードの「重量」を意識する。軽量で機敏にしておく

 読了後感想

コードの書いた量が少ない自分には難しいところが多く、理解することが出来ない箇所がいくつもあった。
リーダブルコードから得られた学びを意識して、今後コードを書いていく。
そしてある程度の力がついてきた半年後にもう一度目を通そうと思う。そして新しい学びをこの記事に加筆していく。