名栋 發表於 2026-1-20 19:40:00

告别 throw exception!为什么 Result<T> 才是业务逻辑的正确选择

<h1 id="引言一个普遍存在的坏味道">引言:一个普遍存在的“坏味道”</h1>
<p>如果你在C#项目中看到这样的代码,一定不会感到陌生:</p>
<pre><code class="language-csharp">public User Login(string username, string password)
{
    var user = FindUser(username);
    if (user == null)
      throw new Exception("用户不存在");// ❌ 熟悉的模式
   
    if (!VerifyPassword(user, password))
      throw new Exception("密码错误");    // ❌ 另一个熟悉的模式
   
    return user;
}
</code></pre>
<p>这种使用异常来处理业务逻辑的做法,几乎成了C#开发的“标准范式”。<br>
可是,<strong>从来如此,便是对的么?</strong></p>
<h1 id="一异常的原罪--我们一直在滥用它">一、异常的“原罪” —— 我们一直在滥用它</h1>
<h2 id="11-异常的本质是什么">1.1 异常的本质是什么?</h2>
<p>首先,我们要明白C#语言里的异常(Exception)的设计初衷:</p>
<pre><code class="language-csharp">// 这些才是异常真正的使用场景:
public void ReadFile(string path)
{
    if (string.IsNullOrEmpty(path))
      throw new ArgumentNullException(nameof(path));// ✅ 参数检查
   
    if (!File.Exists(path))
      throw new FileNotFoundException($"文件不存在: {path}");// ✅ 系统错误
   
    // 尝试读取文件,可能抛出IOException等
    var content = File.ReadAllText(path);
}
</code></pre>
<p>异常是为真正的"异常情况"设计的,比如:</p>
<ul>
<li>系统资源不可用(文件不存在、数据库连接失败)</li>
<li>程序状态异常(空指针、数组越界)</li>
<li>参数验证失败(前置条件不满足)</li>
</ul>
<h2 id="12-业务逻辑--异常情况">1.2 业务逻辑 ≠ 异常情况</h2>
<p>业务错误(用户不存在、密码错误、余额不足)是<strong>可预见的正常业务流程</strong>,而不是异常情况。<br>
把业务错误用异常处理,就像:</p>
<ul>
<li>用"地震警报"来处理"家里没米了"</li>
<li>用"消防车"来运送"快递包裹"</li>
<li>用"手术室"来处理"感冒发烧"</li>
</ul>
<p>这是对异常机制的严重滥用!</p>
<h1 id="二result业务逻辑的优雅降级">二、Result<t>——业务逻辑的"优雅降级"</t></h1>
<h2 id="21-什么是result">2.1 什么是Result<t>?</t></h2>
<p>一个简单,具备基本功能的Result类如下:</p>
<pre><code class="language-csharp">public class Result&lt;T&gt;
{
    public bool Success { get; }
    public T? Value { get; }
    public string? Error { get; }
   
    private Result(T value) { Success = true; Value = value; Error = null; }
    private Result(string error) { Success = false; Value = default; Error = error; }
   
    public static Result&lt;T&gt; Ok(T value) =&gt; new(value);
    public static Result&lt;T&gt; Fail(string error) =&gt; new(error);
}
</code></pre>
<h2 id="22-如何正确使用result">2.2 如何正确使用Result<t>?</t></h2>
<pre><code class="language-csharp">public Result&lt;User&gt; Login(string username, string password)
{
    if (string.IsNullOrEmpty(username))
      return Result&lt;User&gt;.Fail("用户名不能为空");// ✅ 明确返回业务错误
   
    if (string.IsNullOrEmpty(password))
      return Result&lt;User&gt;.Fail("密码不能为空");    // ✅ 明确返回业务错误
   
    var user = FindUser(username);
    if (user == null)
      return Result&lt;User&gt;.Fail("用户不存在");      // ✅ 明确返回业务错误
   
    if (!VerifyPassword(user, password))
      return Result&lt;User&gt;.Fail("密码错误");      // ✅ 明确返回业务错误
   
    return Result&lt;User&gt;.Ok(user);                   // ✅ 明确返回成功
}
</code></pre>
<h1 id="三性能对决--几近碾压的性能差距">三、性能对决 —— 几近碾压的性能差距</h1>
<h2 id="31-部分测试代码">3.1 部分测试代码</h2>
<p>项目环境:</p>
<pre><code>&lt;Project Sdk="Microsoft.NET.Sdk"&gt;
        &lt;PropertyGroup&gt;
                &lt;OutputType&gt;Exe&lt;/OutputType&gt;
                &lt;TargetFramework&gt;net8.0&lt;/TargetFramework&gt;
                &lt;ImplicitUsings&gt;enable&lt;/ImplicitUsings&gt;
                &lt;Nullable&gt;enable&lt;/Nullable&gt;
                &lt;LangVersion&gt;latest&lt;/LangVersion&gt;
        &lt;/PropertyGroup&gt;
