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
ルールの代替手段として以下の選択肢が考えられます。
finally
ブロック内でエラー処理を行う- エラー処理専用の関数を作成する
- 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();
});
どの代替手段が適切か
どの代替手段が適切かは、具体的な状況によって異なります。以下の要素を考慮して判断する必要があります。
- 非同期処理の有無
- コードの可読性
- エラー処理の複雑さ