『リーダブルコード』要約: 明日から使える「読みやすいコード」の原則を実例つきで解説

『リーダブルコード』の要約記事。読みやすさの基本定理を軸に、命名・誤解されない名前・コメントの書き方・制御フローの単純化・関数分割の原則を、オリジナルのコード例のbefore/afterで解説する。コードレビューでの活かし方や、初心者からレビュアーまでどんな人が読むべきかも紹介する。

『リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック』(Dustin Boswell, Trevor Foucher著、オライリージャパン、2012年)は、エンジニアの間で今なお「最初に読むべき技術書」として名前が挙がり続けている一冊だ。本記事では、この本の要点を自分の言葉で整理し直し、書籍に載っている例ではなく自作のコード例(TypeScript / Python)で「読みやすいコードとは何か」を具体的に見ていく。読み終える頃には、本書を通読したくなるくらいの見取り図が持てるはずだ。

1. 『リーダブルコード』とは — なぜ15年以上読み継がれるのか

本書は、変数名の付け方のような小さな話から、関数の分割やコードの再構成といった設計寄りの話まで、「コードの読みやすさ」というただ一点に焦点を絞って書かれた技術書である。特定の言語やフレームワークの解説書ではなく、C++・Python・JavaScriptなど言語をまたいだサンプルを通じて、どんな言語で書いていても通用する原則を示しているのが特徴だ。

初版が出てから10年以上が経つが、色褪せていない理由は大きく2つあると考えている。ひとつは、扱っているテーマが「読みやすさ」という、言語やフレームワークの流行に左右されない普遍的な性質だからだ。もうひとつは、原則の粒度が「今日から一行変えられる」レベルまで具体的に噛み砕かれていることにある。抽象的な設計論ではなく、変数名一つ、if文一つの書き方から始められるので、経験の浅いエンジニアでもすぐに実践に移せる。

対象読者は、プログラミングを始めて半年〜数年のエンジニアが中心だと感じている。一方で、他人のコードをレビューする立場になった中堅エンジニアが「なぜこのコードは読みにくいと感じるのか」を言語化する辞書として読み返す価値も大きい。筆者自身、新人の頃に一度読み、レビュアーになってから読み返して初めて刺さった原則がいくつもあった。

リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック

2. 本書の核心: 「読みやすさの基本定理」

本書全体を貫く一つの軸がある。それは「コードは、他の人(未来の自分を含む)が読んで理解するまでの時間が短くなるように書くべきだ」という考え方だ。本書ではこれを一種の判断基準として繰り返し使っている。

この考え方が優れているのは、「短く書く」「行数を減らす」といった単純な指標に頼っていない点だ。1行にまとめて詰め込んだコードは短くても理解に時間がかかることが多いし、逆に説明変数を増やして数行長くなっても、理解が一瞬で済むなら、そちらの方が「読みやすい」コードだと言える。つまり本書における「良いコード」は、書く時間ではなく読む時間、しかも自分ではなく他者が読む時間を基準に評価される。

この基準を軸に置くと、本書で紹介される個々のテクニック――命名、コメント、制御フロー、関数分割――がすべて同じ目的のための手段だと理解できる。以下の図は、本書全体の話の流れを、1行・1語レベルの細かい改善から、複数ファイルにまたがる設計判断まで、視野を広げていく構成としてまとめたものだ。

『リーダブルコード』全体構成マップ。土台に「読みやすさの基本定理(コードは他の人が理解するまでの時間を最短にするように書く)」を置き、その上に表面上の改善(命名・美しさ・コメント)、ループとロジックの単純化(制御フロー・巨大な式・変数の整理)、コードの再構成(無関係の下位問題の抽出・一度に1つのタスク)、選抜テーマ(テストの読みやすさ・最小の作業道具箱)の4つが、1行単位の話から複数ファイルにまたがる設計判断へと視野を広げる順に並んでいる

以降の章では、この図の左から順に、表面上の改善、ループとロジックの単純化、コードの再構成の3段階を、自作のコード例とともに見ていく。

3. 表面上の改善 — 命名・美しさ・コメント

もっとも取り組みやすく、かつ効果が大きいのがこのレイヤーだ。変数名を一つ変える、空行を一つ入れる、コメントを一行足す。どれも既存のロジックを壊さずに実行できる改善であり、レビューでも指摘しやすい。

3-1 名前に情報を詰め込む

汎用的な単語は、状況に応じてより具体的な単語に置き換えられないか検討する余地がある。以下は、キャッシュ機構を持つデータ取得関数の例だ。

// Before: get は「軽い処理」を連想させるが、実際はネットワークI/Oを伴う
function getUser(id: string) {
  const cached = cache.lookup(id);
  if (cached) return cached;
  return fetchFromApi(`/users/${id}`); // ネットワーク越しの取得
}