&lt;/Project&gt;
</code></pre>
<p>部分测试代码:</p>
<pre><code class="language-csharp"> public class LoginService
{
   private readonly Dictionary&lt;string, User&gt; _users = new()
   {
         ["valid_user"] = new User { Id = 1, Username = "valid_user" }
   };

   
   public User LoginWithException(string username, string password)
   {
         if (!_users.TryGetValue(username, out var user))
             throw new BusinessException("用户不存在");

         if (password != "correct_password")
             throw new BusinessException("密码错误");

         return user;
   }

   
   public Result&lt;User&gt; LoginWithResult(string username, string password)
   {
         if (!_users.TryGetValue(username, out var user))
             return Result&lt;User&gt;.Fail("用户不存在");

         if (password != "correct_password")
             return Result&lt;User&gt;.Fail("密码错误");

         return Result&lt;User&gt;.Ok(user);
   }
}
</code></pre>
<pre><code class="language-csharp">public class PerformanceTester
{
    private readonly LoginService _service = new();
    private readonly Random _random = new(42);

    public void RunAllTests(int iterations = 1000000)
    {
      Console.WriteLine($"性能对比测试 - 迭代次数: {iterations:N0}");
      Console.WriteLine("=".PadRight(60, '='));

      // 测试1:成功路径(正常情况)
      TestSuccessPath(iterations);

      // 测试2:失败路径(错误情况)
      TestErrorPath(iterations);

      // 测试3:混合路径(30%成功率)
      TestMixedPath(iterations, 0.3);
    }

    private void TestSuccessPath(int iterations)
    {
      Console.WriteLine("\n测试1:成功路径(100%成功)");

      // 异常方式
      var exceptionTime = Measure(() =&gt;
      {
            try
            {
                _service.LoginWithException("valid_user", "correct_password");
            }
            catch
            {
                // 不应该发生
            }
      }, iterations, "异常方式");

      // Result方式
      var resultTime = Measure(() =&gt;
      {
            var result = _service.LoginWithResult("valid_user", "correct_password");
            if (!result.Success)
            {
                // 不应该发生
            }
      }, iterations, "Result方式");

      PrintComparison(exceptionTime, resultTime);
    }

    private void TestErrorPath(int iterations)
    {
      Console.WriteLine("\n测试2:失败路径(100%失败)");

      // 异常方式
      var exceptionTime = Measure(() =&gt;
      {
            try
            {
                _service.LoginWithException("invalid_user", "wrong_password");
            }
            catch (BusinessException)
            {
                // 预期异常
            }
      }, iterations, "异常方式");

      // Result方式
      var resultTime = Measure(() =&gt;
      {
            var result = _service.LoginWithResult("invalid_user", "wrong_password");
            if (result.Success)
            {
                // 不应该发生
            }
      }, iterations, "Result方式");

      PrintComparison(exceptionTime, resultTime);
    }

    private void TestMixedPath(int iterations, double successRate)
    {
      Console.WriteLine($"\n测试3:混合路径({successRate:P0}成功率)");

      // 准备测试数据
      var testData = new (string user, string pwd, bool shouldSucceed);
      for (int i = 0; i &lt; iterations; i++)
      {
            testData = _random.NextDouble() &lt; successRate
                ? ("valid_user", "correct_password", true)   // 成功
                : ("invalid_user", "wrong_password", false); // 失败
      }

      // 异常方式
      var exceptionTime = MeasureMixed(testData, true, "异常方式");

      // Result方式
      var resultTime = MeasureMixed(testData, false, "Result方式");

      PrintComparison(exceptionTime, resultTime);
    }

    private static long Measure(Action action, int iterations, string testName)
    {
      // 预热
      for (int i = 0; i &lt; 1000; i++) action();

      GC.Collect();
      GC.WaitForPendingFinalizers();
      GC.Collect();

      var sw = Stopwatch.StartNew();
      for (int i = 0; i &lt; iterations; i++)
      {
            action();
      }
      sw.Stop();

      var opsPerSecond = iterations / (sw.ElapsedMilliseconds / 1000.0);
      Console.WriteLine($"{testName}: {sw.ElapsedMilliseconds,8}ms ({opsPerSecond,12:N0} ops/s)");

      return sw.ElapsedMilliseconds;
    }

    private long MeasureMixed(
      (string user, string pwd, bool shouldSucceed)[] testData,
      bool useException,
      string testName)
    {
      // 预热
      for (int i = 0; i &lt; Math.Min(1000, testData.Length); i++)
      {
            var (user, pwd, _) = testData;
            if (useException)
            {
                try
                {
                  _service.LoginWithException(user, pwd);
                }
                catch { }
            }
            else
            {
                _service.LoginWithResult(user, pwd);
            }
      }

      GC.Collect();
      GC.WaitForPendingFinalizers();
      GC.Collect();

      var sw = Stopwatch.StartNew();

      if (useException)
      {
            for (int i = 0; i &lt; testData.Length; i++)
            {
                var (user, pwd, _) = testData;
                try
                {
                  _service.LoginWithException(user, pwd);
                }
                catch { }
            }
      }
      else
      {
            for (int i = 0; i &lt; testData.Length; i++)
            {
                var (user, pwd, _) = testData;
                _service.LoginWithResult(user, pwd);
            }
      }

      sw.Stop();

      var opsPerSecond = testData.Length / (sw.ElapsedMilliseconds / 1000.0);
      Console.WriteLine($"{testName}: {sw.ElapsedMilliseconds,8}ms ({opsPerSecond,12:N0} ops/s)");

      return sw.ElapsedMilliseconds;
    }

    private static void PrintComparison(long exceptionTime, long resultTime)
    {
      var speedup = exceptionTime / (double)resultTime;
      var improvement = (exceptionTime - resultTime) * 100.0 / exceptionTime;

      if (speedup &gt; 1)
      {
            Console.WriteLine($"Result比Exception快 {speedup:F1}x,性能提升 {improvement:F1}%");
      }
      else
      {
            Console.WriteLine($"差异不大: {speedup:F2}x");
      }
    }
}
</code></pre>
<h2 id="32-测试结果触目惊心">3.2 测试结果:触目惊心</h2>
<p>先上图,看测试结果(<strong>基于RELEASE模式编译</strong>):</p>
<p><img src="https://img2024.cnblogs.com/blog/1553709/202601/1553709-20260120175226554-1542544296.png"></p>
<h2 id="33-并发场景下性能差距依旧不忍直视">3.3 并发场景下:性能差距依旧不忍直视</h2>
<p>并发测试核心代码:</p>
<pre><code class="language-csharp">/// &lt;summary&gt;
/// // 并发测试结果类
/// &lt;/summary&gt;
public class ConcurrentTestResult
{
    public int Concurrency { get; set; }
    public long ExceptionTime { get; set; }
    public long ResultTime { get; set; }
    public double ExceptionOpsPerSecond { get; set; }
    public double ResultOpsPerSecond { get; set; }
}


