ESLintの`no-unsafe-finally`ルール:プログラミングの安全性を高める必須ルール


なぜfinallyブロック内で制御フローステートメントを使用すべきではないのか?

finallyブロックは、tryブロックまたはcatchブロックで発生したエラーに関わらず、常に実行されるブロックです。つまり、finallyブロック内のコードは、tryブロックまたはcatchブロックで返された値や、投げられたエラーを無効にする可能性があります。

以下に、no-unsafe-finallyルールに違反するコードの例と、その問題点を示します。

try {
  return 1;
} finally {
  return 2; // 常に2が返されるため、tryブロックの戻り値(1)が無視される
}

このコードでは、tryブロックは常に1を返しますが、finallyブロック内のreturnステートメントによって、常に2が返されます。これは、予期せぬ動作であり、コードのデバッグを困難にする可能性があります。

no-unsafe-finallyルールの例外

no-unsafe-finallyルールは、以下の場合には違反しません。

  • ジェネレータ関数において、yield表現を使用する場合
  • 関数やクラスの定義など、間接的に制御フローステートメントを使用する場合

no-unsafe-finallyルールの有効化

no-unsafe-finallyルールは、ESLintのデフォルトルールセットには含まれていません。このルールを有効にするには、.eslintrcファイルに以下の設定を追加する必要があります。

{
  "rules": {
    "no-unsafe-finally": "error"
  }
}


違反例1:returnステートメントの使用

try {
  return 1;
} finally {
  return 2; // 常に2が返される
}

修正例

try {
  return 1;
} finally {
  // 何もしない
}

違反例2:throwステートメントの使用

try {
  console.log('tryブロックを実行します');
} catch (error) {
  console.error('catchブロックでエラーを処理します:', error);
} finally {
  throw new Error('finallyブロックでエラーをスローします'); // 常にエラーがスローされる
}

修正例

try {
  console.log('tryブロックを実行します');
} catch (error) {
  console.error('catchブロックでエラーを処理します:', error);
} finally {
  // 何もしない
}

違反例3:breakステートメントの使用

label: try {
  console.log('tryブロックを実行します');
  break label; // ループを中断する
} finally {
  console.log('finallyブロックを実行します'); // 常に実行される
}

修正例

try {
  console.log('tryブロックを実行します');
} finally {
  console.log('finallyブロックを実行します'); // ループが中断されても実行される
}

違反例4:continueステートメントの使用

while (true) {
  try {
    console.log('tryブロックを実行します');
    continue; // 次のループイテレーションにスキップする
  } finally {
    console.log('finallyブロックを実行します'); // 常に実行される
  }
}
while (true) {
  try {
    console.log('tryブロックを実行します');
  } finally {
    console.log('finallyブロックを実行します'); // ループがスキップされても実行される
  }
}


代替手段の検討

no-unsafe-finally ルールの代替手段として以下の選択肢が考えられます。

  1. finally ブロック内でエラー処理を行う
  2. エラー処理専用の関数を作成する
  3. Promise を使用する

それぞれの方法について、以下で詳しく説明します。

finally ブロック内でエラー処理を行う

場合によっては、finally ブロック内でエラー処理を行う方が適切な場合があります。例えば、リソースの解放など、エラー発生に関わらず必ず実行する必要がある処理がある場合です。

try {
  const result = someFunctionThatMightThrow();
  console.log(result);
} catch (error) {
  console.error(error);
} finally {
  // リソースを解放する
  closeResources();
}

エラー処理専用の関数を作成する

より複雑なエラー処理を行う場合は、エラー処理専用の関数を作成することができます。この方法により、finally ブロックをクリーンに保ち、コードの可読性を向上させることができます。

function handleError(error) {
  console.error(error);
}

try {
  const result = someFunctionThatMightThrow();
  console.log(result);
} catch (error) {
  handleError(error);
} finally {
  // 何もしない
}

Promise を使用する

非同期処理においては、Promise を使用してエラー処理を行うことができます。Promise は、非同期操作の結果を処理するための非同期プログラミングの構文です。

someAsyncFunctionThatMightThrow()
  .then(result => {
    console.log(result);
  })
  .catch(error => {
    console.error(error);
  })
  .finally(() => {
    // リソースを解放する
    closeResources();
  });

どの代替手段が適切か

どの代替手段が適切かは、具体的な状況によって異なります。以下の要素を考慮して判断する必要があります。

  • 非同期処理の有無
  • コードの可読性
  • エラー処理の複雑さ