リーダブルコード
2024/05/06
だいぶ前にリーダブルコードをかみくだいた記事を社内のみで公開したので、ここでも記事にしたいと思います。
Amazonリンクはこちらです。読みたい方はご購入ください。

2012/06/23にオライリー・ジャパン社から発行された本です。
技術書にしては比較的古い本ですが、内容は結構普遍的で現代でも通用すると思います。

ちなみに正式なタイトルは「リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック (Theory in practice)」です。サブタイトルまでいれるとめちゃ長いので記事名では省きました。

第Ⅰ部 表面上の改善
1.理解しやすいコード
コードは他の人が最短時間で理解できるように書かなければいけない。
前提としてコードを書く上でいちばん大切な原則である。

「理解する」というのは、変更を加えたりバグを見つけたりできるという意味。
自分1人のPJだとしても自分のコードを覚えていない半年後の自分がいるかもしれない。
そのPJに途中から誰かが参画する可能性もある。
使い捨てだと思っていたコードが他PJで再利用される可能性もある。

1行に処理を詰め込むのは、見た目的に短いコード(簡潔なコード)になるけど、2行に分けたり
コメントをつけたり長いコードにしたほうが、理解しやすいコードになる可能性もある。

コードは短くしたほうがいいけど、「理解するまでにかかる時間」を短くするほうが大切である。
短いからって良いコードとは限らない。

2.名前に情報を詰め込む
名前を見ただけで情報を読み取れるようにする。
変数名でも関数名でもクラス名でも、命名規則は大事。
名前は短いコメントだと思うと良い。
短くても良い名前をつければ、それだけ多くの情報を伝えられる。

明確でない抽象的な単語は避けるべき。
getやsizeは単語として曖昧であり、あまり明確とは言えない。

tmpなど汎用的な命名は避けるべき。
イテレータや制御変数に使われるi,j,kなども簡易説明的な命名をつけると良い。
例えばclub_i,member_iなど。なんならもっと短くci,miとかでも良い。
複数のイテレータや制御変数が生存する箇所がある場合は意識したほうが良いかも。
汎用的な命名をする場合はそれ相応の明確な理由を用意しよう。

スコープが小さい変数は多くの情報を詰め込む必要がないので、名前は短く簡潔なものでも良い。
スコープが大きい変数は長くても十分な情報を詰め込んで明確にする必要がある。
頭文字や省略形を使って短く命名することがあるが、PJ固有の用語を省略するのは良くない。
単語を削除しても必要な情報量が変わらない場合は削除してより簡潔にする。

3.誤解されない名前
名前が「他の意味を間違えられることはないだろうか?」と何度も自問自答する。
命名するときには五回されない名称にすることが大事。積極的に「誤解」を探す。

限界値を含めるときはminとmaxを使う。
limitという名称だと以下と未満の区別で誤解を生むことになる可能性がある。
範囲を指定するときにはfirstやlastを使うと良い。
包含/排他的範囲にはbeginとendを使うと良い。

ブール値(true/false)の変数や関数を命名するときには、
falseとtrueの意味を明確にしなければいけない。
頭にis,has,canなどをつけることでブール型とわかりやすくするのも良い。
また、disable_sslなどの否定系の名称じゃなくてuse_sslなどの肯定形の命名の方が良い。

4.美しさ
見た目が美しいコードのほうが使いやすい。
プログラミングの時間はほとんど読む時間なので、流し読みができれば
理解しやすい(誰にとっても使いやすい)コードだと言える。
美しいコードを書くには一貫性のあるスタイルで書くことが大切。

複数のコードブロックで同じようなことをしていたらシルエットも同じようにする。
NG例(長いし面倒なので本の内容ではなく書き換えてる)
public class CocLabo{
  public static final CompanyMember hogehoge = new CompanyMember(
    hoge1, hoge2, hoge3);

  public static final CompanyMember piyopiyo_piyopiyo =
    new CompanyMember(hoge1, hoge2, hoge3);

  public static final CompanyMember sato = new CompanyMember(
    hoge1, hoge2, hoge3);
}
1行あたりの文字数が定義されているコーディング規約(があると仮定して)の関係で、
`piyopiyo_piyopiyo`だけ余計な改行が入っている。その結果、見た目がバラバラになっている。

