【TypeScript】型安全なエラーハンドリング - 実装パターン集
2024-11-10
2024-11-10
型安全なエラーハンドリング:実装パターン集
TypeScript
を使うと、型安全なエラーハンドリングを実現でき、エラー処理における見落としや予期しない動作を防ぐことが可能です。この記事では、TypeScript
でのエラーハンドリングにおいて型安全性を高めるための実装パターンを紹介し、堅牢で保守性の高いコードの実現を目指します。
TypeScriptで型安全なエラーハンドリングを行うメリット
型安全なエラーハンドリングにより、エラーの発生源やエラーデータの構造が明確になり、エラーの処理漏れや型の不一致を減らすことができます。これにより、実行時エラーが減り、予測可能な動作を持つコードを実現できます。
型安全なエラーハンドリングの主なメリット
- エラー内容の明確化
エラーメッセージやエラーコードが型で定義されるため、エラー内容が明確になり、理解しやすくなります。 - コンパイル時の型チェック
開発中にコンパイルエラーが出ることで、エラーハンドリングの不足やミスを事前に発見できます。 - 保守性の向上
型に基づくエラーハンドリングを行うことで、将来の変更や拡張がしやすくなり、保守性が高まります。
型安全なエラーハンドリングの実装パターン
以下に、TypeScript
で型安全にエラーハンドリングを行うための主要なパターンをいくつか紹介します。
カスタムエラーの作成
TypeScript
では、標準のError
クラスを拡張してカスタムエラーを作成できます。カスタムエラーを使用することで、エラーの種類や詳細情報を明確に定義できます。
class NotFoundError extends Error {
constructor(message: string) {
super(message);
this.name = "NotFoundError";
}
}
class ValidationError extends Error {
constructor(public field: string, message: string) {
super(message);
this.name = "ValidationError";
}
}
// 使用例
try {
throw new ValidationError("email", "Invalid email format");
} catch (error) {
if (error instanceof ValidationError) {
console.log(`Validation error on field: ${error.field}`);
}
}
この例では、NotFoundError
とValidationError
というカスタムエラーを定義しています。それぞれ異なるエラー内容を表現できるため、エラーハンドリングがしやすくなります。
Union型を用いたResultパターン
関数の戻り値としてUnion型を使い、Result
パターンを実装することで、成功と失敗の両方の結果を型安全に扱えます。これは、API呼び出しや非同期処理でよく使われる手法です。
type Result<T> = { success: true; value: T } | { success: false; error: Error };
function parseJSON<T>(jsonString: string): Result<T> {
try {
const parsed = JSON.parse(jsonString);
return { success: true, value: parsed };
} catch (error) {
return { success: false, error: new Error("Invalid JSON format") };
}
}
// 使用例
const result = parseJSON<{ name: string }>('{"name": "Alice"}');
if (result.success) {
console.log("Parsed value:", result.value);
} else {
console.error("Error:", result.error.message);
}
この例では、parseJSON
関数の戻り値が成功か失敗かをsuccess
プロパティで確認でき、型に基づいて処理を分けられるため、安全で読みやすいコードが実現できます。
Either型を用いたエラーハンドリング
Either
型は、成功と失敗のどちらか一方の結果を表す型で、Left
(失敗)とRight
(成功)に分かれます。Either
型を用いることで、エラーが発生する可能性がある処理を安全に行えます。
type Either<L, R> = { type: "left"; value: L } | { type: "right"; value: R };
function divide(a: number, b: number): Either<string, number> {
if (b === 0) {
return { type: "left", value: "Division by zero error" };
}
return { type: "right", value: a / b };
}
// 使用例
const result = divide(10, 0);
if (result.type === "left") {
console.error("Error:", result.value);
} else {
console.log("Result:", result.value);
}
このパターンにより、エラーメッセージや成功結果の処理が統一され、エラーハンドリングが簡潔に記述できます。
Option型によるNullチェック
Option
型は、値があるかもしれないし、ないかもしれない状態を表現するための型で、Some
(値がある)とNone
(値がない)に分かれます。これにより、null
やundefined
によるエラーを回避できます。
type Option<T> = { type: "some"; value: T } | { type: "none" };
function findUser(id: number): Option<{ id: number; name: string }> {
const users = [{ id: 1, name: "Alice" }, { id: 2, name: "Bob" }];
const user = users.find((u) => u.id === id);
return user ? { type: "some", value: user } : { type: "none" };
}
// 使用例
const user = findUser(1);
if (user.type === "some") {
console.log("Found user:", user.value.name);
} else {
console.log("User not found");
}
この例では、ユーザーが見つからない場合にnone
を返すことで、null
やundefined
のチェックを省略できます。
Try-Catchを活用したエラーハンドリング
JavaScriptの標準的なtry-catch
構文も、TypeScript
でカスタムエラーや型チェックと組み合わせて利用することで、さらに型安全なエラーハンドリングが可能です。
async function fetchData(url: string): Promise<string> {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP
error! status: ${response.status}`);
}
return await response.text();
} catch (error) {
if (error instanceof Error) {
console.error("Fetch error:", error.message);
} else {
console.error("Unknown error occurred");
}
throw error; // 必要に応じて再スロー
}
}
// 使用例
fetchData("https://api.example.com/data").catch((error) => {
console.error("Error caught in main:", error);
});
この例では、fetchData
関数内でエラーの詳細をログ出力し、必要に応じてエラーを再スローすることで、エラーを上位で処理することもできます。
型安全なエラーハンドリングのまとめ
TypeScript
を活用した型安全なエラーハンドリングは、コードの可読性や保守性を向上させ、予期しないエラーを減らす効果があります。カスタムエラーの作成や、Result
、Either
、Option
といったパターンを利用することで、柔軟かつ堅牢なエラーハンドリングが実現可能です。プロジェクトの特性やエラー処理の要件に応じて、これらのパターンを適切に組み合わせて活用しましょう。