実践!ESLint「no-lonely-if」の具体例と効果的な書き換え方法
"no-lonely-if" とは?
このルールは、else
ブロックの中に、他のステートメントが一切なく、if
ステートメントが単独で存在することを禁止します。
なぜこのルールがあるのか?
else { if (...) { ... } }
のようなコードは、多くの場合、else if (...) { ... }
の形式に書き換えることで、より簡潔で分かりやすくなります。コードのネストが深くなるのを避け、論理の流れを直線的にすることで、理解しやすくなるのが主な理由です。
具体的な例
悪い例 (このルールで指摘されるコード)
if (foo) {
// foo の場合の処理
} else {
if (bar) { // ここが "lonely if" です
// bar の場合の処理
}
}
このコードは、else
ブロックの中にif (bar)
が単独で存在しているため、"no-lonely-if" ルールに違反します。
良い例 (推奨される書き換え方)
if (foo) {
// foo の場合の処理
} else if (bar) {
// bar の場合の処理
}
このように else if
を使うことで、条件分岐がより明確になり、コードの構造がフラットになります。
別の悪い例
if (condition) {
// ...
} else {
if (anotherCondition) {
// ...
} else {
// ...
}
}
これも else
の中に単独の if/else
ブロックがあるため、推奨されません。
別の良い例
if (condition) {
// ...
} else if (anotherCondition) {
// ...
} else {
// ...
}
例外
ただし、else
ブロックの中に if
以外の他のステートメントが存在する場合は、このルールの対象外となります。
if (condition) {
// ...
} else {
doSomething(); // if 以外のステートメントがある
if (anotherCondition) {
// ...
}
}
この場合は else
ブロックの中に doSomething()
があるため、if (anotherCondition)
は "lonely if" とはみなされず、ルール違反にはなりません。
"no-lonely-if" ルールの一般的なエラー
このルールによって発生するエラーは、基本的に以下のパターンに集約されます。
-
else ブロック内に単独の if ステートメントがある場合
最も一般的なエラーで、以下のようなコードが指摘されます。if (condition1) { // 処理 A } else { if (condition2) { // <- ここで no-lonely-if が発生 // 処理 B } }
ESLint は、これを
else if
に書き換えることを推奨します。if (condition1) { // 処理 A } else if (condition2) { // 処理 B }
-
else ブロック内に if/else ステートメントが単独で存在する場合
if
ステートメントにelse
ブロックが付属している場合でも、それがelse
ブロック内に単独で存在すれば指摘の対象となります。if (condition1) { // 処理 A } else { if (condition2) { // <- ここで no-lonely-if が発生 // 処理 B } else { // 処理 C } }
これも
else if
に書き換えるのが一般的です。if (condition1) { // 処理 A } else if (condition2) { // 処理 B } else { // 処理 C }
トラブルシューティング
"no-lonely-if" エラーに直面した場合のトラブルシューティングは、非常にシンプルです。
-
ESLint のエラーメッセージを確認する
ESLint は、どのファイル、どの行でno-lonely-if
が発生しているかを明確に示します。通常は以下のようなメッセージが表示されます。[ファイル名]:[行番号]:[列番号] Unexpected 'if' as the only statement in an 'else' block. (no-lonely-if)
このメッセージを基に、問題の箇所を特定します。
-
else if に書き換える
ほとんどの場合、エラーとなっているelse { if (...) }
の部分をelse if (...)
に書き換えることで解決します。例
元のコード (エラー)
function checkValue(value) { if (value === null) { console.log("Value is null."); } else { if (typeof value === 'undefined') { console.log("Value is undefined."); } } }
function checkValue(value) { if (value === null) { console.log("Value is null."); } else if (typeof value === 'undefined') { // else if に変更 console.log("Value is undefined."); } }
-
else ブロック内に他の処理があるか確認する
稀に、else
ブロック内にif
ステートメント以外にも処理があるにも関わらず、ESLint が誤ってno-lonely-if
を報告する場合があります(これはESLintのバグであるか、または意図しないコードの書き方によるものかもしれません)。if (conditionA) { // ... } else { console.log("Something happened."); // if 以外の処理 if (conditionB) { // この if は lonely ではない // ... } }
この場合、ESLint は通常エラーを報告しませんが、もし報告された場合は、コードの意図を再確認し、本当に
else if
にできないか検討します。もしできない場合は、その行に// eslint-disable-next-line no-lonely-if
のコメントを追加してルールを一時的に無効にすることも可能です。しかし、これは最終手段であり、コードの可読性を損なわないように極力避けるべきです。 -
自動修正機能 (--fix) を利用する
多くの ESLint ルールと同様に、no-lonely-if
も--fix
オプションで自動修正が可能です。 コマンドラインで ESLint を実行する際に、次のように--fix
を追加します。eslint your_file.js --fix
または、プロジェクト全体を対象にする場合は
eslint . --fix
これにより、ESLint が自動的に
else { if (...) }
をelse if (...)
に書き換えてくれます。ほとんどの場合、このオプションで問題は解決します。
- 「else の中に if があったら全てダメ」ではない
このルールが禁止するのは、else
ブロックの中に 単独でif
ステートメントがある場合です。else
ブロックの中にif
ステートメントがあり、かつ他のステートメント(変数宣言、関数呼び出しなど)も存在する場合は、no-lonely-if
の対象外です。これは、else if
に書き換えられないからです。if (foo) { // ... } else { let result = calculateBar(); // 他のステートメント if (result > 10) { // これは no-lonely-if ではない // ... } }
基本的な違反と修正
違反例 (Bad)
// bad.js
function checkNumber(num) {
if (num > 0) {
console.log("正の数です。");
} else {
// ここが "lonely if" の対象となる
if (num < 0) {
console.log("負の数です。");
}
}
}
checkNumber(5);
checkNumber(-3);
checkNumber(0);
このコードを実行すると、ESLint は以下のようなエラーを報告します。
/path/to/your/project/bad.js
7:5 error Unexpected 'if' as the only statement in an 'else' block no-lonely-if
1 problem (1 error, 0 warnings)
修正例 (Good)
else if
を使用して、より簡潔に記述します。
// good.js
function checkNumber(num) {
if (num > 0) {
console.log("正の数です。");
} else if (num < 0) { // else if に変更
console.log("負の数です。");
}
}
checkNumber(5);
checkNumber(-3);
checkNumber(0); // 何も表示されないが、これは元のコードの意図通り
else ブロックにさらに else が続く場合
違反例 (Bad)
if-else
のブロックが else
の中に単独で存在するケースです。
// bad2.js
function categorizeAge(age) {
if (age < 13) {
console.log("子供です。");
} else {
// この if/else ブロックが "lonely if" の対象
if (age >= 13 && age < 18) {
console.log("ティーンエイジャーです。");
} else {
console.log("大人です。");
}
}
}
categorizeAge(10);
categorizeAge(15);
categorizeAge(25);
修正例 (Good)
複数の else if
を連ねることで、コードの流れがより明確になります。
// good2.js
function categorizeAge(age) {
if (age < 13) {
console.log("子供です。");
} else if (age >= 13 && age < 18) { // else if に変更
console.log("ティーンエイジャーです。");
} else { // 最後の else はそのまま
console.log("大人です。");
}
}
categorizeAge(10);
categorizeAge(15);
categorizeAge(25);
このルールが適用されないケースです。else
ブロックの中に if
以外のステートメントがある場合、if
は「単独」ではないとみなされ、no-lonely-if
の対象外となります。
違反ではない例 (Not Bad)
// not_bad.js
function processStatus(status) {
if (status === "active") {
console.log("ステータスはアクティブです。");
} else {
// else ブロック内に他の処理がある
console.log("非アクティブなステータスです。詳細を確認します。"); // この行があるため "lonely if" ではない
if (status === "inactive") {
console.log("詳細: ステータスは非アクティブです。");
} else {
console.log("詳細: ステータスは不明です。");
}
}
}
processStatus("active");
processStatus("inactive");
processStatus("pending");
このコードでは、else
ブロックの最初に console.log("非アクティブなステータスです。詳細を確認します。");
というステートメントが存在するため、その後の if (status === "inactive") { ... }
は単独の if
とはみなされません。したがって、ESLint は no-lonely-if
エラーを報告しません。
ここでは、"no-lonely-if" の代替となるプログラミング手法をいくつかご紹介します。
switch ステートメントを使用する
複数の条件分岐が特定の変数(または式)の値に基づいて行われる場合、if-else if-else
の連鎖よりも switch
ステートメントの方が適していることがあります。これにより、コードの構造がフラットになり、各ケースが明確になります。
"no-lonely-if" 違反の可能性がある例
function getDayName(dayNumber) {
if (dayNumber === 1) {
return "月曜日";
} else {
if (dayNumber === 2) { // no-lonely-if の候補
return "火曜日";
} else {
if (dayNumber === 3) { // no-lonely-if の候補
return "水曜日";
} else {
return "不明";
}
}
}
}
switch ステートメントによる代替
function getDayName(dayNumber) {
switch (dayNumber) {
case 1:
return "月曜日";
case 2:
return "火曜日";
case 3:
return "水曜日";
default:
return "不明";
}
}
この例では、switch
を使うことで、各条件が dayNumber
の値に直接マッピングされ、非常に読みやすくなります。
オブジェクトリテラル(または Map)を使用する
特に、値に基づいて結果をマッピングするような場合、条件分岐をせずにオブジェクトリテラル(または Map
)を使ってルックアップテーブルを作成する方が、非常に効率的で簡潔になります。
"no-lonely-if" 違反の可能性がある例
function getStatusMessage(statusCode) {
if (statusCode === 200) {
return "OK";
} else {
if (statusCode === 400) { // no-lonely-if の候補
return "Bad Request";
} else {
if (statusCode === 404) { // no-lonely-if の候補
return "Not Found";
} else {
return "Unknown Error";
}
}
}
}
オブジェクトリテラルによる代替
const statusMessages = {
200: "OK",
400: "Bad Request",
404: "Not Found",
};
function getStatusMessage(statusCode) {
return statusMessages[statusCode] || "Unknown Error";
}
この方法は、条件の数が増えるほど、if-else if
の連鎖よりも遥かに管理しやすくなります。
関数の早期リターン(ガード節)
複数の条件チェックがあり、ある条件が満たされない場合にすぐに処理を終了させたい(またはエラーを投げたい)場合、早期リターン(またはガード節)を使用することで、コードのネストを減らし、読みやすくすることができます。
"no-lonely-if" 違反の可能性がある例
function processUserData(user) {
if (user) {
if (user.id) { // no-lonely-if の候補
if (user.name) { // no-lonely-if の候補
// ユーザーIDと名前がある場合の処理
console.log(`Processing user: ${user.name} (${user.id})`);
return true;
} else {
console.error("ユーザー名がありません。");
return false;
}
} else {
console.error("ユーザーIDがありません。");
return false;
}
} else {
console.error("ユーザーデータがありません。");
return false;
}
}
早期リターン(ガード節)による代替
function processUserData(user) {
if (!user) {
console.error("ユーザーデータがありません。");
return false;
}
if (!user.id) {
console.error("ユーザーIDがありません。");
return false;
}
if (!user.name) {
console.error("ユーザー名がありません。");
return false;
}
// すべてのチェックを通過した場合のみ、ここに到達
console.log(`Processing user: ${user.name} (${user.id})`);
return true;
}
この方法では、各条件が個別にチェックされ、満たされない場合はすぐに関数が終了します。これにより、コードのネストが解消され、読みやすさが向上します。
非常に単純な条件で、特定の処理を実行するかどうかを決定する場合、論理演算子 (&&
や ||
) を利用して、より簡潔なコードを書くことができます。
"no-lonely-if" 違反の可能性がある例
function greetUser(user) {
if (user && user.isLoggedIn) {
if (user.name) { // no-lonely-if の候補
console.log(`こんにちは、${user.name}さん!`);
} else {
console.log("こんにちは、ゲストさん!");
}
}
}
論理演算子による代替
function greetUser(user) {
if (user && user.isLoggedIn) {
console.log(`こんにちは、${user.name || "ゲスト"}さん!`);
}
}
この例では、user.name || "ゲスト"
の部分で、user.name
が falsy な値(null
, undefined
, 空文字列など)であれば "ゲスト"
を使用するようにしています。
ESLint の "no-lonely-if" ルールは、「なぜその形が悪いのか」を理解するための良い出発点です。しかし、単に else if
に書き換えるだけでなく、上記のような代替手法を検討することで、より表現豊かで、保守性の高いコードを書くことができます。