OK例
public class CocLabo{
  public static final CompanyMember hogehoge =
    new CompanyMember(hoge1, hoge2, hoge3);

  public static final CompanyMember piyopiyo_piyopiyo =
    new CompanyMember(hoge1, hoge2, hoge3);

  public static final CompanyMember sato =
    new CompanyMember(hoge1, hoge2, hoge3);
}
コードの見た目(シルエット)を一貫性のあるものにするために、適切な改行をそれぞれ入れている。

コードの列を揃えれば、概要がわかりやすくなる。
detail   = request.POST.get('detail')
location = request.POST.get('location')
phone = equest.POST.get('phone')
email = request.POST.get('email')
url = request.POST.get('url')
コードを整列していると、3つ目の定義でtypoしていることが見た目でパッと分かる。
ただし、以下の理由からこれが好きではないというプログラマがいることにも注意。
  • 整列や維持に手間がかかる
  • 1行だけ変更したいのに他行に空白を入れないといけなくて差分が増える

意味のある順番を選択して、常にその順番を守る。
ある場所ではA,B,Cという並びで記述しているのに、別の場所ではA,C,Bのような並びにしてはいけない。

空行を使って、大きなブロックを段落に分ける。
NG例
# ユーザのメール帳をインポートして、システムのユーザと照合する。
# そして、まだ友達になっていないユーザの一覧を表示する。
def suggest_new_friends(user, email_password):
  friends = user.friends()
  friend_emails = set(f.email for f in friends)
  contacts = import_contacts(user.email, email_password)
  contact_emails = set(c.email for c in contacts)
  non_friend_emails = contact_emails - friend_emails
  suggested_friends = User.objects.select(email__in=non_friend_emails)
  display['user'] = user
  display['friends'] = friends
  display['suggested_friends'] = suggested_friends
  return render("suggested_friends.html", display)
段落化されていなくて非常に読みにくい。

OK例
def suggest_new_friends(user, email_password): 
  # ユーザの友達のメールアドレスを取得する。
  friends = user.friends()
  friend_emails = set(f.email for f in friends)

  # ユーザのメールアカウントからすべてのメールアドレスをインポートする。
  contacts = import_contacts(user.email, email_password)
  contact_emails = set(c.email for c in contacts)

  # まだ友達になっていないユーザを探す。
  non_friend_emails = contact_emails - friend_emails
  suggested_friends = User.objects.select(email__in=non_friend_emails)

  # それをページに表示する。
  display['user'] = user
  display['friends'] = friends
  display['suggested_friends'] = suggested_friends
  return render("suggested_friends.html", display)
段落化に加えて、段落ごとに要約コメントを追加することで読みやすくなった。

上記以外にも個人的な好みでスタイルが分かれることもある。
例(クラス定義の開き括弧の位置)
class logger{
  ...
};

class logger
{
  ...
};
どちらもコードの読みやすさに大きな差はないが、
この2つのスタイルを混合させると読みにくいものになってしまう。

5.コメントすべきことを知る
コメントの目的は、書き手の意図を読み手に知らせることである。
コメントは「コードの動作の説明」に使うものではない。

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

コメントはひどい名前の埋め合わせに使うものではない。
コメントで補足するよりは明確な名前に変えるべきである。

コメントは自分の考えを記録する。
なぜこのコードを書いたのかという大切な考えを記録しなければならない。

コードは常にアップデートし続けているので、その過程で欠陥を生む可能性はある。
欠陥を文書化することに躊躇してはいけない。
改善が必要なときはアノテーションコメントなどを利用して記録しておく。

読み手の立場になって考える。
読み手が疑問に思いそう、読み手が質問しそう(書き手に質問しそう)、ハマりそうな罠になりそうな内容には積極的にコメントをする。

ファイルやクラスには全体像のコメントを書く。
新規参画者に「このファイルは○○のためので〜、このクラスは○○を処理してて〜」などの会話が行われることが多いが、それは高レベルのコメントに書くべき情報である。
(「高レベル」は、処理内容が難解、文章力が高いという意味ではなく、階層が上にあるという意味と思われる)
大量の正式文書をかけということではなく、短い適切な文章でも良い。何もないよりはマシ。

