リーダブルコードの7章「制御フローを読みやすくする」を読んだので、内容をまとめています。
条件やループなどの制御フローがないコードは読みやすい。
他の場所に飛んだり枝分かれするのは複雑でコードが分かりにくくなってしまう
忙しい人向けに、まとめから
鍵となる考え
条件やループなどの制御フローはできるだけ「自然」にする。コードの読み手が立ち止まったり読み返したりしないように書く。
具体的なポイント
- 条件式の引数の並び順
- if/elseブロックの並び順
- 三項演算子
- do/whileループを避ける
- 関数から早く返す
- 悪名高きgoto
- ネストを浅くする
- 実行が流れを追えるように
7章の内容
条件式の引数の並び順
if(lenght >= 10)
の方が、if(10 <= length)
よりも読みやすい。
なぜか?
以下のような指針が使える。
左側 | 右側 |
---|---|
「調査対象」の式 | 「比較対象」の式 |
変化する | あまり変化しない |
この指針は英語の用法と似ていて、「もし君が20歳以上ならば」というの方が自然な言い回しになる。
「もし20年が君の年齢以下ならば」は不自然。
if/elseブロックの並び順
if/else文のブロックは、並び順を自由に変えることができる。
だが、この並び順には優劣がある。
- 条件は否定系よりも肯定系を使う。例えば、if(!debug)ではなく、if(debug)を使う
- 単純な条件を先に書く。ifとelseが同じ画面に表示されて見やすい
- 関心を引く条件や目立つ条件は先に書く
具体例 URLにクエリパラメータexpand_allが含まれているかどうか判断して、responseを構築する
「ピンクの象のことを考えないように」と言われてに、ピンクの象を考えてしまう
改善前
if(!url.HasQueryParameter("expand_all"){
response.Render(items);
}else{
xxxxxxxx;
}
↓
改善後
if(url.HasQueryParameter("expand_all"){
xxxxxxxx;
}else{
response.Render(items);
}
三項演算子
簡潔に書けるが、読みやすさの点で議論の余地がある。
鍵となる考え
行数を短くするよりも、他の人が理解するのにかかる時間を短くする
基本的にはif/elseを使い、三項演算子はそれによって簡潔になるときにだけ使う。
具体例
改善前
return exponent >= 0 ? mantissa * (1 << exponent) : mantissa / ( 1 << -exponent );
↓
改善後
if (exponent >= 0 {
return mantisa * ( 1 << exponent );
}else{
return mantissa / ( 1 << -exponent);
}
do/whileループを避ける
関数から早く返す
「ガード節」を使わずに実装するとすごく不自然な実装になるので、関数から早く返すことは望ましい。
参照:ガード節
具体例
public boolean Contains(String str, String substr){
if (str == null || substr == nul) return false;
if (substr.equals("")) return true;
省略
}
ネストを浅くする
ネストの深いコードは理解しにくい。ネストが深くなると、読み手は「精神的スタック」に条件をプッシュしないといけない。
ネストが増える仕組み
新しいコードを追加する人にとっては、挿入したコードは新鮮で「関心を引く」コードになる。
一方で後からそのコードを読む人にとっては、こうした文脈や意図は失われてしまう。
鍵となる考え
変更する時にはコードを新鮮な目で見る。一歩下がって全体を見る。
早めに返してネストを削除する
ネストを削除するには、「失敗ケース」をできるだけ早めに関数から返す。
改善前
if(user_result==SUCCESS){
if(permission_result != SUCCESS){
reply.WriteErrors("error reading permission");
reply.Done();
return;
}
reply.WriteErrors("");
}else{
reply.WriteErrors(user_result)
}
reply.Done();
↓
改善後
if(user_result != SUCCESS){
reply.WriteErrors(user_result)
reply.Done();
return;
}
if(permission_result != SUCCESS){
reply.WriteErrors(permission_result);
reply.Done();
return;
}
reply.WriteErrors("");
reply.Done();
実行が流れを追えるように
ここまではループや条件などのジャンプを簡単に読める方法など、低レベルの制御フローについて説明されてきた。
でもプログラムの高レベルの「流れ」についても考える必要がある。
main()から出発して、心の中でコードを追っていく、関数を次々に呼び出し、プログラムが終了するまで続けていく。プログラミング言語やライブラリには、コードを「舞台裏」で実行する構成要素も存在する。
構成要素 | 高レベルの流れが不明瞭になる理由 |
---|---|
スレッド | どのコードがいつ実行されるのかよく分からない |
シグナル/割り込みハンドラ | 他のコードが実行される可能性がある |
例外 | いろんな関数世に出しが終了しようとする |
関数ポインタと無名関数 | コンパイル実行時に判別できないので、どのコードが実行されるのか分からない |
仮想メソッド | object.vrtualMethod()は未知のサブクラスのコードを呼び出す可能性がある |
これらの構成要素を使うことで、コードが読みやすくなったり、冗長性が低くなる。
一方で使いすぎるとコードの行方を見失うことがあるので、コード全体に占める割合を大きくしないことが大事。
引用
リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック (Theory in practice)