/// &lt;summary&gt;
/// 并发测试器
/// &lt;/summary&gt;
public class ConcurrentPerformanceTester
{
    private readonly LoginService _service = new();
    private readonly Random _random = new(42);

    private readonly double _errorRate = 0.3;


    public async Task RunConcurrentTests(int totalIterations = 1000000)
    {
      Console.WriteLine("\n并发性能测试 - 总迭代次数: {0:N0} - 错误率:{1:P0}", totalIterations, _errorRate);
      Console.WriteLine("=".PadRight(60, '='));

      var concurrencyLevels = new[] { 4, 8, 16, 32, 64, 128, 256 };

      foreach (var concurrency in concurrencyLevels)
      {
            Console.WriteLine($"\n并发数: {concurrency}");

            // 预热
            await Warmup(concurrency);

            // 异常方式并发测试(30%错误率模拟真实场景)
            var exceptionTime = await RunConcurrentExceptionTest(
                concurrency,
                totalIterations,
                errorRate: _errorRate
            );

            // Result方式并发测试
            var resultTime = await RunConcurrentResultTest(
                concurrency,
                totalIterations,
                errorRate: _errorRate
            );

            var exceptionOps = totalIterations / (exceptionTime / 1000.0);
            var resultOps = totalIterations / (resultTime / 1000.0);
            var speedup = exceptionTime / (double)resultTime;

            Console.WriteLine($"异常: {exceptionTime,5}ms ({exceptionOps,8:N0} ops/s)");
            Console.WriteLine($"Result: {resultTime,5}ms ({resultOps,8:N0} ops/s)");
            Console.WriteLine($"Result快 {speedup:F1}x");
      }
    }

    /// &lt;summary&gt;
    /// 并发异常测试
    /// &lt;/summary&gt;
    /// &lt;param name="concurrency"&gt;&lt;/param&gt;
    /// &lt;param name="totalIterations"&gt;&lt;/param&gt;
    /// &lt;param name="errorRate"&gt;&lt;/param&gt;
    /// &lt;returns&gt;&lt;/returns&gt;
    private async Task&lt;long&gt; RunConcurrentExceptionTest(
      int concurrency,
      int totalIterations,
      double errorRate)
    {
      var iterationsPerTask = totalIterations / concurrency;
      var tasks = new Task;

      var sw = Stopwatch.StartNew();

      for (int i = 0; i &lt; concurrency; i++)
      {
            // 每个任务使用自己的随机实例,避免竞争
            var taskRandom = new Random(_random.Next());
            tasks = Task.Run(() =&gt;
            {
                for (int j = 0; j &lt; iterationsPerTask; j++)
                {
                  // 为每次迭代生成测试数据,避免索引问题
                  var (user, pwd) = GenerateTestDataForIteration(taskRandom, errorRate);
                  try
                  {
                        _service.LoginWithException(user, pwd);
                  }
                  catch (BusinessException)
                  {
                        // 预期异常
                  }
                }
            });
      }

      await Task.WhenAll(tasks);
      sw.Stop();

      return sw.ElapsedMilliseconds;
    }

    /// &lt;summary&gt;
    /// 并发Result测试
    /// &lt;/summary&gt;
    /// &lt;param name="concurrency"&gt;&lt;/param&gt;
    /// &lt;param name="totalIterations"&gt;&lt;/param&gt;
    /// &lt;param name="errorRate"&gt;&lt;/param&gt;
    /// &lt;returns&gt;&lt;/returns&gt;
    private async Task&lt;long&gt; RunConcurrentResultTest(
      int concurrency,
      int totalIterations,
      double errorRate)
    {
      var iterationsPerTask = totalIterations / concurrency;
      var tasks = new Task;

      var sw = Stopwatch.StartNew();

      for (int i = 0; i &lt; concurrency; i++)
      {
            var taskRandom = new Random(_random.Next());
            tasks = Task.Run(() =&gt;
            {
                for (int j = 0; j &lt; iterationsPerTask; j++)
                {
                  var (user, pwd) = GenerateTestDataForIteration(taskRandom, errorRate);
                  var result = _service.LoginWithResult(user, pwd);
                  // 不需要额外处理,Result已经包含了成功/失败状态
                }
            });
      }

      await Task.WhenAll(tasks);
      sw.Stop();

      return sw.ElapsedMilliseconds;
    }

    /// &lt;summary&gt;
    /// 为单次迭代生成测试数据
    /// &lt;/summary&gt;
    /// &lt;param name="random"&gt;&lt;/param&gt;
    /// &lt;param name="errorRate"&gt;&lt;/param&gt;
    /// &lt;returns&gt;&lt;/returns&gt;
    private static (string user, string pwd) GenerateTestDataForIteration(Random random, double errorRate)
    {
      if (random.NextDouble() &gt; errorRate)
      {
            // 成功案例
            return ("valid_user", "correct_password");
      }
      else
      {
            // 失败案例 - 随机选择失败类型
            if (random.Next(2) == 0)
                return ("invalid_user", "any_password");// 用户不存在
            else
                return ("valid_user", "wrong_password");   // 密码错误
      }
    }

