🐥

リーダブルコードの要点 まとめ

2024/05/03に公開

ご挨拶

リーダブルコードすべて読んだので、要点をまとめました!
忙しい人は各章のまとめを見てください!

リーダブルコードについて

エンジニアの必読書と書かれていたため、最初の一冊として読んでみることにしました。
この本の目標は「理解しやすいコードを書くこと」です!
気になる方は是非読んでみてください!
https://amzn.to/3VWXZbp
https://a.r10.to/hPU6pe

この記事の対象者

  • 名前の付け方に迷う人
  • リーダブルコードの要点を知りたい人
  • コーディングの体系的な書き方を知りたい人
  • コードレビューをすることがある人
  • 理解しやすいコーディングを目指す人

第1章「理解しやすいコード」

この本では「優れた」コードを明確に定義しています。

それは

コードは他の人が最短時間で理解できるように書かなければならない

です。

初心者の方は特に(私よりも初心者の方は極少数だと思います)これを念頭に置いておきましょう。
また、「他の人」とは未来の自分かもしれません。

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

  • 明確な単語を選ぶ
  • tmpやretvalのような汎用的な名前は避ける
  • 抽象的な名前よりも具体的な名前を使う
  • 接尾辞や接頭辞を使って情報を追加する
  • 名前の長さを決める
  • 名前のフォーマットで情報を伝える

それぞれ詳しく見ていきましょう。

明確な単語を選ぶ

例えばGetPage(url)の場合、以下のような問題が発生します。

  • ページをローカルキャッシュからとってくるのか?
  • ページをインターネットからとってくるのか?
  • ページをデータベースからとってくるのか?

例えばインターネットからとってくる場合にはFetchPage(url)DownloadPage(url)の方が明確でしょう。

他にもGetSetは使いがちなので明確にするようにしましょう。

tmpやretvalのような汎用的な名前は避ける

  • retvalは戻り値だという情報以外なにも表していないのでオススメできない
  • ループイテレーターはi,j,kでもイテレーターであることがひと目でわかるので良いが、例えばclubs[i]よりもclubs[club_i]clubs[ci]のような名前の方が良い(インデックスの最初の文字と配列の名前の最初の文字が一致していればバグ確認しやすい。)
  • tmpは用いてもよいが、一時的なデータ保存という意味合いがない場合は使用をやめよう。

例えば以下のようなコードではtmpが怠慢に使われている。

let tmp = user.name 
tmp += user.phone_number
tmp += user.email
tmp += user.address
template.set("user_info", tmp)

この場合tmpuser_infoのような変数にするべきだろう。

値の単位

例えば、以下のようなコードがあったとする。

var start = (new.Date()).getTime()
console.log(`開始時間は${start}`)

一見正しく見えるかもしれない。しかし、getTime()は「秒」ではなく「ミリ秒」をかえすためうまく動作しない。
このような場合はstartではなくstart_msのほうが良いだろう。

名前の長さを決めよう

  • スコープが小さければ多くの情報を詰め込む必要はない
  • 接頭辞や接尾語は初めて来たメンバーでもわかるかどうかで考える。(ex)strなら分かる
  • テキストエディタの「単語補完」という機能があるので使ってみると良い
  • 名前のフォーマットを決めてメンバ変数とローカル変数の区別できるようにする

第2章のまとめ

  • 明確な単語を選ぶ。GetではなくFetchやDownloadなど用途に合わせて名前を決める。
  • tmpやretvalのような汎用的な名前は避ける。明確な理由がある場合を除く。
  • 具体的な名前を使って詳細に説明する。
  • 変数名に大切な情報を追加する。ミリ秒を表すときには_msなど単位をつける
  • スコープの大きな変数は長い名前でもよいが、小さいものには短く命名する
  • 大文字やアンダースコアに意味を持たせて、変数を見ただけでメンバ変数なのかローカル変数なのかを分かるようにする。

第3章「誤解されない名前」

  • 限界値を含めるときはlimitではなくminとminとmaxを使う
  • 範囲を指定するときはstartやstopではなくfirstとlastを使う
  • 包含/排他的範囲にはbeginとendを使う
  • ブール値の名前は肯定文で「Is.has・can・should」を接頭辞につける

それぞれ詳しく見ていきましょう。

限界値を含めるときはlimitではなくminとminとmaxを使う

例えば、以下のような変数があったとする。

CART_TOO_BIG_LIMIT = 10

この場合、「未満(境界値を含まない)」なのか「以下(境界値を含む)」なのかわからない。
そこで