ライターズブロックになったときは、自分の考えをとりあえず書いてみる。
(ライターズブロック:あれ?何を書けばいいんだ?みたいなスランプ状態)
「ヤバい、これは重複したら面倒になる」って思ったら左記のようにそのまま書いてみる。
そのままでも何もないよりはマシだけど、書き終わったあとに曖昧な表現を書き換えてみる。
例えば上記のコメントを以下のように置き換える。
  • 「ヤバい」→「注意」
  • 「これ」→「このコード」
  • 「面倒」→「実装が難しい」
「注意:このコードは重複を処理できません(実装が難しいため)」に改善できる。

6.コメントは正確で簡潔に
コメントは領域に対する情報の比率が高くなければいけない。
コメントを書くのであれば、正確に(できるだけ明確で詳細に)書くべき。
また、コメントは画面の領域と取られるし、読むのに時間がかかるので、簡潔にまとめる。

コメントを簡潔に書く。
NG例
⁄⁄ int は CategoryType。
⁄⁄ pair の最初の float は 'score'。
⁄⁄ 2つめは 'weight'。
typedef hash_map<int, pair<float, float> > ScoreMap;

OK例
⁄⁄ CategoryType -> (score, weight)
typedef hash_map<int, pair<float, float> > ScoreMap;
3行も使って説明しなくとも、1行で説明することができる。

曖昧な代名詞の使用を避ける。(「これ」とか「それ」とか)
読み手は代名詞を正確に変換しなければならない。
場合によっては代名詞が何を指しているのかわからなくなることもある。
コードを読み勧めていれば代名詞が指しているものがわかるが、それだとコメントの意味がない。

歯切れの悪い曖昧な表現も避ける。
「これまでにクロールしたURLかどうかによって優先度を変える」は曖昧。
「これまでにクロールしていないURLの優先度を高くする」は短いし直接的で明確。

関数の動作を正確に記述する。
NG例
⁄⁄ このファイルに含まれる行数を返す
int CountLines(string filename){ ... }
行といってもいろいろ捉えることができる。
以下の場合どうなるだろう。
  • “”(空のファイル)は0行なのか1行なのか
  • “hello”は0行なのか1行なのか
  • “hello\n”は1行なのか2行なのか
  • “hello\n world”は1行なのか2行なのか
  • “hello\r\n crue\n world\r”は2行なのか3行なのか

OK例
⁄⁄ このファイルに含まれる改行文字(\n)を数える
int CountLines(string filename){ ... }
NG例と比較しても、それほど長くなってはいないが、伝わる情報は格段に増えている。
改行文字(ラインフィード)がない場合は0を返すことがわかる。
また、キャリッジリターンが無視されていることもわかる。

入出力のコーナーケースに実例を使う。
(コーナーケース:ミスの発生しそうなケース、めったに発生しないケース)
仕様がややこしかったりする関数のコメントには実例を書いておけば何よりもわかりやすくなる。

⁄⁄ ...
⁄⁄ 実例 strip("abba/a/ba","ab")は"/a/"を返す
String strip(String src, String chars){ ... }
簡単な実例では役に立たないので注意が必要。

よくわからない引数には名前つき引数を使う。
名前を付けられない言語の場合はインラインコメントを使えば同じことができる
NG例
connect(10,false)
ただ数値とブール値を渡しているけど、何のことだかよくわからない。

OK例
connect(timeout_ms = 10, use_encryption = false)

connect(/* timeout_ms = */ 10, /* use_encryption = */ false);
名前付き引数もしくはインラインコメントのおかげで渡してる値が明確になる。

多くの意味が詰め込まれた言葉や表現を使って、コメントを簡潔に保つ。
冗長なコメントの場合は、情報密度が高い単語や表現に変えられないか考えてみる。

第Ⅱ部 ループとロジックの単純化
7.制御フローを読みやすくする
条件やループなどの制御フローはできるだけ自然にする。
コードの読み手が立ち止まったり読み返したりしないようにする。

条件式の引数の並び順は気にする。
以下の式ではほとんど場合が前者のようが読みやすい。
if( age >= 18 )
if( 18 <= age )
式の順番による読みやすさに指針としては以下が挙げられる。
  • 左辺:「調査対象」の式。変化する。
  • 右辺:「比較対象」の式。変化しない。