    /// &lt;summary&gt;
    /// 预热
    /// &lt;/summary&gt;
    /// &lt;param name="concurrency"&gt;&lt;/param&gt;
    /// &lt;param name="errorRate"&gt;&lt;/param&gt;
    /// &lt;returns&gt;&lt;/returns&gt;
    private async Task Warmup(int concurrency, double errorRate = 0.3)
    {
      var warmupTasks = new Task;

      for (int i = 0; i &lt; warmupTasks.Length; i++)
      {
            warmupTasks = Task.Run(() =&gt;
            {
                var taskRandom = new Random(_random.Next());
                for (int j = 0; j &lt; 100; j++)
                {
                  var (user, pwd) = GenerateTestDataForIteration(taskRandom, errorRate);
                  try
                  {
                        _service.LoginWithException(user, pwd);
                  }
                  catch { }

                  _service.LoginWithResult(user, pwd);
                }
            });
      }

      await Task.WhenAll(warmupTasks);
      GC.Collect();
      GC.WaitForPendingFinalizers();
      GC.Collect();
    }
}
</code></pre>
<p>并发测试结果:<br>
<img src="https://img2024.cnblogs.com/blog/1553709/202601/1553709-20260120180104863-50492398.png"></p>
<p>上述测试可能并不严谨和权威,但是暴露出来的问题还是非常明显的:</p>
<ul>
<li>在100%失败场景下,Result比Exception快了差不多200倍</li>
<li>接近实际业务场景的30%错误率情况下,Result比Exception也快了160多倍</li>
<li>并发场景下,性能差距也有接近百倍</li>
</ul>
<h1 id="四为什么异常在业务场景下如此昂贵">四、为什么异常在业务场景下如此"昂贵"?</h1>
<h2 id="41-clr异常机制的底层原理">4.1 CLR异常机制的底层原理</h2>
<h3 id="411-异常对象的构造过程">4.1.1 异常对象的构造过程</h3>
<p>当我们在C#中抛出异常时,看似简单的一行代码,背后却发生了大量复杂的操作:</p>
<pre><code class="language-csharp">throw new BusinessException("用户不存在");
</code></pre>
<p>这个操作的实际执行流程如下:</p>
<pre><code class="language-csharp">// 伪代码展示异常构造的实际开销
public static Exception CreateException(string message)
{
    // 1. 堆分配:异常对象本身(至少40-64字节)
    var exception = RuntimeHelpers.AllocateException(typeof(BusinessException));
   
    // 2. 字段初始化(调用构造函数链)
    exception._message = message;// 字符串分配
    exception._stackTrace = null;
    exception._innerException = null;
    exception._helpURL = null;
    exception._source = null;
   
    // 3. 捕获调用堆栈(最昂贵的部分!)
    exception.CaptureStackTrace();
   
    return exception;
}

private void CaptureStackTrace()
{
    // 4. 获取当前线程的调用堆栈
    var frames = new StackFrame;// 分配数组
    var frameCount = StackTraceHelper.CaptureStackTrace(
      frames, 0,// 起始位置
      false,      // 是否需要文件信息
      null);      // 异常对象本身
   
    // 5. 格式化成字符串(可能涉及大量字符串操作)
    this._stackTrace = FormatStackTrace(frames, frameCount);
}
</code></pre>
<h3 id="412-堆栈跟踪的真实代价">4.1.2 堆栈跟踪的真实代价</h3>
<p>让我们深入看看<code>CaptureStackTrace</code>到底做了什么:</p>
<pre><code class="language-csharp">// Windows上的实际实现(简化)
internal static unsafe int CaptureStackTrace(
    StackFrame[] frames,
    int startIndex,
    bool needFileInfo,
    Exception exception)
{
    // 1. 调用系统API获取当前线程的上下文
    CONTEXT context;
    RtlCaptureContext(&amp;context);
   
    // 2. 遍历调用堆栈(性能杀手!)
    STACKFRAME64 stackFrame = new STACKFRAME64();
    while (StackWalk64(
      IMAGE_FILE_MACHINE_AMD64,
      GetCurrentProcess(),
      GetCurrentThread(),
      &amp;stackFrame,
      &amp;context,
      null,
      SymFunctionTableAccess64,
      SymGetModuleBase64,
      null))
    {
      // 3. 解析每个栈帧的信息
      frames = new StackFrame(
            stackFrame.AddrPC.Offset,
            needFileInfo ? GetSourceInfo(stackFrame) : null);
      
      if (frameCount &gt;= frames.Length) break;
    }
   
    return frameCount;
}
</code></pre>
<p>关键点:</p>
<ul>
<li>每个throw操作都要遍历整个调用堆栈</li>
<li>堆栈遍历涉及多个系统调用和内存访问</li>
<li>需要将内存地址解析为方法名、文件名、行号等</li>
<li>在Release模式下,JIT优化可能会影响堆栈信息</li>
</ul>
<h3 id="413-jit和aot编译对异常的影响">4.1.3 JIT和AOT编译对异常的影响</h3>
<pre><code class="language-csharp">// 考虑以下代码
public int Process(int value)
{
    try
    {
      return ProcessValue(value);// 可能抛出异常
    }
    catch (Exception)
    {
      return -1;
    }
}

// JIT编译器需要生成:
// 1. 正常执行路径的代码
// 2. 异常处理表(EH表)
// 3. 堆栈展开代码
// 4. finally块执行逻辑(如果有)
</code></pre>
<p>EH表的结构:</p>
<pre><code>Method Exception Handling Table:
Start   LengthHandler Type    Class         Filter
0x00000x00200x0030CLAUSEException       null
</code></pre>
<p>每个try-catch块都会在方法的元数据中添加EH表条目,增加方法大小和加载时间。</p>
<h2 id="42-异常处理的内存分配细节">4.2 异常处理的内存分配细节</h2>
<h3 id="421-异常对象的内存布局">4.2.1 异常对象的内存布局</h3>
<pre><code class="language-csharp">// Exception类的简化内存布局
class Exception
{
    // 对象头(8-16字节)
    MethodTable* _methodTable;// 8字节
    // 同步块索引(可选)
   