// After: 処理の実態(重い可能性がある取得)を動詞に反映する
function fetchUserWithCache(id: string) {
  const cached = cache.lookup(id);
  if (cached) return cached;
  return fetchFromApi(`/users/${id}`);
}

get という単語は「軽量なアクセサ」という期待を読み手に抱かせやすい。実装にネットワーク呼び出しやディスクI/Oが混じっているなら、fetchload のような動詞に変えるだけで、呼び出し側が「これは呼ぶたびにコストがかかるかもしれない」と身構えられるようになる。筆者は以前、get〇〇() の中に想定外の重い集計処理が隠れていて、ループの中で無邪気に呼ばれてパフォーマンス問題になった経験があり、それ以来この観点をレビューで必ず確認するようにしている。

3-2 誤解されない名前を使う

真偽値の変数名は特に誤解を生みやすい。次の例を見てほしい。

// Before: disable なのか disabled なのか、真偽の向きが名前から読み取れない
let disable = false;
if (!disable) {
  submitForm();
}

// After: is_ 接頭辞と、真であるときの意味が名前だけでわかる
let isSubmitDisabled = false;
if (!isSubmitDisabled) {
  submitForm();
}

disable のような動詞そのものを真偽値の変数名にすると、「これから無効化する」という命令なのか「すでに無効化されている」という状態なのかが文脈依存になってしまう。is_has_ を付けるだけで、この変数が「状態を表すフラグである」ことと「true のときに何を意味するか」がコードを読むだけでわかるようになる。地味な改善だが、否定条件が重なるコード(if (!isNotDisabled) のような二重否定)を防ぐ効果も大きい。

3-3 コードの美しさ ― レイアウトで構造を語らせる

似た処理は似た形に揃えるだけで、差分が一目でわかるようになる。

# Before: 3つの代入が同じ構造を持つことが視覚的に伝わらない
user_name = request.form.get("name")
user_email = request.form.get("email")
user_phone_number = request.form.get("phone")

# After: 縦を揃えることで「同じパターンの繰り返し」だと一瞬で伝わる
user_name         = request.form.get("name")
user_email        = request.form.get("email")
user_phone_number = request.form.get("phone")

縦の位置を揃える整形は好みが分かれる部分もあるが、少なくとも「この3行は構造的に同じことをしている」という事実を、読み手に文章として説明せずに伝えられる点は強力だ。ただし、フォーマッタを自動整形にかけているチームでは崩れてしまうこともあるため、チームの整形ルールと衝突しないかは事前に確認した方がよい。

3-4 コメントは「なぜ」を語らせる

コメントの価値は、コードを読めばわかることを繰り返すことではなく、コードだけでは伝わらない背景を補うことにある。

// Before: コードを読めばわかることをそのまま書いている
// リストをソートする
items.sort((a, b) => a.priority - b.priority);

// After: なぜこの順序・この実装にしたのかという背景を書く
// 優先度が同じ場合は挿入順を保持したいので、安定ソートである
// Array.prototype.sort の仕様(ES2019以降)に依存している
items.sort((a, b) => a.priority - b.priority);

「何をしているか」はコードそのものが雄弁に語ってくれる。コメントが本当に価値を持つのは、「なぜその実装を選んだか」「なぜ一見不自然に見えるコードになっているか」を書いたときだ。筆者はレビューで「このコメント、コードを読めばわかりますよね」という指摘をよくするが、逆に「なぜ」が抜けているコードには「これはなぜこの順番なんですか」と質問することの方が多い。それが、後から書き足すべきコメントの内容そのものになる。

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

命名やコメントで表面を整えたら、次は制御フローそのものの複雑さに手を付ける段階だ。ここでのゴールは、コードを読む人が頭の中にスタックを積まずに済むようにすることにある。

4-1 ガード節でネストを浅くする

条件分岐が深くネストすると、今どの条件の中にいるのかを読み手が覚えておかなければならなくなる。

// Before: 正常系がネストの奥に埋もれている
function calculateShippingFee(order: Order): number {
  if (order.items.length > 0) {
    if (order.address !== null) {
      if (order.address.country === "JP") {
        return order.weight * 100;
      } else {
        return order.weight * 300;
      }
    } else {
      throw new Error("address is required");
    }
  } else {
    return 0;
  }
}

// After: 異常系・早期リターンを先に片付け、正常系をネストの外に出す
function calculateShippingFee(order: Order): number {
  if (order.items.length === 0) return 0;
  if (order.address === null) throw new Error("address is required");

  return order.address.country === "JP"
    ? order.weight * 100
    : order.weight * 300;
}

ガード節(異常系や早期リターンを先頭でまとめて処理してしまう書き方)を使うと、残ったコードは「正常系だけを考えればいい」状態になる。ネストが1段減るだけでも、読み手が保持しておくべき前提条件の数が減り、体感の読みやすさは大きく変わる。個人的には、関数の中に3段以上のネストが出てきたら、まずガード節で減らせないかを最初に疑うようにしている。

