TypeScriptでカスタムエラークラスと型ガードを活用したエラーハンドリング

TypeScriptでエラーハンドリングを効果的に行うためには、カスタムエラークラスと型ガードを組み合わせることが重要です。これにより、エラーの特定と処理が容易になり、コードの可読性とメンテナンス性が向上します。本記事では、TypeScriptにおけるカスタムエラークラスの定義方法や、型ガードの基礎知識を踏まえた上で、両者を組み合わせた実践的なエラーハンドリング手法を詳しく解説します。これにより、より堅牢で拡張性のあるエラーハンドリングを実現できます。

目次

TypeScriptにおけるエラーハンドリングの基本

TypeScriptでは、エラーハンドリングの基本的な手段として、JavaScriptと同様にtry...catch構文が用いられます。tryブロック内で発生したエラーをキャッチし、catchブロック内でそのエラーに対して適切な処理を行います。

TypeScriptの利点は、静的型付けを活用して、発生しうるエラーを型で表現し、エラーハンドリングをより安全で確実なものにする点です。具体的には、エラーメッセージやエラーコードを持つカスタムエラークラスを定義することで、エラーハンドリングを一元化し、エラー発生時の原因追及を容易にすることが可能です。

さらに、型ガードと組み合わせることで、特定の型のエラーのみを処理するようにすることもできます。これにより、特定の状況で発生するエラーに対して、よりきめ細かな対応が可能となります。

カスタムエラークラスの概要

TypeScriptでは、標準的なErrorクラスを拡張してカスタムエラークラスを作成することができます。カスタムエラークラスを使用することで、エラーの詳細な情報を含む独自のエラーメッセージやプロパティを持たせることができ、特定のエラーに対して適切な処理を行うことが容易になります。

カスタムエラークラスの定義

カスタムエラークラスは、通常のクラス定義と同様に作成しますが、Errorクラスを継承し、messageプロパティやその他のカスタムプロパティを追加します。以下は、シンプルなカスタムエラークラスの例です。

class CustomError extends Error {
    constructor(message: string) {
        super(message);
        this.name = "CustomError";
    }
}

この例では、CustomErrorクラスはErrorクラスを拡張し、エラーメッセージを受け取ってsuper()で親クラスに渡します。また、エラーメッセージに加え、エラーの種類を識別するためにnameプロパティも設定しています。

カスタムエラークラスの利点

  • エラーメッセージの一元管理:特定の状況に応じたエラーメッセージやプロパティを定義できるため、エラーの詳細な情報を管理しやすくなります。
  • エラーの分類が容易:複数のカスタムエラークラスを定義することで、異なる種類のエラーを明確に分類でき、それぞれのエラーに対して適切な処理を行えます。
  • デバッグの効率化:独自のエラー情報を含むため、デバッグ時にエラーの原因や発生場所を迅速に特定できます。

カスタムエラークラスを利用することで、エラーハンドリングをより柔軟で効果的に実装でき、システムの堅牢性を高めることができます。

型ガードの基礎

型ガードは、TypeScriptで特定の型を確認し、型に応じた安全な処理を行うための重要な機能です。特に、複数の型が混在する場合に、どの型が使われているかを確認することで、適切な処理を分岐させることができます。これにより、型エラーを防ぎ、コードの安全性と可読性を向上させることができます。

型ガードの基本構文

型ガードは、typeofinstanceofを使用して特定の型をチェックする構文を提供します。例えば、typeofはプリミティブ型の判定に使い、instanceofはクラスのインスタンスかどうかを判定する際に用います。

function example(input: string | number) {
    if (typeof input === 'string') {
        console.log(`The string is: ${input}`);
    } else {
        console.log(`The number is: ${input}`);
    }
}

この例では、inputstringnumberかによって異なる処理を行います。型ガードを使用することで、inputがどちらの型か明確に判断でき、安全な操作が可能になります。

ユーザー定義型ガード

TypeScriptでは、より複雑な型判定を行いたい場合に、ユーザー定義の型ガード関数を作成することができます。関数の戻り値としてinput is Typeの形式を使用することで、特定の型かどうかを判定することができます。

function isCustomError(error: any): error is CustomError {
    return error instanceof CustomError;
}