    // 实例字段
    string _message;         // 8字节(引用)
    IDictionary _data;         // 8字节(通常为null)
    Exception _innerException; // 8字节
    string _helpURL;          // 8字节
    string _source;         // 8字节
    string _stackTrace;       // 8字节(字符串,实际分配更大)
    object _stackTraceString; // 8字节(可能不同格式)
    object _remoteStackTrace; // 8字节
    int _remoteStackIndex;    // 4字节
    int _HResult;             // 4字节
   
    // 总共:至少80字节(64位系统)
    // 加上字符串内容:可能数百到数千字节
}
</code></pre>
<h3 id="422-gc的影响">4.2.2 GC的影响</h3>
<pre><code class="language-csharp">// 高频抛出异常会显著影响GC
public void TestExceptionGC()
{
    var list = new List&lt;Exception&gt;();
   
    for (int i = 0; i &lt; 10000; i++)
    {
      try
      {
            throw new Exception($"Error {i}");
      }
      catch (Exception ex)
      {
            list.Add(ex);// 大量对象进入第0代堆
      }
    }
}

</code></pre>
<p>上述代码会导致:</p>
<ul>
<li>触发频繁的Gen0 GC</li>
<li>如果ex被长时间引用,可能进入Gen1/Gen2</li>
<li>增加GC暂停时间</li>
<li>降低缓存局部性</li>
</ul>
<h2 id="43-cpu级别的性能影响">4.3 CPU级别的性能影响</h2>
<h3 id="431-现代cpu的异常处理开销">4.3.1 现代CPU的异常处理开销</h3>
<pre><code class="language-assembly">; x64汇编层面的异常处理
; 正常路径:
process_value:
    mov eax,       ; 加载值
    add eax, 100      ; 计算
    ret               ; 返回
   
; 异常路径:
throw_exception:
    ; 1. 保存所有寄存器到堆栈
    push rbx
    push rbp
    push r12
    push r13
    push r14
    push r15
    sub rsp, 28h      ; 分配堆栈空间
   
    ; 2. 调用异常构造函数
    call Exception..ctor
   
    ; 3. 设置SEH(结构化异常处理)
    mov , rcx; 保存异常对象
    call __CxxThrowException@8
   
    ; 4. 清理堆栈
    add rsp, 28h
    pop r15
    pop r14
    pop r13
    pop r12
    pop rbp
    pop rbx
</code></pre>
<p>CPU层面的问题:</p>
<ul>
<li><strong>分支预测失败</strong>:异常路径很少执行,CPU分支预测器难以优化</li>
<li><strong>缓存失效</strong>:异常处理代码通常不在指令缓存中</li>
<li><strong>流水线停顿</strong>:异常处理需要保存/恢复大量寄存器状态</li>
<li><strong>内存访问模式差</strong>:EH表查找导致随机内存访问</li>
</ul>
<h3 id="432-对比正常返回和异常返回">4.3.2 对比正常返回和异常返回</h3>
<pre><code class="language-csharp">// Result&lt;T&gt;的正常返回路径
return Result&lt;User&gt;.Fail("用户不存在");
// 汇编:
; 1. 构造Result对象(可能在栈上)
; 2. 设置Success=false
; 3. 设置Error字段
; 4. 返回(普通ret指令)

// 异常返回路径
throw new BusinessException("用户不存在");
// 汇编:
; 1. 堆分配异常对象
; 2. 捕获堆栈跟踪
; 3. 设置SEH帧
; 4. 调用kernel32!RaiseException
; 5. 堆栈展开
; 6. 查找catch块
; 7. 执行catch块代码
</code></pre>
<h2 id="44-对比其他编程语言">4.4 对比其他编程语言</h2>
<h3 id="441-java的异常机制">4.4.1 Java的异常机制</h3>
<pre><code class="language-java">// Java的异常使用看起来和C#相似
public User login(String username, String password)
    throws UserNotFoundException, InvalidPasswordException
{
    User user = findUser(username);
    if (user == null) {
      throw new UserNotFoundException("用户不存在");
    }
    if (!verifyPassword(user, password)) {
      throw new InvalidPasswordException("密码错误");
    }
    return user;
}
</code></pre>
<p>Java异常的特点:<br>
1.检查型异常(Checked Exception):强制处理或声明<br>
2.性能开销与C#类似:同样需要捕获堆栈跟踪<br>
3.JVM的优化:HotSpot JVM有更成熟的异常优化:</p>
<ul>
<li>内联缓存(Inline Cache)</li>
<li>栈上替换(On-Stack Replacement)</li>
<li>但业务异常仍然昂贵</li>
</ul>
<p><strong>重要区别:</strong></p>
<pre><code class="language-java">// Java 14+引入了Records,但异常开销依旧
public record Result&lt;T&gt;(T value, String error) {
    public boolean isSuccess() { return error == null; }
}

// Java社区也在转向Result模式,特别是响应式编程
public Mono&lt;User&gt; login(String username, String password) {
    return Mono.fromCallable(() -&gt; findUser(username))
      .switchIfEmpty(Mono.error(new UserNotFoundException()))
      .filter(user -&gt; verifyPassword(user, password))
      .switchIfEmpty(Mono.error(new InvalidPasswordException()));
}
</code></pre>
<h3 id="442-go的错误处理哲学">4.4.2 Go的错误处理哲学</h3>
<pre><code class="language-go">// Go的错误处理:显式返回错误
func Login(username, password string) (*User, error) {
    user, err := findUser(username)
    if err != nil {
      return nil, fmt.Errorf("查找用户失败: %w", err)
    }
   
    if !verifyPassword(user, password) {
      return nil, errors.New("密码错误")
    }
   
    return user, nil
}

