🔎 Result パターンとは?
例外を使わず、成功/失敗を戻り値で表現するデザインパターン。
関数から値を返しつつ、「成功/失敗」「エラーメッセージ」「エラーの種類」などをまとめて扱えます。
var result = await _userService.GetUserAsync(id);
if (result.IsFailure)
{
return BadRequest(result.Error);
}
return Ok(result.Value);
例外が発生しうるが「通常フロー」として扱いたいケースで強力。
🧠 なぜ Result パターンが必要なのか?
❌ 例外は高コストでフローが汚れやすい
例外はスタックトレースを生成するため 重い。
かつ「例外発生 → try/catch → 再度 throw → ログ」などコードが煩雑になりがち。
try {
var user = repository.Find(id);
if (user == null) throw new NotFoundException();
} catch (NotFoundException ex) {
// ※これって例外?
}
これ、エラーというより「条件が満たないだけ」だったりしませんか?
🟢 Result パターンのメリット
1. 例外を通常フローから排除できる
例外は「本当に異常なケース」のみに使える。
2. 呼び出し側で成功/失敗を明確に判定できる
IsSuccess / IsFailure が可読性を高める。
3. 関数の責務がわかりやすくなる
「値を返す or エラーを返す」のどちらかに統一される。
4. Clean Architecture / DDD と相性が良い
特に Application 層でほぼ必須レベルに浸透。
🛠 基本的な実装
✔ Result(値なし)
public class Result
{
public bool IsSuccess { get; }
public bool IsFailure => !IsSuccess;
public string Error { get; }
protected Result(bool isSuccess, string error)
{
IsSuccess = isSuccess;
Error = error;
}
public static Result Success()
=> new Result(true, null);
public static Result Failure(string error)
=> new Result(false, error);
}
✔ Result(値あり)
public class Result<T> : Result
{
public T Value { get; }
private Result(bool isSuccess, T value, string error)
: base(isSuccess, error)
{
Value = value;
}
public static Result<T> Success(T value)
=> new Result<T>(true, value, null);
public static new Result<T> Failure(string error)
=> new Result<T>(false, default, error);
}
🧩 実務では「ErrorCode」も付けると強い
API / バッチでエラーの種類を識別したい場合はコード化すると便利。
public enum ErrorCode
{
NotFound,
Validation,
Unauthorized,
DbError
}
public class Error
{
public ErrorCode Code { get; }
public string Message { get; }
public Error(ErrorCode code, string message)
{
Code = code;
Message = message;
}
}
public class Result<T>
{
public bool IsSuccess { get; }
public Error Error { get; }
public T Value { get; }
// ...省略...
}
🧵 呼び出し側での利用例(WebAPI)
[HttpGet("{id}")]
public async Task<IActionResult> GetUser(int id)
{
var result = await _service.GetUserAsync(id);
if (result.IsFailure)
{
return result.Error.Code switch
{
ErrorCode.NotFound => NotFound(result.Error.Message),
ErrorCode.Validation => BadRequest(result.Error.Message),
_ => StatusCode(500, result.Error.Message)
};
}
return Ok(result.Value);
}
レスポンスの分岐が明確になり、例外ログも減る。
Anti-pattern(やってはいけない使い方)
全部 Result、例外は完全禁止
例外を完全に排除するのは誤り。
- DB接続失敗
- 予期せぬ NullReference
- メモリ不足
- ネットワーク障害
こういう「異常系」は例外にすべき。
どこで Result を使うべきか?
| 層 | Result を使う? | 理由 |
|---|---|---|
| Domain | △(使わなくてもよい) | Domainは例外ベースでもOK。好み。 |
| Application | ◎ 必須級 | ユースケースの失敗/成功を返すのに最適 |
| Infrastructure | △ | DB接続失敗は例外、バリデーションはResultなど |
| Presentation (API) | ◎ | StatusCode 分岐がしやすい |
まとめ
- Result パターンは例外を通常フローから排除するモダンな設計
-
Success/FailureとValue/Errorを一つにまとめられる - Clean Architecture / DDD と非常に相性が良い
- 例外は 本当に異常なケース のみに使うべき
- ErrorCode をつけると運用レベルでさらに便利