この型ガードを使用すれば、エラーがCustomErrorであるかどうかを判定し、特定の処理を行うことができます。これは、カスタムエラークラスとの組み合わせで非常に有用です。

型ガードの利点

  • 型安全性の向上:コード内で型を確実に判定し、間違った型のデータ操作を防ぎます。
  • コードの可読性向上:型ごとに処理を分岐させることで、コードの意図が明確になり、可読性が向上します。
  • エラーハンドリングの精度向上:カスタムエラークラスとの組み合わせで、特定のエラーに対する正確な処理が可能になります。

型ガードを効果的に使用することで、TypeScriptの型システムを最大限に活用し、バグの少ない堅牢なコードを作成できます。

カスタムエラークラスと型ガードの組み合わせ

カスタムエラークラスと型ガードを組み合わせることで、より精密で柔軟なエラーハンドリングが可能になります。特定のエラークラスに対する型ガードを活用することで、エラーの種類ごとに適切な処理を行い、エラー管理の効率を大幅に向上させることができます。

組み合わせる利点

  1. エラーハンドリングの精度向上: 複数のエラーが発生するシステムでは、エラーを単純にキャッチするだけではなく、それぞれのエラーの種類を正確に判別する必要があります。型ガードを使用することで、カスタムエラークラスを使ったエラーチェックを細かく制御でき、正しいエラーハンドリングが可能になります。
  2. エラーメッセージのカスタマイズ: カスタムエラークラスに固有のプロパティやメソッドを持たせることで、エラーメッセージや状態を柔軟に制御し、ユーザーに適切なフィードバックを提供できます。

具体例:カスタムエラークラスと型ガードの実装

以下に、カスタムエラークラスと型ガードを組み合わせたエラーハンドリングの例を示します。

class NotFoundError extends Error {
    constructor(message: string) {
        super(message);
        this.name = "NotFoundError";
    }
}

class ValidationError extends Error {
    constructor(message: string) {
        super(message);
        this.name = "ValidationError";
    }
}

function isNotFoundError(error: any): error is NotFoundError {
    return error instanceof NotFoundError;
}

function isValidationError(error: any): error is ValidationError {
    return error instanceof ValidationError;
}

function handleError(error: Error) {
    if (isNotFoundError(error)) {
        console.log(`Not Found: ${error.message}`);
    } else if (isValidationError(error)) {
        console.log(`Validation Error: ${error.message}`);
    } else {
        console.log(`Unknown Error: ${error.message}`);
    }
}

この例では、NotFoundErrorValidationErrorという2種類のカスタムエラークラスを定義しています。isNotFoundErrorisValidationErrorといった型ガード関数を使い、エラーの型をチェックし、エラーハンドリングを細分化しています。これにより、エラーの種類ごとに異なる処理を実行できます。

カスタムエラークラスと型ガードの応用

カスタムエラーと型ガードを組み合わせることで、複数のエラータイプをシステム的に管理することが可能になります。特に、複雑な業務ロジックや外部サービスとの連携において、この組み合わせは非常に有効です。特定のエラーに対して、異なるリカバリー処理を実行することができ、システム全体の信頼性が向上します。

型ガードを活用してエラーの種類を判別し、それぞれのエラーに対して適切なフィードバックやリカバリーを行うことで、ユーザー体験の向上やデバッグの効率化を図ることができます。

エラーハンドリングにおけるユニオン型と型ガード

TypeScriptでは、複数の型を許容するユニオン型を活用することで、より柔軟なエラーハンドリングが可能になります。ユニオン型を用いると、一つの変数に対して異なる型のデータを扱うことができ、型ガードと組み合わせることで、それぞれの型に応じた適切な処理を実装できます。

ユニオン型の概要

ユニオン型は、変数が複数の異なる型を持つことができることを示します。例えば、エラー処理において、stringError、またはカスタムエラークラスなどの複数の型を使って、異なるエラーパターンを一つの関数で処理できます。

function handleError(error: NotFoundError | ValidationError | string) {
    if (typeof error === 'string') {
        console.log(`Error message: ${error}`);
    } else if (error instanceof NotFoundError) {
        console.log(`Not Found Error: ${error.message}`);
    } else if (error instanceof ValidationError) {
        console.log(`Validation Error: ${error.message}`);
    }
}