// 调用方必须显式处理错误
user, err := Login("test", "123")
if err != nil {
    // 处理错误
    switch {
    case strings.Contains(err.Error(), "密码错误"):
      // 特定处理
    default:
      // 通用处理
    }
}
</code></pre>
<p>Go的设计选择:</p>
<ul>
<li><strong>没有异常机制</strong>:只有错误返回值</li>
<li><strong>错误是值(errors are values)</strong>:可以像普通值一样传递</li>
<li><strong>强制显式处理</strong>:无法忽略错误(除非使用_)</li>
<li><strong>零成本抽象</strong>:错误处理几乎没有运行时开销</li>
</ul>
<p>Go的错误性能优势:</p>
<pre><code class="language-go">// Go的errors.New实际上很简单
func New(text string) error {
    return &amp;errorString{text}
}

type errorString struct {
    s string
}

func (e *errorString) Error() string {
    return e.s
}

// 没有堆栈跟踪,没有复杂构造
// 只是一个包含字符串的结构体
</code></pre>
<h3 id="443-rust的result类型">4.4.3 Rust的Result类型</h3>
<pre><code class="language-rust">// Rust的错误处理:基于枚举的Result
fn login(username: &amp;str, password: &amp;str) -&gt; Result&lt;User, LoginError&gt; {
    let user = find_user(username)?;// ?操作符自动传播错误
   
    if !verify_password(&amp;user, password) {
      return Err(LoginError::InvalidPassword);
    }
   
    Ok(user)
}

// 错误类型定义
#
enum LoginError {
    UserNotFound,
    InvalidPassword,
    DatabaseError(DbError),
}

// 使用match处理
match login("test", "123") {
    Ok(user) =&gt; println!("欢迎 {}", user.name),
    Err(LoginError::UserNotFound) =&gt; println!("用户不存在"),
    Err(LoginError::InvalidPassword) =&gt; println!("密码错误"),
    Err(e) =&gt; println!("其他错误: {:?}", e),
}
</code></pre>
<p>Rust的设计特点:</p>
<ul>
<li>零成本抽象:Result在运行时通常是普通枚举</li>
<li>模式匹配:编译器确保所有情况都被处理</li>
<li>错误传播运算符(?):简化错误传播</li>
<li>丰富的错误库:anyhow、thiserror等</li>
</ul>
<p>Rust的性能优势:</p>
<pre><code>编译后的Result通常优化为:
1.成功:存储User
2.失败:存储错误码(通常是整数)
3.没有堆分配,没有虚函数调用
</code></pre>
<h3 id="444-c的错误处理">4.4.4 C++的错误处理</h3>
<pre><code class="language-cpp">// C++的多种错误处理方式

// 1. 异常(类似C#/Java)
User login(const std::string&amp; username, const std::string&amp; password) {
    auto user = find_user(username);
    if (!user) {
      throw UserNotFoundException("用户不存在");
    }
    if (!verify_password(*user, password)) {
      throw InvalidPasswordException("密码错误");
    }
    return *user;
}

// 2. 错误码(传统方式)
int login(const std::string&amp; username,
          const std::string&amp; password,
          User&amp; out_user) {
    User user;
    int err = find_user(username, user);
    if (err != 0) return err;
   
    if (!verify_password(user, password)) {
      return ERROR_INVALID_PASSWORD;
    }
   
    out_user = user;
    return 0;// 成功
}

// 3. std::expected(C++23)
std::expected&lt;User, Error&gt; login(const std::string&amp; username,
                                 const std::string&amp; password) {
    auto user = find_user(username);
    if (!user) {
      return std::unexpected(Error::UserNotFound);
    }
    if (!verify_password(*user, password)) {
      return std::unexpected(Error::InvalidPassword);
    }
    return *user;
}
</code></pre>
<p>C++的选择:</p>
<ul>
<li>游戏和嵌入式:通常禁用异常,使用错误码</li>
<li>性能敏感应用:避免异常,因为零开销原则</li>
<li>现代C++:倾向于std::expected等类型安全方案</li>
</ul>
<h2 id="45-net-core的改进和局限">4.5 .NET Core的改进和局限</h2>
<p>.Net 8.0版本,官方团队针对异常处理这块进行了大幅的优化,包括预分配异常对象(PREallocated Exception)、延迟堆栈跟踪生成(Lazy Stack Trace)、堆栈跟踪缓存和复用、新的堆栈跟踪算法等多种手段,同时也对JIT和RunTime进行了针对性优化。但是目前来看,依旧还有很大的提升空间。<br>
详情请见官方团队的博客:https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-8/#exception-handling</p>
<pre><code class="language-csharp">// .NET 8.0 对参数异常的特殊优化
public void ValidateUser(string username, int age)
{
    // 这些调用在 .NET 8.0 中非常高效
    ArgumentNullException.ThrowIfNull(username);
    ArgumentOutOfRangeException.ThrowIfNegative(age);
    ArgumentException.ThrowIfNullOrEmpty(username);
   
    // 但注意:业务异常不在此优化范围内!
    if (!IsValidUsername(username))
      throw new BusinessException("无效用户名"); // 代价仍然昂贵
}
</code></pre>
<h1 id="五result的进阶优势">五、Result<t>的进阶优势</t></h1>
<h2 id="51-丰富的错误信息">5.1 丰富的错误信息</h2>
<pre><code class="language-csharp">public class Result&lt;T&gt;
{
    public bool Success { get; }
    public T? Value { get; }
    public string ErrorCode { get; }    // 错误代码
    public string ErrorMessage { get; } // 错误消息
    public Dictionary&lt;string, object&gt; Metadata { get; } // 附加信息
}

// 使用:
var result = Login("test", "wrong");
if (!result.Success)
{
    switch (result.ErrorCode)
    {
      case "USER_NOT_FOUND":
            // 用户不存在,跳转注册页面
            break;
      case "INVALID_PASSWORD":
            // 密码错误,显示提示
            break;
      case "ACCOUNT_LOCKED":
            // 账户锁定,显示锁定时间
            var lockTime = result.Metadata["LockUntil"];
            break;
    }
}
</code></pre>
<h2 id="52-函数式编程支持">5.2 函数式编程支持</h2>
<pre><code class="language-csharp">// Map - 转换成功的值
var userResult = GetUser(123);
var userName = userResult.Map(user =&gt; user.Name.ToUpper());

