`noImplicitReturns` で防ぐ「なんとなく」のバグ。TypeScriptの堅牢性を底上げする静的解析の流儀
現場でコードをレビューしていると、たまに見かけるんです。「あれ、この関数、条件によっては何も返してないけど大丈夫?」というコードが。
JavaScriptの仕様上、関数が何も返さない場合、それは自動的に `undefined` を返します。しかし、この「暗黙の `undefined`」は、フロントエンド開発における「隠れた地雷」の筆頭です。
今回は、TypeScriptのコンパイラオプション `noImplicitReturns` を使って、この地雷を設計段階で爆破する方法について解説します。
—
なぜ `noImplicitReturns` が必要なのか
JavaScriptのエンジン(V8など)は非常に寛容です。`if` 文で条件分岐を書いた際、片方のパスで `return` を書き忘れても、ブラウザは怒りません。
function getStatusLabel(code) {
if (code === 200) return “Success”;
// ここで何も返さなければ、自動的に undefined が返る
}
この結果、呼び出し元で `getStatusLabel(404).toUpperCase()` のような操作をした瞬間、「Cannot read properties of undefined」という、お馴染みのランタイムエラーが爆誕します。
`noImplicitReturns` を `true` に設定すると、TypeScriptは「すべてのコードパスで値が返されているか」を厳密にチェックします。もし返り値が漏れていれば、コンパイルエラーとして警告を出してくれるのです。
tsconfig.json への設定
設定は非常にシンプルです。`compilerOptions` に追記するだけ。
{
“compilerOptions”: {
“strict”: true,
“noImplicitReturns”: true,
// …その他設定
}
}
「`strict: true` にしてるから大丈夫じゃないの?」と思われがちですが、実は `strict` モードでも `noImplicitReturns` は有効化されません。明示的に指定する必要があります。ここ、意外と見落としがちなポイントです。
—
実践:現場で役立つ書き換えパターン
現場でよくある「戻り値の漏れ」を、どう修正すべきか。いくつかのパターンを紹介します。
パターン1:条件分岐の網羅(網羅性チェック)
もっとも多いケースです。`if-else` で終わるのではなく、最終的なデフォルト値を返すように設計します。
/
- 悪い例:ifの条件に漏れがあると undefined が返る
/
function getRolePriority(role: ‘admin’ | ‘user’): number {
if (role === ‘admin’) return 10;
if (role === ‘user’) return 1;
// ここでコンパイルエラー!「戻り値がない」と指摘される
}
/
- 良い例:デフォルト値を定義する
/
function getRolePriorityRefined(role: ‘admin’ | ‘user’): number {
if (role === ‘admin’) return 10;
return 1; // どんなパターンでも必ず値を返す
}
パターン2:throw を活用する
エラーハンドリングを重視する場合、例外を投げるのも立派な「出口」です。TypeScriptは `throw` を「決して戻らないパス(never型)」として認識してくれるため、戻り値チェックをパスできます。
function getElementById(id: string): HTMLElement {
const element = document.getElementById(id);
// 見つからなかったら例外を投げる
if (!element) {
throw new Error(`Element with id ${id} not found.`);
}
// ここに来たときは必ず element が存在する
return element;
}
—
ブラウザの裏側で起きていること
少し深掘りしましょう。なぜTypeScriptはこれほど厳しく言うのか。
JavaScriptエンジンは、関数が終了する際に「戻り値レジスタ」になんらかの値がセットされていることを期待します。`return` がない関数は、内部的に「空の戻り値」をセットしますが、これはTypeScriptの型システム上では「戻り値が `void` または `undefined`」であることを意味します。
もし、関数の戻り値型を `string` と明示しているのに `return` がない場合、それは「型定義と実装の乖離」です。これを放置すると、型安全性を信じきったエンジニアがその戻り値に対して安全なメソッド(`toUpperCase()` など)を呼び出し、実行時にクラッシュします。
`noImplicitReturns` を有効にすることは、「型定義という契約を、実装レベルで強制的に守らせる」という、堅牢なアプリケーション作りのための契約履行プロセスなのです。
まとめ:今日から始める一歩
`noImplicitReturns` をオンにすると、既存のコードから大量の赤線が出るかもしれません。最初は面倒に感じるでしょう。ですが、それは「これまで見えていなかったバグの種が可視化された」というポジティブなサインです。
1. `tsconfig.json` に追加する。
2. 赤線が出た箇所を「例外を投げるべきか」「デフォルト値を返すべきか」の観点で修正する。
3. チームの型安全性を一段階引き上げる。
派手なライブラリを入れるよりも、こうした地味な設定の積み重ねが、半年後の自分を救うことになります。さあ、今すぐエディタを開いて設定してみましょう。何が起きるか、楽しみですね。

コメント