例えば言葉にしても、「君の年齢が18歳以上ならば」は自然だが、「18歳が君の年齢以下ならば」は不自然だ。

if/else文の条件の順番には優劣がある。条件は否定形よりも肯定形をなるべく利用する。
単純な条件を先に書く。目立つ(関心を引く)条件を先に書く。
ただし、上記は衝突することもある。その時は順番は自分で考えて判断する。
優劣は衝突しても、優先度は明確に決まることが多い。

三項演算子やdo/whileループの使用は避ける。
三項演算子は使うとコードは短く簡潔になるが、多くの場合可読性が下がる。
do/whileループはループ条件がコードの下にあり、読み勧めていく上で不自然である。

ネストを浅くする。ネストが深いコードは読み手の集中力が必要となり、理解しにくい。
ネストを削除するには「失敗ケース」で早めに返す(returnする)と良い。

8.巨大な式を分割する
巨大な式は飲み込みやすい大きさに分割する。
人間は1度に3〜4のものしか考えることができないらしい。
つまり、コードの式(塊)が大きくなれば、それだけ理解が難しくなる。

式を簡単に分割するには、式を表す変数を使えば良い。この変数を「説明変数」という。
式を説明する必要がない場合でも式を変数に代入しておくと便利。
大きなコードの塊を小さな名前に置き換えて、管理や把握を簡単にすることができる。
同じコードの再利用を避けてひとつにする(DRYにする)べき。
(DRY:Don't Repert Yourselfの略、同じもん書くなという意味)

条件文を書くときはド・モルガンの法則を使ってロジックを操作する方法がある。
この法則を使うことで論理式を読みやすくすることができる。
ド・モルガンの法則の理解が難しい場合は「notを分配して(notをくくりだして)and/orを反転する」と覚えれば良い。

if(!(file_exists && !is_protected)) Error("ファイルを読み込めません");
if(!file_exists || is_protected)) Error("ファイルを読み込めません");
前者の論理式「「ファイルがある かつ 保護されていない 」ではない場合」は
後者の論理式「ファイルがない または 保護されている 場合」に書き換えられる。

式の分割だけでなく、巨大なコードブロックを分割する上でも同じ技法が使える。
NG例
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("highighted");
    $("#thumbs_down" + message_num).removeClass("highlighted");
  }
};
上記のコードは同じコードが再利用されていて読みにくく、すぐに理解できない。
実は5つ目の文字列”highlighted”だけtypoしているが、それも気が付きにくい。

OK例
var update_highlight = function (message_num) {
  var thumbs_up = $("#thumbs_up" + message_num);
  var thumbs_down = $("#thumbs_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);
  }
};
同じ式を説明変数として関数の最上位に抽出することで、理解しやすいコードになる。

9.変数と読みやすさ
変数を適当に使うとプログラムが理解しにくくなる。
変数が多いと変数を追跡するのが難しくなる。
変数のスコープが大きいとスコープを把握する時間が長くなる。
変数が頻繁に変更されると現在の値を把握するのが難しくなる。

邪魔な変数を削除する。意味がない変数を積極的に削除する。
役に立たないような一時変数は積極的に削除すべき。

now = datetime.datetime.now()
root_message.last_view_time = now
上記の 変数nowは以下の理由から意味がないといえるのでわざわざ定義する必要がない。
  • 複雑な式を分割していない。
  • より明確になっていない。datetime.datetime.now()のままでも十分に明確。
  • 1度しか使っていないので、重複コードの削除になっていない。
また、中間結果を保持するためだけに使っている変数や、ループ制御を制限するためだけに使っているような変数も邪魔な変数と言えるので積極的に削除すべき。

変数のスコープが小さい方が理解しやすいコードになる。
グローバル変数を避けるというアドバイスは聞きがちである。
グローバル変数は追跡が難しく、既存の名前空間を汚染するなどの不具合に繋がる。
グローバル変数に限らず、すべての変数のスコープを縮めるのは良い考えである。

一度だけ書き込む変数を使う。言い換えると、変数は一度だけ書き込む。
変数の値が頻繁に変更されると現在値の判断が難しくなり、コードの理解が難しくなる。
永続的に変更されない変数は扱いやすい。イミュータブルな型宣言も方法のひとつ。

