【初心者向け】ESLint no-else-returnで学ぶJavaScriptの効率的な制御フロー

2025-05-27

ESLintの「no-else-return」ルールは、if文の中でreturnthrowbreakcontinueなどの制御フローを中断するステートメントが使われた場合に、不必要なelseブロックを禁止するためのものです。

具体的には、以下のようなコードパターンをESLintが警告します。

function example(x) {
  if (x > 10) {
    return true;
  } else {
    return false;
  }
}

このコードは、ifブロックの中でreturn true;によって関数が終了するため、elseブロックは実行されることがありません。このような場合、elseブロックは冗長であり、コードの可読性を低下させる可能性があります。

「no-else-return」ルールが推奨する書き方は以下の通りです。

function example(x) {
  if (x > 10) {
    return true;
  }
  return false;
}

このように、if条件が満たされない場合は、ifブロックの外にあるreturn false;が実行されます。

  • 潜在的なバグの防止
    複雑なネストされたif-else構造を避けることで、論理的な誤りが発生する可能性を減らします。
  • 保守性の向上
    短く、より直接的なコードは、将来的な変更やデバッグを容易にします。
  • コードの可読性の向上
    不必要なelseブロックを削除することで、コードの流れがよりシンプルになり、理解しやすくなります。


一般的なエラー

  1. 単純なif-else構造
    最も一般的なのは、先ほど例に挙げたような、ifブロック内でreturnなどの制御フロー中断ステートメントを使用しているにもかかわらず、不要なelseブロックが存在する場合です。

    // エラー例
    function checkValue(value) {
      if (value > 0) {
        return "positive";
      } else {
        return "non-positive";
      }
    }
    

    トラブルシューティング
    elseブロックを削除し、ifブロックの後に続く処理として記述します。

    // 修正例
    function checkValue(value) {
      if (value > 0) {
        return "positive";
      }
      return "non-positive";
    }
    
  2. else ifとの混同
    else ifは、前のif条件が偽だった場合に別の条件を評価するために使用されます。else ifelseブロックとは異なる役割を持つため、「no-else-return」ルールはelse ifに対して警告を発することはありません。エラーが発生した場合は、意図したロジックがelse ifで表現できるかどうかを確認してください。

    function checkNumber(num) {
      if (num > 0) {
        return "positive";
      } else if (num < 0) {
        return "negative";
      } else {
        return "zero";
      }
    }
    // これはエラーになりません
    
  3. 早期returnと後続処理の意図
    まれに、ifブロック内で早期にreturnした後、elseブロック内で何らかの副作用(ログ出力など)を行いたい場合があります。しかし、「no-else-return」ルールはこのような構造も不要なelseと判断します。

    // エラーになる可能性
    function processData(data) {
      if (!data) {
        console.log("データが無効です。");
        return;
      } else {
        // 何らかのデータ処理
        const result = data.map(item => item * 2);
        return result;
      }
    }
    

    トラブルシューティング
    副作用を伴う処理は、ifブロックの前に記述するか、ifブロックの条件を反転させて早期returnしないように構造を変更することを検討します。

    // 修正例1:副作用を前に記述
    function processData(data) {
      if (!data) {
        console.log("データが無効です。");
        return;
      }
      const result = data.map(item => item * 2);
      return result;
    }
    
    // 修正例2:条件を反転
    function processData(data) {
      if (data) {
        const result = data.map(item => item * 2);
        return result;
      }
      console.log("データが無効です。");
      return;
    }
    
  4. 複雑なネスト
    複数のif-else文が深くネストされている場合、「no-else-return」ルールが意図しない警告を出すように見えることがあります。これは、コードの構造自体が複雑になっている兆候である可能性が高いため、リファクタリングを検討する良い機会です。