// Bind - 链式操作
var orderResult = GetUser(123)
    .Bind(user =&gt; GetOrder(user.CurrentOrderId))
    .Bind(order =&gt; ValidateOrder(order));

// Match - 模式匹配
var message = loginResult.Match(
    success: user =&gt; $"欢迎回来,{user.Name}!",
    failure: error =&gt; $"登录失败: {error.Message}"
);
</code></pre>
<h2 id="53-更好的api设计">5.3 更好的API设计</h2>
<pre><code class="language-csharp">// Web API中的使用

public IActionResult Login( LoginRequest request)
{
    var result = _authService.Login(request);
   
    return result.Match&lt;IActionResult&gt;(
      success: user =&gt; Ok(new { success = true, user }),
      failure: error =&gt; BadRequest(new {
            success = false,
            errorCode = error.Code,
            message = error.Message
      })
    );
}

// 客户端获得清晰的响应:
// 成功: { success: true, user: { ... } }
// 失败: { success: false, errorCode: "INVALID_PASSWORD", message: "密码错误" }
</code></pre>
<h1 id="六-result模式的一些弊端">六、 Result<t>模式的一些弊端</t></h1>
<h2 id="61-if地狱问题条件判断泛滥">6.1 "if地狱"问题(条件判断泛滥)</h2>
<p>最常被诟病的问题,就是代码中充斥大量的 if (!result.Success) 检查。</p>
<pre><code class="language-csharp">// "if地狱"的典型例子
public Result&lt;Order&gt; ProcessOrder(int userId, OrderRequest request)
{
    var userResult = GetUser(userId);
    if (!userResult.Success)
      return Result&lt;Order&gt;.Fail(userResult.Error);
   
    var validationResult = ValidateOrder(request);
    if (!validationResult.Success)
      return Result&lt;Order&gt;.Fail(validationResult.Error);
   
    var inventoryResult = CheckInventory(request.Items);
    if (!inventoryResult.Success)
      return Result&lt;Order&gt;.Fail(inventoryResult.Error);
   
    var paymentResult = ProcessPayment(userResult.Value, request);
    if (!paymentResult.Success)
      return Result&lt;Order&gt;.Fail(paymentResult.Error);
   
    // ... 更多检查
}
</code></pre>
<p>这个问题,使用函数式编程思想可以巧妙解决,这一块JAVA做的真心挺不错的。</p>
<pre><code class="language-csharp">// 使用 Railway-Oriented Programming
public Result&lt;Order&gt; ProcessOrder(int userId, OrderRequest request)
{
    return GetUser(userId)
      .Bind(user =&gt; ValidateOrder(request)
            .Bind(validated =&gt; CheckInventory(request.Items)
                .Bind(inventory =&gt; ProcessPayment(user, request)
                  .Map(payment =&gt; CreateOrder(user, validated, payment)))));
}

// 或者使用扩展方法
public Result&lt;Order&gt; ProcessOrder(int userId, OrderRequest request)
{
    return GetUser(userId)
      .Then(user =&gt; ValidateOrder(request))
      .Then(validated =&gt; CheckInventory(request.Items))
      .Then(inventory =&gt; ProcessPayment(user, request))
      .Map(payment =&gt; CreateOrder(user, validated, payment));
}
</code></pre>
<h2 id="62-值类型structvs-引用类型class的两难选择">6.2 值类型(struct)vs 引用类型(class)的两难选择</h2>
<pre><code class="language-csharp">public readonly struct Result&lt;T&gt;// 值类型
{
    private readonly T? _value;
    private readonly string? _error;
   
    // 问题1:T是引用类型时,struct存储的是引用
    // 问题2:struct复制开销(如果T是大对象)
    // 问题3:装箱/拆箱开销
    // 问题4:不能为null,需要额外状态标记
}

public class Result&lt;T&gt;// 引用类型
{
    // 问题:每个Result都是堆分配,增加GC压力
    // 即使成功情况也要分配对象
}

</code></pre>
<p>折中方案:针对值类型和引用类型分别优化,针对通用的Result对象进行缓存和复用。</p>
<pre><code class="language-csharp">/// &lt;summary&gt;
/// 结果基类
/// &lt;/summary&gt;
/// &lt;typeparam name="T"&gt;&lt;/typeparam&gt;
public abstract class Result&lt;T&gt;
{
    /// &lt;summary&gt;
    /// 是否成功
    /// &lt;/summary&gt;
    public abstract bool IsSuccess { get; }
    /// &lt;summary&gt;
    /// 具体返回值(成功时有效)
    /// &lt;/summary&gt;
    public abstract T? Value { get; }
    /// &lt;summary&gt;
    /// 错误码
    /// &lt;/summary&gt;
    public abstract string? ErrorCode { get; }
    /// &lt;summary&gt;
    /// 错误信息
    /// &lt;/summary&gt;
    public abstract string? ErrorMessage { get; }

    // 缓存单例成功(仅针对 default(T))
    private static readonly ConcurrentDictionary&lt;Type, Result&lt;T&gt;&gt; _successCache = new();
    //只按 errorCode 缓存(避免按 message 无限增长)
    private static readonly ConcurrentDictionary&lt;string, Result&lt;T&gt;&gt; _errorCache = new();
}

/// &lt;summary&gt;
/// 仅用于性能关键路径,不存储大对象
/// &lt;/summary&gt;
/// &lt;typeparam name="T"&gt;&lt;/typeparam&gt;
public readonly ref struct ValueResult&lt;T&gt; where T : struct
{
//省略
}