4-2 巨大な式は説明変数で分解する

一行に詰め込まれた複雑な条件式は、たとえ動作が正しくてもレビューで止まりやすい。

# Before: 条件式が長く、何を判定しているのかが一読でわからない
if (user.age >= 18 and user.country == "JP" and
        not user.is_suspended and user.email_verified):
    grant_access(user)

# After: 意味のまとまりごとに説明変数へ分解する
is_adult = user.age >= 18
is_domestic = user.country == "JP"
is_in_good_standing = not user.is_suspended and user.email_verified

if is_adult and is_domestic and is_in_good_standing:
    grant_access(user)

説明変数を導入すると行数は増えるが、それぞれの変数名が「この式は何を判定しているのか」を代わりに語ってくれるため、条件式そのものを読み解く負荷がなくなる。しかもこの分解は、後から「日本国内かどうかの判定条件が変わった」ときに、変更箇所を1行だけに閉じ込めやすくするという副次的な利点もある。式を分解するかどうか迷ったときは、「この条件、口頭で他人に説明するとしたら何と言うか」を自問すると、そのままよい変数名が見つかることが多い。

5. コードの再構成

表面上の改善やロジックの単純化を積み重ねても、関数そのものが複数の役割を抱えていると読みにくさは残る。ここからは、関数・モジュール単位での再構成の話になる。

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

ある関数が、本来のビジネスロジックとは無関係な「下位問題」を内包していないかを見る視点だ。

// Before: 注文の合計金額計算に、日付フォーマットの詳細ロジックが混在している
function buildInvoiceSummary(order: Order): string {
  const d = order.createdAt;
  const formatted = `${d.getFullYear()}/${String(d.getMonth() + 1).padStart(2, "0")}/${String(d.getDate()).padStart(2, "0")}`;

  const total = order.items.reduce(
    (sum, item) => sum + item.price * item.quantity,
    0,
  );
  return `${formatted} の注文合計: ${total}円`;
}

// After: 日付フォーマットという無関係の下位問題を独立した関数に抽出する
function formatDateJP(date: Date): string {
  return `${date.getFullYear()}/${String(date.getMonth() + 1).padStart(2, "0")}/${String(date.getDate()).padStart(2, "0")}`;
}

function buildInvoiceSummary(order: Order): string {
  const total = order.items.reduce(
    (sum, item) => sum + item.price * item.quantity,
    0,
  );
  return `${formatDateJP(order.createdAt)} の注文合計: ${total}円`;
}

buildInvoiceSummary が本来やるべきことは「注文の要約文字列を組み立てる」ことであり、「日付をどうフォーマットするか」はそれとは独立した、汎用性のある下位問題だ。これを別関数に切り出すと、buildInvoiceSummary 自身の見通しがよくなるだけでなく、formatDateJP は他の場所でも再利用できるようになる。筆者の経験では、この手の「日付フォーマット」「文字列のサニタイズ」「単位変換」といった無関係の下位問題は、意識しないと本体のロジックにどんどん混入していく。関数を読んでいて「これは今扱っている問題と関係ない話だな」と感じたら、その部分こそ抽出のサインだと考えている。

5-2 一度に1つのタスクをする

一つの関数が複数のタスクを順番にこなしていると、途中から追うのが難しくなる。

# Before: バリデーション・変換・保存という3つのタスクが1つの関数に同居している
def register_user(raw_data: dict) -> User:
    if "email" not in raw_data or "@" not in raw_data["email"]:
        raise ValueError("invalid email")
    if len(raw_data.get("password", "")) < 8:
        raise ValueError("password too short")

    normalized_email = raw_data["email"].strip().lower()
    user = User(email=normalized_email, password_hash=hash_password(raw_data["password"]))

    db.session.add(user)
    db.session.commit()
    return user

# After: タスクごとに関数を分け、register_user は「進行役」に徹する
def validate_registration(raw_data: dict) -> None:
    if "email" not in raw_data or "@" not in raw_data["email"]:
        raise ValueError("invalid email")
    if len(raw_data.get("password", "")) < 8:
        raise ValueError("password too short")


def build_user(raw_data: dict) -> User:
    normalized_email = raw_data["email"].strip().lower()
    return User(email=normalized_email, password_hash=hash_password(raw_data["password"]))


def register_user(raw_data: dict) -> User:
    validate_registration(raw_data)
    user = build_user(raw_data)
    db.session.add(user)
    db.session.commit()
    return user