第Ⅲ部 コードの再構成
10.無関係の下位問題を抽出する
PJ固有のコードから汎用コードを分離する。
関数で達成したい目標を明確にすることが大事である。
その目標とは直接は無関係の下位問題を積極的に見つけて抽出する。
  • その関数やコードブロックで達成したい一番の目標はなにか?と自問する。
  • コードの各行に対して、上記の目標に直接的に効果があるのか?あるいは無関係の下位問題を解決しているのか?と自問する。
  • 無関係の下位問題を解決しているコードが相当量あれば、それを抽出して別の関数にする。
ユーティリティコードや汎用コードは「無関係の下位問題」の良い例と言える。
そのような汎用的なコードは他PJでも再利用もできる。
ただし、やりすぎには注意。度を超えて小さい関数を作りすぎると、コードが散らばりすぎて逆に読みにくくなってしまう。

11.一度に1つのことを
コードはひとつずつタスクを行うようにしなければいけない。
一度に複数のことをするコードは理解しにくい。
大きな関数は小さな複数の関数に分割したほうがいい。もちろん、関数に限った話ではない。
分解するための手順は以下。
  • コードが行っているタスクをすべて列挙する。
  • タスクをできるだけ異なる関数に分割する。少なくとも異なる領域に分割する。

12.コードに思いを込める
プログラムのことを簡単な言葉で説明する。
ソースコードはプログラムの動作を説明する最も大切な手段である。
自分より知識がない人が理解できるようにコードを書くべき。
ロジックを明確に説明することが大事。

ユーザに権限があるかどうかを確認して、権限がなければユーザに知らせるコードを例にする。
NG例
$is_admin = is_admin_request();
if ($document) {
  if (!$is_admin && ($document['username'] != $_SESSION['username'])) {
    return not_authorized();
  }
} else {
  if (!$is_admin) {
    return not_authorized();
  }
}
上記はロジックがたくさんある。もっとロジックを単純化できるはず。
単純化したロジックは以下のように説明できる
  • 権限あり
    • 管理者
    • 文書の所有者
  • 権限なし
    • その他
上記の説明どおりにコードを書いてみると良い。

OK例
if (is_admin_request()) {
  ⁄⁄ 権限あり
} elseif ($document && ($document['username'] == $_SESSION['username'])) {
  ⁄⁄ 権限あり
} else {
  return not_authorized();
}
一見、if文の中身が空になっていて変なコードに思える。
でもコードは短くなり、否定形がなくなりロジックも単純になった。
つまりこちらのほうが理解しやすい。

プログラム(あるいは自分の考え)は言葉にすることで明確な形になる。

13.コードを短くする
もっとも読みやすいコードは、何も書かれていないコードだ。
コードは少ないほうがテストや保守などの労力はもちろんかからなくなる。
要件を詳しく調べれば、コードレスで実現できることもある。
プログラムで100%あらゆる入力を処理する必要はない。
新しいコードをむやみに書かないようにすることが大事。

コードを小さく保つことは重要。
コードをできるだけ小さく維持し続けるために以下をしなければいけない。
  • 汎用コードを作って重複コードを削除する。(DRYにする)
  • 未使用のコードや無用の機能を削除する。
  • PJをサブPJに分割する
  • コードの「重量」を意識する。軽量にしておく。

自前で汎用コードを作成しなくとも、既存のライブラリで解決できることも多々ある。
たまには使用している言語やフレームワークの標準ライブラリを読んでみても良い。
ライブラリのすべてを覚える必要があるわけではなくて、何ができそうか感じ取る程度でも良い。

第Ⅳ部 選抜テーマ
これまでの内容を使って実際にやってみよう!的な内容だったので割愛。

所感
全体を通して、コーディングする人材であり続ける限り非常に役立つ書籍だと思いました。
言語を問わない内容なので読んで損はないと思います。プログラマ・コーダーにはおすすめです。

(殆ど割愛してるが)リファクタの実例も多く、文章ではわかりにくい部分もイメージしやすかったです。
ただ、全てが全て同意か?と言われたら微妙な内容もあったので、鵜呑みにはしないほうが良いですね。
翻訳書だから訳が微妙だ(よくわかんない)と感じる部分もちょっとはありました。

かみくだいたにしてはちょっと冗長だったかもしれないと反省。

というわけで「リーダブルコード」のかみくだきはおしまいです。