MAX_CART_TOO_BIG = 10

とすれば明確になる。

範囲を指定するときはfirstとlastを使う

startは明確な名前だがstopの場合、stopが包含の意味合いがあるかどうか曖昧である。
そこで、包含関係がある場合には[start,stop]ではなく[first,last]とすべきである。
排他的な意味合いがある場合には[begin,end]とすべきである。

第3章まとめ

  • 限界値を含めるときはlimitではなくminとminとmaxを使う
  • 範囲を指定するときはstartやstopではなくfirstとlastを使う
  • 包含/排他的範囲にはbeginとendを使う
  • ブール値の名前は肯定文で「Is.has・can・should」を接頭辞につける

第4章「美しさ」

この章では美しさについて語られている。
どうやらインターネットで検索してみると自動でしてくれるツールが多いようなので割愛する。

第4章まとめ

  • 複数のコードブロックで同じようなことをしているものはシルエットも同じにする
  • メソッドを使うことで整形する
  • 縦のラインをまっすぐに入れるようにする
  • ある場所でABCという風に並んでいるなら他の場所でBCAのように並べてはならない
  • 空行を使って大きなブロックを論理的な「段落」に分ける

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

  • コメントするべきでは「ない」こと
  • コメントすべきこと
  • 読み手の気持ちを考える

それぞれ詳しく見ていきましょう。

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

  • ひどいコードはコメントを付けるのではなくコードを変える
  • コードを読めばすぐにわかるものはコメントにしない

例えば、以下のようなコードがあったとする。

// Accountクラスの定義
class Account{
  public:
    // コンストラクタ
    Account();
}

このような場合、全くコメントの意味がない。

通常は「補助的なコメント」が必要になることはなく、
「優れたコード>ひどいコード+優れたコメント」
という考えを肝に銘じたい。

コメントすべきこと

一言でいうと自分の考えを記録する内容である。

  • 計算速度やエラー発生についても記述しておくことで再度試すことやテストの時間を短縮できる
  • コードの欠陥にコメントを付ける(ex) //○○を使って整理した方が良い
  • 定数にコメントを付けると良い場合が多い
  • //TODO: と書かれていれば後で手を付けるという意味を持つ

ここで、TODO関連の記法を示す。

記法 典型的な意味
TODO: あとで手をつける
FIXME: 既知の不具合があるコード
HACK: あまり綺麗じゃない解決策
XXX: 危険!大きな問題がある

読み手の気持ちを考える

  • 要約コメントを書く
  • 初めて読む人が詰まりそうな部分にだけコメントを書く

第5章まとめ

  • コメントすべきでないこと
    • コードから推測が容易なもの
    • コードの改善余地があるもの
  • コメントすべきこと
    • 自分の考えをまとめたもの
    • これからコードをどうしたいのかを書いたもの(ex)TODOコメント
    • 要約コメント

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

  • 代名詞は使わない
  • 入出力のコーナーケースに実例を使う
  • コードの意図を書く
  • 名前付き引数コメントを書く

それぞれ確認していきましょう。

入出力のコーナーケースに実例を使う

例えば、以下のようなコードがあったとする。

// 'src'の先頭や末尾にある'chars'を除去する。
String Strip(String src, String chars){}

この場合、以下のような疑問が生じる。

  • charsは除去する文字列なのか、順序のない文字集合なのか?
  • srcの末尾に複数のcharsがあったらどうなるのか?

そこで以下のように実例を交えてコメントを記述する。

// 'src'の先頭や末尾にある'chars'を除去する。
// Strip("aaba/a/ba", "ab")は"/a"を返す
String Strip(String src, String chars){}

コードの意図を書く

コードの処理内容を書くのではなく、なぜそれを実行したのかを書く。

// listを逆順にイテレートする
// 処理

上記のコメントは処理内容を書いただけのものである。
この場合は以下のように記述すると良い。

// 値段の高い順に表示する
// 処理

名前付き引数コメント

よくわからない引数については名前付き引数で呼び出す。
例えば、以下のようなコードがあったとする。

Connect(10, false)

上記のコードだと、10もbool値も何かわからない。
この場合は以下のように記述すると良い。

Connect(/*timuout_ms = */ 10, /*use_encryption = */ false)

第6章まとめ

  • コメントは簡潔に書く
  • 代名詞は使わない
  • 入出力のコーナーケースに実例を使う
  • コードの意図を書く
  • 名前付き引数コメントを書く

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

  • 条件式の引数の並び順
  • if/elseの並び順
  • do-whileループを避ける
  • 関数から早く返す
  • ネストを浅くする