分割後の register_user は「検証する」「組み立てる」「保存する」という3つのタスクを順番に呼び出すだけの、いわば進行役になっている。個々のタスクは名前を読むだけで何をしているか把握できるので、関数全体を読まなくても「バリデーションだけ直したい」ときは validate_registration だけを見ればよい。テストを書くときも、タスクごとに独立してテストできる恩恵は大きい。「この関数、“と"で説明が必要になっていないか」――たとえば「検証して保存する関数」のように――は、分割すべきかどうかを判断する簡単な目安になる。

6. 実務でどう活きたか — コードレビュー観点のチェックリスト化

本書を読んで一番変わったのは、コードを書くときよりも、他人のコードをレビューするときの視点だった。「なんとなく読みにくい」という感覚を、本書の原則に沿って言語化できるようになったのが大きい。筆者が実務のレビューで実際に使っている観点を、本書の章立てに沿って簡単なチェックリストにまとめてみた。

  • 命名: その名前だけで「何のためのものか」が伝わるか。汎用語(data, process, handle など)をそのまま使っていないか。
  • 真偽値: is_ has_ can_ などで、真であるときの意味が一意に読み取れるか。
  • コメント: コードを読めばわかることの繰り返しになっていないか。逆に「なぜ」が必要な箇所にコメントが抜けていないか。
  • 制御フロー: ネストが3段以上になっていないか。ガード節で正常系をネストの外に出せないか。
  • 式の複雑さ: 一行の条件式が長すぎないか。説明変数で意味を分解できないか。
  • 関数の責務: その関数は「〇〇と△△をする」のように “と” で説明しないと言い表せない状態になっていないか。

このチェックリストは、レビューの最初の一巡目でざっと機械的に流すために使っている。設計の妥当性やテストカバレッジといった、より高次の議論に入る前に、この段階の指摘をレビューの前段で済ませておくと、レビュー全体の議論が本質的な部分に集中しやすくなる、というのが実感だ。

7. どんな人におすすめか / 読み方のコツ

対象読者として特に効果が大きいと感じるのは、実務でコードを書き始めて半年〜2、3年ほどのエンジニアだ。すでに動くコードは書けるが、「なぜこの書き方の方が良いのか」を言葉で説明する経験がまだ少ない時期に読むと、日々のコーディングの判断基準がはっきりする。

一方で、他人のコードをレビューする機会が増えてきた中堅エンジニアにも読み直す価値がある。「読みにくい」と感じた瞬間に、その感覚の正体を本書の語彙(命名、コメント、制御フロー、関数分割など)で言語化できるようになると、レビューコメントの説得力が変わってくる。

読み方としては、最初から通読するよりも、自分が今書いているコードや、直近でレビューしたコードを片手に、該当しそうな章から拾い読みする方が定着しやすい。各章は独立性が高く、コード例も短いため、拾い読みに向いた構成になっている。実際に自分のコードに一つでも原則を当てはめてみると、本の内容が一気に自分の言葉として身につく。

まとめ

『リーダブルコード』は、「コードは他の人が理解するまでの時間を最短にするように書く」という一つの基本定理から出発し、命名・コメントといった表面上の改善から、制御フローの単純化、そして関数やモジュール単位の再構成まで、視野を段階的に広げながら「読みやすさ」を具体的なテクニックに落とし込んだ本だ。抽象論ではなく、今日の1行から実践できる粒度の指針が多いため、経験年数を問わず得るものがある一冊だと感じている。この記事で紹介したのはあくまで骨格であり、本書には各原則についてさらに多くの具体例と丁寧な解説が収録されている。気になった原則があれば、ぜひ原著を手に取ってみてほしい。

リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック

他の分野の定番書は エンジニアにおすすめの技術書10選 にまとめている。

よくある質問(FAQ)

初心者でも読める?

プログラミング自体の基礎(変数、関数、条件分岐、ループ)を一通り学び終えていれば読める内容になっている。サンプルコードはC++・Python・JavaScriptなど複数の言語で書かれているが、特定言語の高度な機能には依存していないため、いずれか一つの言語の基礎文法がわかれば読み進められる。

今読んでも古くない?

扱っているテーマが「命名」「コメント」「制御フロー」「関数分割」といった、特定の言語機能やフレームワークに依存しない普遍的な話題であるため、初版から時間が経った今でも内容の骨格は通用すると感じている。もちろん個々のサンプルコードの書き方には時代を感じる部分もあるが、原則そのものはどの言語・どの技術スタックでも読み替えて適用できる。

何時間で読める?

分量は200ページ強で、各章が短く独立しているため、通読であれば数時間程度で読み切れるボリュームだ。ただし、本記事で触れたように、実際のコードに当てはめながらじっくり読むと、その分時間はかかる。まずは通読して全体像をつかみ、後から必要な章を読み返す、という読み方が向いている本だと思う。

参考文献

  • Dustin Boswell, Trevor Foucher, 『リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック』, オライリージャパン (2012)