<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Go on Orangex-position0的技术博客</title><link>https://Orangex-position0.github.io/tags/go/</link><description>Recent content in Go on Orangex-position0的技术博客</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><lastBuildDate>Thu, 10 Sep 2026 00:00:00 +0800</lastBuildDate><atom:link href="https://Orangex-position0.github.io/tags/go/index.xml" rel="self" type="application/rss+xml"/><item><title>常见编程语言的错误处理方式对比</title><link>https://Orangex-position0.github.io/posts/programming-language/error-handling-comparison/</link><pubDate>Thu, 10 Sep 2026 00:00:00 +0800</pubDate><guid>https://Orangex-position0.github.io/posts/programming-language/error-handling-comparison/</guid><description>&lt;img src="https://Orangex-position0.github.io/" alt="Featured image of post 常见编程语言的错误处理方式对比" /&gt;&lt;h1 id="常见编程语言的错误处理方式对比"&gt;&lt;a href="#%e5%b8%b8%e8%a7%81%e7%bc%96%e7%a8%8b%e8%af%ad%e8%a8%80%e7%9a%84%e9%94%99%e8%af%af%e5%a4%84%e7%90%86%e6%96%b9%e5%bc%8f%e5%af%b9%e6%af%94" class="header-anchor"&gt;&lt;/a&gt;常见编程语言的错误处理方式对比&#10;&lt;/h1&gt;&#10; &lt;blockquote&gt;&#10; &lt;p&gt;先说明，笔者主要以后端开发视角来讨论，比较熟悉 Java、Go 和 Rust。本文也会顺带提到 Python、TypeScript/JavaScript、Swift、Kotlin、Scala、Haskell、Zig 等语言，但重点不是做语言百科（因为我大部分也不会……），而是比较不同错误处理设计背后的取舍。&lt;/p&gt;&#10;&#10; &lt;/blockquote&gt;&#10;&lt;p&gt;最近刷面经时，我看到过这样一道问题：&lt;/p&gt;&#10;&#10; &lt;blockquote&gt;&#10; &lt;p&gt;你简历上写了不止一种编程语言。那你能不能对比一下，这几门语言的错误处理方式有什么不同？&lt;/p&gt;&#10;&#10; &lt;/blockquote&gt;&#10;&lt;p&gt;这个问题看起来很普通，好像只是在问类似 Java 的 &lt;code&gt;try-catch-finally&lt;/code&gt;、Go 的 &lt;code&gt;if err != nil&lt;/code&gt;、Rust 的 &lt;code&gt;Result&amp;lt;T, E&amp;gt;&lt;/code&gt;。但如果继续追问下去，它其实会变成一个更底层的问题：&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;一门语言到底希望程序员如何处理失败？&lt;/strong&gt;&lt;/p&gt;&#10;&lt;p&gt;是让正常路径保持干净，把失败作为异常抛给上层？还是把失败作为普通返回值，让调用者在每个调用点显式判断？或者把可恢复失败写进类型系统，让编译器逼你承认它？&lt;/p&gt;&#10;&lt;p&gt;所以这篇文章不只是罗列语法，而是想借几门常见语言，梳理主流错误处理模型，以及它们在 API 契约、控制流、类型系统和工程实践里的取舍。&lt;/p&gt;&#10;&lt;h2 id="1-为什么错误处理值得单独讨论"&gt;&lt;a href="#1-%e4%b8%ba%e4%bb%80%e4%b9%88%e9%94%99%e8%af%af%e5%a4%84%e7%90%86%e5%80%bc%e5%be%97%e5%8d%95%e7%8b%ac%e8%ae%a8%e8%ae%ba" class="header-anchor"&gt;&lt;/a&gt;1. 为什么错误处理值得单独讨论&#10;&lt;/h2&gt;&lt;p&gt;错误处理不是一个“语法小节”。它会直接影响代码的形状。&lt;/p&gt;&#10;&lt;p&gt;比如同样是读取一个配置文件，失败时可能有几种写法：&lt;/p&gt;&#10;&lt;p&gt;Java 代码：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;loadConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IOException&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;throw&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ConfigException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;failed to load config&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Go 代码：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;LoadConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;load config: %w&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Rust 代码：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;load_config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;failed to load config&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这几段代码背后的问题并不只是语法不同，而是语言在帮我们回答这些问题：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;调用者能不能从函数签名看出这里可能失败？&lt;/li&gt;&#10;&lt;li&gt;失败会不会中断当前控制流，并跳到别的地方？&lt;/li&gt;&#10;&lt;li&gt;调用者是否必须处理失败？&lt;/li&gt;&#10;&lt;li&gt;错误是业务分支、外部环境失败，还是程序 bug？&lt;/li&gt;&#10;&lt;li&gt;错误应该在当前层处理，还是传到 HTTP handler、CLI main、任务调度器这类边界统一处理？&lt;/li&gt;&#10;&lt;li&gt;日志、重试、降级、资源清理应该放在哪里？&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;所以对比错误处理方式，本质上是在比较不同语言如何处理同一个矛盾：&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;正常路径越干净，错误路径往往越隐蔽；错误路径越显式，主路径往往越容易被打断。&lt;/strong&gt;&lt;/p&gt;&#10;&lt;h2 id="2-错误处理机制分类"&gt;&lt;a href="#2-%e9%94%99%e8%af%af%e5%a4%84%e7%90%86%e6%9c%ba%e5%88%b6%e5%88%86%e7%b1%bb" class="header-anchor"&gt;&lt;/a&gt;2. 错误处理机制分类&#10;&lt;/h2&gt;&lt;p&gt;先不急着按语言对比。更好的方式是先按机制分类，因为很多语言不是只有一种错误处理方式，而是在不同场景下组合使用多种机制。&lt;/p&gt;&#10;&lt;p&gt;大体上，常见错误处理机制可以分成三组：&lt;/p&gt;&#10;&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart LR&#10; A["错误处理机制"]&#10;&#10; A --&gt; B["Non-local Control Flow&lt;br/&gt;错误改变控制流"]&#10; A --&gt; C["Value-based&lt;br/&gt;错误作为返回值"]&#10; A --&gt; D["Type-modeled&lt;br/&gt;错误进入类型系统"]&#10;&#10; B --&gt; B1["Exceptions"]&#10;&#10; C --&gt; C1["Sentinel Values"]&#10; C --&gt; C2["Error Tuples"]&#10;&#10; D --&gt; D1["Sum Types"]&#10; D --&gt; D2["Error Unions"]&lt;/pre&gt;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;类别&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;机制&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;核心想法&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;典型语言或风格&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Non-local control flow&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Exceptions&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;错误脱离普通返回路径，沿调用栈传播&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Java、C#、Python、JavaScript、C++&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Value-based error handling&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Sentinel Values&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;用约定好的特殊值表示失败&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;C 风格 API、系统调用、部分标准库 API&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Value-based error handling&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Error Tuples&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;错误作为普通返回值返回&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Go、Odin&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Type-modeled error handling&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Sum Types&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;用类型表达 &lt;code&gt;success OR error&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Rust、Haskell、Scala、TypeScript union、Kotlin sealed class&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Type-modeled error handling&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Error Unions&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;语言原生提供专用的 &lt;code&gt;error OR value&lt;/code&gt; 类型&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Zig、C3&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;h3 id="21-exceptions错误作为非局部控制流"&gt;&lt;a href="#21-exceptions%e9%94%99%e8%af%af%e4%bd%9c%e4%b8%ba%e9%9d%9e%e5%b1%80%e9%83%a8%e6%8e%a7%e5%88%b6%e6%b5%81" class="header-anchor"&gt;&lt;/a&gt;2.1 Exceptions：错误作为非局部控制流&#10;&lt;/h3&gt;&lt;p&gt;&lt;img alt="Exceptions：错误作为非局部控制流" class="gallery-image" data-flex-basis="426px" data-flex-grow="177" height="941" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://Orangex-position0.github.io/posts/programming-language/error-handling-comparison/exceptions-control-flow.png" srcset="https://Orangex-position0.github.io/posts/programming-language/error-handling-comparison/exceptions-control-flow_hu_1cc13aa5dbe8e49f.png 800w, https://Orangex-position0.github.io/posts/programming-language/error-handling-comparison/exceptions-control-flow_hu_78c8ed9805c8cf32.png 1600w, https://Orangex-position0.github.io/posts/programming-language/error-handling-comparison/exceptions-control-flow.png 1672w" width="1672"&gt;&lt;/p&gt;&#10;&lt;p&gt;Exception 的核心设计是：&lt;strong&gt;失败不通过普通返回值返回，而是通过 &lt;code&gt;throw&lt;/code&gt; 改变控制流，沿调用栈向上传播，直到遇到能处理它的 &lt;code&gt;catch&lt;/code&gt;。&lt;/strong&gt;&lt;/p&gt;&#10;&lt;p&gt;典型语言包括 Java、C#、Python、JavaScript/TypeScript 和 C++。&lt;/p&gt;&#10;&lt;p&gt;比如下面这段 Java 代码：错误被 catch 并处理，而不是作为函数返回值被处理&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;userService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;UserNotFoundException&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;404&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getMessage&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;异常的好处很直观：正常路径很干净。中间层如果没有处理能力，可以不在每一层手动判断错误，而是让异常继续向上传播，到真正有上下文的位置统一处理。&lt;/p&gt;&#10;&lt;p&gt;这也是为什么 Web 框架、RPC 框架、任务调度框架经常喜欢用全局异常处理器：service 层抛出异常，controller advice、middleware 或 handler 在边界把异常转换成 HTTP 响应、日志、告警或退出码。&lt;/p&gt;&#10;&lt;p&gt;但异常的代价也来自同一个地方：错误路径是非局部的。调用点看起来只是普通函数调用，但执行过程中可能突然跳出当前流程。除了 Java checked exception 这类特殊设计，多数语言的异常集合不会进入函数签名。调用者想知道一个函数可能抛什么异常，往往只能依赖文档、源码、测试和经验。&lt;/p&gt;&#10;&lt;p&gt;所以异常很适合“跨层传播”和“边界统一处理”，但不适合所有失败。比如缓存 miss、查找不到、表单字段校验失败这些高频普通分支，如果全部用异常表达，代码语义会变重，性能和可读性也可能受影响。&lt;/p&gt;&#10;&lt;h3 id="22-sentinel-values错误作为特殊返回值"&gt;&lt;a href="#22-sentinel-values%e9%94%99%e8%af%af%e4%bd%9c%e4%b8%ba%e7%89%b9%e6%ae%8a%e8%bf%94%e5%9b%9e%e5%80%bc" class="header-anchor"&gt;&lt;/a&gt;2.2 Sentinel Values：错误作为特殊返回值&#10;&lt;/h3&gt;&lt;p&gt;&lt;img alt="Sentinel Values：错误作为特殊返回值" class="gallery-image" data-flex-basis="426px" data-flex-grow="177" height="941" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://Orangex-position0.github.io/posts/programming-language/error-handling-comparison/sentinel-values.png" srcset="https://Orangex-position0.github.io/posts/programming-language/error-handling-comparison/sentinel-values_hu_98849f400df6c763.png 800w, https://Orangex-position0.github.io/posts/programming-language/error-handling-comparison/sentinel-values_hu_cc2a249089a33e27.png 1600w, https://Orangex-position0.github.io/posts/programming-language/error-handling-comparison/sentinel-values.png 1672w" width="1672"&gt;&lt;/p&gt;&#10;&lt;p&gt;Sentinel Value 的核心设计是：&lt;strong&gt;用约定好的特殊返回值表示失败。&lt;/strong&gt;&lt;/p&gt;&#10;&lt;p&gt;比如 C 风格 API 中常见的 &lt;code&gt;NULL&lt;/code&gt;、&lt;code&gt;-1&lt;/code&gt;、&lt;code&gt;EOF&lt;/code&gt;，或者某些 API 用空字符串、特殊 enum 表示失败。&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;fd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;O_RDONLY&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fd&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// handle error&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这种方式非常朴素：没有额外语言机制，也不需要异常系统或类型系统配合。函数就是返回一个值，调用者按约定检查这个值。&lt;/p&gt;&#10;&lt;p&gt;问题也很明显：它太依赖约定。调用者忘记检查，编译器通常不会提醒；特殊值还可能和合法值冲突。比如 &lt;code&gt;-1&lt;/code&gt; 到底是错误，还是一个合法业务值？&lt;code&gt;null&lt;/code&gt; 到底表示不存在，还是出错，还是没有加载？&lt;/p&gt;&#10;&lt;p&gt;因此 Sentinel Value 更适合非常局部、非常简单、约定足够稳定的场景。现代业务代码里，最好不要让这种特殊值穿过复杂业务边界，否则错误语义很容易丢失。&lt;/p&gt;&#10;&lt;h3 id="23-errors-as-values--error-tuples错误作为普通返回值"&gt;&lt;a href="#23-errors-as-values--error-tuples%e9%94%99%e8%af%af%e4%bd%9c%e4%b8%ba%e6%99%ae%e9%80%9a%e8%bf%94%e5%9b%9e%e5%80%bc" class="header-anchor"&gt;&lt;/a&gt;2.3 Errors as Values / Error Tuples：错误作为普通返回值&#10;&lt;/h3&gt;&lt;p&gt;&lt;img alt="Errors as Values / Error Tuples：错误作为普通返回值" class="gallery-image" data-flex-basis="426px" data-flex-grow="177" height="941" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://Orangex-position0.github.io/posts/programming-language/error-handling-comparison/error-tuples.png" srcset="https://Orangex-position0.github.io/posts/programming-language/error-handling-comparison/error-tuples_hu_67d051239aff2b64.png 800w, https://Orangex-position0.github.io/posts/programming-language/error-handling-comparison/error-tuples_hu_2a85e83245f3cdf3.png 1600w, https://Orangex-position0.github.io/posts/programming-language/error-handling-comparison/error-tuples.png 1672w" width="1672"&gt;&lt;/p&gt;&#10;&lt;p&gt;Go 是这一类设计最典型的代表。它的核心设计是：&lt;strong&gt;把错误作为普通返回结果，让调用者在每个调用点显式处理。&lt;/strong&gt;&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FindUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;find user %s: %w&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Go 官方文章《Errors are values》里有一个很重要的视角：错误不是特殊语法，错误就是普通值。既然是普通值，它就可以被传递、包装、比较、转换，也可以进入普通控制流。&lt;/p&gt;&#10;&lt;p&gt;这让 Go 的失败路径非常可见。代码审查时，一眼就能看到调用方有没有检查 &lt;code&gt;err&lt;/code&gt;，有没有补充上下文，有没有把错误吞掉。&lt;/p&gt;&#10;&lt;p&gt;代价是样板明显。大量 &lt;code&gt;if err != nil&lt;/code&gt; 会把主路径切碎。如果只是机械地 &lt;code&gt;return err&lt;/code&gt;，虽然形式上显式了，但错误里缺少上下文，排查问题时仍然很痛苦，这也是很多人写 Go 时抱怨的一个点。&lt;/p&gt;&#10;&lt;p&gt;另外，Go 的 &lt;code&gt;error&lt;/code&gt; 本质上是接口。函数签名能告诉你“这里可能失败”，但不能精确告诉你“会失败成哪些类型”。如果调用方需要识别错误类别，就要靠 sentinel error、自定义 error type、&lt;code&gt;errors.Is&lt;/code&gt;、&lt;code&gt;errors.As&lt;/code&gt; 和团队约定来维持一致性。&lt;/p&gt;&#10;&lt;p&gt;所以 Go 的错误处理不是“更啰嗦的异常”，而是一种设计选择：把失败放回普通控制流，让调用者在当前上下文里决定是重试、降级、包装后返回，还是立即处理。&lt;/p&gt;&#10;&lt;h3 id="24-sum-types错误作为互斥分支"&gt;&lt;a href="#24-sum-types%e9%94%99%e8%af%af%e4%bd%9c%e4%b8%ba%e4%ba%92%e6%96%a5%e5%88%86%e6%94%af" class="header-anchor"&gt;&lt;/a&gt;2.4 Sum Types：错误作为互斥分支&#10;&lt;/h3&gt;&lt;p&gt;&lt;img alt="Sum Types：错误作为互斥分支" class="gallery-image" data-flex-basis="426px" data-flex-grow="177" height="941" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://Orangex-position0.github.io/posts/programming-language/error-handling-comparison/sum-types.png" srcset="https://Orangex-position0.github.io/posts/programming-language/error-handling-comparison/sum-types_hu_e3737cd6011ec1d5.png 800w, https://Orangex-position0.github.io/posts/programming-language/error-handling-comparison/sum-types_hu_d4581cb7d61f12a4.png 1600w, https://Orangex-position0.github.io/posts/programming-language/error-handling-comparison/sum-types.png 1672w" width="1672"&gt;&lt;/p&gt;&#10;&lt;p&gt;Sum Type 的核心设计是：&lt;strong&gt;用类型表达 &lt;code&gt;success OR error&lt;/code&gt; 两种互斥状态，让调用者不能把失败当作成功使用。&lt;/strong&gt;&lt;/p&gt;&#10;&lt;p&gt;Rust 的 &lt;code&gt;Result&amp;lt;T, E&amp;gt;&lt;/code&gt; 是最容易理解的例子，下面是 &lt;code&gt;Result&lt;/code&gt; 的源码：表示结果只能有两种状态，成功或失败。&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;E&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;E&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;一个可能失败的函数可以返回 &lt;code&gt;Result&lt;/code&gt;：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;load_config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;: &lt;span class="kp"&gt;&amp;amp;&lt;/span&gt;&lt;span class="kt"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-&amp;gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ConfigError&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;::&lt;span class="n"&gt;fs&lt;/span&gt;::&lt;span class="n"&gt;read_to_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;parse_config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这里的错误也是看作返回值，但它和 Go 的 &lt;code&gt;(T, error)&lt;/code&gt; 有一个关键差别：成功值和失败值是互斥分支。你拿到的是一个 &lt;code&gt;Result&lt;/code&gt;，必须处理、转换、匹配，或者向上传播。&lt;/p&gt;&#10;&lt;p&gt;Rust 还会明确区分两类失败：&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;类型&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;含义&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;典型表达&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;recoverable error&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;预期内的失败&lt;/strong&gt;，调用方有机会恢复、重试、降级，或选择替代路径&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;Result&amp;lt;T, E&amp;gt;&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;unrecoverable error&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;无法安全继续的错误&lt;/strong&gt;，程序进入不应继续的状态，通常代表 bug 或不变量被破坏&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;panic!&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;absence&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;没有错误细节的缺值&lt;/strong&gt;，只关心“有没有”而不是“为什么失败”&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;Option&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;这类设计的优点是错误进入了函数类型。尤其当错误集合用 enum 表达时，模式匹配可以配合&lt;strong&gt;穷尽性检查&lt;/strong&gt;：如果未来新增一种错误分支，编译器更容易提醒调用方还有地方没处理。&lt;/p&gt;&#10;&lt;p&gt;代价是 API 设计压力更前置。你需要决定错误类型长什么样、底层错误要不要暴露、跨模块错误如何转换、库层和应用层是否使用不同错误抽象。错误建模太粗，类型约束价值有限；建模太细，调用链又会变得笨重。&lt;/p&gt;&#10;&lt;h3 id="25-error-unions语言原生的-error-or-value"&gt;&lt;a href="#25-error-unions%e8%af%ad%e8%a8%80%e5%8e%9f%e7%94%9f%e7%9a%84-error-or-value" class="header-anchor"&gt;&lt;/a&gt;2.5 Error Unions：语言原生的 error-or-value&#10;&lt;/h3&gt;&lt;p&gt;&lt;img alt="Error Unions：语言原生的 error-or-value" class="gallery-image" data-flex-basis="426px" data-flex-grow="177" height="941" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://Orangex-position0.github.io/posts/programming-language/error-handling-comparison/error-unions.png" srcset="https://Orangex-position0.github.io/posts/programming-language/error-handling-comparison/error-unions_hu_ee33b45482a90b76.png 800w, https://Orangex-position0.github.io/posts/programming-language/error-handling-comparison/error-unions_hu_3d6f20a6430ba906.png 1600w, https://Orangex-position0.github.io/posts/programming-language/error-handling-comparison/error-unions.png 1672w" width="1672"&gt;&lt;/p&gt;&#10;&lt;p&gt;Error Union 和 Sum Type 思路接近，但它不是用通用 ADT 自己建模，而是由语言直接提供面向错误处理的专用机制。&lt;/p&gt;&#10;&lt;p&gt;以 Zig 为例，函数返回类型可以写成 &lt;code&gt;Error!T&lt;/code&gt;，表示结果要么是错误集合里的某个错误，要么是成功值 &lt;code&gt;T&lt;/code&gt;。&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-zig" data-lang="zig"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;loadConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;openFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.{});&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;parseConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这种方式的核心设计是：&lt;strong&gt;语言原生支持 &lt;code&gt;error OR value&lt;/code&gt;，让错误进入返回类型，同时保留轻量的传播语法。&lt;/strong&gt;&lt;/p&gt;&#10;&lt;p&gt;它比手写 &lt;code&gt;Result&amp;lt;T, E&amp;gt;&lt;/code&gt; 更贴近错误处理场景，也比隐式异常更容易看见失败路径。不过这类机制目前主要存在于 Zig、C3 等语言中，生态范围和主流语言相比还比较小。&lt;/p&gt;&#10;&lt;h2 id="3-错误处理范式与取舍"&gt;&lt;a href="#3-%e9%94%99%e8%af%af%e5%a4%84%e7%90%86%e8%8c%83%e5%bc%8f%e4%b8%8e%e5%8f%96%e8%88%8d" class="header-anchor"&gt;&lt;/a&gt;3. 错误处理范式与取舍&#10;&lt;/h2&gt;&lt;p&gt;上一章讨论的是机制，这一章讨论背后的设计哲学。因为真正写代码时，问题通常不是“该用哪种语法”，而是“这个失败应该被建模成什么”。&lt;/p&gt;&#10;&lt;h3 id="31-异常范式失败是非局部控制流"&gt;&lt;a href="#31-%e5%bc%82%e5%b8%b8%e8%8c%83%e5%bc%8f%e5%a4%b1%e8%b4%a5%e6%98%af%e9%9d%9e%e5%b1%80%e9%83%a8%e6%8e%a7%e5%88%b6%e6%b5%81" class="header-anchor"&gt;&lt;/a&gt;3.1 异常范式：失败是非局部控制流&#10;&lt;/h3&gt;&lt;p&gt;代表语言或风格：&lt;code&gt;Java&lt;/code&gt;、&lt;code&gt;C#&lt;/code&gt;、&lt;code&gt;Python&lt;/code&gt;、&lt;code&gt;JavaScript&lt;/code&gt;、&lt;code&gt;C++&lt;/code&gt;&lt;/p&gt;&#10;&lt;p&gt;异常范式的核心取舍是：&lt;strong&gt;让正常路径保持连续，把失败交给更有上下文的上层处理。&lt;/strong&gt;&lt;/p&gt;&#10;&lt;p&gt;这套思想非常适合分层应用。底层函数发现文件不存在、数据库连接失败、权限不足时，不一定知道该如何恢复；真正能决策的位置可能是 HTTP handler、CLI 入口、消息消费入口或事务边界。&lt;/p&gt;&#10;&lt;p&gt;异常让中间层不必机械传递错误，主流程看起来更接近业务叙事，比如下面这段 python 代码：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user_service&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;order_service&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;list_orders&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;render_profile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;但问题是，失败路径隐藏起来了。&lt;code&gt;get_user&lt;/code&gt; 可能抛认证异常、数据库异常、用户不存在异常，也可能抛一个意料之外的运行时异常。如果函数签名和文档不说，调用者很难只靠调用点判断。&lt;/p&gt;&#10;&lt;p&gt;所以异常范式的关键不是“抛得越多越好”，而是分层：中间层可以传播，边界层必须负责转换；真正可恢复、需要调用方分支决策的失败，不应该只藏在一个宽泛异常里。&lt;/p&gt;&#10;&lt;h3 id="32-显式返回范式错误是普通返回值"&gt;&lt;a href="#32-%e6%98%be%e5%bc%8f%e8%bf%94%e5%9b%9e%e8%8c%83%e5%bc%8f%e9%94%99%e8%af%af%e6%98%af%e6%99%ae%e9%80%9a%e8%bf%94%e5%9b%9e%e5%80%bc" class="header-anchor"&gt;&lt;/a&gt;3.2 显式返回范式：错误是普通返回值&#10;&lt;/h3&gt;&lt;p&gt;代表语言或风格：&lt;code&gt;Go&lt;/code&gt;、&lt;code&gt;Odin&lt;/code&gt;&lt;/p&gt;&#10;&lt;p&gt;显式返回范式的核心取舍是：&lt;strong&gt;把错误作为普通返回结果，让调用者在每个调用点显式处理。&lt;/strong&gt;&lt;/p&gt;&#10;&lt;p&gt;它拒绝“失败突然跳走”的控制流。函数返回什么，调用者就检查什么；调用者有当前上下文，也最适合决定下一步。&lt;/p&gt;&#10;&lt;p&gt;这在基础设施代码里尤其有价值。比如一个 RPC 调用失败，当前层可能要决定是否重试；一个缓存读取失败，当前层可能要决定是否降级到数据库；一个文件解析失败，当前层可能要补充文件路径、行号、任务 ID 等上下文。&lt;/p&gt;&#10;&lt;p&gt;显式返回的优势是清楚，代价是对于主路径来说“噪声多”。它把错误处理放在主路径旁边，读者不会错过，但也容易被大量错误分支打断。&lt;/p&gt;&#10;&lt;p&gt;因此 Go 代码质量的关键不只是写 &lt;code&gt;if err != nil&lt;/code&gt;，而是每次传播时都想清楚：这里要不要恢复？要不要包装上下文？调用方未来是否需要用 &lt;code&gt;errors.Is&lt;/code&gt; 或 &lt;code&gt;errors.As&lt;/code&gt; 识别它？&lt;/p&gt;&#10;&lt;h3 id="33-类型驱动范式可恢复失败进入类型"&gt;&lt;a href="#33-%e7%b1%bb%e5%9e%8b%e9%a9%b1%e5%8a%a8%e8%8c%83%e5%bc%8f%e5%8f%af%e6%81%a2%e5%a4%8d%e5%a4%b1%e8%b4%a5%e8%bf%9b%e5%85%a5%e7%b1%bb%e5%9e%8b" class="header-anchor"&gt;&lt;/a&gt;3.3 类型驱动范式：可恢复失败进入类型&#10;&lt;/h3&gt;&lt;p&gt;代表语言或风格：&lt;code&gt;Rust&lt;/code&gt;、&lt;code&gt;Haskell&lt;/code&gt;、&lt;code&gt;OCaml&lt;/code&gt;、typed &lt;code&gt;Result&lt;/code&gt; 风格&lt;/p&gt;&#10;&lt;p&gt;类型驱动范式的核心取舍是：&lt;strong&gt;把可恢复失败纳入函数返回类型，让调用者必须面对 &lt;code&gt;success OR error&lt;/code&gt; 两种可能。&lt;/strong&gt;&lt;/p&gt;&#10;&lt;p&gt;它和普通错误返回有相似之处：失败仍然是返回值。但它更进一步，把失败变成函数类型的一部分。&lt;/p&gt;&#10;&lt;p&gt;这带来两个好处。&lt;/p&gt;&#10;&lt;p&gt;第一，调用者不能假装没有失败。一个 &lt;code&gt;Result&amp;lt;User, FindUserError&amp;gt;&lt;/code&gt; 不是 &lt;code&gt;User&lt;/code&gt;，你必须处理它。&lt;/p&gt;&#10;&lt;p&gt;第二，错误集合如果是封闭的，就可以借助模式匹配对错误做穷尽性处理：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;match&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;find_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FindUserError&lt;/span&gt;::&lt;span class="n"&gt;NotFound&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;render_404&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FindUserError&lt;/span&gt;::&lt;span class="n"&gt;PermissionDenied&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;render_403&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FindUserError&lt;/span&gt;::&lt;span class="n"&gt;Storage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;render_500&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这对业务规则、协议解析、编译器、状态机和工作流非常有价值，因为这些场景下“失败有哪几种”本身就是领域模型的一部分。&lt;/p&gt;&#10;&lt;p&gt;代价也很明确：错误类型设计会提前变成 API 设计问题。库作者需要决定哪些错误是稳定契约，哪些只是内部实现细节。错误类型越精确，调用者拿到的信息越多，但 API 演化成本也越高。&lt;/p&gt;&#10;&lt;h3 id="34-显式传播范式调用点必须标记失败"&gt;&lt;a href="#34-%e6%98%be%e5%bc%8f%e4%bc%a0%e6%92%ad%e8%8c%83%e5%bc%8f%e8%b0%83%e7%94%a8%e7%82%b9%e5%bf%85%e9%a1%bb%e6%a0%87%e8%ae%b0%e5%a4%b1%e8%b4%a5" class="header-anchor"&gt;&lt;/a&gt;3.4 显式传播范式：调用点必须标记失败&#10;&lt;/h3&gt;&lt;p&gt;代表语言或风格：&lt;code&gt;Swift&lt;/code&gt;、带显式 &lt;code&gt;try&lt;/code&gt; 标记的错误传播模型&lt;/p&gt;&#10;&lt;p&gt;显式传播范式的核心取舍是：&lt;strong&gt;让调用者看见失败传播，但不一定把完整错误集合冻结进函数类型。&lt;/strong&gt;&lt;/p&gt;&#10;&lt;p&gt;Swift 的 &lt;code&gt;throws&lt;/code&gt; 是很典型的例子。调用一个可能失败的函数时，调用点必须写 &lt;code&gt;try&lt;/code&gt;：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;config&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="n"&gt;loadConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这比 unchecked exception 更显式，因为读代码的人至少能看到“这里可能失败”。但它又不像 Rust 的 &lt;code&gt;Result&amp;lt;T, E&amp;gt;&lt;/code&gt; 那样，默认把具体错误集合写进返回类型。&lt;/p&gt;&#10;&lt;p&gt;这种设计站在异常和类型化错误之间：传播是显式的，错误集合保持开放。好处是库作者更容易演化 API；代价是调用者知道这里可能失败，却未必知道具体会失败成哪些类型。&lt;/p&gt;&#10;&lt;h3 id="35-函数式范式失败是可组合的数据"&gt;&lt;a href="#35-%e5%87%bd%e6%95%b0%e5%bc%8f%e8%8c%83%e5%bc%8f%e5%a4%b1%e8%b4%a5%e6%98%af%e5%8f%af%e7%bb%84%e5%90%88%e7%9a%84%e6%95%b0%e6%8d%ae" class="header-anchor"&gt;&lt;/a&gt;3.5 函数式范式：失败是可组合的数据&#10;&lt;/h3&gt;&lt;p&gt;代表语言或风格：&lt;code&gt;Haskell&lt;/code&gt;、&lt;code&gt;Scala&lt;/code&gt;、TypeScript 的 &lt;code&gt;Result&lt;/code&gt; union、Kotlin sealed class&lt;/p&gt;&#10;&lt;p&gt;函数式范式的核心取舍是：&lt;strong&gt;把错误从控制流变成数据流，让失败路径可以被组合、转换和测试。&lt;/strong&gt;&lt;/p&gt;&#10;&lt;p&gt;在这种视角下，错误不是一次“跳转事件”，而是一个普通数据结构。它可以被 &lt;code&gt;map&lt;/code&gt;、&lt;code&gt;flatMap&lt;/code&gt;、组合、聚合，也可以作为领域模型的一部分传递。&lt;/p&gt;&#10;&lt;p&gt;TypeScript 里常见的 discriminated union 就很适合表达业务失败：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ts" data-lang="ts"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;RegisterResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ok&lt;/span&gt;: &lt;span class="kt"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ok&lt;/span&gt;: &lt;span class="kt"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;reason&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;email_taken&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;weak_password&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;invalid_invite&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;: &lt;span class="kt"&gt;RegisterResult&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;showSuccess&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;showError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这类写法特别适合表单校验、协议解析、领域规则、状态机和工作流。因为调用方真正关心的不是“有没有异常”，而是“失败是哪一种，下一步应该怎么走”。&lt;/p&gt;&#10;&lt;p&gt;代价是抽象重量更高。简单逻辑如果过度套上 &lt;code&gt;Either&lt;/code&gt;、&lt;code&gt;Result&lt;/code&gt;、effect system 或复杂 helper，反而会让代码变难读。函数式错误处理最好用在失败路径本身需要组合和建模的地方。&lt;/p&gt;&#10;&lt;h3 id="36-现代错误处理的分层趋势"&gt;&lt;a href="#36-%e7%8e%b0%e4%bb%a3%e9%94%99%e8%af%af%e5%a4%84%e7%90%86%e7%9a%84%e5%88%86%e5%b1%82%e8%b6%8b%e5%8a%bf" class="header-anchor"&gt;&lt;/a&gt;3.6 现代错误处理的分层趋势&#10;&lt;/h3&gt;&lt;p&gt;“传统”和“现代”不能简单按语言年龄划分。C++ 很老，但 RAII、&lt;code&gt;std::expected&lt;/code&gt; 体现了很现代的错误处理思想；TypeScript 很新，但运行时仍然继承 JavaScript 的动态异常模型。&lt;/p&gt;&#10;&lt;p&gt;更准确的分水岭，是看失败是否显式进入 API 契约：&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;问题&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;传统异常倾向&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;现代类型化倾向&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;失败是否进入函数类型&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;通常不进入，Java checked exception 是例外&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;Result&lt;/code&gt;、&lt;code&gt;Either&lt;/code&gt;、sealed class、union 会进入&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;调用点是否显式&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;unchecked exception 通常不显式&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;?&lt;/code&gt;、&lt;code&gt;try&lt;/code&gt;、&lt;code&gt;if err != nil&lt;/code&gt;、pattern match 显式&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;错误集合是否可穷尽&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;catch 少数 case，其余泛化处理&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;enum、ADT、union 鼓励穷尽&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;API 演化成本&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;错误集合较隐式，演化轻一些&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;错误类型越精确，演化成本越高&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;bug 和可恢复失败是否分开&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;依赖实践和约定&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Rust 最明确，Go 也区分 &lt;code&gt;error&lt;/code&gt; 和 &lt;code&gt;panic&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;现代错误处理不是简单消灭异常，而是更强调分层：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;业务失败：尽量显式、机器可读，适合错误码、错误枚举、&lt;code&gt;Result&lt;/code&gt;、union、sealed class。&lt;/li&gt;&#10;&lt;li&gt;外部环境失败：保留 cause 和上下文，逐层传播，在边界转换成日志、响应、重试或告警。&lt;/li&gt;&#10;&lt;li&gt;程序 bug：尽早暴露，使用 &lt;code&gt;panic&lt;/code&gt;、assert、unchecked exception 或 fail fast。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;这也是我理解的核心趋势：异常没有消失，只是从“所有失败的默认答案”，逐渐回到更合适的位置。&lt;/p&gt;&#10;&lt;h2 id="4-为什么现代错误处理越来越强调显式失败"&gt;&lt;a href="#4-%e4%b8%ba%e4%bb%80%e4%b9%88%e7%8e%b0%e4%bb%a3%e9%94%99%e8%af%af%e5%a4%84%e7%90%86%e8%b6%8a%e6%9d%a5%e8%b6%8a%e5%bc%ba%e8%b0%83%e6%98%be%e5%bc%8f%e5%a4%b1%e8%b4%a5" class="header-anchor"&gt;&lt;/a&gt;4. 为什么现代错误处理越来越强调显式失败&#10;&lt;/h2&gt;&lt;p&gt;讨论到这里，容易得出一个过度简化的结论：现代语言是不是都在反对异常？&lt;/p&gt;&#10;&lt;p&gt;我觉得不是。更准确的说法是：现代错误处理越来越强调&lt;strong&gt;把调用者需要决策的失败显式暴露出来&lt;/strong&gt;。&lt;/p&gt;&#10;&lt;p&gt;原因主要有三个。&lt;/p&gt;&#10;&lt;p&gt;第一，API 边界越来越重要。服务化、SDK、开放 API、微服务让错误经常跨过进程、网络、语言和团队边界。这个时候，错误如果只是一段 message 或隐式异常，调用者很难稳定分支。&lt;/p&gt;&#10;&lt;p&gt;第二，并发和异步让隐式失败更危险。同步调用里，异常还有一条相对直观的调用栈；但进入 Promise、goroutine、线程池、异步任务之后，失败可能发生在另一个时间点或另一个执行单元里。显式错误结果、结构化并发和 typed result 的价值，在于让失败可以被等待、组合、聚合，并和取消、超时一起处理。&lt;/p&gt;&#10;&lt;p&gt;第三，业务失败需要机器可读，而不是人类可读 message。用户名重复、余额不足、权限不足、库存不足这类失败通常不是程序事故，而是业务分支。它们需要被前端展示、国际化、统计、重试或引导用户操作，所以更适合被建模成错误码、错误枚举或领域错误类型。&lt;/p&gt;&#10;&lt;p&gt;所以问题不是“异常 vs 返回值”二选一，而是：这个失败到底应该由谁看见、谁处理、谁负责转换成用户或系统能理解的结果。&lt;/p&gt;&#10;&lt;h2 id="5-如果面试中被问到如何对比几门语言的错误处理"&gt;&lt;a href="#5-%e5%a6%82%e6%9e%9c%e9%9d%a2%e8%af%95%e4%b8%ad%e8%a2%ab%e9%97%ae%e5%88%b0%e5%a6%82%e4%bd%95%e5%af%b9%e6%af%94%e5%87%a0%e9%97%a8%e8%af%ad%e8%a8%80%e7%9a%84%e9%94%99%e8%af%af%e5%a4%84%e7%90%86" class="header-anchor"&gt;&lt;/a&gt;5. 如果面试中被问到：如何对比几门语言的错误处理？&#10;&lt;/h2&gt;&lt;p&gt;那么回到文章开头提到的面试题：“你简历上写了几门语言，那对比一下它们的错误处理方式”，不要急着背语法。更好的回答方式，是先给出比较维度，再把语言放进去。&lt;/p&gt;&#10;&lt;p&gt;可以这样回答：&lt;/p&gt;&#10;&#10; &lt;blockquote&gt;&#10; &lt;p&gt;我会从“错误是否显式”“调用方是否被强制处理”“错误是否进入 API 契约”这几个角度对比。&lt;/p&gt;&#10;&lt;p&gt;Java 主要依赖异常。checked exception 会进入方法签名，能表达一部分可恢复失败契约；unchecked exception 则更像隐式的非局部控制流，适合跨层传播并在边界统一处理。Python 和 JavaScript 也主要使用异常，不过异常集合通常不进入函数签名，更多依赖文档、测试和约定。&lt;/p&gt;&#10;&lt;p&gt;Go 把错误作为普通返回值，调用者通常在每个调用点显式判断。它的控制流很清楚，代码审查也容易发现错误有没有被处理，但会带来重复的 &lt;code&gt;if err != nil&lt;/code&gt;，而且错误分类依赖约定。&lt;/p&gt;&#10;&lt;p&gt;Rust 更进一步，把可恢复错误放进 &lt;code&gt;Result&amp;lt;T, E&amp;gt;&lt;/code&gt;，借助类型系统和 &lt;code&gt;?&lt;/code&gt; 传播，让失败路径既显式又能被编译器检查。TypeScript、Kotlin、Scala 也可以用 union、sealed class、Either/Result 这类方式，把业务失败建模成数据。&lt;/p&gt;&#10;&lt;p&gt;所以我理解错误处理的重点不是哪种语法更高级，而是先判断失败的性质：调用者能恢复的失败应该显式建模，跨层的系统失败可以在边界统一处理，程序 bug 则应该尽早暴露。&lt;/p&gt;&#10;&#10; &lt;/blockquote&gt;&#10;&lt;p&gt;如果面试官继续追问，可以按下面四个维度展开：&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;维度&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;可以怎么说&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;错误如何表示&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Java、Python、JavaScript 常用异常对象；Go 用 &lt;code&gt;error&lt;/code&gt; 返回值；Rust 用 &lt;code&gt;Result&amp;lt;T, E&amp;gt;&lt;/code&gt;；TypeScript 可以用 union type 表达业务失败。&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;错误如何传播&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;异常会沿调用栈自动传播；Go 需要手动返回；Rust 可以用 &lt;code&gt;?&lt;/code&gt; 显式传播；JavaScript 异步场景里还要处理 Promise rejection。&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;调用方是否被强制处理&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Java checked exception、Rust &lt;code&gt;Result&lt;/code&gt;、Swift &lt;code&gt;try&lt;/code&gt; 更强调调用点可见；unchecked exception、Python、JavaScript 更依赖约定。&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;适合什么场景&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;异常适合跨层传播和边界统一处理；显式返回适合调用者要立即决策的失败；类型化错误适合业务规则、协议解析、编译器、工作流等需要穷尽处理的场景。&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;最后给出自己的判断，会比单纯罗列语言更好：&lt;/p&gt;&#10;&#10; &lt;blockquote&gt;&#10; &lt;p&gt;我不认为存在一种绝对最好的错误处理机制。关键是先区分错误性质：业务失败、外部环境失败、程序 bug。业务失败应该尽量显式、机器可读；外部环境失败要保留上下文，并在合适的边界转换成日志、响应或重试策略；程序 bug 则应该 fail fast。不同语言的差异，本质上是在“主流程简洁性、错误路径显式性、API 契约稳定性、类型系统约束”之间做取舍。&lt;/p&gt;&#10;&#10; &lt;/blockquote&gt;&#10;&lt;h2 id="6-总结"&gt;&lt;a href="#6-%e6%80%bb%e7%bb%93" class="header-anchor"&gt;&lt;/a&gt;6. 总结&#10;&lt;/h2&gt;&lt;p&gt;错误处理方式的差异，表面上是 &lt;code&gt;try-catch&lt;/code&gt;、&lt;code&gt;if err != nil&lt;/code&gt;、&lt;code&gt;Result&lt;/code&gt;、&lt;code&gt;throw&lt;/code&gt; 的差异，本质上是语言设计哲学的差异。&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;异常范式追求主路径简洁，适合跨层传播和边界统一处理，但错误路径容易隐藏。&lt;/li&gt;&#10;&lt;li&gt;显式返回范式把失败放回普通控制流，调用点清楚，但会带来样板和噪音。&lt;/li&gt;&#10;&lt;li&gt;类型驱动范式把可恢复失败纳入函数类型，让编译器参与错误处理，但也要求更认真地设计错误模型。&lt;/li&gt;&#10;&lt;li&gt;函数式范式则进一步把失败当作可组合的数据，适合复杂领域建模和工作流。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;我现在更倾向于用一个分层原则来选择错误处理方式：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;调用方能恢复、需要分支决策的失败，应该显式建模。&lt;/li&gt;&#10;&lt;li&gt;外部环境失败，应该保留上下文，并在系统边界统一转换。&lt;/li&gt;&#10;&lt;li&gt;违反程序不变量、无法安全继续的状态，应该尽早暴露。&lt;/li&gt;&#10;&lt;li&gt;不要解析错误 message 做业务判断，也不要每一层都重复 log 同一个错误。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;这样看，错误处理就不再是“哪门语言更优雅”的争论，而是一个 API 设计问题：你希望失败在哪里被看见，被谁处理，以及以什么形式进入系统契约。&lt;/p&gt;&#10;&lt;h2 id="references"&gt;&lt;a href="#references" class="header-anchor"&gt;&lt;/a&gt;References&#10;&lt;/h2&gt;&lt;ul&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://docs.oracle.com/en/java/javase/26/docs/specs/jls/jls-11.html" target="_blank" rel="noopener"&#10; &gt;Java Language Specification, Chapter 11 Exceptions&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://doc.rust-lang.org/stable/book/ch09-00-error-handling.html" target="_blank" rel="noopener"&#10; &gt;Rust Book, Error Handling&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://go.dev/blog/errors-are-values" target="_blank" rel="noopener"&#10; &gt;Go Blog, Errors are values&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://go.dev/doc/faq#exceptions" target="_blank" rel="noopener"&#10; &gt;Go FAQ, Why does Go not have exceptions?&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://www.typescriptlang.org/docs/handbook/2/narrowing.html" target="_blank" rel="noopener"&#10; &gt;TypeScript Handbook, Narrowing and Exhaustiveness&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://docs.python.org/3.12/tutorial/errors.html" target="_blank" rel="noopener"&#10; &gt;Python Tutorial, Errors and Exceptions&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://github.com/swiftlang/swift-evolution/blob/main/proposals/0000-error-handling-rationale-and-proposal.md" target="_blank" rel="noopener"&#10; &gt;Swift Evolution, Error Handling Rationale and Proposal&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://learn.microsoft.com/en-us/dotnet/standard/design-guidelines/exceptions" target="_blank" rel="noopener"&#10; &gt;Microsoft Framework Design Guidelines, Exceptions&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://www.bilibili.com/video/BV1gJS9YeEsz/" target="_blank" rel="noopener"&#10; &gt;什么是正确的错误处理方法【让编程再次伟大#21】_哔哩哔哩_bilibili&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://www.youtube.com/watch?v=zOUsVf1LsKg" target="_blank" rel="noopener"&#10; &gt;Exploring the ways different languages handle errors - YouTube&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;</description></item></channel></rss>