第17章 エラーハンドリング
17.1 例外の基本
PHP
<?php
try {
throw new Exception("Something went wrong");
} catch (Exception $e) {
echo "Error: " . $e->getMessage();
echo "File: " . $e->getFile();
echo "Line: " . $e->getLine();
echo "Trace: " . $e->getTraceAsString();
} finally {
echo "Cleanup";
}
// 複数の例外タイプ
try {
// ...
} catch (InvalidArgumentException $e) {
// 引数エラー
} catch (RuntimeException $e) {
// 実行時エラー
} catch (Exception $e) {
// その他
}
// PHP 8.0+ 複数の例外を1つの catch で
try {
// ...
} catch (InvalidArgumentException | RuntimeException $e) {
// どちらかの例外
}
// PHP 8.0+ 変数なしの catch
try {
// ...
} catch (NotFoundException) {
// 例外オブジェクトを使わない場合
}
TypeScript
try {
throw new Error("Something went wrong");
} catch (e) {
if (e instanceof Error) {
console.log("Error:", e.message);
console.log("Stack:", e.stack);
}
} finally {
console.log("Cleanup");
}
// 型アサーション
try {
throw new Error("error");
} catch (e: unknown) {
if (e instanceof Error) {
console.log(e.message);
} else if (typeof e === "string") {
console.log(e);
}
}
// never型を返す関数
function fail(message: string): never {
throw new Error(message);
}
VB.NET
Try
Throw New Exception("Something went wrong")
Catch ex As Exception
Console.WriteLine("Error: " & ex.Message)
Console.WriteLine("Source: " & ex.Source)
Console.WriteLine("Stack: " & ex.StackTrace)
Finally
Console.WriteLine("Cleanup")
End Try
' 複数の例外タイプ
Try
' ...
Catch ex As ArgumentException
' 引数エラー
Catch ex As InvalidOperationException
' 無効な操作
Catch ex As Exception
' その他
End Try
' When 句による条件付きキャッチ
Try
' ...
Catch ex As Exception When ex.Message.Contains("timeout")
' タイムアウトエラーのみ
Catch ex As Exception
' その他
End Try
17.2 例外クラス階層
PHP
Throwable
├── Error(内部エラー)
│ ├── ArithmeticError
│ ├── AssertionError
│ ├── CompileError
│ ├── ParseError
│ ├── TypeError
│ └── ValueError
└── Exception(ユーザー例外)
├── ErrorException
├── LogicException
│ ├── BadFunctionCallException
│ ├── BadMethodCallException
│ ├── DomainException
│ ├── InvalidArgumentException
│ ├── LengthException
│ └── OutOfRangeException
└── RuntimeException
├── OutOfBoundsException
├── OverflowException
├── RangeException
├── UnderflowException
└── UnexpectedValueException
TypeScript
Error
├── RangeError
├── ReferenceError
├── SyntaxError
├── TypeError
└── URIError
// 組み込みエラーは少ないため、カスタムエラーを定義することが多い
VB.NET
Exception
├── SystemException
│ ├── ArgumentException
│ │ ├── ArgumentNullException
│ │ └── ArgumentOutOfRangeException
│ ├── ArithmeticException
│ │ ├── DivideByZeroException
│ │ └── OverflowException
│ ├── FormatException
│ ├── IndexOutOfRangeException
│ ├── InvalidCastException
│ ├── InvalidOperationException
│ ├── NotImplementedException
│ ├── NotSupportedException
│ ├── NullReferenceException
│ └── OutOfMemoryException
└── ApplicationException(カスタム例外の基底クラス、非推奨)
17.3 カスタム例外
PHP
<?php
class UserNotFoundException extends Exception {
public function __construct(
public readonly int $userId,
string $message = "User not found",
int $code = 0,
?Throwable $previous = null
) {
parent::__construct($message, $code, $previous);
}
public function getUserId(): int {
return $this->userId;
}
}
class ValidationException extends Exception {
public function __construct(
public readonly array $errors,
string $message = "Validation failed"
) {
parent::__construct($message);
}
}
// 使用
try {
throw new UserNotFoundException(123);
} catch (UserNotFoundException $e) {
echo "User ID: " . $e->userId;
}
TypeScript
class UserNotFoundError extends Error {
constructor(public readonly userId: number) {
super(`User not found: ${userId}`);
this.name = "UserNotFoundError";
// ES5 ターゲットの場合に必要
Object.setPrototypeOf(this, UserNotFoundError.prototype);
}
}
class ValidationError extends Error {
constructor(
public readonly errors: Record<string, string[]>,
message = "Validation failed"
) {
super(message);
this.name = "ValidationError";
Object.setPrototypeOf(this, ValidationError.prototype);
}
}
// 使用
try {
throw new UserNotFoundError(123);
} catch (e) {
if (e instanceof UserNotFoundError) {
console.log("User ID:", e.userId);
}
}
VB.NET
Public Class UserNotFoundException
Inherits Exception
Public ReadOnly Property UserId As Integer
Public Sub New(userId As Integer)
MyBase.New($"User not found: {userId}")
Me.UserId = userId
End Sub
Public Sub New(userId As Integer, message As String)
MyBase.New(message)
Me.UserId = userId
End Sub
Public Sub New(userId As Integer, message As String, inner As Exception)
MyBase.New(message, inner)
Me.UserId = userId
End Sub
End Class
Public Class ValidationException
Inherits Exception
Public ReadOnly Property Errors As Dictionary(Of String, String())
Public Sub New(errors As Dictionary(Of String, String()))
MyBase.New("Validation failed")
Me.Errors = errors
End Sub
End Class
' 使用
Try
Throw New UserNotFoundException(123)
Catch ex As UserNotFoundException
Console.WriteLine($"User ID: {ex.UserId}")
End Try
17.4 エラー伝播
PHP
<?php
// 例外の再スロー
function process(): void {
try {
doSomething();
} catch (Exception $e) {
// ログを取って再スロー
log($e->getMessage());
throw $e;
}
}
// 例外のラップ
function processWithWrap(): void {
try {
doSomething();
} catch (DatabaseException $e) {
throw new ServiceException("Service failed", 0, $e);
}
}
// グローバルエラーハンドラ
set_exception_handler(function (Throwable $e) {
error_log($e->getMessage());
http_response_code(500);
echo "Internal Server Error";
});
TypeScript
// 例外の再スロー
async function process(): Promise<void> {
try {
await doSomething();
} catch (e) {
console.error(e);
throw e;
}
}
// 例外のラップ
async function processWithWrap(): Promise<void> {
try {
await doSomething();
} catch (e) {
throw new ServiceError("Service failed", { cause: e });
}
}
// cause プロパティ(ES2022+)
class ServiceError extends Error {
constructor(message: string, options?: ErrorOptions) {
super(message, options);
}
}
// グローバルエラーハンドラ(Node.js)
process.on('uncaughtException', (error) => {
console.error('Uncaught Exception:', error);
process.exit(1);
});
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
});
VB.NET
' 例外の再スロー
Sub Process()
Try
DoSomething()
Catch ex As Exception
Log(ex.Message)
Throw ' 元の例外をそのまま再スロー
End Try
End Sub
' 例外のラップ
Sub ProcessWithWrap()
Try
DoSomething()
Catch ex As DatabaseException
Throw New ServiceException("Service failed", ex)
End Try
End Sub
' InnerException の活用
Sub HandleException(ex As Exception)
Console.WriteLine($"Error: {ex.Message}")
Dim inner = ex.InnerException
While inner IsNot Nothing
Console.WriteLine($"Caused by: {inner.Message}")
inner = inner.InnerException
End While
End Sub
' グローバルエラーハンドラ(Windows Forms)
Sub Main()
AddHandler Application.ThreadException,
Sub(sender, e)
MessageBox.Show(e.Exception.Message)
End Sub
AddHandler AppDomain.CurrentDomain.UnhandledException,
Sub(sender, e)
Dim ex = DirectCast(e.ExceptionObject, Exception)
MessageBox.Show(ex.Message)
End Sub
Application.Run(New MainForm())
End Sub
17.5 Result型パターン
PHP
<?php
class Result {
private function __construct(
private readonly bool $success,
private readonly mixed $value,
private readonly ?string $error
) {}
public static function ok(mixed $value): self {
return new self(true, $value, null);
}
public static function fail(string $error): self {
return new self(false, null, $error);
}
public function isOk(): bool {
return $this->success;
}
public function getValue(): mixed {
if (!$this->success) {
throw new RuntimeException("Cannot get value from failed result");
}
return $this->value;
}
public function getError(): ?string {
return $this->error;
}
public function map(callable $fn): self {
if (!$this->success) {
return $this;
}
return self::ok($fn($this->value));
}
}
// 使用
function divide(float $a, float $b): Result {
if ($b === 0.0) {
return Result::fail("Division by zero");
}
return Result::ok($a / $b);
}
$result = divide(10, 2);
if ($result->isOk()) {
echo $result->getValue();
} else {
echo $result->getError();
}
TypeScript
type Result<T, E = Error> =
| { ok: true; value: T }
| { ok: false; error: E };
function ok<T>(value: T): Result<T, never> {
return { ok: true, value };
}
function err<E>(error: E): Result<never, E> {
return { ok: false, error };
}
function divide(a: number, b: number): Result<number, string> {
if (b === 0) {
return err("Division by zero");
}
return ok(a / b);
}
// 使用
const result = divide(10, 2);
if (result.ok) {
console.log(result.value);
} else {
console.log(result.error);
}
// パターンマッチング風
function match<T, E, R>(
result: Result<T, E>,
patterns: { ok: (value: T) => R; err: (error: E) => R }
): R {
return result.ok ? patterns.ok(result.value) : patterns.err(result.error);
}
const message = match(result, {
ok: (value) => `Result: ${value}`,
err: (error) => `Error: ${error}`
});
VB.NET
Public Class Result(Of T)
Public ReadOnly Property IsSuccess As Boolean
Public ReadOnly Property Value As T
Public ReadOnly Property ErrorMessage As String
Private Sub New(isSuccess As Boolean, value As T, errorMessage As String)
Me.IsSuccess = isSuccess
Me.Value = value
Me.ErrorMessage = errorMessage
End Sub
Public Shared Function Ok(value As T) As Result(Of T)
Return New Result(Of T)(True, value, Nothing)
End Function
Public Shared Function Fail(errorMessage As String) As Result(Of T)
Return New Result(Of T)(False, Nothing, errorMessage)
End Function
Public Function Map(Of U)(fn As Func(Of T, U)) As Result(Of U)
If Not IsSuccess Then
Return Result(Of U).Fail(ErrorMessage)
End If
Return Result(Of U).Ok(fn(Value))
End Function
Public Function Match(Of U)(
onSuccess As Func(Of T, U),
onFailure As Func(Of String, U)) As U
If IsSuccess Then
Return onSuccess(Value)
End If
Return onFailure(ErrorMessage)
End Function
End Class
' 使用
Function Divide(a As Double, b As Double) As Result(Of Double)
If b = 0 Then
Return Result(Of Double).Fail("Division by zero")
End If
Return Result(Of Double).Ok(a / b)
End Function
Dim result = Divide(10, 2)
If result.IsSuccess Then
Console.WriteLine(result.Value)
Else
Console.WriteLine(result.ErrorMessage)
End If
' パターンマッチング風
Dim message = result.Match(
Function(v) $"Result: {v}",
Function(e) $"Error: {e}"
)
17.6 ベストプラクティス
例外を使うべき場面
- 予期しないエラー(ファイルが見つからない、ネットワークエラー)
- 回復不能な状態
- プログラミングエラー(不正な引数)
例外を避けるべき場面
- 予期されるフロー制御(ユーザー入力の検証)
- パフォーマンスが重要な場面
- 正常な代替パスがある場合
// 悪い例:例外でフロー制御
function findUser(id: number): User {
const user = database.get(id);
if (!user) {
throw new UserNotFoundError(id);
}
return user;
}
// 良い例:Result型または null
function findUser(id: number): User | null {
return database.get(id);
}
// または
function findUser(id: number): Result<User, string> {
const user = database.get(id);
if (!user) {
return err("User not found");
}
return ok(user);
}