トラブルシューティング

  • ESLintのバージョン確認
    古いバージョンのESLintを使用している場合、バグや予期せぬ挙動が発生する可能性があります。ESLintおよび関連プラグインを最新バージョンにアップデートしてみることも有効なトラブルシューティング手段です。

  • 意図的なelseブロックの場合
    ごくまれに、コードの可読性や特定のロジックのために、あえてelseブロックを残したい場合があります。そのような場合は、ESLintのコメントディレクティブを使用して、特定行のルールを一時的に無効にすることができます。ただし、この方法は最終手段として慎重に使用するべきです。

    function someFunction(value) {
      if (value > 10) {
        return "greater";
      } else {
        // eslint-disable-next-line no-else-return
        return "less or equal";
      }
    }
    
  • ESLintの設定確認
    まず、.eslintrc.jsなどの設定ファイルで「no-else-return」ルールがどのように設定されているか確認します。警告 ( "warn" ) なのか、エラー ( "error" ) なのか、あるいは無効 ( "off" ) なのかを確認してください。

    // .eslintrc.js の例
    module.exports = {
      rules: {
        "no-else-return": "error", // エラーとして扱う
        // "no-else-return": "warn",  // 警告として扱う
        // "no-else-return": "off",   // 無効にする
      },
    };
    


単純な真偽値を返す関数

// ESLint: no-else-return エラー
function isPositive(num) {
  if (num > 0) {
    return true;
  } else {
    return false;
  }
}

// ESLint推奨:
function isPositiveRefactored(num) {
  if (num > 0) {
    return true;
  }
  return false;
}

この例では、if文の条件が真の場合にtrueを返し、偽の場合にelseブロックでfalseを返しています。しかし、ifブロック内でreturnしているため、elseブロックは不要です。リファクタリング後のコードでは、if条件が満たされない場合は、その後のreturn false;が実行されます。

文字列を返す関数

// ESLint: no-else-return エラー
function checkStatus(code) {
  if (code === 200) {
    return "成功";
  } else {
    return "失敗";
  }
}

// ESLint推奨:
function checkStatusRefactored(code) {
  if (code === 200) {
    return "成功";
  }
  return "失敗";
}

こちらも同様に、ifブロックで結果を返しているため、elseブロックは冗長です。リファクタリング後のコードは、より直接的に意図を表現しています。

throwステートメントを含む関数

// ESLint: no-else-return エラー
function validateInput(input) {
  if (!input) {
    throw new Error("入力が無効です。");
  } else {
    return "入力は有効です。";
  }
}

// ESLint推奨:
function validateInputRefactored(input) {
  if (!input) {
    throw new Error("入力が無効です。");
  }
  return "入力は有効です。";
}

ifブロック内で例外をthrowしている場合も、その時点で関数の実行は中断されるため、elseブロックは不要です。

早期リターンによるネストの削減

「no-else-return」は、ネストされたif-else文をよりフラットな構造にするのに役立つことがあります。

// ESLint: no-else-return エラー (潜在的)
function processItem(item) {
  if (item) {
    if (item.isValid) {
      // 何らかの処理
      return item.value * 2;
    } else {
      return 0;
    }
  } else {
    return null;
  }
}

// ESLint推奨: 早期リターンでネストを削減
function processItemRefactored(item) {
  if (!item) {
    return null;
  }
  if (!item.isValid) {
    return 0;
  }
  return item.value * 2;
}

この例では、リファクタリング後のコードでは、無効な条件を最初にチェックして早期にreturnすることで、ネストが浅くなり、コードの可読性が向上しています。直接的なelseブロックはありませんが、「no-else-return」の考え方は、不要なelseを避け、コードの流れを明確にするのに役立っています。

breakまたはcontinueを含むループ

ループ内でbreakcontinueを使用する場合も、「no-else-return」の考え方が適用されます。

// ESLint: no-else-return エラー (構造によっては)
function findFirstPositive(numbers) {
  for (let i = 0; i < numbers.length; i++) {
    if (numbers[i] > 0) {
      return numbers[i];
    } else {
      // ここに不要な処理を書かない
    }
  }
  return undefined;
}

// ESLint推奨:
function findFirstPositiveRefactored(numbers) {
  for (let i = 0; i < numbers.length; i++) {
    if (numbers[i] > 0) {
      return numbers[i];
    }
  }
  return undefined;
}

