9
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Result パターン

9
Posted at

🔎 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 / FailureValue / Error を一つにまとめられる
  • Clean Architecture / DDD と非常に相性が良い
  • 例外は 本当に異常なケース のみに使うべき
  • ErrorCode をつけると運用レベルでさらに便利
9
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?