こんばんは。マル太です。
前回は、リーダブルコードの第6章「コメントは正確に、簡潔に」についてまとめました。
今回は、第7章「制御フローを読みやすくする」についてまとめます。
この第7章あたりからコード例が増え、理解するのが難しくなってきました。
そこで、今回は特に「制御フローを読みやすくする」ための具体的なテクニックに焦点を当て、分かりやすく解説していきます。
同じように感じている方の参考になれば幸いです。
制御フローを読みやすくする重要性
コードの制御フロー(if文やループなど)は、プログラムの動作を決定づける重要な部分です。
制御フローが読みづらいと、コード全体の理解が難しくなり、バグの発生原因にも繋がります。
制御フローを読みやすくするためのポイント
1. 条件式の引数の並び順
条件式は、左側に「変化しやすい値」、右側に「変化しにくい値」 を置くようにしましょう。
こうすることで、式の意図がより明確になります。
if (count > 100) {
// 処理
}
if (100 < count) {
// 処理
}
count
はプログラムの実行中に変化する可能性がありますが、100
は固定値です。
変化しやすい値を左側に置くことで、「count
が100
を超えた時に」という意図がより明確になります。
2. if/elseブロックの並び順
if/elseブロックは、「肯定形を先」に、「否定形を後」に書くようにしましょう。
また、「単純な条件を先」に、「複雑な条件を後」に書くことで、コードの流れが理解しやすくなります。
※「肯定形を先」に、「否定形を後」に書く例⤵️
if (status == "success") {
// 成功時の処理
} else {
// エラー処理
}
if (status != "success") {
// エラー処理
} else {
// 成功時の処理
}
肯定形を先に書くことで、コードの流れが自然になり、理解しやすくなります。
※「単純な条件を先」に、「複雑な条件を後」に書く例⤵️
if (value > 0) {
// 単純な条件を満たす場合の処理
} else if (value > 10 && value < 100 && (value % 2 == 0 || value % 3 == 0)) {
// 複雑な条件を満たす場合の処理
}
if (value > 10 && value < 100 && (value % 2 == 0 || value % 3 == 0)) {
// 複雑な条件を満たす場合の処理
} else if (value > 0) {
// 単純な条件を満たす場合の処理
}
単純な条件を先に書くことで、コードの構造が把握しやすくなります。
(複雑な条件は後でじっくり読みましょう。)
3. 三項演算子
三項演算子は、簡潔な条件分岐にのみ使用しましょう。
複雑な条件式に使うと、コードが読みにくくなってしまいます。
※良い例⤵️
int max;
if (a > b) {
max = a;
} else {
max = b;
}
上記のif文を、三項演算子で書くと以下のようになります。
int max = (a > b) ? a : b;
シンプルな条件分岐であれば、三項演算子を使うことでコードを簡潔に記述できますね。
※悪い例⤵️
String message;
if (score >= 90) {
message = "素晴らしい!";
} else if (score >= 60) {
message = "合格です!";
} else {
message = "不合格です...";
}
上記のif文を、三項演算子をネストして使うことで1行で書くことができます。
String message = (score >= 90) ? "素晴らしい!" : ((score >= 60) ? "合格です!" : "不合格です...");
しかし逆にコードが読みにくくなってしまいますね。
これなら初めのif文の方が読みやすいです
4. do/whileループを避ける
do/whileループは、条件判定がループの最後に行われるため、コードの流れが分かりにくくなる場合があります。
可能な限り、whileループを使用しましょう。
int[] numbers = {1, 2, 3, 4, 5};
int i = 0;
do {
System.out.println(numbers[i]);
i++;
} while (i < numbers.length);
int[] numbers = {1, 2, 3, 4, 5};
int i = 0;
while (i < numbers.length) {
System.out.println(numbers[i]);
i++;
}
5. 関数から早く返す
関数の処理途中で条件を満たしたら、早めにreturn文で関数を終了させることで、ネストを浅くし、コードを読みやすくすることができます。
boolean isValid(User user) {
if (user == null) {
return false;
}
if (user.getAge() < 20) {
return false;
}
if (!user.hasValidLicense()) {
return false;
}
return true;
}
boolean isValid(User user) {
if (user != null) {
if (user.getAge() >= 20) {
if (user.hasValidLicense()) {
return true;
} else {
return false;
}
} else {
return false;
}
} else {
return false;
}
}
悪い例では、ネストが深くなっており、コードの流れを追いにくくなっています。
良い例では、条件を満たさない場合にreturn false;
で関数を早期に終了させているため、ネストが浅くなり、コードが読みやすくなっています。
6. 悪名高きgoto
goto文は、コードの流れを複雑にするため、使用を避けましょう。
(読みにくいコードのことを「スパゲッティコード」と言うみたいです。)
代わりに、「構造化プログラミング」の手法を用いてコードを記述しましょう。
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
System.out.println("ループ終了");
int i = 0;
label1:
if (i >= 10) {
goto label2;
}
System.out.println(i);
i++;
goto label1;
label2:
System.out.println("ループ終了");
悪い例では、goto
文を使ってループ処理を実現しています。
しかし、goto
文によってコードの流れが複雑になり、どこでループが終了するのか分かりにくくなっています。
良い例では、for
文を使ってループ処理を実現しています。
for
文はループの開始条件、終了条件、増減式を明確に記述できるため、コードの流れが分かりやすくなっています。
※構造化プログラミングって?
構造化プログラミングとは、プログラムを分かりやすく、保守しやすいものにするためのプログラミング手法です。
具体的には、プログラムを 「順次」「選択」「反復」 という3つの基本的な構造で組み立てることで、コードの流れを整理し、可読性を高めます。
3つの基本構造
1.順次: 上から順番に処理を実行していく構造。
2.選択: 条件によって処理を分岐させる構造。(if文など)
3.反復: 条件が満たされるまで処理を繰り返す構造。(for文、while文など)
構造化プログラミング以前は、goto文を使ってプログラムの流れを制御するのが一般的だったようです。
7. ネストを浅くする
ネストが深くなると、コードの可読性が低下します。
早期リターンやループ内部の処理を関数化することで、ネストを浅くすることができます。
まとめ
今回は、「リーダブルコード」第7章「制御フローを読みやすくする」の内容をまとめました。
7章で紹介されているポイントを参考に、より質の高いコードを書けるように心がけていきたいですね。
次回は、第8章「巨大な式を分割する」についてまとめます。