それぞれ詳しく見ていきましょう。

条件式の引数の並び順

調査対象の式は左側に
例えば、以下のようなコードがあったとする。

if (length > 10)

if (10 < length)

この2つであれば上の方が読みやすいはずだ。

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

if/elseの並び順

  • 条件は否定形よりも肯定形を使う。例えばif (!debug)ではなくif (debug)を使う
  • 単純な条件を先に書く

do-whileループを避ける

do-whileループは最低1回実行されてしまう。whileループであれば実行される条件が一目でわかるのでdo-whileループはwhileループに置き換える。

関数から早く返す

関数から早く返すようなコードは以下の通りだ。

public boolean Contains = (String str, String substr) => {
  if (str == null || substr == null) return false; 
  if (substr.equals("")) return true;
  //処理
};

ネストを浅くする

関数から早く返すと同様に、失敗ケースを早めに返すようにすることでネストを浅くしよう。

第7章まとめ

  • 条件式において調査対象の式は左側に
  • if/elseを使用する際は否定形ではなく肯定形を使う
  • do-whileループを避ける
  • goto文は使わない
  • 関数から早く返す
  • ネストを浅くする

第8章「巨大な式を分割する」

  • 説明変数を利用する
  • 要約変数を利用する
  • ド・モルガンの法則を使う
  • 複雑なロジックと格闘する

それぞれ確認していきましょう。

説明変数を利用する

例えば、以下のようなコードがあったとする。

if line.split(':')[0].split() == "root":
//処理
};

説明変数を使えば以下のようになる。

username = line.split(':')[0].split()
if username == "root":
//処理
};

要約変数を利用する

例えば以下のようなコードがあったとする。

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){
  // 文書は読み取り専用
}

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

ド・モルガンの法則を使用して分かりやすくする。
ド・モルガンの法則は以下の通りだ。

  1. not (a or b or c) ⇔ (not a) and (not b) and (not c)
  2. not (a and b and c) ⇔ (not a) or (not b) or (not c)
    覚えにくい場合は
    notを分配してand/orを反転する
    と覚えておこう。

複雑なロジックと格闘する

if文のロジックが複雑になりすぎた場合にはより優雅なロジックにするよう考えてみよう。
例えば、以下のようなコードがあったとする。

if (// 複雑なロジック);
  return true;
}
else {
  return false;
}

この場合、複雑なロジックを読まなければならない。そこで複雑なロジックではない条件を考えてみよう。すると以下のように変更できる場合もあるだろう。

if (// 簡単なロジック);
  return false;
}
if (// 簡単なロジック);
  return false;
}
else {
  return true;
}

第8章まとめ

  • 説明変数を利用する
  • 要約変数を利用する
  • ド・モルガンの法則を使う。覚え方はnotを分配してand/orを反転する
  • 複雑なロジックと格闘する際には複雑なロジックではない側のロジックを検討しよう。

第9章「変数と読みやすさ」

  • 不要な変数を削除する
  • 変数のスコープを小さくする
  • 変数の書き換えはあまり行わないようにする

それぞれ確認していきましょう。

不要な変数を削除する

例えば、Pythonで以下のようなコードがあったとする。

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

この場合のnowは必要ないだろう。なぜならば、datetime.datetime.now()でも十分明確だったからだ。
よってこのコードは以下のように書き換えると良い。

root_message.last_view_time = datetime.datetime.now()

この例以外にも中間結果を削除すると良い。例えばfor文の中でtrue,falseに切り替えてからfor文終了後にif文を入れる必要はないということだ。

変数のスコープを小さくする

例えば、以下のようなコードがあったとする。

string str_;

void Method1() {
  str_ = "fjfj"
  method2();

void Method2(){
  // str_を使用している
  }

void Method3(){
  // str_を使用していない
};

この場合、str_を以下のようにローカル変数に変換すると良い。

void Method1() {
  string str = "fjfj"
  method2();

void Method2(){
  // strを使用している
  }

void Method3(){
  // このメソッドはstrが見えない
}

第9章まとめ

  • 不要な変数を削除する
    • 十分意味が明確で複雑な式を分割しているわけではない変数は消そう
    • 中間結果は消せるなら消そう
  • 変数のスコープを小さくする
  • 変数の書き換えはあまり行わないようにする

最後に

感想としては難しかったです!第10章以降は難しくて割愛しました。。。
またこの先に呼んでみます!
最後にこの本のリンク張っておきます!

https://amzn.to/3VWXZbp
https://a.r10.to/hPU6pe

Discussion