この例では、NotFoundErrorValidationError、および単純なstring型のエラーメッセージを処理するユニオン型の関数を作成しています。それぞれの型に応じた処理を行うために、型ガードを用いてエラーの種類を特定し、適切な処理を分岐しています。

ユニオン型と型ガードの組み合わせ

型ガードを使用して、ユニオン型の中からどの型が使用されているかを判定し、それに基づいて異なる処理を行うことができます。TypeScriptの型ガードを使うことで、開発者は安全かつ型安全なエラーハンドリングを実現できます。

例えば、以下のようなユニオン型を定義することができます。

type ErrorType = NotFoundError | ValidationError | AuthenticationError;

function processError(error: ErrorType) {
    if (error instanceof NotFoundError) {
        console.log("Resource not found");
    } else if (error instanceof ValidationError) {
        console.log("Validation failed");
    } else if (error instanceof AuthenticationError) {
        console.log("Authentication required");
    }
}

この例では、ErrorTypeとして定義された複数のカスタムエラークラスを一つの関数で処理しています。それぞれのエラー型に対して、型ガードを利用して正確なエラーメッセージを出力しています。

利点と活用方法

  • 柔軟なエラーハンドリング: ユニオン型を用いることで、異なる種類のエラーを一つの関数で処理でき、コードのシンプルさが保たれます。
  • 型安全な処理: 型ガードを使用することで、型に応じた安全な処理を行い、予期せぬエラーを防止します。
  • スケーラブルなエラーハンドリング: 新たなエラータイプを追加する際も、既存のユニオン型と型ガードを活用すれば、簡単にエラーハンドリングを拡張できます。

ユニオン型と型ガードを組み合わせたエラーハンドリングは、特に多様なエラーシナリオが発生する大規模なプロジェクトや、複雑なビジネスロジックにおいて非常に役立ちます。

エラーハンドリングのベストプラクティス

TypeScriptでエラーハンドリングを実装する際、効果的でメンテナンスしやすいコードを書くためには、いくつかのベストプラクティスに従うことが重要です。これにより、コードの堅牢性が向上し、予期せぬエラーが発生しても、システム全体が適切に動作し続けるようになります。

1. カスタムエラークラスの使用

カスタムエラークラスを定義することで、エラーを分類し、特定のエラーに対して適切な処理を行うことができます。エラーメッセージやプロパティをカスタマイズすることで、デバッグ時に役立つ情報を提供し、エラー処理の可読性が向上します。エラーハンドリングを一元化する際に、これらのカスタムクラスを活用することが推奨されます。

class CustomError extends Error {
    constructor(message: string) {
        super(message);
        this.name = "CustomError";
    }
}

2. 型ガードでエラーを細かく分類

型ガードを用いて、エラーがどの種類のエラーかを明確にし、そのエラーに対して適切な処理を行います。型安全なエラーハンドリングを行うことで、予期せぬエラーが発生するリスクを軽減できます。

function isCustomError(error: any): error is CustomError {
    return error instanceof CustomError;
}

3. エラーのログを適切に管理

エラーが発生した際には、必ず詳細なログを記録しておくことが重要です。エラーメッセージやスタックトレース、発生時のコンテキストをログに残すことで、デバッグやトラブルシューティングが効率的に行えます。エラーの原因や影響を把握するための十分な情報を記録することが、システムの安定性向上につながります。

4. エラーをキャッチしすぎない

try...catch構文を乱用すると、エラーハンドリングの意図が曖昧になり、根本的なエラーの解決が難しくなります。エラーがどこで発生したのかを正確に追跡し、必要な箇所でのみエラーハンドリングを行うようにしましょう。無闇にエラーをキャッチして処理を隠してしまうことは、バグの発見やシステムの安定性に悪影響を与える可能性があります。

5. 非同期処理でのエラーハンドリング

非同期処理 (async/awaitPromise) では、エラーをキャッチする方法として、try...catch構文やPromise.catchメソッドを使用します。特にAPI通信やファイル操作のような外部リソースとのやり取りにおいて、適切にエラーをキャッチし、リカバリー処理を行うことが重要です。

async function fetchData() {
    try {
        const response = await fetch("https://api.example.com/data");
        const data = await response.json();
        return data;
    } catch (error) {
        console.error("Error fetching data:", error);
        throw error; // 再スローして上位で処理
    }
}

