“避免不必要的抽象”这句话,在 Go 圈子里几乎人人都听过。
当你试图引入 ORM、泛型 Map/Reduce、接口或者复杂的设计模式时,往往会收到类似的提醒。这句话本身并没有错,难点在于:到底什么是“不必要”的?
函数是抽象吗?汇编是抽象吗?如果不加定义地“避免抽象”,我们最终只能对着硅片大喊大叫。
在 GopherCon UK 2025 上,John Cinnamond 做了一场与众不同的演讲。他没有展示任何炫酷的并发模式,而是搬出了马丁·海德格尔(Martin Heidegger)和伊曼努尔·康德(Immanuel Kant),试图用哲学的视角,为我们解开关于 Go 抽象的困惑。
注:海德格尔与《存在与时间》
马丁·海德格尔(Martin Heidegger)是 20 世纪最重要的哲学家之一。他在 1927 年的巨著《存在与时间》(Being and Time) 中,深入探讨了人(此在)如何与世界互动。John Cinnamond 在演讲中引用的核心概念——“上手状态” (Ready-to-hand) 和 “在手状态” (Present-at-hand),正是海德格尔用来描述我们与工具(如锤子)之间关系的术语。
这套理论很适合解释:为什么优秀的工具(或代码抽象)应该是“透明”的,而糟糕的工具会强行占据我们的注意力。
我们都在使用的“必要”抽象
首先承认一个事实:编程本身就建立在无数层抽象之上。
- 泛型:这是对类型的抽象。虽然 Go 曾长期拒绝它,但在技术上它常常是必要的,否则重复代码会泛滥。
- 接口:这是对行为的抽象。
io.Reader 让我们不必关心数据来自文件还是网络。
- 函数:这是对指令序列的抽象。没有它,我们只能写长长的
main。
- 汇编语言:这是对机器码的抽象。
所以,当我们说“避免不必要的抽象”时,更贴近事实的表达其实是——避免“不恰当” (Inappropriate) 的抽象。
那问题来了:如何判断一个抽象是否“恰当”?

何为抽象?——一场有目的的“细节隐藏”
在讨论“正确”的抽象之前,先回到基本定义。John Cinnamond 在演讲中给出一个精炼的说法:
“抽象是一种表示 (Representation),但它是一种刻意移除被表示事物某些细节的表示。”

把这个定义拆开看,会更清楚:
-
抽象是一种“表示”,而非事物本身
它不是代码的实体,而是代码的地图或模型。比如,一辆模型汽车是真实汽车的表示;而 Gopher 吉祥物是地鼠的抽象——它刻意省略了真实地鼠的所有细节,只保留了核心特征。

-
抽象是“有目的的”细节移除
它不是“不精确”或“粗糙”的同义词。抽象是有意为之的:它不试图覆盖所有方面,而是只关注某个特定维度。

-
抽象在编程中具有动态性
- 不确定引用 (Indefinite Reference):一个抽象(如
io.Reader)通常可以指代许多不同的具体实现。
- 开放引用 (Open Reference):抽象的内容或它所指代的事物可以随着时间而改变。
为什么要刻意移除细节?John 总结了几个常见动机:
- 避免重复代码:把重复逻辑提取到抽象中。
- 统一不同的实现:允许以统一方式处理本质上不同的数据结构(例如所有实现了
Read 方法的类型)。
- 推迟细节:隐藏那些当下不重要、或开发者不关心的细节(例如你坐火车参会,不需要知道每节车厢的编号)。
- 揭示领域概念:用抽象表达业务领域中的核心概念。
- 驾驭复杂性:最根本的理由——没有抽象,我们的大脑无法一次性处理所有细节,也就很难解决复杂问题。
但请注意:抽象并不等价于“越多越好”。 John 把抽象分为三类(这对你判断抽象质量非常关键):
-
基于“它是如何工作的” (How it works)
为了复用实现而提取的抽象。比如你发现两处代码都在做“检查用户是否是管理员”,就把它提取成函数。
这类抽象通常更脆弱:实现细节一变,抽象可能就失效。
-
基于“它做了什么” (What it does)
Go 里接口(Interface)最典型的用法。比如 io.Reader:不关心它是文件还是网络,只关心它能“读取字节”。这是一种行为抽象。
-
基于“它是什么” (What it is)
基于领域模型的抽象。比如一个 User 结构体代表系统中的实体,强调本质属性。
现实中,好的抽象往往是这三者的混合体。但在设计阶段,你至少得先想明白:你抽象的是“行为”,还是“实现”?搞反了,后续维护往往会很痛。

