💊

良書「リーダブルコード」を今更ながらまとめてみた

2023/02/28に公開

プログラマーのバイブル(?)である「リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック」を今更ながら読んでみたので自分なりにまとめてみます。

https://www.oreilly.co.jp/books/9784873115658/

まとめ方は、各章で個人的に気になった部分をピックアップして説明したり、箇条書きでリストアップしていきます。

はじめに

この本は文字通り「リーダブルな(=読みやすい)コード」を書くための、基本的な考え方や実践方法が体系的に整理されている本です。

そして本書の目的は「コードをよくすること」です。
じゃあその「良いコード」の定義とは何か。
それは「読みやすく、書いていることを理解しやすいコード」とこの本では言っています。

コーディングの中で一番大切にすべきことは「読みやすさ」

プログラミングは、コードを「書く」時間よりもコードを「読む」時間の方が多くなりがちです。

チーム開発の場合、他のメンバーが書いたコードを読む機会に多く出会います。コードを綺麗にすることは開発効率向上やバグの発見を素早く行えるメリットもあります。

理解しやすいコードを書くことは、エンジニアが大切にすべき指標のひとつになります。

1章 理解しやすいコード

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

理解しやすいコードは優れた設計やテストのしやすさに繋がります。コードを綺麗に書くことは他のメンバーのためでもあると同時に、数ヶ月後の自分のためでもあるということです。

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

明確な単語を選ぶ

単語から何も伝わってこないものは使用を避けるべきで、もっと明確な単語がないか実装前に考える必要があります。

カラフルな単語を選ぶ

英語は表現豊かな言語ゆえ、選べる言語はたくさん存在します。以下に一例を乗せます:

単語 代表案
send deliver, dispatch, announce, distribute, route
find search, etract, locate, recover
start launch, create, begin, open
make reate, set up, build, generate, compose, add, new

一つの動詞をとってみても、ニュアンスの近いものがいくつもありますね。

汎用的な名前を避けるべきで(あるいは、使う状況を選ぶ)、変数名に「tmp」や「retval」などの汎用的で空虚な名前の利用はできるだけ控え、変数名は「変数の値」を表すような名前にすることが望ましいです。

ただし汎用的な名前でも、使う状況次第では利用できるそうで、例えば、変数名 tmp の場合を、以下の「2つの値を入れ替える」例で見てみます。

Code
if (right < left) {
  tmp = right;
  right = left;
  left = tmp;
}

この変数の目的は「値の一時的な保管」のため、「tmp」という名前で変数の意味を伝えていることになります。このような場合は「tmp」という変数名で問題はありません。

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

メソッド名の場合、メソッドの動作をそのまま表しているような名前にするべきで、曖昧で間接的な表現は避けることが望ましいです。

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

値の単位(hex_id, kbps など)や重要な属性(plaintext_password, html_utf8 など)を追加することで、値を変換するコードやセキュリティまわりのコード実装で有効に働きます。

名前の長さを決める

長い名前は覚えにくく、画面を大きく占領してしまうため、できるだけ避けた方が望ましいです。ただ、コードの読みやすさを損なわなければ長くても問題がない場合もあります。

メソッド名や変数名に「頭文字」を付けたり、「省略形」を使うことは、新しいメンバーに対して誤解を招く恐れがあるため使用は控えましょう(理解できるなら問題はないそうですが)。

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

アンダースコアやダッシュ、大文字で名前に情報を追加する。

3章 誤解されない名前

最善の名前とは誤解されない名前であるとこの本では言っています。

  • 名前が「他の意味と間違えられることはないだろうか?」と何度も自問自答する
  • 限界値を示すときは「min_」「max_」を付ける
  • 範囲を示すときは「first_」「last_」を付ける
    v包括的範囲には「begin_」「end_」を付ける
  • ブール値は「ishascanshould」を使ったり、肯定的で分かりやすいものを付ける
  • getXXX という名前は変数へのアクセッサの意味として認知されているため、そのような処理以外でこのメソッド名を使用しない

4章 美しさ

プログラミングの時間のほとんどは、コードを「読む」時間に費やされるので、コードは読みやすく美しくする意識が必要です。

  • コードを一つの塊と捉えて、コードのシルエットを一定に保つようにする
  • コードの「列」や「順序」を意識して読みやすく
  • 間違ったスタイルを使っているコードでも、一貫性を損なうくらいなら、その既存のスタイルを踏襲する。一貫性を保つ方が大事である

美しさを意識したコードは簡潔で読みやすく、またテストコードが埋め込みやすくなるというメリットがあります。

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

コメントの目的は読み手にコードを理解してもらうことです。

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

  • コードからすぐに分かることをコメントに書かない
  • コードの読みにくさを補うコメントは必要ない。コメントを書く前にコードを修正する