6. 再スローによるエラー伝搬

キャッチしたエラーを再度スローして上位の関数やレイヤーで処理させることは、有用なエラーハンドリング技術です。再スローにより、エラーの影響範囲を上位層まで伝え、全体的なエラーハンドリングをコントロールすることができます。これにより、エラー発生場所のコンテキストを保持したまま、エラーハンドリングが行えます。

7. ユーザーへの適切なフィードバック

エラーが発生した際に、ユーザーには適切で有用なフィードバックを提供することが重要です。ユーザーがエラーの原因を理解し、次のアクションを適切に取れるようなメッセージを提示することで、ユーザー体験の向上につながります。具体的なエラーメッセージとともに、エラー発生時のサポート手段を提供することも考慮すべきです。

まとめ

エラーハンドリングのベストプラクティスを守ることで、システムの信頼性を向上させ、コードのメンテナンス性を高めることができます。カスタムエラークラス、型ガード、非同期処理の適切なハンドリング、再スローの活用などを駆使して、エラー発生時の影響を最小限に抑えることが重要です。

カスタムエラーの実装例

カスタムエラークラスを実装することで、エラーハンドリングを強化し、より詳細で管理しやすいエラーメッセージや情報を提供できます。ここでは、実際にカスタムエラーを定義し、エラーを効果的に処理する方法について説明します。

カスタムエラークラスの具体例

カスタムエラークラスを定義することで、標準のErrorクラスに独自のプロパティを追加したり、特定のエラーメッセージを提供したりすることができます。以下は、2つのカスタムエラークラス、NotFoundErrorValidationErrorを実装した例です。

class NotFoundError extends Error {
    constructor(resource: string) {
        super(`${resource} not found.`);
        this.name = "NotFoundError";
    }
}

class ValidationError extends Error {
    field: string;

    constructor(field: string, message: string) {
        super(`${field} validation failed: ${message}`);
        this.name = "ValidationError";
        this.field = field;
    }
}

この例では、NotFoundErrorは見つからなかったリソースに対するエラーメッセージを提供し、ValidationErrorはバリデーションが失敗したフィールドに関する詳細なエラーメッセージを出力します。

エラーの利用例

これらのカスタムエラークラスを実際に使用する場面を考えてみましょう。以下のコードでは、データベースから特定のリソースを取得し、見つからない場合にはNotFoundErrorをスローし、データのバリデーションに失敗した場合にはValidationErrorをスローします。

function getResource(id: number) {
    const resources = [1, 2, 3]; // 例として簡単なデータベース
    if (!resources.includes(id)) {
        throw new NotFoundError(`Resource with ID ${id}`);
    }
    return { id, name: "Resource" };
}

function validateResource(resource: any) {
    if (!resource.name) {
        throw new ValidationError("name", "Name is required");
    }
    return true;
}

このコードでは、指定したIDのリソースが見つからない場合にNotFoundErrorを発生させ、リソースの名前がバリデーションに失敗した場合にはValidationErrorを発生させます。

カスタムエラーのキャッチと処理

発生したカスタムエラーをtry...catch構文でキャッチし、型ガードを使用してエラーの種類に応じた適切な処理を行います。

try {
    const resource = getResource(5); // 存在しないID
    validateResource(resource);
} catch (error) {
    if (error instanceof NotFoundError) {
        console.error(`Error: ${error.message}`);
    } else if (error instanceof ValidationError) {
        console.error(`Validation failed for ${error.field}: ${error.message}`);
    } else {
        console.error(`Unexpected error: ${error.message}`);
    }
}

この例では、getResourceで発生したNotFoundErrorvalidateResourceで発生したValidationErrorをキャッチし、それぞれに対応した処理を行います。NotFoundErrorの場合は、リソースが見つからなかったことを伝え、ValidationErrorの場合はどのフィールドでバリデーションが失敗したかを具体的に表示します。

利点

  1. エラーの詳細な管理: カスタムエラークラスを使うことで、発生したエラーの詳細を記録し、追跡しやすくなります。
  2. コードの可読性向上: 特定のエラーに対する処理が明確になり、コードの可読性が向上します。
  3. デバッグの効率化: カスタムエラーのプロパティやメッセージによって、デバッグ時にエラーの発生源や理由を簡単に特定できます。

