実践!ESLint「no-lonely-if」の具体例と効果的な書き換え方法

2025-05-27

"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" ルールの一般的なエラー

このルールによって発生するエラーは、基本的に以下のパターンに集約されます。

  1. else ブロック内に単独の if ステートメントがある場合
    最も一般的なエラーで、以下のようなコードが指摘されます。

    if (condition1) {
      // 処理 A
    } else {
      if (condition2) { // <- ここで no-lonely-if が発生
        // 処理 B
      }
    }
    

    ESLint は、これを else if に書き換えることを推奨します。

    if (condition1) {
      // 処理 A
    } else if (condition2) {
      // 処理 B
    }
    
  2. 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" エラーに直面した場合のトラブルシューティングは、非常にシンプルです。

  1. ESLint のエラーメッセージを確認する
    ESLint は、どのファイル、どの行で no-lonely-if が発生しているかを明確に示します。通常は以下のようなメッセージが表示されます。

    [ファイル名]:[行番号]:[列番号] Unexpected 'if' as the only statement in an 'else' block. (no-lonely-if)
    

    このメッセージを基に、問題の箇所を特定します。

  2. 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.");
      }
    }
    
  3. 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 のコメントを追加してルールを一時的に無効にすることも可能です。しかし、これは最終手段であり、コードの可読性を損なわないように極力避けるべきです。

  4. 自動修正機能 (--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 に書き換えるだけでなく、上記のような代替手法を検討することで、より表現豊かで、保守性の高いコードを書くことができます。