良いコメントとは

  • コードの背景を説明している(なぜこういう実装になっているかとか)
  • コードの欠陥を示している
  • ファイルやクラスの全体像を説明している(どんな特徴を持ったものなのかとか)。新しくジョインするメンバーのコードの理解にも役立つ
  • 実装者がハマりそうな部分を前もって指摘している

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

  • コメントは画面の領域を取られ、読むのにも時間がかかるため、簡潔なものにする
  • 曖昧な代名詞(「あの」や「その」など)は避ける
  • メソッドの入出力のコメントには実例(例:〇〇を入れたら〇〇が返る)を入れる

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

条件式の式の並び順

条件式やループはできるだけ「自然」にすることが求められます。
以下の条件式は、一般的に上の方が読みやすいとされています。

Code
# 読みやすい
if (length > 10)
 
# 読みにくい
if (10 < length)

条件式には以下の指針が存在します。

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

if/else 文の並び順

  • 条件は否定形より肯定系を使う。例えば、if (!debug) ではなく if (debug)
  • 単純な条件を先に書く
  • 関心を引く条件や目立つ条件は先に書く

三項演算子

行が短くなるというメリットはありますが、デバッガでステップ実行できなかったり、場合によっては読みにくくなるというデメリットが存在します。

三項演算子を使用可能かどうかはケースバイケースで、その指針は以下になります。

  • 行数を短くするよりも、他の人が理解するのにかかる時間を短くする
  • 基本的には if~else を使用する。三項演算子で簡潔になるときだけ使用する

do~while ループは避ける

do~while はコードブロックを再実行する条件式が下にあり、下から上に読んでいくことになるので、普通の条件式とは異なるため読みにくいです。この本では基本的に do~while よりも while ループの使用をおすすめされています。

関数から早く返す

return 文は積極的な利用がおすすめされています。早めに return することで「ガード節」を作れるためです。

Code
checkSomething(str) {
  if (str == null) { return false; }
 
  // do something
}

ネストを浅くする

  • ネストが深いと、常に条件式を意識しないといけなくなる。ネストが増えるたびにコードを追うのに集中力が必要になる
  • 返り値は早めに返す
  • スレッド、シグナル、割り込みハンドラ、例外、関数ポインタ、無名関数、仮想メソッドはコードを追うのが難しくなるため、このようなコードが全体の割合を占めないように注意する必要がある(これらはコードが読みやすくなったり、冗長性が低くなったりするメリットがある)

8章 巨大な式を分割する

説明変数を用いて分割する

式を簡単に分割するには、式の意味を説明してくれる「説明変数」を用いるのがいいそうです。

Code
# 変更前
if line.split(':')[0].strip() == "root":
 
# 変更後
username = line.split(':')[0].strip():
if username == "root":

要約変数を用いて分割する

大きなコードの塊を小さな名前に置き換えておくことで、コードの管理や把握を簡単にできるようになります。これを「要約変数」と呼ぶそうです。

Code
# 変更前
 
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) {
  // 文章は読み取り専用
}

短絡評価の悪用

「頭がいい」コードに気をつけましょう。あとで他のメンバーがコードを読む時に分かりにくくなるためです。

以下のコードは動作的には同じものなのですが、上の方は一度立ち止まって考えないといけなくなるので、あまりおすすめのできないコードのようです。

Code
// 変更前
assert((!(bucket = FindBucket(key))) || !bucket->IsOccupied());
 
// 変更後
bucket = FindBucket(key);
if (bucket != NULL) assert(!bucket->IsOccpuied());

9章 変数と読みやすさ

変数を適当に使うと以下のような問題がありプログラムが読みにくくなる恐れがあります。

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

中間結果を削除する

以下の index_to_remove は中間結果を保持するための変数であり、これを使わなくても同じ事が実現できます。タスクはできるだけ早く完了する方が望ましいです。

Code
// 変更前
 
var remove_one = function (array, value_to_remove) {
  var index_to_remove = null;
  for ( var i=0; i < array.length; i += 1) {
    if (array[i] === value_to_remove) {
      index_to_remove = i;
      break;
    }
  }
  if (index_to_remove !== null) {
    array.splice(index_to_remove, 1);
  }
};
 
// 変更後
 
var remove_one = function (array, value_to_remove) {
  for (var i =0; i < array.length; i+= 1) {
  if (array[i] === value_to_remove) {
    array.splice(i, 1);
      return;
    }
  }
}

制御フロー変数を削除する

本書では以下の done のような変数を「制御フロー変数」と呼んでいます。

この制御フロー変数は、プログラムを制御するための変数であり、実際のプログラムに関係のあるデータは含まれていません。