このように、カスタムエラークラスを実装することで、エラーハンドリングを強化し、エラー発生時の対応がより容易かつ効果的になります。

型ガードを用いたエラーのトラブルシューティング

型ガードを用いることで、エラーのトラブルシューティングが効率的かつ安全に行えます。特に、複数のエラータイプが考えられる場面では、型ガードを使ってエラーの種類を正確に判別し、それに応じた対処を行うことが重要です。型ガードを正しく実装することで、予期せぬエラーの取り扱いがより簡単になり、システムの信頼性が向上します。

型ガードによるエラー判定のメリット

型ガードは、エラーがどの型に該当するかを判別し、適切な処理を行うための強力なツールです。例えば、カスタムエラークラスを複数定義している場合、そのエラーがどのクラスに属しているかを型ガードで確認できます。これにより、エラーの原因に応じて異なる処理を分岐させることができます。

型ガードを使用したエラーハンドリングの例

次に、型ガードを使ってエラーの種類を判別し、それぞれに応じたエラーハンドリングを行う実例を紹介します。

class NetworkError extends Error {
    constructor(message: string) {
        super(message);
        this.name = "NetworkError";
    }
}

class DatabaseError extends Error {
    constructor(message: string) {
        super(message);
        this.name = "DatabaseError";
    }
}

function isNetworkError(error: any): error is NetworkError {
    return error instanceof NetworkError;
}

function isDatabaseError(error: any): error is DatabaseError {
    return error instanceof DatabaseError;
}

ここでは、NetworkErrorDatabaseErrorという2つのカスタムエラークラスを定義し、それぞれの型ガード関数でエラーの種類を確認できるようにしています。

型ガードによるエラーハンドリングの実装

型ガードを用いたエラーハンドリングの実際の処理フローを以下に示します。

function handleError(error: Error) {
    if (isNetworkError(error)) {
        console.error("Network issue occurred:", error.message);
        // ネットワークエラーに対する特定の処理
    } else if (isDatabaseError(error)) {
        console.error("Database error:", error.message);
        // データベースエラーに対する特定の処理
    } else {
        console.error("Unknown error:", error.message);
    }
}

try {
    // エラーを発生させるテスト
    throw new NetworkError("Failed to connect to the server.");
} catch (error) {
    handleError(error as Error);
}

この例では、try...catchブロック内で発生したNetworkErrorをキャッチし、型ガード関数isNetworkErrorを使用してネットワークエラーであることを判定しています。その後、ネットワークエラーに対応する特定の処理を行います。同様に、DatabaseErrorが発生した場合も、それに応じた処理が行われます。

型ガードによるデバッグの効率化

型ガードを使うことで、以下のようにデバッグが効率化されます。

  1. エラータイプに応じた対応: 型ガードを利用することで、エラータイプに応じた処理を柔軟に切り分けることができ、特定のエラーに対して適切なリカバリー処理が可能になります。
  2. 型安全なコード: 型ガードによって型が安全に判別されるため、誤った型のデータを操作するリスクがなくなります。これにより、予期せぬエラーの発生が防がれます。
  3. エラーメッセージのカスタマイズ: 型ガードを使ってエラーの種類を特定し、それぞれのエラーに応じたカスタムメッセージを出力することで、エラー内容が明確になり、トラブルシューティングが容易になります。

まとめ

型ガードを用いることで、エラーハンドリングにおいて複数のエラータイプを効率的に管理でき、エラー発生時のトラブルシューティングが迅速に行えます。適切な型ガードを実装することにより、エラーハンドリングを安全かつ精密に行うことができ、システム全体の信頼性を高めることができます。

応用例:外部APIと非同期処理におけるエラーハンドリング

TypeScriptでは、非同期処理と外部APIの呼び出しはよくあるシナリオです。このような状況でのエラーハンドリングには特有の課題があります。非同期処理では、エラーが発生する可能性のある箇所が多く、適切に対処しないとシステム全体の安定性に影響を与えることがあります。ここでは、カスタムエラークラスと型ガードを活用した外部APIと非同期処理におけるエラーハンドリングの応用例を紹介します。

非同期処理におけるカスタムエラー

