错误处理有不同的方式。

JavaScript 和 Python 是抛出异常, Rust 语言是变相抛出异常。
C 语言和 Go 语言则是返回一个错误值,你必须判断该值是否为 -1 或空值。
我一直想知道,哪一种方式更好?
前不久,我读到一篇多年前的文章,明确提出抛出异常好于返回状态码。他的理由很有说服力,文章好像还没有中译,我就翻译出来了。
异常与返回状态码
作者:内德·巴切尔德(Ned Batchelder)
原文网址:nedbatchelder.com

在软件中,错误处理有两种方式:抛出异常(throwing exceptions)和返回状态码(returning status codes)。
几乎所有人都认为异常是更好的处理方式,但有些人仍然更喜欢返回状态码。本文解释为什么异常是更好的选择。
一、代码干净
异常可以让你在大部分代码中省去错误处理步骤。它会自动通过不捕捉异常的层,向上传递。你因此可以编写完全没有错误处理逻辑的代码,这有助于保持代码的简洁易读。
让我们比较一下,编写同一个简单函数的两种方法。
先是返回状态码。
STATUS DoSomething(int a, int b) { STATUS st; st = DoThing1(a); if (st != SGOOD) return st; st = DoThing2(b); if (st != SGOOD) return st; return SGOOD; }
上面示例中,必须判断DoThing1(a)和DoThing2(b)的返回值是否正常,才能进行下一步。
如果是抛出异常,就不需要中间的错误判断了。
void DoSomething(int a, int b) { DoThing1(a); DoThing2(b); }
这只是最简单的情况,如果遇到复杂的场景,状态码带来的噪音会更严重,异常则可以保持代码的整洁。
二、有意义的返回值
状态码会占用宝贵的返回值,你不得不增加代码,判断返回值是否正确。
有些函数本来只需要返回一个正常值,现在不得不增加返回错误的情况。随着时间的推移,代码量不断增长,函数变得越来越大,返回值也越来越复杂。
比如,很多函数的返回值是有重载的:"如果失败,则返回 NULL",或者失败返回 -1。结果就是每次调用这个方法,都需要检查返回值是否是 NULL 或 -1。如果函数后来增加新的错误返回值,则必须更新所有调用点。
如果是抛出异常,那么函数就总是成功的情况下才返回,所有的错误处理也可以简化放在一个地方。
三、更丰富的错误信息
状态码通常是一个整数,能够传递的信息相当有限。假设错误是找不到文件,那么是哪一个文件呢?状态码无法传递那么多信息。
返回状态码的时候,最好记录一条错误消息,放在专门的错误日志里面,调用者可以从中获取详细信息。
异常完全不同,它是类的实例,因此可以携带大量信息。由于异常可以被子类化,不同的异常可以携带不同的数据,从而形成非常丰富的错误消息体系。
四、可以处理隐式代码
某些函数无法返回状态码。例如,构造函数就没有显式的返回值,因此无法返回状态码。还有一些函数(比如析构函数)甚至无法直接调用,更不用说返回值了。
这些没有返回值的函数,如果不使用异常处理,你不得不想出其他方法来给出错误信息,或者假装这些函数不会失败。简单的函数或许可以做到无故障,但代码量会不断增长,失败的可能性也随之增加。如果没有办法表达失败,系统只会变得更加容易出错,也更加难以捉摸。
五、错误的可见性
考虑一下,如果程序员疏忽了,没有写错误处理代码,会发生什么情况?
如果返回的状态码没有经过检查,错误就不会被发现,代码将继续执行,就像操作成功一样。代码稍后可能会失败,但这可能是许多步操作之后的事情,你如何将问题追溯到最初错误发生的地方?
相反的,如果异常未被立刻捕获,它就会在调用栈中向上传递,要么到达更高的 catch 块,要么到达顶层,交给操作系统处理,操作系统通常会把错误呈现给用户。这对程序是不好的,但错误至少是可见的。你会看到异常,能够判断出它抛出的位置,以及它应该被捕获的位置,从而修复代码。
这里不讨论错误未能报出的情况,这种情况无论是返回状态码还是抛出异常,都没用。
所以,对于报出的错误没有被处理,可以归结为两种情况:一种是返回的状态码会隐藏问题,另一种是抛出异常会导致错误可见。你会选择哪一种?
六、反驳
著名程序员 Joel Spolsky 认为,返回状态码更好,因为他认为异常是一种糟糕得多的 goto 语句。
"异常在源代码中是不可见的。阅读代码块时,无法知道哪些异常可能被抛出,以及从哪里抛出。这意味着即使仔细检查代码,也无法发现潜在的错误。"
"异常为一个函数创建了太多可能的出口。要编写正确的代码,你必须考虑每一条可能的代码路径。每次调用一个可能抛出异常的函数,但没有立即捕获异常时,函数可能突然终止,或者出现其他你没有想到的代码路径。"
这些话听起来似乎很有道理,但如果改为返回状态码,你就必须显式地检查函数每一个可能的返回点。所以,你是用显式的复杂性换取了隐式的复杂性。这也有缺点,显式的复杂性会让你只见树木不见森林,代码会因此变得杂乱无章。
当面临这种显式复杂性时,程序员会写得不胜其烦,最后要么用自定义的方法隐藏错误处理,要么索性省略错误处理。
前者隐藏错误处理,只会将显式处理重新变为隐式处理,并且不如原始的 Try 方法方便和功能齐全。后者省略错误处理更糟糕,程序员假设某种错误不会发生,从而埋下风险。
七、总结
返回状态码很难用,有些地方根本无法使用。它会劫持返回值。程序员很容易不去写错误处理代码,从而在系统中造成无声的故障。
异常优于状态码。只要你的编程语言提供了异常处理工具,请使用它们。
(完)

