让编译器成为你的副驾驶:告别“防御性编程”,拥抱“类型驱动开发”

本文永久链接 – https://tonybai.com/2026/01/04/stop-lying-to-the-compiler
大家好,我是Tony Bai。
“半夜被值班的运维同事叫醒,发现生产环境崩了,原因是一个深藏在业务逻辑里的 nil 指针异常。”
这个场景,对于每个后端开发者来说都是挥之不去的噩梦。事后复盘时,我们往往会懊恼:“为什么这里没加 if != nil 判断?”然后,我们在代码里撒上一把防御性检查的“盐”,祈祷下次好运。
但这真的是解决之道吗?
最近,Daniel Beskin 的一篇深度好文《The Compiler Is Your Best Friend, Stop Lying to It》(编译器是你最好的朋友,别再对它撒谎了),为我们提供了一个全新的视角:这些运行时崩溃,本质上是因为我们在编译时对编译器撒了谎。
我们告诉编译器“这是一个字符串”,但实际上它可能是 nil;我们告诉编译器“这个函数返回一个整数”,但实际上它可能抛出一个 panic。当我们停止撒谎,开始用类型系统表达真实意图时,编译器将从一个“报错机器”,变成我们最强大的“安全副驾驶”。

我们对编译器撒过的“谎”
在 Go 语言的日常开发中,我们常常为了“方便”而向编译器撒谎,埋下了日后爆炸的地雷。
谎言一:隐形的 nil
当我们定义 func Process(u *User) 时,我们告诉编译器:“给我一个 User,我处理它。” 但在 Go 中,指针可以是 nil。
* 谎言:我承诺会处理一个 User。
* 真相:我可能会收到一个 nil,然后炸掉。
* 后果:为了弥补这个谎言,我们需要在函数内部写无数的 if u == nil 防御性代码。一旦遗漏,就是生产事故。
谎言二:盲目的类型断言与 any
当我们使用 interface{} (或 any) 时,我们实际上是在对编译器说:“别管这个,我知道我在做什么。”
* 谎言:这个 any 类型的变量,其实是一个 int。
* 真相:它可能是一个 string,或者 nil。
* 后果:运行时的 panic: interface conversion: interface {} is string, not int。
谎言三:隐藏的副作用与 Panic
当我们看到一个函数签名 func Parse(s string) int 时,编译器认为它是一个将字符串映射为整数的函数。
* 谎言:这是一个纯粹的转换函数。
* 真相:如果字符串格式不对,我会直接 panic,中断整个 goroutine。
* 后果:调用者无法通过函数签名预知风险,导致程序在边缘情况下意外崩溃。
停止撒谎,开启“对话”
如何重建与编译器的信任关系?答案是:将运行时的检查,提前到编译时的类型定义中。
策略一:让非法状态无法表示
这是消除 nil 和无效数据的终极心法。
- 场景:一个配置项 Port,如果是 0 表示随机端口,如果是正数表示指定端口。
- 糟糕的设计:Port int。你必须在代码各处检查 Port < 0 的情况,并且含义模糊。
-
诚实的设计:
type Port int // 使用构造函数来保证 Port 的合法性 func NewPort(p int) (Port, error) { if p < 0 || p > 65535 { return 0, fmt.Errorf("invalid port") } return Port(p), nil }一旦你通过 NewPort 拥有了一个 Port 类型的值,编译器就为你担保:它一定是一个合法的端口号。你后续不再需要防御性检查(未通过NewPort获得的除外)。
策略二:用类型区分概念
- 场景:用户 ID 和 订单 ID 都是 int64。
- 糟糕的设计:func GetOrder(userID, orderID int64)。调用者很容易把两个 ID 传反,而编译器毫无察觉。
-
诚实的设计:
type UserID int64 type OrderID int64 func GetOrder(uid UserID, oid OrderID) { ... }现在,如果你试图把 UserID 传给 OrderID,编译器会直接报错。这不是繁琐,这是编译器在帮你 Review 代码。
策略三:显式的可空性
虽然 Go 没有 Rust 的 Option
- 场景:更新用户信息,只更新非空字段。
- 诚实的设计:
go
type UpdateUserRequest struct {
Name *string // nil 表示不更新,非 nil 表示更新为新值
Age *int
}
这里,指针不再是“可能导致崩溃的引用”,而是“可选值”的显式类型标记。这让代码的意图对编译器和人类都一目了然。
编译器是你的朋友,不是敌人
很多时候,我们觉得编译器很烦人:它阻止我们快速写出“能跑”的代码,强迫我们处理每一个 err,纠结于类型转换。
但 Daniel Beskin 提醒我们:编译器是你唯一一个会不厌其烦地帮你检查每一个细节、永远不会疲倦、永远不会因为“差不多就行”而放过 Bug 的队友。
当你觉得编译器在“阻碍”你时,停下来想一想:是不是我在试图对它撒谎?
- 如果类型不匹配,是不是我的数据模型设计得不够清晰?
- 如果错误处理太繁琐,是不是因为我试图把不确定的状态传递得太远?
小结:睡个好觉的秘诀
“防御性编程”是一种补救措施,它假设代码是脆弱的。而“类型驱动开发”是一种预防措施,它利用编译器构建坚固的堡垒。
当我们开始尊重类型,停止用 any 和隐式约定来糊弄编译器时,我们获得的回报是巨大的:
- 重构时的自信:修改一个类型,编译器会告诉你所有需要调整的地方。
- 更少的测试:你不需要测试“端口号是否为负数”,因为类型系统保证了它不可能为负。
- 更安稳的睡眠:因为你知道,那些导致半夜崩溃的低级错误,早在你按下 go build 的那一刻,就被忠诚的编译器拦截在了门外。
资料链接:https://blog.daniel-beskin.com/2025-12-22-the-compiler-is-your-best-friend-stop-lying-to-it
你的“撒谎”时刻
读完这篇文章,你是否也意识到了自己曾在代码中对编译器撒过的“谎”?在你的项目中,有哪些因为类型定义不清而导致的“血案”?或者,你有哪些利用类型系统来规避 Bug 的独门绝技?
欢迎在评论区分享你的反思与心得! 让我们一起学会“诚实”编程,睡个好觉。
如果这篇文章颠覆了你对编译器的认知,别忘了点个【赞】和【在看】,并转发给你的团队,一起提升代码的“诚实度”!
还在为“复制粘贴喂AI”而烦恼?我的新专栏 《AI原生开发工作流实战》 将带你:
- 告别低效,重塑开发范式
- 驾驭AI Agent(Claude Code),实现工作流自动化
- 从“AI使用者”进化为规范驱动开发的“工作流指挥家”
扫描下方二维码,开启你的AI原生开发之旅。

你的Go技能,是否也卡在了“熟练”到“精通”的瓶颈期?
- 想写出更地道、更健壮的Go代码,却总在细节上踩坑?
- 渴望提升软件设计能力,驾驭复杂Go项目却缺乏章法?
- 想打造生产级的Go服务,却在工程化实践中屡屡受挫?
继《Go语言第一课》后,我的《Go语言进阶课》终于在极客时间与大家见面了!
我的全新极客时间专栏 《Tony Bai·Go语言进阶课》就是为这样的你量身打造!30+讲硬核内容,带你夯实语法认知,提升设计思维,锻造工程实践能力,更有实战项目串讲。
目标只有一个:助你完成从“Go熟练工”到“Go专家”的蜕变! 现在就加入,让你的Go技能再上一个新台阶!

商务合作方式:撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求,请扫描下方公众号二维码,与我私信联系。

© 2026, bigwhite. 版权所有.
Related posts:
评论