理解抽象的本质后,你可能会想:既然抽象能驾驭复杂性,那是不是抽象越多越好?
先别急着下结论。评判抽象是否“恰当”,不能只盯着代码本身,还有一个经常被忽略的现实:抽象不仅存在于代码里,也存在于人与人的协作里。
抽象的代价——代码是写给人看的
John 提醒我们:软件开发本质上是一项社会活动 (Social Activity)。
“除非你是为了自己写着玩,否则你的代码总是写给别人看的。团队是一个微型社会,它有自己的习俗、信仰和‘传说’(Lore)。”
引入一个新的抽象,等价于给这个“微型社会”引入一种新规则。它带来的代价至少包括:
-
社会成本(Social Cost)
如果抽象与团队现有习惯相悖——比如在一个从未用过函数式编程的 Go 团队里强推 Monad——阻力往往会非常大。
-
团队的保守性
成熟团队往往趋于保守,改变既定习惯需要能量。你不能只因为某个抽象在理论上很美就引入它,你得证明:收益足以覆盖它带来的社会摩擦成本。
-
认知负担是共享的
抽象对你来说可能很清晰,但如果它让队友困惑,那就是在消耗整个团队的智力预算。

因此,判断抽象是否“恰当”,不能只问“我能不能写出来”,还得问:它是否合群? 这也是把海德格尔哲学引入工程讨论的现实基础。
锤子哲学——“上手状态” vs “在手状态”
John 引用了海德格尔在《存在与时间》中的概念:Ready-to-hand(上手状态) 与 Present-at-hand(在手状态)。
- 上手状态 (Ready-to-hand):当你熟练使用锤子钉钉子时,注意力在“钉钉子”这件事上。锤子本身在意识中是透明的,它像身体的延伸。
- 在手状态 (Present-at-hand):当锤子坏了(锤头掉了),或你拿到一把陌生、设计古怪的工具时,注意力被迫从“钉钉子”转移到“锤子本身”。你开始研究它的结构、重量和用法。
这对代码意味着什么?
如果一个抽象让你频繁从“解决业务问题”切换到“研究工具本身”,它很可能就是一个坏抽象。

注:通过学习和实践,在手状态 (Present-at-hand) 的抽象可以转变为上手状态 (Ready-to-hand) 的抽象。
真理的检验——“本质真理” vs “巧合真理”
接着,John 又借用了康德关于真理的分类,引导我们思考抽象的持久性:
- 分析真理 (Analytic Truth):由定义决定的真理。比如“所有单身汉都没结婚”。在代码里类似
unnecessary abstractions are unnecessary,正确但没太大信息量。
- 综合真理 (Synthetic Truth):由外部事实决定的真理。比如“外面在下雨”,真假取决于环境。
- 本质真理 (Essential Truth):不由定义决定,但反映世界更稳定的结构规律。比如“物质由原子构成”。
这对抽象意味着什么?
当你提取一个抽象时,问问自己:它代表的是代码的“本质真理”,还是仅仅是一个“巧合”?
举个例子:你有一段过滤商品的代码,既要按“价格”过滤,也要按“库存”过滤。你提取了一个抽象:Filter(Product) bool。
- 如果未来过滤需求(颜色、大小等)都能用这个签名解决,你就捕捉到了一个本质真理,这个抽象会更稳固。
- 但如果新需求是“过滤掉重复商品”,这需要知道所有商品的状态,而不仅仅是单个商品。原本的
Filter(Product) bool 签名就会瞬间失效。
如果你提取抽象只是因为几段代码“长得像”(巧合),而不是因为它们“本质上是一回事”,那么需求一变,这个抽象就会崩塌,反而变成负担。
从这个角度看,好的抽象不是被“创造”出来的,而是被“发现”(Recognized)出来的——它是对问题结构的识别与捕捉。
实战指南——如何引入抽象?
最后,John 给出了一个评估抽象是否“恰当”的五步清单(也很适合当作团队 code review 的共同语言):
- 明确收益 (Benefit):你到底是为了解决重复、隐藏细节,还是仅仅因为觉得它“很酷”?
- 考虑社会成本 (Social Cost):编程是社会活动。这个抽象符合团队的习惯吗?引入它会不会消耗大量团队认知成本?(例如在 Go 里强推 Monad 等函数式范式)
- 是否处于“上手状态” (Ready-to-hand):它能融入开发者直觉吗?还是会成为注意力的绊脚石?
- 是否本质 (Essential):它是否捕捉到了问题的核心结构,能经得起未来变化?
- 是否涌现 (Emergent):它是从现有代码中“识别”出来的模式,还是你强加给代码的枷锁?

小结:保持怀疑,但别放弃好奇
Go 社区“避免不必要的抽象”的文化,本质上是在防御认知负担。工程实践里,为了抽象而抽象的代码确实太常见了;而这些“看起来很优雅”的东西,往往会把团队拖进维护泥潭。
但也别走到另一个极端:把抽象当洪水猛兽。正确且必要的抽象依然是强大的武器,它让我们能够驾驭规模、复杂度与协作成本。
如果我们能像海德格尔审视锤子那样审视自己的代码:区分“上手”与“在手”,区分“本质”与“巧合”,就更有机会在 后端架构 的真实约束中,找到适合自己团队的“恰当抽象”。

资料链接: https://www.youtube.com/watch?v=oP_-eHZSaqc
延伸阅读与讨论:你也可以到 云栈社区 继续交流,看看大家在项目里是如何权衡抽象与复杂度的。