AmaF 说:
异常的核心优势和核心问题都是控制流的不可预测,这在真正定位问题的时候会带来很多麻烦,生产环境中还是要谨慎使用。
2025年10月22日 09:39 | # | 引用
Jiro 说:
“显式的复杂性换取了隐式的复杂性”
我觉得显示错误处理谈不上复杂,繁琐而已(用了ai补全可能也谈不上了),更像是逼你在调用时想清楚,这会让你获得确定感(这可能也是c, go适用于底层infra的原因之一)
而异常推迟了这种行为,错误发生了你才知道
我觉得可以类比静态语言vs动态语言
2025年10月22日 09:58 | # | 引用
wwtw 说:
我选择用显式的复杂性换取隐式的复杂性。显式复杂性可以通过各种工程方法来有意识控制。对于支持多个返回值以及自定义 error 类型的语言来说这篇文章已经略显过时了。
2025年10月22日 10:01 | # | 引用
Louis 说:
很多人认识异常是通过Java,会形成以下想法:由于Java中异常是检查异常(Checked exception),稍复杂一点的方法可能抛出的异常种类太多,如果显式地逐个检查,复杂度比得上返回状态码的写法,形成很高的显式复杂性。
"当面临这种显式复杂性时,程序员会写得不胜其烦,最后要么用自定义的方法隐藏错误处理,要么索性省略错误处理。"
面对Java中的异常,实际上程序员往往也只会粒度地catch一下异常,隐藏大部分的信息,或者索性在一个集中的地方简单省略处理一下,此时文中提到的如"更丰富的错误信息"等特点也会被略去。
如果改成非检查异常(Unchecked exception),那么最后往往变成没有强制检查就省略错误处理。
更大的问题在于,在今天业界为了用快速开发、堆砌功能来抢占市场,在竞争压力下会使用先上线再修bug的"敏捷"做法,并形成一种普遍流行的方法论,牺牲工程质量来达到其它目标,类似的叫法有"小步快跑"、"fail fast, fail often"等,全面良好的错误处理等优秀工程实践往往放在优先级列表的后面,在这个背景下用异常还是返回状态码差异并不会太大。我将之概括为:如果用不好返回状态码,那么也往往用不好异常,反之亦然。
2025年10月22日 10:17 | # | 引用
lakki 说:
完全无法认同这个观点,代码简不简洁有时候没那么重要,清晰明了好理解易于接手才更重要,而当我接受一份 python 代码时我根本不知道哪里会抛出异常,会抛出哪些异常,需不需要特殊处理
但是如果返回状态码的话就非常简单明了,因为不处理的那些都是显示跳过的,如果连跳过都没有那你的代码编译器也不会通过
关于可见性和更丰富的错误信息的观点更是搞笑,异常竟然比状态码更有可见性吗?不知道作者脑子是不是被 java 之类的给腐蚀了,至于错误信息,这完全是人的因素更多的一条,和状态码还是异常关系根本不大
总结:
如果是使用异常处理错误,那就要接受你的服务可以抛出未处理或捕获的异常,但凡要服务严谨稳定一点的,状态码绝对比异常更好用
2025年10月22日 11:15 | # | 引用
xxbdh 说:
内德·巴切尔德的原文初衷比较奇怪,既然开篇提出“几乎所有人都认为异常是更好的处理方式”,那还有什么必要解释这件事情呢?
至于文中讨论的具体内容,表面上比较“抛出异常”和“返回状态码”两种代码编写上的错误处理方式,实则是比较“简洁编码”和“谨慎编码”两种开发习惯。
文中认为:“抛出异常”方式可以使代码简洁易读,既能简化处理逻辑,也能丰富消息内容,还很好的解决了可见性问题。
我的观点:对于经验丰富、谨慎认真的程序员,“抛出异常”方式更有助于从程序全局和多线程角度去思考编程逻辑,也不会造成重大的程序缺陷;而对于刚刚入门、马虎大意的程序员,“返回状态码”方式有助于督促提醒仔细核查每处调用的返回状态,养成良好的编程习惯,及时对开发中的程序进行“消缺”。
因为抛出异常是面向对象编程思想的概念,很多编程习惯和理念从“串行”转为“并行”,对很多初学者来说,需要花费很多的精力去额外关注各种当前代码之外其它多线并行过程,很容易遗忘对异常的捕捉和处理,导致程序的异常堆栈失去控制。
而返回状态码方式则是结构化编程的思路,因为状态码一般需在调用之后马上就检查,所以它提供了一种即时处理错误的暗示,这种“随用随处理”的机制,可以避免初级程序员去犯很多低级错误,代码审核也更加容易。
但如果已经形成了一个良好的编程习惯和系统的编程思维,那么采用“返回状态码”的方式也可以让程序显得简洁易读,通过整体的程序架构设计和代码构造,也可以规避很多缺陷。
2025年10月22日 11:32 | # | 引用
yono 说:
这个讨论当然应该是分应用场景的。
例如我,
在我编写C代码时更倾向于返回状态值,我编写确定的程序,类似硬件的软件,所有的错误情况我都需要知道,其中一部分可以通过重启一些组件以自恢复,无法自恢复的异常说明整个系统存在问题无法健康工作,那必须采取安全的保护措施。
在我编写js或者python时,显然整个系统我并不能把控,引入了过多可能不安全的库,我更倾向于抛出异常,减少心智压力是显而易见的好处。保证当他健康时能健康工作就好了,不健康也不会产生巨大的损失。
2025年10月22日 18:46 | # | 引用
Nansen Li 说:
哪种好究竟是何种标准?显式处理VS隐式处理?Golang vs Java?换言之,那些所谓常见的对立,哪种更好?外向vs内向?保守vs激进?实际上,答案却是没有答案,世界需要每一面,有些场景可能这个更好,而其他场景可能那个更好,无非是一些trade off罢了。
2025年10月22日 20:20 | # | 引用
Berry 说:
Go语言返回的error其实不止状态码,是个对象能表达多些信息,虽然我用的时候多数是字符串
2025年10月22日 21:48 | # | 引用
小PP 说:
现在AI的时代,错误值对AI更友好。
2025年10月22日 23:08 | # | 引用
王念一 说:
> 异常完全不同,它是类的实例,因此可以携带大量信息。
这是在同一个运行时下而言的,碰上跨运行时开发的场景就老实了。
所以大家都别吵了,HTTP 错误码加 JSON 错误信息的结构约定是对的。
管你啥玩意只要不是 200 就去读 JSON 吧。
2025年10月23日 06:20 | # | 引用
测试用户 说:
很有启发的文章,赞同异常优于状态码的观点
2025年10月23日 09:16 | # | 引用
岭南的荔枝 说:
有没有可能捕获异常,然后转换成状态码返回(狗头)
2025年10月23日 09:18 | # | 引用
杨晓龙 说:
我感觉代码干净完全是技术人的吹毛求疵,只要功能强大,其他都是浮云。
2025年10月23日 09:52 | # | 引用
refbuck 说:
web 开发的话,你需要有一个明确的错误信息给到用户,什么错误都是“系统错误”产品怕是不接受。
2025年10月23日 09:58 | # | 引用
GooseGoose 说:
Java中的checked exception和status code有什么区别? 我理解的checked exception也可以被当作成一种status code?
2025年10月23日 10:15 | # | 引用
GooseGoose 说:
我对error handling的理解主要是从 recoverable / non-recoverable 来区分的。
受这篇文章启发
https://speakerdeck.com/miguelgrinberg/error-handling-in-the-real-world
从这个角度来看,status code vs exceptions, 有这么大的区别吗?
2025年10月23日 10:48 | # | 引用
皮皮仔 说:
我是喜欢状态码的(目前)。
有个常见的对状态码的误解,就是“如果一个函数返回了异常,那么它的调用者、调用者的调用者、……都要返回异常”。导致“到处都是异常处理代码,淹没一般的业务逻辑”。
这是错的。其实,在收到异常码的第一时间,就处理掉,这是一种很好的(可能不是最好)异常处理方式。这强制调用者们,必须心里明确知道有哪些异常。明确之后,异常就不再是异常,全是 expected result。于是,一个函数收到 error 时,不必继续返回 error,它留下日志,返回“成功”,就可以了。
我是一个前端程序员,几乎只写 js/ts,两种异常处理都用。
大部分的异常,都靠状态码。
少量极端的、我暂时还没想好怎么处理的、很复杂的,我才会偷懒用 throw。因为 throw 之后,所有调用者的代码都不会被执行了,是一种“暂时摆烂”的状态。然后在最外层调用,记录一下日志。其实 error 到最外层,已经完全搞不清楚是哪里抛出了异常,因为“可能的原因”实在太多了。
2025年10月23日 11:08 | # | 引用
皮皮仔 说:
另外,异常处理,不仅仅是为了终端用户,我们根本不可能告诉用户真实的错误原因,往往全说是“网络异常”、“内部异常”、“未知错误”。
其实异常处理,最重要的让开发者快速定位异常。
2025年10月23日 11:13 | # | 引用
chaos 说:
高性能场景中,我有过压测,返回码性能更好。
2025年10月23日 13:36 | # | 引用
codeeror 说:
也许这个问题要分什么类型的语言,如果是plsql,在选择面对oracle抛出的奇奇怪怪的异常还是自己写一个返回状态码之间,我还是更倾向于自己返回状态码处理。
2025年10月23日 16:10 | # | 引用
xxyangyoulin 说:
异常更好。通常绝大部分逻辑都不能中途发生错误,发生错误则需要停止后续操作。异常让编写这样的代码时,程序员可以假装“无事发生”
2025年10月23日 17:29 | # | 引用
davix 说:
Go的觀點正好與此相反,而且返回的error可以包含任意內容,並非狀態碼。
2025年10月23日 20:05 | # | 引用
老鱼 说:
异常可以携带更多信息,尤其是出错时的调用堆栈(出错位置、当前函数帧的内容),这一点直接秒了状态码。根本没啥可以商量的。
值得讨论的是,使用什么样的语法来处理异常。像 Java 那样使用 checked exception 就会导致没有 IDE 根本写不下去。其实非常简单,只要由编译器纪录最终有没有处理异常就行了。如果一个函数不处理,那么它的调用者就必须处理,直到最终的 main() 必须处理异常。不处理就打印警告。其它的和 java, python 差不了多少。
这样,为了避免太多警告,程序员也不会滥用异常。
2025年10月24日 01:19 | # | 引用
x 说:
看着文章中的内容,我恰好得出了相反的结论。
2025年10月24日 09:32 | # | 引用
13太 说:
最近正好遇到了,思考父子调用时,异常信息的传递方式的烦恼,
> 如果遇到复杂的场景,状态码带来的噪音会更严重,异常则可以保持代码的整洁。
目前使用通用类Result来传递的状态,深有同感,
每次调用后都需要进行status检查,带来的噪音太影响主要业务逻辑的可读性了
2025年10月24日 10:06 | # | 引用
walker 说:
第一个例子, 写的时候简单了, 但是使用的地方却要来捕获, 如果一套系统, 全是用这种机率写的, 那么将是漫天的try-catch, 除非, 你们设计成只要有异常就应该暴露到最顶层, 中间层不允许处理, 那倒也省事
2025年10月24日 12:14 | # | 引用
xieer86 说:
可以编写完全没有错误处理逻辑的代码 -> 就是懒惰的coder最喜欢的excuse
2025年10月24日 16:18 | # | 引用
步丈九州 说:
我是写C/C++工业软件的。我倒是觉得状态码结合退出是一个好主意,而异常比较差。但是感觉对于在线服务啥的,或许异常更好一点。毕竟前者遇到重大错误应该停止运行,后者应该有一个兜底方案。
2025年10月25日 11:42 | # | 引用
03013106 说:
编码容易,运维困难
出来混早晚要还的
2025年10月25日 17:19 | # | 引用
FishCat233 说:
感觉都差不多,错误处理重点应该在 recoverable 和 non-recoverable 的区分而非具体形式上吧。
2025年10月26日 21:27 | # | 引用
红领巾 说:
Great minds think alike.
2025年10月27日 10:56 | # | 引用
20027952 说:
没有好坏之分,看场景吧
底层的、具体的错误用状态码,偶发的、未知的错误用catch
如一般的操作,不会关注磁盘满了、内存不够吧,但是会偶发的,需要用catch
2025年10月27日 14:20 | # | 引用
H. 说:
这个问题是异常发生后语言以及运行机制本身的处理能力决定的。底层代码抛出异常一点用都没有吧因为没有stack能帮你追溯问题根源,如果有的话,当然异常太好用了。文章点出开发人员头大容易迷惑的一个问题很重要,很多bug的根源本身就是语言的写法的问题,考虑周全的去处理所有可能发生的错误的状态码的结构糟透了。谁愿意用bash去写应用程序。
2025年10月27日 15:49 | # | 引用
danny 说:
如果考虑到一致性问题(比如数据库事务),用返回状态值的话可能需要十分严谨的设计和实现。
2025年10月28日 16:58 | # | 引用
WhoCare 说:
首先状态码和异常在功能上没有本质区别,只是【形式上的区别】,所以讨论的重点应该是【代码清晰度】和【引导使用者更好的做错误处理】。从这个角度看,应该两者混用而不是押注某一边。
考虑场景:
- 一个无状态修改的上下文(按 C++ 的说法,就是强异常安全),错误处理封装的比较好,那么异常机制更好
- 如果函数提供的功能对错误敏感,使用者应该做不同处理,那么状态码就更好
2025年10月30日 11:16 | # | 引用
caiyun 说:
结论脱离工程实践,那一点玩具代码就觉得噪音了,生产级别的项目里的错误处理怕不是要失明了。谈论代码实践脱离工程就是在空谈,为何错误作为值处理再次流行就是因为人们看到了异常隐式处理在工程上的弊端。
无论是异常还是基于类型系统的错误,良好的工程代码中都需要错误处理。不是说你用异常就可以随便抛出去不管了的,也不是说用错误值就必须都得判断每个错误。噪音可能是糟糕的语言语法设计带来的,而不是错误处理本身。
还有文章本身就是在讨论一个不完整的问题,因为返回错误码就已经是一种错误实践了,更优秀的做法是应该返回结构化数据。状态码的概念本身是低抽象层次语言下的产物,因为类型系统简单缺少必要的语言构造表达信息或是受限于性能或是系统足够简单,所以使用了错误码这种失真的数据。在类型系统健全的语言中,错误仅仅是一个类型,并不特殊,它可以简单,也可以复杂,可以利用特殊的语言构造上抛也可以编写逻辑进行判断处理,是足够自由的,怎么用取决于你的项目。
然后我回顾了源文章,我不知道这篇2003年的老文章来套用当前世界的大规模软件工程需求是何意味。
2025年10月31日 14:19 | # | 引用
caiyun 说:
补充一下,异常和结构化错误在语言中不等价。异常是一种特殊语言构造,它破坏了程序的控制流,虽然它能和结构化错误同样能表达丰富的信息,但是他们本质是不一样的。
2025年10月31日 14:24 | # | 引用
vision57 说:
如果没有异常机制,那么错误码检查代码会遍地拉屎,不堪忍受的开发者会用Goto手搓异常机制,而代码部署之后,进程级别panic还等着你。
2025年10月31日 17:59 | # | 引用
jefw 说:
自己写应用 想怎么搞怎么搞 反正有问题也是恶心自己
写库包返回状态码?锤爆????
2025年11月 3日 23:57 | # | 引用
weatune 说:
我一直使用go, 也认为返回值更好, 看了前面分析觉得你们说的挺有道理, 但是, 差点给你们骗了, 捕获异常很可能给攻击者留下一个异常攻击位置, 让程序跳出控制变成黑客的攻击点. 返回值不会脱离控制, 所有可能的错误都有返回值, 就不会有那么多 0日攻击了, 虽然捕获异常写起来省事多了, 不过那是对程序, 对客户的不负责任.
2025年11月 5日 23:58 | # | 引用
嗷嗷爱生活 说:
我写过java,也写过go,总结下来就是:如果大多数错误的处理方式都一样,用异常的优势就会非常明显,而java写web系统、业务系统,就属于这种情况。
2026年1月23日 17:43 | # | 引用
Copper 说:
异常能够携带具体的错误细节信息(虽然在工程中不一定设计成给出特别多的信息,但是异常毕竟是个类,能力是摆在那里的),而状态码除了1个数字之外什么都没有,这一点是秒的。
同时,返回错误码如果不检查不处理,那么程序就带病运行,相当于是默认值;而异常如果程序员写代码的时候不捕获,那么就会往更高一层抛,一直到最后运行时铺货把程序给停机,相当于默认程序出问题就停机了。这种也是异常秒杀。
异常如果想要程序“带病运行”,需要程序员显式的catch异常并且处理的时候写下“带病继续运行”的代码;当然,实际当中程序员反正已经catch异常进行处理了,工作量已经上去了,那么当然就尽量处理成不要“带病运行”,而是进行补救措施消除带病再继续运行,或者exit(FAILURE);了嘛。
2026年1月29日 11:33 | # | 引用