TypeScriptで複数のエラーを判別!instanceof型ガードの使い道と例外処理の基本
生徒
「プログラムを実行しているときに、いろいろな種類のエラーが発生して困っています。エラーの種類ごとに処理を変える方法はありますか?」
先生
「TypeScriptでは、instanceofという仕組みを使うことで、発生したエラーがどのタイプなのかを正確に判別することができますよ。」
生徒
「エラーの種類を見分けることができれば、ネットワークの失敗なのか、入力ミスなのかを区別して対応できそうですね!」
先生
「その通りです!今回はinstanceofを使った型ガードというテクニックを、初心者の方にも分かりやすく解説しますね。」
1. エラーハンドリングと例外処理とは?
プログラミングの世界では、想定外の事態が起こることを例外(れいがい)と呼びます。例えば、インターネットがつながっていないのにデータを取得しようとしたり、存在しないファイルを開こうとしたりする場合です。これらの問題が発生したときに、プログラムを強制終了させずに、安全に処理を継続させるための仕組みを例外処理(エラーハンドリング)と言います。
パソコンを触ったことがない方にとって「エラー」は怖いものに感じるかもしれませんが、料理に例えると分かりやすくなります。レシピ通りに作っていても「材料が足りない」「火が強すぎる」といったトラブルが起きますよね。その時に「材料を買いに行く」のか「火を弱める」のか、状況に合わせて判断して行動するのがエラーハンドリングの役割です。
TypeScriptでは、この例外処理を丁寧に行うことで、ユーザーにとって使いやすく、壊れにくいアプリケーションを作ることができます。特に、何が原因で失敗したのかを特定することは、プログラミングにおいて非常に重要なステップとなります。
2. 複数のエラーが発生する理由
一つの処理の中で、エラーの原因は一つとは限りません。例えば、ユーザーが会員登録をする画面を想像してみてください。ここでは、以下のような複数のエラーが考えられます。
- 入力されたメールアドレスの形式が正しくない(入力エラー)
- サーバーとの通信が途切れてしまった(通信エラー)
- すでに同じ名前のユーザーが登録されている(データベースエラー)
これらのエラーに対して、すべて「エラーが発生しました」という同じメッセージを表示するだけでは、ユーザーはどうすればいいか分かりません。メールアドレスが間違いなら「修正してください」と言い、通信エラーなら「時間を置いてやり直してください」と伝える必要があります。このように、エラーの種類を判別して個別の対応をすることが、質の高いプログラムへの第一歩です。
3. try-catch文の基本をおさらい
エラーを捕まえるための基本的な構文がtry-catchです。まずはこれを使って、エラーをキャッチする方法を見てみましょう。
try {
// エラーが起きる可能性のある処理
throw new Error("何らかの不具合が発生しました");
} catch (error) {
// エラーが起きた時に実行される処理
console.log("エラーをキャッチしました!");
}
ここで使っているthrowは、意図的にエラーを発生させるキーワードです。new Errorは、標準的なエラーオブジェクトを作成しています。catchの部分で、投げられたエラーを受け取っています。しかし、このままでは「どんな種類のエラーか」までは詳しく分かりません。そこで登場するのが、今回メインで解説する判別方法です。
4. instanceofを使った型ガードとは?
instanceof(インスタンス・オブ)とは、あるオブジェクトが特定のクラス(設計図)から作られたものかどうかを確認するための命令です。これを使うことで、「このエラーは通信エラークラスから作られたものか?」「それとも入力エラークラスからか?」というチェックが可能になります。
このように、特定の条件でデータの種類(型)を絞り込む仕組みを、TypeScriptでは型ガードと呼びます。ガードレールのように、プログラムが間違った方向に進まないように保護してくれるイメージです。これを使うと、特定のタイプのエラーであると確信した状態で、そのエラー専用のプロパティ(情報)にアクセスできるようになります。
5. 独自のエラークラスを作ってみよう
エラーを判別するためには、まず判別対象となるエラーの種類を自分で定義するのが一般的です。これを「カスタムエラークラス」と呼びます。以下のコードでは、データベースの通信失敗と、データの入力ミスという二つのエラーを定義しています。
// データベースエラーの定義
class DatabaseError extends Error {
constructor(message: string) {
super(message);
this.name = "DatabaseError";
}
}
// 入力エラーの定義
class ValidationError extends Error {
constructor(message: string) {
super(message);
this.name = "ValidationError";
}
}
function processData(input: string) {
if (input === "") {
throw new ValidationError("入力が空です。文字を入れてください。");
}
// 通信失敗を想定
throw new DatabaseError("サーバーに接続できませんでした。");
}
ここではextends Errorという書き方をして、標準のエラーを元にして新しい種類のエラーを作っています。これにより、標準的な機能は備えつつ、名前だけが違う独自のエラーが完成します。プログラミング未経験の方は、まず「名前のついた専用のエラーラベルを作った」と考えてください。
6. 複数のエラーを判別する実践的な書き方
それでは、実際にinstanceofを使って、複数のエラーを仕分けしてみましょう。if文を使って、上から順番にチェックしていくのが定石です。
try {
processData(""); // ここでエラーが発生する
} catch (e) {
if (e instanceof ValidationError) {
// 入力ミスの場合の処理
console.log("【バリデーション失敗】: " + e.message);
} else if (e instanceof DatabaseError) {
// データベース接続失敗の場合の処理
console.log("【システムエラー】: " + e.message);
} else {
// それ以外の予期せぬエラー
console.log("未知のエラーが発生しました");
}
}
このプログラムを実行すると、エラーの種類に応じて出力されるメッセージが変わります。もしValidationErrorであれば、入力ミスに関する案内が表示されます。このように条件分岐させることで、一つのcatchブロックの中で複数のトラブルに対応できるようになります。これが例外処理の醍醐味です。
7. なぜinstanceofが必要なのか
「エラーのメッセージ内容を文字列で比較すればいいのでは?」と思うかもしれません。例えばif (e.message === "入力が空です")といった書き方です。しかし、この方法はおすすめできません。なぜなら、メッセージの文章が少し変わっただけで判別ができなくなってしまうからです。
対してinstanceofは、メッセージの内容ではなく、そのオブジェクトの「正体(型)」で判断します。文章が翻訳されて多言語化されたとしても、あるいはメッセージが修正されたとしても、型が変わらなければ正しく動作し続けます。正確で堅牢なプログラムを作るためには、文字列の比較ではなく、型による判別が不可欠なのです。これは大規模な開発になればなるほど、その恩恵を感じることができます。
8. 実行結果を確認してみよう
実際に上記のコードを動かしたときの様子を確認しましょう。まずは、入力エラーが発生した場合の動作です。
【バリデーション失敗】: 入力が空です。文字を入れてください。
次に、データベースエラーが発生した場合の動作です。
【システムエラー】: サーバーに接続できませんでした。
このように、一つの処理系統でありながら、発生した事象に合わせてアウトプットを柔軟に切り替えることができています。これができると、ユーザーに対して「インターネットの設定を確認してください」といった具体的なアドバイスを出すことが可能になり、親切な設計になります。
9. 初心者が注意すべきポイント
instanceofを使う際に気をつけたいのは、チェックする順番です。もし基本となるErrorクラスを一番最初にチェックしてしまうと、すべての独自エラーはErrorの仲間であるため、そこで判定が終わってしまいます。必ず、具体的で特殊なエラーから順番に確認し、最後に一般的なエラーや「それ以外」を処理するようにしましょう。
また、TypeScriptのcatchで受け取る変数は、デフォルトではunknown(何だか分からないもの)という型になっています。そのため、いきなりプロパティにアクセスしようとするとエラーになることがありますが、instanceofでチェックした後は、TypeScriptが「あ、これはValidationErrorなんだな」と理解してくれるので、安全にプログラムを書くことができます。これを賢く活用して、エラーに強いコードを目指しましょう。