非同期処理では、通常のtry...catch構文を使ってエラーをキャッチしますが、外部APIの通信エラーやレスポンスエラーを適切に処理するために、カスタムエラークラスを導入することで、エラーの種類や状況に応じた処理を行うことができます。

class ApiError extends Error {
    constructor(message: string, public statusCode: number) {
        super(message);
        this.name = "ApiError";
    }
}

class TimeoutError extends Error {
    constructor(message: string) {
        super(message);
        this.name = "TimeoutError";
    }
}

この例では、外部APIからのエラーを管理するApiErrorクラスと、タイムアウトが発生した際のエラーを管理するTimeoutErrorクラスを定義しています。それぞれのエラーは特定の状況で発生するため、後に説明する型ガードでそれを判別します。

外部APIとの通信におけるエラーハンドリングの実装

非同期処理では、APIのレスポンスが成功したかどうかを確認し、エラーが発生した場合には適切に処理を行います。以下の例では、外部APIからデータを取得し、エラーが発生した場合にApiErrorTimeoutErrorをスローし、それを型ガードで処理します。

async function fetchData(url: string): Promise<any> {
    try {
        const response = await fetch(url);

        if (!response.ok) {
            throw new ApiError(`API error: ${response.statusText}`, response.status);
        }

        const data = await response.json();
        return data;

    } catch (error) {
        if (error.name === 'AbortError') {
            throw new TimeoutError("Request timed out");
        } else {
            throw error;
        }
    }
}

このコードでは、fetch関数を使用して外部APIからデータを取得しています。レスポンスがokでない場合には、ApiErrorをスローし、リクエストがタイムアウトした場合にはTimeoutErrorをスローしています。

型ガードを用いた非同期エラーハンドリングの応用

次に、これらのカスタムエラーを型ガードを用いて処理する実例を示します。

function isApiError(error: any): error is ApiError {
    return error instanceof ApiError;
}

function isTimeoutError(error: any): error is TimeoutError {
    return error instanceof TimeoutError;
}

async function processData(url: string) {
    try {
        const data = await fetchData(url);
        console.log("Data fetched successfully:", data);
    } catch (error) {
        if (isApiError(error)) {
            console.error(`API Error: ${error.message} (Status Code: ${error.statusCode})`);
            // APIエラーに特化したリカバリー処理
        } else if (isTimeoutError(error)) {
            console.error("Timeout Error:", error.message);
            // タイムアウトエラーに特化したリカバリー処理
        } else {
            console.error("An unexpected error occurred:", error);
            // その他のエラーに対する処理
        }
    }
}

この例では、fetchData関数で発生したApiErrorTimeoutErrorを型ガードで判定し、それぞれのエラーに応じた処理を行っています。APIエラーの場合はステータスコードとメッセージを表示し、タイムアウトエラーの場合はそのメッセージを処理します。それ以外のエラーが発生した場合も、汎用的なエラーハンドリングを行います。

非同期処理におけるエラーハンドリングのポイント

  1. 複数のエラータイプに対応: 外部APIのエラーやネットワークのタイムアウトなど、さまざまなエラータイプに対応できるよう、カスタムエラークラスを定義して型ガードで判別する。
  2. 適切なリカバリー処理: エラーの種類ごとに適切なリカバリー処理を実装し、システム全体が安定して動作するようにする。例えば、APIエラー時には再試行を行う、タイムアウト時にはユーザーにリトライを促すなどの対応を考える。
  3. 型ガードによるエラーチェック: 型ガードを使ってエラーの種類を特定し、安全で適切な処理を行うことで、エラー発生時の予測可能性が高まり、システムの安定性が向上する。

まとめ

外部APIと非同期処理におけるエラーハンドリングでは、カスタムエラークラスと型ガードを組み合わせることで、さまざまなエラーに対して柔軟かつ精密な対応が可能になります。これにより、エラーが発生した場合にも、適切なリカバリーやデバッグが行いやすくなり、ユーザーにとってもスムーズなエクスペリエンスを提供できます。

演習問題:カスタムエラークラスと型ガードの実装

ここでは、カスタムエラークラスと型ガードを活用したエラーハンドリングの理解を深めるための演習問題を提示します。この演習では、実際にカスタムエラーを定義し、型ガードを用いてエラーハンドリングを実装します。手を動かしてコードを記述することで、実践的なスキルを身につけることができます。