/// &lt;summary&gt;
/// 引用类型结果,仅用于性能关键路径
/// &lt;/summary&gt;
/// &lt;typeparam name="T"&gt;&lt;/typeparam&gt;
public readonly ref struct RefResult&lt;T&gt; where T : class
{
//省略
}
</code></pre>
<h2 id="63-异步编程的复杂性">6.3 异步编程的复杂性</h2>
<p>在异步编程中,可能每个异步操作都需要处理Result,这也大大增加了代码复杂度,其实也属于上面说的“if 地狱”范畴。</p>
<pre><code class="language-csharp">public async Task&lt;Result&lt;User&gt;&gt; LoginAsync(string username, string password)
{
    // 每个异步操作都需要处理Result
    var userResult = await FindUserAsync(username);
    if (!userResult.Success)
      return Result&lt;User&gt;.Fail(userResult.Error);
   
    var validationResult = await ValidatePasswordAsync(userResult.Value, password);
    if (!validationResult.Success)
      return Result&lt;User&gt;.Fail(validationResult.Error);
   
    return Result&lt;User&gt;.Ok(userResult.Value);
}

// 对比异常版本:
public async Task&lt;User&gt; LoginAsync(string username, string password)
{
    var user = await FindUserAsync(username);// 抛异常则直接中断后续逻辑
    await ValidatePasswordAsync(user, password); // 抛异常则直接中断后续逻辑
    return user;
}
</code></pre>
<h2 id="64-类型系统冗长">6.4 类型系统冗长</h2>
<p>每一个接口方法都要包裹Result,再加上异步的Task,分页请求结果模型PagedResult,再加上点其他东西,就会出现令人头皮发麻的泛型参数爆炸&lt;&lt;&lt;&lt;&gt;&gt;&gt;&gt;。<br>
下面的代码,是项目里面的真实代码</p>
<pre><code class="language-csharp">   /// &lt;summary&gt;
    /// 员工业务服务接口
    /// &lt;/summary&gt;
    public interface IEmployeeService
    {
      Task&lt;Result&lt;long&gt;&gt; CreateAsync(CreateEmployeeDto dto);
      Task&lt;Result&lt;EmployeeDto&gt;&gt; GetByIdAsync(long id);
      Task&lt;Result&gt; UpdateAsync(UpdateEmployeeDto dto);
      Task&lt;Result&gt; DeleteAsync(long id);
      Task&lt;Result&lt;PagedResult&lt;EmployeeDto&gt;&gt;&gt; GetPageListAsync(EmployeePageListDto dto);
      Task&lt;Result&lt;List&lt;EmployeeDto&gt;&gt;&gt; GetListAsync(string? keyword = null);
      Task&lt;Result&lt;Dictionary&lt;string,long&gt;&gt;&gt; GetEmployeeAliases(List&lt;long&gt;? employeeIds = null, bool includeShowName = true);
    }
</code></pre>
<h2 id="65-其他问题">6.5 其他问题</h2>
<p>当然,这种模式还有一些其他的问题,比如团队成员的接受度,团队学习成本,与现有代码/生态的兼容性,与第三方包的兼容性等,这里就不一一说明了。</p>
<h1 id="七常见问题与答疑">七、常见问题与答疑</h1>
<p><strong>Q:异常不是更方便吗?一行代码就能中断流程</strong><br>
A:方便不等于正确。goto语句也很"方便",但现代编程中我们避免使用它。异常在业务逻辑中就是"远程goto",破坏了代码的可读性和可维护性。</p>
<p><strong>Q:Result<t>需要更多的if判断,代码更冗长</t></strong></p>
<pre><code class="language-csharp">// 简洁的处理方式
var result = Login("test", "123");
if (!result.Success) return result;

// 或者使用模式匹配
var message = result switch
{
    { Success: true, Value: var user } =&gt; $"欢迎 {user.Name}",
    { Error: var error } =&gt; $"错误: {error}",
    _ =&gt; "未知状态"
};
</code></pre>
<p><strong>Q:我们的项目很小,性能影响不大</strong><br>
A:即使不考虑性能,从代码质量和维护性角度,Result<t>也是更好的选择。好的习惯应该从项目初期就开始培养。</t></p>
<h1 id="八总结">八、总结</h1>
<p>Result<t> 不是银弹,它有它适用的场景,也有相应的一些弊端。选择的关键不在于哪个"更好",而在于哪个"更适合"当前的场景和约束。明智的工程师会根据具体情况做出平衡的选择。<br>
适合使用 Result<t> 的场景:</t></t></p>
<ul>
<li>高频失败的校验逻辑(表单验证、业务规则检查)</li>
<li>需要明确错误分类的业务流程</li>
<li>API边界(需要结构化错误响应)</li>
<li>与外部系统交互(需要处理各种失败模式)</li>
<li>需要组合的复杂业务逻辑</li>
</ul>
<p>仍然适合使用异常的场景:</p>
<ul>
<li>真正的系统故障(内存不足、数据库崩溃)</li>
<li>程序状态异常(空引用、索引越界)</li>
<li>不满足前置条件(无效参数)</li>
<li>开发阶段的断言检查</li>
<li>极低失败率的操作</li>
</ul>
<p>关键建议:</p>
<ul>
<li>不要全盘替换:Result和异常各有适用场景</li>
<li>分层设计:不同架构层使用不同策略</li>
<li>团队共识:建立明确的规范和边界</li>
<li>渐进采用:从核心业务开始,逐步扩展</li>
<li>监控反馈:通过日志和监控验证选择</li>
</ul>
<p>最终原则:</p>
<ul>
<li>异常:用于"不应该发生"的事情</li>
<li>Result:用于"可能发生但需要处理"的事情</li>
</ul><br><br>
来源:https://www.cnblogs.com/diamondhusky/p/19508626
頁: [1]
查看完整版本: 告别 throw exception!为什么 Result<T> 才是业务逻辑的正确选择