<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>第 7 章 错误处理 on Go 语言原本</title><link>http://golang.design/under-the-hood/zh-cn/part2lang/ch07errors/</link><description>Recent content in 第 7 章 错误处理 on Go 语言原本</description><generator>Hugo</generator><language>zh-cn</language><atom:link href="http://golang.design/under-the-hood/zh-cn/part2lang/ch07errors/index.xml" rel="self" type="application/rss+xml"/><item><title>7.1 问题的演化</title><link>http://golang.design/under-the-hood/zh-cn/part2lang/ch07errors/value/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>http://golang.design/under-the-hood/zh-cn/part2lang/ch07errors/value/</guid><description>&lt;h1 id="71-问题的演化"&gt;7.1 问题的演化&lt;/h1&gt;
&lt;p&gt;在 &lt;a href="../../ch06func/panic"&gt;6.3&lt;/a&gt; 里我们已经把那道分水岭画了出来：如何处理错误，是语言设计的
一处根本抉择。异常派（C++、Java、Python）用 &lt;code&gt;try/catch&lt;/code&gt; 把错误路径从正常逻辑里抽走，主干
干净，代价是错误路径变得隐式，可能从任意调用点抛出；值派（Go、Rust、以及 C 的返回码传统）
把错误当作普通返回值显式传递，啰嗦，但每一条错误路径都白纸黑字、无处可藏。Go 选了值派，
只为「真正异常」保留一个轻量的 &lt;code&gt;panic&lt;/code&gt;/&lt;code&gt;recover&lt;/code&gt;。这一节要讲的，是这个选择落地之后，
「错误作为值」这一主张本身又经历了怎样的演化：从一个只能打印的字符串，长成一棵可以被程序
逐层追问「你到底是不是那个错误」的树。&lt;/p&gt;
&lt;h2 id="711-错误即值"&gt;7.1.1 错误即值&lt;/h2&gt;
&lt;p&gt;Go 把错误定义成一个内建接口，全部的约定只有一个方法：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#008000"&gt;// src/builtin/builtin.go&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#00f"&gt;type&lt;/span&gt; &lt;span style="color:#2b91af"&gt;error&lt;/span&gt; &lt;span style="color:#00f"&gt;interface&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	Error() &lt;span style="color:#2b91af"&gt;string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;任何实现了 &lt;code&gt;Error() string&lt;/code&gt; 的类型，都可以当作 &lt;code&gt;error&lt;/code&gt; 传递。它不是一种特殊的语言构造，
而是一个再普通不过的接口值，能赋值、能比较、能放进切片、能作为字段、能跨 channel 传递。
这正是 Rob Pike 在《Errors are values》里反复强调的一句话：错误是值，而值可以被编程。
程序员不是被动地「捕获」一个从天而降的异常，而是主动地拿一个值去做判断、包装、传播或吞下。&lt;/p&gt;
&lt;p&gt;这个主张在函数签名上有一处固定的体现：错误总是作为最后一个返回值显式交还给调用方。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;5
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;f, err := os.Open(name)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#00f"&gt;if&lt;/span&gt; err != &lt;span style="color:#00f"&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#00f"&gt;return&lt;/span&gt; err
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#00f"&gt;defer&lt;/span&gt; f.Close()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#008000"&gt;// 使用 f&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;满屏的 &lt;code&gt;if err != nil&lt;/code&gt; 常被诟病啰嗦，但它恰是值派的代价与收益的同一面：错误既然是值，
就必须像别的值一样被显式接住，编译器不会替你把它偷偷送到别处。这与 &lt;a href="../../ch06func/panic"&gt;6.3&lt;/a&gt;
里 Go 的整体气质一脉相承，显式优于隐式。把错误降格为一个一方法接口，换来的是最大的自由：
标准库不需要预先规定一套错误等级体系，任何人都能用最贴合自己场景的类型去实现 &lt;code&gt;error&lt;/code&gt;，
从一个字符串常量，到一个携带行号、文件名、底层系统调用号的结构体。代价是这份自由把责任
也一并交还给了用户，怎样定义错误、怎样让调用方能可靠地辨认它，都得自己安排。这一节余下的
篇幅，讲的就是 Go 社区与标准库为这份责任摸索出的答案。&lt;/p&gt;</description></item><item><title>7.2 错误值检查</title><link>http://golang.design/under-the-hood/zh-cn/part2lang/ch07errors/inspect/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>http://golang.design/under-the-hood/zh-cn/part2lang/ch07errors/inspect/</guid><description>&lt;h1 id="72-错误值检查"&gt;7.2 错误值检查&lt;/h1&gt;
&lt;p&gt;错误一旦在调用链里向上传播，处理它的代码与产生它的代码往往隔着许多层。这就带来一个
朴素却棘手的问题：拿到一个层层传上来的 &lt;code&gt;error&lt;/code&gt;，调用方如何判断「它到底是不是某个特定的错误」，
又如何从中取回当初那个携带了上下文的具体错误值？这一节回答的就是这件事，以及 Go 为此
在 &lt;code&gt;errors&lt;/code&gt; 包里立下的那套约定。&lt;/p&gt;
&lt;p&gt;最朴素的错误就是一句话。&lt;code&gt;errors.New&lt;/code&gt; 把一个字符串包成 &lt;code&gt;error&lt;/code&gt;，内部只是 &lt;code&gt;errorString&lt;/code&gt;
这一极简实现：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;5
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#00f"&gt;package&lt;/span&gt; errors
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#00f"&gt;type&lt;/span&gt; errorString &lt;span style="color:#00f"&gt;struct&lt;/span&gt; { s &lt;span style="color:#2b91af"&gt;string&lt;/span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#00f"&gt;func&lt;/span&gt; (e *errorString) Error() &lt;span style="color:#2b91af"&gt;string&lt;/span&gt; { &lt;span style="color:#00f"&gt;return&lt;/span&gt; e.s }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#00f"&gt;func&lt;/span&gt; New(text &lt;span style="color:#2b91af"&gt;string&lt;/span&gt;) &lt;span style="color:#2b91af"&gt;error&lt;/span&gt; { &lt;span style="color:#00f"&gt;return&lt;/span&gt; &amp;amp;errorString{text} }
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;实践中常把它和 &lt;code&gt;fmt.Sprintf&lt;/code&gt; 拼起来用，凑出一句带变量的错误消息。可一旦错误被压成字符串，
它的「可处理性」便几乎归零：调用方只能拿这句话去和别的字符串比对，无从知道它的来源，
也无从取回原始的错误值。Go 1.13 在 &lt;code&gt;errors&lt;/code&gt; 包里补上的 &lt;code&gt;Unwrap&lt;/code&gt;、&lt;code&gt;Is&lt;/code&gt;、&lt;code&gt;As&lt;/code&gt;，
以及 go1.26 新增的泛型 &lt;code&gt;AsType&lt;/code&gt;，要解决的正是这个「错误一旦上浮便不可追问」的困境。
它们的共同前提，是先把错误串成一条可以回溯的链。&lt;/p&gt;</description></item><item><title>7.3 错误格式与上下文</title><link>http://golang.design/under-the-hood/zh-cn/part2lang/ch07errors/context/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>http://golang.design/under-the-hood/zh-cn/part2lang/ch07errors/context/</guid><description>&lt;h1 id="73-错误格式与上下文"&gt;7.3 错误格式与上下文&lt;/h1&gt;
&lt;p&gt;一个好的错误，不只是「出错了」，而是能告诉人：在做什么的时候、因为什么、出了什么错。问题的
演化（&lt;a href=".././value"&gt;7.1&lt;/a&gt;）与值检查（&lt;a href=".././inspect"&gt;7.2&lt;/a&gt;）给了我们传递与检视错误的机制，这一节
谈机制之上的工程实践，错误的文本应当怎么写、上下文如何沿调用链一层层累积、以及当人写的文字
不够用时，栈轨迹与结构化日志如何按需补上。贯穿其间的，是 Go 在错误信息上的一个总体倾向：
人写的语义上下文，优于机器采的栈轨迹。&lt;/p&gt;
&lt;h2 id="731-error-string-的约定"&gt;7.3.1 &lt;code&gt;Error() string&lt;/code&gt; 的约定&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;error&lt;/code&gt; 接口只要求一个方法：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#00f"&gt;type&lt;/span&gt; &lt;span style="color:#2b91af"&gt;error&lt;/span&gt; &lt;span style="color:#00f"&gt;interface&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Error() &lt;span style="color:#2b91af"&gt;string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;围绕这唯一的方法，Go 社区形成了一套不成文却处处可见的风格约定：错误字符串首字母小写、
结尾不加标点。约定背后的原因很实际。错误极少独立出现，它们几乎总是被层层包装、首尾相接，
拼成一条更长的字符串。设想三层调用各自补一句上下文，最终用户看到的是：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;read config: open /etc/app.conf: permission denied
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这是三段错误用 &lt;code&gt;: &lt;/code&gt; 拼接而成的一条因果链。若每一段都按句子的写法首字母大写、末尾加句号，
拼起来便成了「Read config: Open /etc/app.conf: Permission denied.」，一串大写字母与句号
散落在句子中间，读起来支离破碎。小写、无尾标点的片段，才能顺畅地嵌进任意位置，串成一条
连贯的链。这条约定细小，却是 Go 错误信息能层层拼接而不失可读的前提。&lt;/p&gt;
&lt;p&gt;需要澄清一处常见的说法：检查这条约定的并不是 &lt;code&gt;go vet&lt;/code&gt;。历史上它由 &lt;code&gt;golint&lt;/code&gt; 负责，&lt;code&gt;golint&lt;/code&gt;
归档后，这项检查（编号 ST1005）由 &lt;code&gt;staticcheck&lt;/code&gt; 等第三方工具承担。&lt;code&gt;go vet&lt;/code&gt; 不管错误字符串
的大小写，但它确实有一个与错误相关的检查，针对 &lt;code&gt;log/slog&lt;/code&gt; 的结构化日志调用（见 7.3.5）。&lt;/p&gt;</description></item><item><title>7.4 错误语义</title><link>http://golang.design/under-the-hood/zh-cn/part2lang/ch07errors/semantics/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>http://golang.design/under-the-hood/zh-cn/part2lang/ch07errors/semantics/</guid><description>&lt;h1 id="74-错误语义"&gt;7.4 错误语义&lt;/h1&gt;
&lt;p&gt;&lt;a href=".././inspect"&gt;7.2&lt;/a&gt; 与 &lt;a href=".././context"&gt;7.3&lt;/a&gt; 站在错误的调用方一侧，讲清了拿到一个 &lt;code&gt;error&lt;/code&gt;
之后如何沿链检查、如何叠加上下文。这一节把视角调到另一侧，站在库作者一侧问一个先于检查的问题：
当一个包要把失败暴露给外界时，它应当把错误做成什么&lt;strong&gt;形式&lt;/strong&gt;？是导出一个可比较的值，
还是导出一个带字段的类型，抑或什么都不导出？&lt;/p&gt;
&lt;p&gt;这并非纯粹的风格之争。错误的形式一旦发布，就成了包的公开契约的一部分，和函数签名一样难以更改。
调用方会照着这个形式写下分支逻辑，而这些分支逻辑反过来把调用方与库&lt;strong&gt;耦合&lt;/strong&gt;在了一起。
不同的形式，承诺给契约的东西不同，因而日后能改动的自由度也不同。本节把社区里成型的几种做法
摆在一起，按「向 API 契约承诺了什么」这条线索逐一称量它们的代价，最后给出一个可操作的取舍次序。&lt;/p&gt;
&lt;h2 id="741-哨兵错误向契约承诺一个值"&gt;7.4.1 哨兵错误：向契约承诺一个值&lt;/h2&gt;
&lt;p&gt;最直接的做法是导出一个预先创建好的错误值，让调用方拿它做相等比较。标准库里这样的例子俯拾皆是：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#00f"&gt;package&lt;/span&gt; io
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#008000"&gt;// EOF 是 Read 在没有更多输入时返回的错误。&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#00f"&gt;var&lt;/span&gt; EOF = errors.New(&lt;span style="color:#a31515"&gt;&amp;#34;EOF&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="highlight"&gt;&lt;div style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#00f"&gt;package&lt;/span&gt; sql
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#008000"&gt;// ErrNoRows 在 QueryRow 没有命中任何行时由 Row.Scan 返回。&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#00f"&gt;var&lt;/span&gt; ErrNoRows = errors.New(&lt;span style="color:#a31515"&gt;&amp;#34;sql: no rows in result set&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;调用方据此分支：&lt;/p&gt;</description></item><item><title>7.5 错误处理的未来</title><link>http://golang.design/under-the-hood/zh-cn/part2lang/ch07errors/future/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>http://golang.design/under-the-hood/zh-cn/part2lang/ch07errors/future/</guid><description>&lt;h1 id="75-错误处理的未来"&gt;7.5 错误处理的未来&lt;/h1&gt;
&lt;p&gt;前几节里我们见过 &lt;code&gt;error&lt;/code&gt; 接口的朴素、&lt;code&gt;%w&lt;/code&gt; 包装与错误链的检视。读者读到这里，心里多半积着
一个没有说出口的疑问：&lt;code&gt;if err != nil&lt;/code&gt; 这三行模板，年复一年地铺满每个 Go 程序，难道就没有
办法收掉吗？这个疑问并不孤单。从 2018 年的 go2 草案到 2025 年的一纸结论，Go 团队为它前后
推敲了七年，提出过 &lt;code&gt;check&lt;/code&gt;/&lt;code&gt;handle&lt;/code&gt;、&lt;code&gt;try&lt;/code&gt;、&lt;code&gt;?&lt;/code&gt; 等多套语法，又把它们一一收回。本节把这条
没有走通的语法之路，与另一条悄然走通的库演进之路，并排摆出来，并交代 Go 团队最终给出的
判断：在可见的将来，错误处理不会迎来专门的语法。&lt;/p&gt;
&lt;p&gt;理解这个结局，比记住任何一套被否决的语法都重要。它不是搁置，而是一次有据可依的「不做」，
背后是 Go 对「显式优于简洁」的一次郑重重申。&lt;/p&gt;
&lt;h2 id="751-被否决的语法尝试"&gt;7.5.1 被否决的语法尝试&lt;/h2&gt;
&lt;h3 id="check--handle2018-年的-go2-草案"&gt;check / handle：2018 年的 go2 草案&lt;/h3&gt;
&lt;p&gt;错误处理语法化的第一次正式尝试，是 2018 年随「Go 2」蓝图公布的 &lt;code&gt;check&lt;/code&gt;/&lt;code&gt;handle&lt;/code&gt; 草案。
它引入两个关键字：&lt;code&gt;check&lt;/code&gt; 是一个表达式，对返回 &lt;code&gt;(T, error)&lt;/code&gt; 的调用求值，错误为 &lt;code&gt;nil&lt;/code&gt; 时
取出 &lt;code&gt;T&lt;/code&gt;，否则把控制权交给最近的 &lt;code&gt;handle&lt;/code&gt; 块；&lt;code&gt;handle&lt;/code&gt; 是一个语句，声明当前函数里检查
失败时的善后逻辑。一个典型的文件拷贝在草案下写成这样：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 5
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 6
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 7
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 8
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 9
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;10
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;11
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;12
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;13
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;14
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;15
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;16
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;17
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;18
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#00f"&gt;func&lt;/span&gt; CopyFile(src, dst &lt;span style="color:#2b91af"&gt;string&lt;/span&gt;) &lt;span style="color:#2b91af"&gt;error&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	handle err {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#00f"&gt;return&lt;/span&gt; fmt.Errorf(&lt;span style="color:#a31515"&gt;&amp;#34;copy %s %s: %v&amp;#34;&lt;/span&gt;, src, dst, err)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	r := check os.Open(src) &lt;span style="color:#008000"&gt;// 失败则跳到上面的 handle&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#00f"&gt;defer&lt;/span&gt; r.Close()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	w := check os.Create(dst)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	handle err { &lt;span style="color:#008000"&gt;// handle 可叠加，后声明的先生效&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		w.Close()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		os.Remove(dst)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	check io.Copy(w, r)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	check w.Close()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#00f"&gt;return&lt;/span&gt; &lt;span style="color:#00f"&gt;nil&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;它的野心在 &lt;code&gt;handle&lt;/code&gt; 链：多个 &lt;code&gt;handle&lt;/code&gt; 按声明逆序生效，让人联想到 &lt;code&gt;defer&lt;/code&gt; 的栈式语义，能把
「出错时回滚」的清理逻辑沿调用顺序层层铺开。代价也正在于此。读者要读懂某一行 &lt;code&gt;check&lt;/code&gt;
失败后会发生什么，得在脑子里维护一个 &lt;code&gt;handle&lt;/code&gt; 栈，与 &lt;code&gt;defer&lt;/code&gt; 栈彼此独立又彼此纠缠。社区
普遍反馈它「为省下 &lt;code&gt;if&lt;/code&gt; 而引入了一套新的、需要单独学习的控制流」，复杂度未必比它消除的更低。
草案最终被判定过于复杂而未进入提案阶段。&lt;/p&gt;</description></item><item><title>7.6 进一步阅读的参考文献</title><link>http://golang.design/under-the-hood/zh-cn/part2lang/ch07errors/ref/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>http://golang.design/under-the-hood/zh-cn/part2lang/ch07errors/ref/</guid><description>&lt;h1 id="46-进一步阅读的参考文献"&gt;4.6 进一步阅读的参考文献&lt;/h1&gt;
&lt;table class="bib"&gt;
&lt;tr&gt;
&lt;td&gt;[Gerrand, 2010]&lt;/td&gt;&lt;td&gt;Andrew Gerrand. "Defer, Panic and Recover." August 2010. &lt;a href="https://blog.golang.org/defer-panic-and-recover"&gt;https://blog.golang.org/defer-panic-and-recover&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;[Gerrand, 2011]&lt;/td&gt;&lt;td&gt;Andrew Gerrand. "Error handling in Go." July 2011. &lt;a href="https://blog.golang.org/error-handling-and-go"&gt;https://blog.golang.org/error-handling-and-go&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;[Pike, 2015]&lt;/td&gt;&lt;td&gt;Rob Pike. "Errors are values." January 2015. &lt;a href="https://blog.golang.org/errors-are-values"&gt;https://blog.golang.org/errors-are-values&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;[Cheney, 2016a]&lt;/td&gt;&lt;td&gt;Dave Cheney. My philosophy for error handling. April 2016. &lt;a href="https://dave.cheney.net/paste/gocon-spring-2016.pdf"&gt;https://dave.cheney.net/paste/gocon-spring-2016.pdf&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;[Cheney, 2016b]&lt;/td&gt;&lt;td&gt;Dave Cheney. Don’t just check errors, handle them gracefully. April 2016. &lt;a href="https://dave.cheney.net/2016/04/27/dont-just-check-errors-handle-them-gracefully"&gt;https://dave.cheney.net/2016/04/27/dont-just-check-errors-handle-them-gracefully&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;[Cheney, 2016c]&lt;/td&gt;&lt;td&gt;Dave Cheney. Stack traces and the errors package. June, 12 2016. &lt;a href="https://dave.cheney.net/2016/06/12/stack-traces-and-the-errors-package"&gt;https://dave.cheney.net/2016/06/12/stack-traces-and-the-errors-package&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;[Cheney, 2016d]&lt;/td&gt;&lt;td&gt;Dave Cheney. pkg/errors: Simple error handling primitives. Last access: Jan 14, 2019 &lt;a href="https://github.com/pkg/errors/tree/614d223910a179a466c1767a985424175c39b465"&gt;https://github.com/pkg/errors/tree/614d223910a179a466c1767a985424175c39b465&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;[Pike, 2017]&lt;/td&gt;&lt;td&gt;Rob Pike. Error handling in Upspin. December 06, 2017. &lt;a href="https://commandcenter.blogspot.com/2017/12/error-handling-in-upspin.html"&gt;https://commandcenter.blogspot.com/2017/12/error-handling-in-upspin.html&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;[Cox, 2018]&lt;/td&gt;&lt;td&gt;Russ Cox. "Error Values — Problem Overview." August 2018. &lt;a href="https://github.com/golang/proposal/blob/master/design/go2draft-error-values-overview"&gt;https://github.com/golang/proposal/blob/master/design/go2draft-error-values-overview&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;[Lohuizen, 2018]&lt;/td&gt;&lt;td&gt;Marcel van Lohuizen. "Error Handling — Draft Design." August 2018. &lt;a href="https://github.com/golang/proposal/blob/master/design/go2draft-error-handling"&gt;https://github.com/golang/proposal/blob/master/design/go2draft-error-handling&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;[Neil, 2019]&lt;/td&gt;&lt;td&gt;Damien Neil. Go 1.13 lunch decision about error values. May 6, 2019. &lt;a href="https://github.com/golang/go/issues/29934#issuecomment-489682919"&gt;https://github.com/golang/go/issues/29934#issuecomment-489682919&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;[Cox, 2019a]&lt;/td&gt;&lt;td&gt;Russ Cox. Response, Response regarding "proposal: Go 2 error values". May 7, 2019. &lt;a href="https://github.com/golang/go/issues/29934#issuecomment-490087200"&gt;https://github.com/golang/go/issues/29934#issuecomment-490087200&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;[Cox, 2019b]&lt;/td&gt;&lt;td&gt;Russ Cox. "Experiment, Simplify, Ship." August 2019. &lt;a href="https://blog.golang.org/experiment"&gt;https://blog.golang.org/experiment&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;[Amsterdam and Mills, 2019]&lt;/td&gt;&lt;td&gt;Jonathan Amsterdam and Bryan C. Mills. Error Values: Frequently Asked Questions. August 2019. &lt;a href="https://github.com/golang/go/wiki/ErrorValueFAQ"&gt;https://github.com/golang/go/wiki/ErrorValueFAQ&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;[Griesemer, 2019]&lt;/td&gt;&lt;td&gt;Robert Griesemer, "Proposal: A built-in Go error check function, 'try'". July 2019. &lt;a href="https://github.com/golang/go/issues/32437#issuecomment-512035919"&gt;https://github.com/golang/go/issues/32437#issuecomment-512035919&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;[Neil and Amsterdam, 2019]&lt;/td&gt;&lt;td&gt;Damien Neil and Jonathan Amsterdam. Working with Errors in Go 1.13. October 17, 2019. &lt;a href="https://blog.golang.org/go1.13-errors"&gt;https://blog.golang.org/go1.13-errors&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;[Amsterdam, 2019]&lt;/td&gt;&lt;td&gt;Jonathan Amsterdam. "proposal: Go 2 error values." Jan 25, 2019. &lt;a href="https://github.com/golang/go/issues/29934"&gt;https://github.com/golang/go/issues/29934&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;!-- &lt;a href="https://groups.google.com/d/msg/golang-nuts/_sE6BxUDVBw/TFzmZzrhCQAJ"&gt;https://groups.google.com/d/msg/golang-nuts/_sE6BxUDVBw/TFzmZzrhCQAJ&lt;/a&gt;
&lt;a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0709r3.pdf"&gt;http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0709r3.pdf&lt;/a&gt;
&lt;a href="https://stackoverflow.com/questions/46586/goto-still-considered-harmful"&gt;https://stackoverflow.com/questions/46586/goto-still-considered-harmful&lt;/a&gt; --&gt;</description></item></channel></rss>