ループ内で条件が満たされた場合にreturnbreakなどで処理を中断する場合、elseブロックに特別な処理がないのであれば、省略するべきです。



早期リターン (Early Return)

これは最も一般的で推奨される代替手法です。if条件が特定のケースに合致した場合に、すぐにreturnすることで、その後のelseブロックを不要にします。

// no-else-return に違反する可能性
function getValue(flag) {
  if (flag) {
    return "true case";
  } else {
    return "false case";
  }
}

// 早期リターンを用いた代替
function getValueAlternative(flag) {
  if (flag) {
    return "true case";
  }
  return "false case";
}

この例では、flagが真の場合にすぐに値を返し、そうでない場合はifブロックの外にあるreturn文が実行されます。

三項演算子 (Ternary Operator)

単純な条件分岐で値を返す場合に、三項演算子を使うことでif-else構造を一行で表現できます。ただし、複雑な条件になると可読性が低下する可能性があるため、注意が必要です。

// no-else-return に違反する可能性
function getType(value) {
  if (typeof value === 'number') {
    return "number";
  } else {
    return "other";
  }
}

// 三項演算子を用いた代替
function getTypeAlternative(value) {
  return typeof value === 'number' ? "number" : "other";
}

変数への代入と条件分岐

結果を格納する変数を事前に宣言しておき、if文の中で条件に応じてその変数を代入する方法です。最後に変数の値を返します。

// no-else-return に違反する可能性
function getMessage(isValid) {
  if (isValid) {
    return "有効です";
  } else {
    return "無効です";
  }
}

// 変数への代入を用いた代替
function getMessageAlternative(isValid) {
  let message;
  if (isValid) {
    message = "有効です";
  } else {
    message = "無効です";
  }
  return message;
}

// より簡潔にした形 (早期リターンと組み合わせることも可能)
function getMessageAlternativeConcise(isValid) {
  let message = "無効です"; // デフォルト値を設定
  if (isValid) {
    message = "有効です";
  }
  return message;
}

最後の例では、デフォルト値を設定しておき、条件が満たされた場合にのみ値を更新しています。これにより、elseブロックを完全に排除できます。

switch文またはオブジェクトルックアップ

複数の条件分岐がある場合、if-else if-...-elseの連鎖を避けるために、switch文やオブジェクトルックアップテーブルを使用することがあります。これらは必ずしもelseを直接置き換えるわけではありませんが、構造をより明確にし、冗長なelseを避けるのに役立ちます。

// if-else if-...-else の例
function getCategory(type) {
  if (type === 'A') {
    return "カテゴリ1";
  } else if (type === 'B') {
    return "カテゴリ2";
  } else {
    return "その他";
  }
}

// switch文を用いた代替
function getCategorySwitch(type) {
  switch (type) {
    case 'A':
      return "カテゴリ1";
    case 'B':
      return "カテゴリ2";
    default:
      return "その他";
  }
}

// オブジェクトルックアップを用いた代替
function getCategoryObject(type) {
  const categories = {
    'A': "カテゴリ1",
    'B': "カテゴリ2"
  };
  return categories[type] || "その他";
}

オブジェクトルックアップは、特に条件が多い場合にコードを簡潔にする強力な方法です。

Null合体演算子 (Nullish Coalescing Operator ??) と 論理OR演算子 (||)

デフォルト値を設定する場合などに、これらの演算子を利用することで、if-else構造を簡潔に記述できます。

// if-else の例
function getName(user) {
  if (user && user.name) {
    return user.name;
  } else {
    return "名無し";
  }
}

// Null合体演算子を用いた代替
function getNameNullish(user) {
  return user?.name ?? "名無し";
}

// 論理OR演算子を用いた代替 (falsyな値に注意が必要)
function getNameOr(user) {
  return user?.name || "名無し";
}

Null合体演算子は、左辺がnullまたはundefinedの場合に右辺の値を返します。論理OR演算子は、左辺がfalsyな値(null, undefined, false, 0, "", NaN)の場合に右辺の値を返します。