惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

月光博客
月光博客
有赞技术团队
有赞技术团队
S
SegmentFault 最新的问题
宝玉的分享
宝玉的分享
量子位
小众软件
小众软件
The Cloudflare Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
大猫的无限游戏
大猫的无限游戏
C
Check Point Blog
G
Google Developers Blog
博客园 - 叶小钗
H
Help Net Security
Jina AI
Jina AI
Y
Y Combinator Blog
Last Week in AI
Last Week in AI
GbyAI
GbyAI
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Apple Machine Learning Research
Apple Machine Learning Research
MyScale Blog
MyScale Blog
T
Tailwind CSS Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Vercel News
Vercel News

Jiajun的技术笔记

你好,2026! TiDB 源码阅读(六):TiDB Coprocessor 源码解析 性能优化的核心思想 TiDB 源码阅读(五):索引 TiDB 源码阅读(四):AST、逻辑计划、物理计划 CockroachDB Serverless Architecture podman 无故退出 Cursor Control-L (CTRL-L) Keyboard Shortcuts in Terminal Replace docker with podman Using xmonad with xfce4 A RC script for freebsd frpc 自己动手写一个k8s controller AI 会取代你的(编程)岗位吗? 自建DERP服务器提升Tailscale连接速度(使用Nginx转发) 自动升级Docker容器 再读《程序员修炼之道-从小工到专家》 让浏览器下载文件 再读《软件随想录》/《黑客与画家》/《软技能》 HTTP 压力测试中的 Coordinated Omission 2的补码 编程语言中的 context 是什么? flutter macOS 构建出错 Flatpak 使用小记 Golang CAS 操作是怎么实现的 PostgreSQL 当MQ来使用 Clash 结合 工作VPN 的网络设计 使用 PostgreSQL 搭建 JuiceFS PostgreSQL 配置优化和日志分析 有GitHub Copilot?那就可以搭建你的ChatGPT4服务 窗口函数的使用(以PG为例)
C, Go, Python的错误处理和异常机制杂谈
Jiajun Huang · 2019-08-01 · via Jiajun的技术笔记

很多语言都混用了错误和异常,本文中,我们对错误和异常进行定义:

  • 错误(error) 是不可恢复的,只能通过修复程序或者输入输出来修正的
  • 异常(exception)是可以恢复的,可以通过捕捉异常来重新让运行中的程序继续运行

但是,很多语言由于各种原因,混用了上述两个概念。

  • C语言中没有异常,所有的异常,都是通过错误来展示(errno),根据判断errno之后决定如何处理
  • Go语言与C一脉相承,虽然有异常(panic+recover),但是一般不会在业务代码中使用,而是使用错误(例如 io.EOF)
  • Python有错误和异常两个概念,但是一般错误只用于例如语法错误等这类,而异常则是代码中常用的处理方式

我们来看看三种语言是如何处理系统调用的错误的。

C语言

对于系统调用出错来说,是这样的流程(直接引用《FreeBSD设计与实现》上的原文):

Eventually, the system call returns to the calling process, either successfully or unsuccessfully. On the PC architecture, success or failure is returned as the carry bit in the user process’s program status longword: If it is zero, the return was successful; otherwise, it was unsuccessful. On many machines, return values of C functions are passed back through a general-purpose register (for the PC, data register EAX). The routines in the kernel that implement system calls return the values that are normally associated with the global variable errno. After a system call, the kernel system-call handler leaves this value in the register. If the system call failed, a C library routine moves that value into errno, and sets the return register to -1. The calling process is expected to notice the value of the return register, and then to examine errno. The mechanism involving the carry bit and the global variable errno exists for historical reasons derived from the PDP-11.

简单来说,就是系统调用返回时,会放一个错误码在寄存器上,进入到用户空间的代码之后(通常来说也就是glibc或者 其它C标准库里的系统调用包装代码里),会对错误码进行检查。如果是0,那么没有报错,否则,就根据错误码, 设置errno,然后这个系统调用的包装函数返回-1。

Go

对于Go来说,情况与C类似,不过是替换成Go内置的错误,例如 io.EOF

Python

Python的官方解释器是使用C写的,因此,错误返回时,已经能够获取errno来判断错误,而Python的系统调用错误异常便是 建立在这个基础之上,检查errno,如果有错误,那么则抛出对应的异常。

错误判断和抛出异常的区别

对于C和Go的开发者来说,每一次调用都要判断一下返回是否出错,这就是Go语言饱受诟病的 if err != nil {} 的来源。 这种方式需要逐层处理,例如如果想把错误抛到上一层,就得加一行 if err != nil {return err},而Python的异常 则可以自动跨越多个函数调用抛出。

错误日志和处理

什么时候应该记录日志?如果只使用Go标准库中的 errors 库,那么很麻烦的一个地方在于,errors 无法包含错误发生时 的信息,因此古老的方式是发生错误的地方打一行日志:

if err != nil {
    log.Errorf("blablabla, error blablabla")
    return err
}

这样层级多了之后就容易发生错误日志多次重复,非常容易被错误日志埋没。

我们可以参考Python中的处理方式:

if err:
    raise SomeException("exception context")

在异常中包含错误发生时的错误信息(或者说上下文),然后在上层统一进行处理。

当然,Go语言中,Go 2草案对此进行了改进,社区中也有Go 1.X 的实现:https://github.com/pkg/errors:

_, err := ioutil.ReadAll(r)
if err != nil {
        return errors.Wrap(err, "read failed")
}

参考资料: