はじめに
こんにちは
リーダブルコードの社内向け輪読会の資料です。
以下記事の続きです。
今回は7~9章をやります。
第7章 制御フローを読みやすくする
第7章ではコードを読んでいて、if文の制御やループ処理などをどの様に意識すれば読みやすくなるのかといった話がされています。
条件式の引数の並び順
if (length >= 10)
if (10 <= length)
上記二つの条件式の場合どちらが読みやすいかと言われれば、
前者の if (length >= 10)
である。
一方で、下記のパターンはどうだろうか?
while (bytes_received < bytes_expected)
while (bytes_expected > bytes_received)
こちらも前者の方が読みやすい方が多いのではないだろうか?
これには我々が普段下記のような指針を無意識に使っているからである。
左辺 | 右辺 |
---|---|
「調査対象」の式。変化する | 「比較対象」の式。あまり変化しない。 |
if/else ブロックの並び順
if/elseのブロックは開発者が順番を自由に決める事が出来る。
if (a == b) {
//肯定条件ケース
} else {
//否定条件ケース
}
if (a != b) {
//否定条件ケース
} else {
//肯定条件ケース
}
上記二つは共に同じ内容だが、どちらの方が読みやすいだろうか?
条件分の並び順には優劣が存在する。
- 条件は否定系よりも肯定形を使う
-
if (!test)
ではなく、if (test)
を使用する
-
- 単純な条件を先に書く
- if/elseが同一画面に収まって表示されるので視認性が上がる
- 関心を引く条件や目立つ条件を先に書く
- 目立つ条件文が後に来ると、その内容に意識を奪われてしまう為
読み手を意識して、複雑なコードにしないように心がける
今回7章に記載された2つの例を記載したが、リーダブルコード本書全体に共通して言える事だが、
「読み手を意識して読みやすくすることを心がけよ」が繰り返し言われている。
条件分の順番や、if文内のネストを深くしすぎない工夫などをして複雑にしない事が述べられている。
コードを書くうえでどうしても複雑にならざるを得ない場合もあるかもしれないが、そのコードをコミットする前に
「もう少し上手くできないか?」、「もう少し分かりやすくならないか?」を1度立ち止まって考えてからコミットする様に心がけることが大事だと思います。
第8章 巨大な式を分割する
人間は一度に3~4の「もの」までしか考えられないそうだ。
コードの塊が大きくなればなるほど考えなければならない事も増え、それだけ理解が難しくなる。
この章では、コードの塊をなるべく分割して、人間が考えられる大きさに分割することを趣旨にしている章になります。
要約変数
式を変数に代入しておくと便利な場合がある。
大きなコードや、確認や考えるのに時間がかかるコードを小さな名前に置き換えて、管理や把握を簡単にする変数のことを要約変数と呼びます。
if (request.user.id == document.owner_id) {
// ユーザーはこの文章を編集できる
}
if (request.user.id != document.owner_id) {
// 文章は読み取り専用
}
final boolean user_owner_document = (requenst.user.id == document.owner_id);
if (user_owner_document) {
// ユーザーはこの文章を編集できる
}
if (!user_owner_document) {
// 文章は読み取り専用
}
複雑なロジックと格闘する
A 始点:1 終点:3
B 始点:3 終点:6
C 始点:6 終点:8
D 始点:2 終点:8
上記のような範囲線があったとする。
始点の値は範囲内に含める(始点 >= 値)が、
終点は値は範囲内に含めない(終点 < 値)
上記条件の時に各変数の始点終点で重なりがあるかどうかのチェックロジックを書こうとすると考慮しなければならない点が多くなってしまう。
この様な場合は一度立ち止まって、他の手法がないかを検討してみることが重要である。
複雑な条件文になった場合、もっと簡単な方法が無いかを考えてみる。
一つの方法としては「反対」から問題を解決してみるという手がある。
例えば、下記の様な手段である。
- 配列を逆順にイテレートしてみる
- データを後ろから挿入してみる
いつもと反対の事や、今のロジックを逆転の発想をしてみることである。
今回のロジックであると、「重ならない」条件を考えてみることになる。
- 一方の範囲の終点が、ある範囲の始点よりも前にある場合
- 一方の範囲の始点が、ある範囲の終点よりも後にある場合
この二つの条件が満たされたときに範囲線は重ならないことになる。
bool Range::OverlapsWith(Range other) {
if (other.end <= begin) return false; //一方の終点が、この始点よりも前にある
if (other.begin >= end) return false; //一方の始点が、この終点よりも後にある
return true; // 残ったものは重なっている
}
巨大な式を1度に理解しようとすると難しいため、巨大な式を分割して分かりやすい方法が紹介されていた。
大きな式の値を保持する説明変数や、要約変数を利用するメリットとしては下記が挙げられる
- 巨大な式を分割できる
- 簡潔な名前で式を説明することで、コードを文書化する事ができる
- コードの主要な「概念」を読み手が認識しやすくする
細かく分割することで、コードの行数は増えるかもしれないが可読性が上がるのであれば是非分割をして、分かりやすいコードを心がけて欲しい。
第9章 変数と読みやすさ
この章では、変数を適当に使うとコードが理解しにくくなるという事を話しています。
具体的には下記の3つの課題に取り組んでいく
- 変数が多いと変数を追跡するのが難しくなる
- 変数のスコープが大きいとスコープを把握する時間が長くなる
- 変数が頻繁に変更されると現在の値を把握するのが難しくなる
変数を削除する
役に立たない一時変数
now = datetime.datetime.now
root_message.last_view_time = now
上記のコードでnowは使う必要がない。
必要がない理由としては
- 複雑な指揮を分割していない
- datetime.datetime.now()のままでも十分に明確である
- 一度しか使っていないので、常服コードの削除になっていない
上記の様な変数は、コードを編集した「残骸」である。
「残骸」は最終的にはきちんと掃除すべきである。
制御フロー変数を削除する
boolean done = false;
while (/* 条件 * / && !done){
...
if (...) {
done = true;
continue;
}
}
上記のdoneの変数は制御変数として利用されているが、
そもそもこのような変数を利用しなくても制御ができる為、削除すべきである。
while (/* 条件 * /){
...
if (...) {
break;
}
}
変数のスコープを縮める
グローバル変数は極力避ける理由は、どこでどの様に使われるのかを追跡するのが難しいからである。
また、名前空間を汚染する可能性があり、ローカル変数と衝突する可能性もある。
(グローバル変数を利用しているのか、ローカル変数を利用しているのか判別しづらくなる等)
その為、変数のスコープは極力縮めるのが良いとされている。
スコープを縮めるためには、アクセスをできるだけ制限して、変数のことが「見えてしまう」コードを減らすのがいいとされている。
これは、1度に考えなければならない変数を減らせる為である。
上記から、なるべくローカル変数を利用するか、指定の関数内で宣言しプライベートな変数としてスコープを縮めることが分かりやすいコードには重要である。
変数は1度だけ書き込む
変数が頻繁に変更され続けるコードは読みづらく、理解しにくい。これは値を追跡する難易度が格段に上がってしまう為である。
その為、変数は1度だけ書き込むのが良い。
変更されない変数は、コードを読む上で多くの事を考えずに済み読みやすくなる為だからだ。
おわりに
今回もリーダブルにできましたね。
次回もリーダブルにしていきましょう。
参考文献
リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック p.83~109
URL:https://www.oreilly.co.jp/books/9784873115658/