[C#] 從 try/catch 到 Result Pattern:讓錯誤回到主流程的寫法

  • 325
  • 0

最近在整理一段舊系統的商業邏輯時,發現錯誤處理幾乎清一色都是  try/catch + throw

現在其實已經是  21 世紀了,很多時候有更好的方法可以處理 try catch 跟以前學習的方式有點不同

這篇單純記錄我實際套用 Result Pattern 後的想法與最小可行寫法筆記一下..

先講重點結論 - 如果一個失敗是你"早就知道會發生"的狀況,那就不該用 exception 來表達

Result Pattern 的價值不在於多厲害,而是把失敗直接寫進回傳型別,讓控制流程變得在主流程可以得知

不用在外面呼叫也是一堆 try/catch 處理

我常寫的 code:


    public User GetUserById(int id)
    {
        var user = _repository.Find(id);
        if (user is null)
            throw new UserNotFoundException("User is not Existed.");

        return user;
    }
  	
  

這一段程式碼其實沒有錯,只是實務成本太高呼叫端只能被迫去接你的 exception ,其實這資料(用戶)不存在

是一個可以被預期的可能,然後可以被正常表達的處理狀態,就可以調整使用 Result Pattern


Result Pattern 和新想法 ,就是不要用 throw 來中斷流程,而是回傳一個成功或失敗的結果。

於是可以試著把 method 改成這樣

	public Result GetUserById(int id){
    	//...
    }
    

這邊給一個比較簡單我也常用的Result<T> 設計


   
    
public class Error
{
    public string Message { get; }

    public Error(string message)
    {
        Message = message;
    }
}

public class Result
{
    public bool IsSuccess { get; }
    public T Value { get; }
    public Error Error { get; }

    private Result(T value)
    {
        IsSuccess = true;
        Value = value;
    }

    private Result(Error error)
    {
        IsSuccess = false;
        Error = error;
    }

    public static Result Success(T value) => new(value);

    public static Result Failure(string message) =>
        new(new Error(message));
    
}
    
    

  

這樣我們就可以重新實做 GetUserById 


 
  
public Result GetUserById(int id)
{
    var user = _repository.Find(id);

    if (user == null)
        return Result.Failure("User is not Existed.");

    return Result.Success(user);
}
    
    
//呼叫方式

var result = service.GetUserById(id);

if (!result.IsSuccess)
{
    Console.WriteLine(result.Error.Message);
    return;
}

Process(result.Value);
  
  
  

其實看到現在,或許你覺得無聊跟多此一舉,但是其實在實務上你在在設計 API ,我們也會常常使用

Result Pattern 畢竟對方呼叫是 200 正確,但是 service provider 端這邊可能會必須要告訴呼叫端 server 發生出的錯誤

之後這樣的 code 改成 API 也會比較方便,真正的把 try catch 留給我們不能預期的錯誤在去攔截

畢竟以前我常常很喜歡透過各種 Exception 去表示錯誤,我現在幾乎都是 Result + Exception 混用

讓商業邏輯回到 if/else,程式碼會更好讀,也更好測試

等到真的需要錯誤分類或更細緻的錯誤處理時,再慢慢把它補回來就好

在這之前就多打點字,不過現在都有 AI 幫忙寫了,在現在 AI 時代更應該把程式碼寫得更加的結構好維護

--

本文原文首發於個人部落格:[C#] 從 try/catch 到 Result Pattern:讓錯誤回到主流程的寫法

--

---

The bug existed in all possible states.
Until I ran the code.