Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

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

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

はじめに

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

本書の目的は「コードをよくすること」である。ではその「良いコード」の定義とは何か。

それは「読みやすいコード(理解しやすいコード)」ということである。

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

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

チーム開発の場合、他のメンバーが書いたコードを読む機会に多く出会う。

コードを綺麗にすることは開発効率向上やバグの発見を素早く行えるメリットがある。

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

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」という名前で変数の意味を伝えている。

「tmp」という名前は生存期間が短くて、一時的な保管が最も大切な変数にだけ使う。

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

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

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

値の単位(hex_id, kbps など)や重要な属性(plaintext_password, html_utf8 など)を追加する。

値を変換するコードやセキュリティまわりのコード実装で有効に働く。

名前の長さを決める

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

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

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

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

3章 誤解されない名前

最善の名前とは誤解されない名前である。

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

4章 美しさ

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

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

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

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

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

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

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

良いコメントとは

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

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

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

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

条件式の式の並び順

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

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

# 読みにくい
if (10 < length)

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

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

if/else 文の並び順

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

三項演算子

行が短くなるというメリットはあるが、読みにくかったりデバッガでステップ実行できなくなるというデメリットがある。

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

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

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つを意識すると、コードの質は飛躍的に向上することが分かった。実務でもこの辺りを意識してハッピーなプログラマー人生にしていきたい。

本書は「読み物としてのコード」をどう構築していくかということの、基本的な考え方や実践方法が学べます。

まだ読んでいない方はぜひ一度手にとってみてください。

maecha
インターネットムズカシイ
https://maecha.com
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away