こういった変数はうまくプログラミングすれば削除することができます。

Code
// 変更前
 
boolean done = false;  // 制御フロー変数
 
while (/* condition */ && !done) {
  // 何らかの処理
 
  if(...) {
    done = true;
    continue;
  }
}
 
// 変更後
 
while(/* condition */) {
  // 何らかの処理
  if (...) {
    break;
  }
}

変数のスコープを縮める

  • グローバル変数はできるだけ避ける。これはプログラマの間では認知されている
  • 変数のスコープはできるだけ縮める(「変数を追うために読むコード」が少なくなる)
  • 大きなクラスは分割して小さなクラスごとにまとめられないか考える
  • メンバ変数はできるだけローカル変数に変更する
  • クラスのメンバ変数へのアクセスを減らす方法として、メソッドをできるだけ static なものにする

定義の位置を下げる

基本的には変数は使う「直前」に定義したほうが望ましいです。変数の定義が早すぎると、変数を覚えておきながらコードの読み書きをしないといけなくなるためです。最初から全ての変数を知る必要はありません。

変数は一度だけ書き込む

変数を操作する場所が増えると現在値の判断が難しくなります。逆に永続的に変更されない変数の場合は、const (C++) や final (Java) などのイミュータブルなものにしましょう。

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

プロジェクト固有のコードから汎用コードを分離することで、プロジェクトから切り離された境界線上の業務固有の問題に集中することができます。

例えば「与えられた地点から最も近い場所を見つける」処理があったとすると、処理を「2つの地点から距離を計算する」部分と「値の簡単な操作と入出力」部分に分けることができます。

汎用コードはプロジェクトから完全に切り離されているため、開発もテストもしやすいメリットもあります。

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

一度に複数のことをするコードは理解するのに苦労します。コードをできるだけ異なる関数やクラスに分割することが望ましいです。

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

コードは「ロジックを説明する文章」になっていると分かりやすいです。コードは読み物で、コードを読むことはエンジニアが最も多くの時間を費やす時間だからです。

13章 短いコードを書く

最も読みやすいコードというのは、何も書かれていないコードのことです。

コードをできるだけ小さく保つことはプロジェクトが進むにつれてその恩恵は大きくなっていきます。

  • 汎用的な「ユーティリティ」コードを作って重複コードを削除する
  • 過剰な機能は持たせない
  • 未使用のコードや不要なファイルなどは削除する
  • 標準ライブラリで使えそうなものを確認する
  • 最も簡単に問題を解決できそうな要求を考える

冒険、興奮、ジェダイはそんなものを求めてはおらん。 - ヨーダ(スターウォーズ)

14章 テストと読みやすさ

テストを読みやすくするということは、テスト以外のコードを読みやすくすることと同じくらい大切な部分になります。

読みにくい(扱いづらい)テストは以下のような弊害をもたらす可能性があります。

  • テストを修正したくないがために、本物のコードを修正しない
  • 新しいコードを追加した時にテストを書かなくなる

テストに優しい開発

まず、テストしやすいコードとは、明確なインターフェースがあります。状態や「セットアップ」がなく、検査するデータが隠されていない状態です。

テストするコードは「テストしやすいように設計する」ように心がけ、これがテストに優しい設計の鉄則になります。また、あとでテストを書こうと意識しながらコード書くことも重要になってきます。

テストのやりすぎ問題

テストは素晴らしいものですが、やりすぎは禁物です。場合によってはテストに集中しすぎると以下のような問題が起こる可能性があります。

  • テストのために本物のコードの読みやすさを犠牲にしてしまう。本物のコード、テストコード両者に利点がないといけない。本物のコードは単純で疎結合なもの、テストコードは読み書きしやすいものを意識する
  • テストカバレッジを100%にしないと気が済まない。90%付近からユーザーインターフェースやどうでもいいエラーケースが含まれていることが多い。現実的にはテストカバレッジが100%になることはない
  • テストがプロジェクト開発の邪魔になる。プロジェクトの一部に過ぎないテストが、プロジェクト開発の全体を支配しているような状況 のこと。エンジニアリングの時間を犠牲にしてまで書かなくてはいけないテストなのか考える必要がある。

おわり

本書は「読み物としてのコード」をどう構築していくかということの、基本的な考え方や実践方法が学べます。「コードは読み物」「読み手への配慮」この2つを意識すると、コードの質を向上させることができそうです。実務でもこの辺りを意識してハッピーなエンジニア人生にしていきたいです。

※ ↑意識してもなかなか上手く書けない場合は、あとは、まあ、ChatGPT や Copilot に助けてもらいましょ。

こちらからは以上です。

Discussion