TypeScriptのエラーハンドリング入門!例外処理とErrorオブジェクトを徹底解説
生徒
プログラミングをしていて、急に画面が止まったり、赤い文字で英語が出てきたりするのが怖いです。TypeScriptでこれを防ぐ方法はありますか?
先生
それはエラーが発生している状態ですね。TypeScriptでは例外処理という仕組みを使って、エラーが起きてもプログラムを止めずに、優しく対処することができます。
生徒
エラーを優しく受け止めることができるんですね!具体的にどうやって書けばいいのでしょうか?
先生
まずは、エラーの正体であるErrorオブジェクトや、try-catchという書き方を一緒に学んでいきましょう!
1. エラーハンドリングと例外処理とは何か
プログラミングを学んでいると、必ず遭遇するのがエラーです。エラーとは、プログラムが予想外の動きをしたり、計算ができなくなったりして動かなくなる状態を指します。これを放置すると、アプリが突然終了してしまい、使っているユーザーを困らせてしまいます。
エラーハンドリングとは、そうした困った事態に備えて、あらかじめ「もし失敗したら、こう動いてね」という指示を出しておくことを言います。また、プログラムの実行中に発生する予期せぬ問題を例外と呼び、それを適切に処理することを例外処理と呼びます。
例えば、インターネットがつながらないときや、数字を入力すべき場所に文字が入力されたときなど、現実の世界でもトラブルはつきものです。TypeScriptでは、これらのトラブルをスマートに解決するための道具が最初から用意されています。これらを使いこなすことで、壊れにくい、信頼されるプログラムを作ることができるようになります。
2. TypeScriptの例外はどんな型なのか
TypeScriptにおいて、例外が発生したときに投げられる情報の多くはunknown型やany型として扱われます。これは、プログラムがエラーを投げるとき、実は数字や文字列、あるいは独自のデータなど、どんなものでも投げることができてしまうからです。
しかし、一般的にはJavaScriptやTypeScriptの標準的なルールとして、Errorオブジェクトという特別な形式のデータが使われます。このオブジェクトには、なぜエラーが起きたのかというメッセージや、どこでエラーが起きたのかという情報が含まれています。
初心者のうちは、「エラーが起きたときには、エラーに関する情報が詰まった箱が飛んでくる」とイメージすると分かりやすいでしょう。その箱の中に何が入っているかを確認して、次の行動を決めるのがプログラミングの役割です。TypeScriptでは、この飛んできた箱の中身をしっかりと確認するための仕組みが非常に充実しています。
3. Errorオブジェクトの基本的な使い方
エラーが発生したときの情報を扱うのがErrorオブジェクトです。これは、TypeScriptに最初から組み込まれている機能で、自分で新しくエラーの情報を作るときにも使います。最もよく使われるのは、エラーの名前を示すnameプロパティと、詳しい理由を示すmessageプロパティです。
自分からエラーを発生させたいときは、new Error("メッセージ")という書き方をします。これは、わざと警告を出すようなイメージです。例えば、ユーザーがパスワードを間違えたときに「間違っていますよ」というエラーを作って投げることができます。
// エラーオブジェクトを作成して変数に入れる
const myError = new Error("何かの問題が発生しました!");
// エラーの中身を表示してみる
console.log(myError.name); // Errorと表示される
console.log(myError.message); // 何かの問題が発生しました!と表示される
Error
何かの問題が発生しました!
このように、Errorオブジェクトを使うことで、何が原因で問題が起きたのかを文字として記録しておくことができます。これは、後で不具合を直すときに非常に重要な手がかりとなります。
4. try-catch構文でエラーを捕まえる方法
エラーが発生してもプログラムを止めないための魔法の言葉がtry-catch(トライ・キャッチ)です。名前の通り、「まずはやってみて(try)、もしエラーが出たら捕まえる(catch)」という動きをします。
使い方はとてもシンプルです。エラーが起きそうな処理をtryのブロックで囲み、もしエラーが起きたら実行したい処理をcatchのブロックに書きます。これにより、エラーが起きてもcatchの中身が実行されるだけで、その後のプログラムは無事に動き続けることができます。
try {
// ここでエラーが発生するかもしれない処理を行う
console.log("計算を開始します");
// 存在しない関数を呼んでエラーを発生させてみる(例として)
throw new Error("計算に失敗しました");
console.log("この行は実行されません");
} catch (error) {
// エラーが発生したときだけ、ここが実行される
console.log("エラーを捕まえらました!");
if (error instanceof Error) {
console.log("原因はこれです:" + error.message);
}
}
console.log("プログラムを終了します");
計算を開始します
エラーを捕まえらました!
原因はこれです:計算に失敗しました
プログラムを終了します
このコードを見ると、途中でエラーが起きているのに、最後の「プログラムを終了します」までしっかり実行されているのがわかりますね。これが例外処理の最大のメリットです。
5. throwを使って自分でエラーを投げる
プログラムを作っていると、システム的なエラーだけでなく、「この値がマイナスなのはおかしい」といった、自分たちで決めたルールに反する場合にエラーを発生させたいことがあります。そのときに使うのがthrow(スロウ)キーワードです。
throwは「投げる」という意味です。エラーというボールを投げると、一番近くにあるcatchというグローブがそれを受け取ってくれます。もしcatchがないと、プログラムはボールを受け取れずに止まってしまいます。これを防ぐために、throwとtry-catchはセットで考えるのが基本です。
function checkAge(age: number) {
if (age < 0) {
// 0歳未満はありえないので、エラーを投げる
throw new Error("年齢にマイナスの値は入力できません。");
}
console.log("年齢は" + age + "歳ですね。");
}
try {
checkAge(-5); // わざとマイナスの値を入れてみる
} catch (e) {
console.log("エラーが発生しました。");
if (e instanceof Error) {
console.log("メッセージ:" + e.message);
}
}
エラーが発生しました。
メッセージ:年齢にマイナスの値は入力できません。
自分自身でエラーの条件を決めることで、プログラムが変なデータで動き続けるのを防ぎ、より安全なシステムを作ることができます。
6. finallyで最後に必ず実行する処理
エラーが起きても起きなくても、最後に絶対やっておきたい処理がある場合に使うのがfinally(ファイナリー)です。例えば、ファイルの読み込みを閉じたり、通信を終了したり、画面の「読み込み中」という表示を消したりする処理です。
tryの中で成功しても、catchでエラーを捕まえても、最後に必ずfinallyの中が実行されます。これにより、後片付けを忘れることがなくなり、メモリーの無駄遣いや予期せぬ動作を防ぐことができます。初心者の方は、お掃除の時間だと考えると分かりやすいでしょう。
function processData() {
console.log("データベースに接続します...");
try {
console.log("データを処理中...");
// ここで何か複雑な処理をする
throw new Error("途中で通信が切れました");
} catch (err) {
console.log("エラー対応を行います");
} finally {
// エラーの有無にかかわらず必ず実行される
console.log("データベースとの接続を閉じます(後片付け完了)");
}
}
processData();
データベースに接続します...
データを処理中...
エラー対応を行います
データベースとの接続を閉じます(後片付け完了)
このように、一連の流れの最後に必ず行いたいことを保証してくれるのがfinallyの役割です。
7. 型安全なエラーハンドリングのためのinstanceof
TypeScriptでは、catchで受け取ったエラーが本当にErrorオブジェクトなのかを確認することが推奨されます。なぜなら、先ほど説明した通り、エラーとして文字列や数字が投げられる可能性もゼロではないからです。そこで使うのがinstanceofという命令です。
これを使うと、「このエラーはErrorという型ですか?」と確認することができます。もしそうであれば、messageプロパティを安全に使うことができます。TypeScriptは型に厳しい言語なので、このように「本当にそのデータで合っているか」をチェックすることで、プログラミングミスを劇的に減らすことができます。
もしこのチェックをしないと、TypeScriptは「そのエラーの中身が何か分からないから、messageなんていう項目があるか保証できないよ」と警告を出してしまいます。この丁寧な確認作業こそが、高品質なコードへの第一歩です。
8. 実践的なエラーメッセージの作り方
エラーメッセージを作るときは、なるべく「何が起きたか」と「どうすればいいか」が伝わるように工夫しましょう。ただ「エラー」とだけ表示されても、ユーザーは何をしていいか分かりません。例えば、「ネットワークに接続できません。Wi-Fiの設定を確認してください」といった具体的な内容です。
開発者にとっても同じです。後でログ(プログラムの記録)を見たときに、どこで何が起きたのかがすぐ分かるメッセージにしておくことで、修理の時間を短縮できます。Errorオブジェクトを上手に使って、未来の自分や仲間のプログラマーに親切な情報を残すように心がけましょう。
また、TypeScriptでは独自のエラークラスを作ることもできますが、まずは標準のErrorオブジェクトを使いこなし、messageを適切に設定することから始めてみてください。それがエラーハンドリングの基本であり、最も大切なことです。
9. 初心者が注意すべきエラー処理のポイント
エラー処理を書くときに一番やってはいけないのが、「catchの中身を空にする」ことです。エラーが起きたのに何もしないで隠してしまうと、後でどこで問題が起きたのか全く分からなくなってしまいます。これを専門用語で「エラーを飲み込む」と言ったりします。
必ずconsole.logなどでエラーの内容を表示するか、何らかの対策を行うようにしましょう。また、何でもかんでもtry-catchで囲むのではなく、本当にエラーが起きそうな場所や、エラーが起きたら困る場所に絞って使うのがコツです。プログラム全体を一つの巨大なtryで囲んでしまうと、どこでミスをしたのか探しにくくなるからです。
まずは小さな部分からエラー処理を試してみて、徐々に範囲を広げていくのが上達の近道です。TypeScriptの便利な機能を活用して、エラーを怖がらずにプログラミングを楽しんでいきましょう!