はじめに
こちらは著書「リーダブルコード」の学習記録です。
忙しい人のために、重要そうな部分を抜粋してまとめました。
7章 制御フローを読みやすくする
if/elseブロックの並び順
例えば、以下のように書くのと、
if(a ==b) {
// 第1のケース
} else {
// 第2のケース
}
以下のように書くのは同じ。
if(a != b) {
// 第2のケース
} else {
// 第1のケース
}
この並び順には優劣がある。
- 条件は否定形よりも肯定形を使う
- 単純な条件を先に書く
- 関心を引く条件や目立つ条件を先に書く
しかし否定形の条件であっても、単純で関心や注意を引く場合もある。そういうときは、それを先に処理しましょう。
if(!file) {
// エラーをログに記録する
} else {
...
}
ネストを浅くする
ネストが深くなると、読み手はコードの意図を理解するために、頭の中で条件を記憶して全体の流れを把握していかなければならない(すなわち精神的スタックに条件をプッシュすること)
if(user_result == SUCCESS) {
if(permission_result != SUCCESS) {
reply.Done();
return;
}
} else {
reply_WriteErrors(user_result);
}
reply.Done();
上記の例で言うと、user_result
とpermission_result
の値を常に覚えておかないといけない。それにifブロックが終了するたびに、覚えておいた値を反対にしなければならない。
加えて、SUCCESSとSUCCESSの否定形が交互に出てくるので、余計ややこしい。
早めに返してネストを削除する
上記のコードの改善方法の一つとしては、「失敗ケース」をできるだけ早めに関数から返すこと。
if(user_result != SUCCESS) {
reply.WriteErrors(user_result);
reply.Done();
return;
}
if(permission_result != SUCCESS) {
reply.WriteErrors(user_result);
reply.Done();
return;
}
reply.WriteErrors("");
reply.Done();
8章 巨大な式を分割する
説明変数
式を簡単に分割するには、式を表す変数を使えばいい。
この変数を説明変数という。
if line.split(':')[0].strip() == "root":
...
説明変数を使うと、以下のようになる。
username = line.split(':')[0].strip()
if username == "root":
...
短絡評価の悪用
短絡評価は、ブール演算子などでよく行われる。例えば、if(a || b)のaがtrueなら、bは評価されない。一見便利そうだけど、悪用すると複雑なロジックになる。
assert((!(bucket = FindBucket(key))) || !bucket->IsOccupied());
「このキーのバケツを取得する。バケツがnullではなかったら、中身が入ってないかを確認する」と言う意味。
これを以下のコードと比較してみる。
bucket = FindBucket(key);
if(bucket != NULL) assert(!bucket->IsOccupied());
2行になったけど、前者より理解しやすくなっただろう。
ならば、なぜ1行で書こうとするのか?
著者曰く、「自分は頭がいい」と思っていたらしい。その時はロジックを簡潔なコードに落とし込むことに一種の喜びを感じたと言う。
(ふむふむ、自分もそういう癖がたまにあるなと反省)
それよりもあとで他の人がコードを読むときにわかりやすいかどうかという視点を忘れずに持ち続けたいところ。
9章 変数と読みやすさ
変数を適当に使うと、プログラムが理解しにくくなる。
以下はそれに関する3つの問題
- 変数が多いと変数を追跡するのが難しくなる
- 変数のスコープが大きいとスコープを把握する時間が長くなる
- 変数が頻繁に変更されると現在の値を把握するのが難しくなる
これらの問題にどう対処するか?
変数を削除する
以下はpythonのコードにある変数now
now = datetime.datetime.now()
root_message.last_view_time = now
nowを使う意味はあるか?
意味がない理由を以下に挙げる。
- 複雑な式を分割していない
- より明確になってない。datetime.datetime.now()のままでも十分明確
- 一度しか使っていないので、重複コードの削除になってない
以下のようにnowがなくても理解できる
root_message.last_view_time = datetime.datetime.now()
変数のスコープを縮める
メンバ変数と言うのは、クラスの中で小さなグローバル変数になっているとも言える。以下の変数str_
はその例。
大きなクラスでは、すべてのメンバ変数を追跡したり、どのメソッドが変数を変更しているかを把握するのは難しい。
class LargeClass {
string str_;
void Method1() {
str_ = ...;
Method2();
}
void Method2() {
// str_を使っている
}
// str_を使っていないメソッドがたくさんある
};
上記のstr_をローカル変数にしてみると...
class LargeClass {
void Method1() {
str_ = ...;
Method2(str);
}
void Method2(string str) {
// str_を使っている
}
// その他メソッドはstrが見えない
};
このようにstr_が使用されるスコープを狭めることで、コードの全体の流れが把握しやすくなる。
JavaScriptで「プライベート」変数を作る
submitted = false; // グローバル変数
const submit_form = function(form_name) {
if(submitted) {
return; //二重投稿禁止
}
...
submitted = true;
};
submmited
のようなグローバル変数は、コードを読む人を不安にさせる。
それはsubmit_form()
関数だけで使っているように見えるけど、他のファイルからグローバル変数submitted
を使っている可能性も考えられるからだ。
この問題を回避するためには、変数submitted
をクロージャを包めばいい。
const submit_form = (function() {
const submitted = false; // 注意:以下の関数からしかアクセスされない
return function(form_name) {
if(submitted) {
return; //二重投稿禁止
}
...
submitted = true;
};
}());
上記の最終行の括弧で外側の無名関数が即時に実行されて、内側の関数を返している。
内側の関数だけがアクセスできるプライベートスコープを作れる。
これで読み手は「submittedが使用される範囲」や同じ名前のグローバル変数との衝突を気にすることが減る。
まとめ
- 制御フローを読みやすくするために肯定形・単純な条件・目立つ条件を先に書く
- 巨大な式は分割する
- 不要な変数は削除する
- 変数のスコープをできるだけ小さくする