演習問題の概要

次のシナリオを想定して、エラーの処理を行ってください。

  1. 商品のデータベースを検索する関数findProductByIdがあります。データベース内に存在しないIDを指定された場合、ProductNotFoundErrorをスローしてください。
  2. 検索した商品に不正なデータ(例えば、価格が負の値)が含まれていた場合、InvalidProductErrorをスローしてください。
  3. それぞれのエラーに応じた適切なメッセージを出力するために、型ガードを実装してください。

手順

ステップ 1: カスタムエラークラスの定義

まず、商品が見つからない場合に発生するProductNotFoundErrorと、商品データが無効な場合に発生するInvalidProductErrorを作成します。

class ProductNotFoundError extends Error {
    constructor(productId: number) {
        super(`Product with ID ${productId} not found.`);
        this.name = "ProductNotFoundError";
    }
}

class InvalidProductError extends Error {
    constructor(message: string) {
        super(`Invalid product: ${message}`);
        this.name = "InvalidProductError";
    }
}

ステップ 2: 商品を検索する関数の実装

次に、商品を検索する関数findProductByIdを実装し、条件に応じて適切なエラーをスローします。

interface Product {
    id: number;
    name: string;
    price: number;
}

const products: Product[] = [
    { id: 1, name: "Laptop", price: 1500 },
    { id: 2, name: "Phone", price: 800 },
    { id: 3, name: "Tablet", price: -100 } // 無効な価格
];

function findProductById(productId: number): Product {
    const product = products.find(p => p.id === productId);

    if (!product) {
        throw new ProductNotFoundError(productId);
    }

    if (product.price < 0) {
        throw new InvalidProductError("Price cannot be negative.");
    }

    return product;
}

ステップ 3: 型ガードの実装

次に、エラーの種類を判別するための型ガード関数を作成します。

function isProductNotFoundError(error: any): error is ProductNotFoundError {
    return error instanceof ProductNotFoundError;
}

function isInvalidProductError(error: any): error is InvalidProductError {
    return error instanceof InvalidProductError;
}

ステップ 4: エラーハンドリングの実装

最後に、これらのエラーをキャッチして、それぞれに適した処理を行います。

function handleProductSearch(productId: number) {
    try {
        const product = findProductById(productId);
        console.log(`Product found: ${product.name}, Price: ${product.price}`);
    } catch (error) {
        if (isProductNotFoundError(error)) {
            console.error(error.message);
        } else if (isInvalidProductError(error)) {
            console.error(error.message);
        } else {
            console.error("An unexpected error occurred.");
        }
    }
}

ステップ 5: 動作確認

handleProductSearch関数に様々な商品IDを渡して、適切にエラーハンドリングが行われることを確認します。

handleProductSearch(2); // 正常に商品が見つかる場合
handleProductSearch(3); // 無効な価格のエラー
handleProductSearch(10); // 商品が見つからないエラー

課題の解答例

  1. 商品が見つからなかった場合、ProductNotFoundErrorがスローされ、"Product with ID X not found."というメッセージが表示されます。
  2. 無効な価格が含まれていた場合、InvalidProductErrorがスローされ、"Invalid product: Price cannot be negative."というメッセージが表示されます。
  3. 正常な商品が見つかった場合、その商品情報がコンソールに出力されます。

まとめ

この演習では、カスタムエラークラスと型ガードを用いたエラーハンドリングの基本的な実装方法を学びました。実際に手を動かしてコードを書くことで、エラーハンドリングの流れを理解し、実践的なスキルを磨くことができます。カスタムエラーと型ガードの組み合わせは、複雑なシステムでも強力なエラーハンドリング手法となります。

まとめ

本記事では、TypeScriptにおけるカスタムエラークラスと型ガードを活用したエラーハンドリングの重要性と具体的な実装方法について解説しました。カスタムエラークラスを使用することでエラーの詳細な情報を管理し、型ガードを用いることでエラーの種類を特定し、より精密で安全な処理が可能になります。これらの技術を組み合わせることで、堅牢でメンテナンス性の高いコードを実現し、システムの信頼性とデバッグ効率を向上させることができます。

コメント

コメントする

目次