Logo

site iconTonyBai | 白明

重复
请复制 RSS 到你的阅读器,或快速订阅到 :

Inoreader Feedly Follow Feedbin Local Reader

TonyBai | 白明 RSS 预览

为什么 Go 社区强调避免不必要的抽象?—— 借用海德格尔哲学寻找“正确”的答案

2026-01-16 08:04:27

本文永久链接 – https://tonybai.com/2026/01/16/go-community-the-right-kind-of-abstraction

大家好,我是Tony Bai。

“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),正是海德格尔用来描述我们与工具(如锤子)之间关系的术语。这套理论极好地解释了为什么优秀的工具(或代码抽象)应该是“透明”的,而糟糕的工具则会强行占据我们的注意力。

img{512x368}

我们都在使用的“必要”抽象

首先,让我们承认一个事实:编程本身就是建立在无数层抽象之上的。

  • 泛型:这是对类型的抽象。虽然 Go 曾长期拒绝它,但在技术上它是必要的,否则我们将充斥着重复代码。
  • 接口:这是对行为的抽象。io.Reader 让我们不必关心数据来自文件还是网络。
  • 函数:这是对指令序列的抽象。没有它,我们只能写长长的 main 函数。
  • 汇编语言:这是对机器码的抽象。

所以,当我们说“避免不必要的抽象”时,我们真正想表达的其实是——避免“不恰当” (Inappropriate) 的抽象

那么,如何判断一个抽象是否“恰当”?

何为抽象?—— 一场有目的的“细节隐藏”

在深入探讨“正确”的抽象之前,我们必须先回到最基本的定义。John Cinnamond 在演讲中给出了一个精炼而深刻的定义:

“抽象是一种表示 (Representation),但它是一种刻意移除被表示事物某些细节的表示。”

让我们拆解这个定义:

  1. 抽象是一种“表示”,而非事物本身
    它不是代码的实体,而是代码的地图或模型。例如,一辆模型汽车是真实汽车的表示,但 Gopher 吉祥物是地鼠的抽象——它刻意省略了真实地鼠的所有细节,只保留了核心特征。

  1. 抽象是“有目的的”细节移除
    这与仅仅是“不精确”或“粗糙”不同。抽象是有意为之的,它不试图精确描绘所有方面,而是只关注某个特定维度

  1. 抽象在编程中具有动态性
    • 不确定引用 (Indefinite Reference):一个抽象(如 io.Reader)通常可以指代许多不同的具体实现。
    • 开放引用 (Open Reference):抽象的内容或它所指代的事物可以随着时间而改变。

为什么要刻意移除细节?John 总结了几个核心动机:

  • 避免重复代码:将重复的逻辑提取到抽象中。
  • 统一不同的实现:允许以统一的方式处理本质上不同的数据结构(如所有实现了 Read 方法的类型)。
  • 推迟细节:隐藏那些当下不重要、或开发者不关心的细节(例如,你坐火车参会,不需要知道每节车厢的编号)。
  • 揭示领域概念:用抽象来更好地表达业务领域中的核心概念。
  • 驾驭复杂性:这是最核心的理由——没有抽象,我们无法在大脑中一次性处理所有细节,也就无法解决复杂的问题。

但请记住,并非所有抽象都是一样的。John 将它们分为三类:

  1. 基于“它是如何工作的” (How it works)
    这是为了代码复用而提取的抽象。例如,你发现两处代码都在做“检查用户是否是管理员”的逻辑,于是将其提取为一个函数。这种抽象关注的是内部机制。 (这类抽象通常比较脆弱,一旦实现细节变化,抽象可能就会失效。)

  2. 基于“它做了什么” (What it does)
    这是 Go 语言中接口(Interface)最典型的用法。例如 io.Reader,我们不关心它是文件还是网络连接,我们只关心它能“读取字节”。这是一种行为抽象。

  3. 基于“它是什么” (What it is)
    这是基于领域模型的抽象。例如一个 User 结构体,它代表了系统中的一个实体。这种抽象关注的是本质属性。

在现实中,好的抽象往往是这三者的混合体,但在设计时,明确你是在抽象“行为”还是“实现”,对于判断抽象的质量至关重要。

理解了抽象的本质,我们可能会觉得:既然抽象能驾驭复杂性,那是不是越多越好?

且慢。在急于评判一个抽象是否“恰当”之前,我们必须先意识到一个常被技术人员忽略的现实:抽象不仅存在于代码中,更存在于人与人的互动里。 这将我们引向了一个更现实的考量维度。

抽象的代价 —— 代码是写给人看的

John 提醒我们,软件开发本质上是一项社会活动 (Social Activity)

“除非你是为了自己写着玩,否则你的代码总是写给别人看的。团队是一个微型社会,它有自己的习俗、信仰和‘传说’(Lore)。”

引入一个新的抽象,本质上是在向这个微型社会引入一种新的文化或规则。这意味着:

  1. 你需要支付“社会成本”:如果这个抽象与团队现有的习惯(Lore)相悖——比如在一个从未用过函数式编程的 Go 团队里强推 Monad——你将遭遇巨大的阻力。
  2. 团队的保守性:成熟的团队往往趋于保守,改变既定习惯需要巨大的能量。你不能仅仅因为一个抽象在理论上很美就引入它,你必须证明它的收益足以覆盖它带来的社会摩擦成本
  3. 认知负担是共享的:一个抽象对你来说可能很清晰,但如果它让队友感到困惑,那就是在消耗团队的整体智力资源。

因此,当我们评判一个抽象是否“恰当”时,不能只看代码本身,还必须看它是否“合群”。这正是我们接下来要引入海德格尔哲学的现实基础。

锤子哲学 —— “上手状态” vs. “在手状态”

John 引用了海德格尔在《存在与时间》中的一个著名概念:Ready-to-hand (上手状态)Present-at-hand (在手状态)

  • 上手状态 (Ready-to-hand):当你熟练使用一把锤子钉钉子时,你的注意力完全在钉钉子这件事上,锤子本身在你意识中是“透明”的。你感觉不到它的存在,它只是你身体的延伸。
  • 在手状态 (Present-at-hand):当锤子突然坏了(比如锤头掉了),或者你拿到一把设计奇特的陌生工具时,你的注意力被迫从“钉钉子”转移到了“锤子”本身。你开始审视它的构造、重量和用法。

这对代码意味着什么?

  • 好的抽象是“上手状态”的:比如 for 循环。作为经验丰富的开发者,你使用它时是在思考“我要遍历数据”,而不是“这个循环语法是怎么编译的”。它透明、顺手,让你专注于解决问题。

  • 坏的抽象是“在手状态”的:比如一个复杂的、过度设计的 ORM 或者一个晦涩的 Monad 库。当你使用它时,你的思维被迫中断,你需要停下来思考:“这个函数到底在干什么?这个参数是什么意思?”

如果一个抽象让你频繁地从“解决业务问题”中抽离出来去思考“工具本身”,那么它很可能是一个坏的抽象

注:通过学习和实践,在手状态 (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 给出了一个评估抽象是否“恰当”的五步清单:

  1. 明确收益 (Benefit):你到底是为了解决重复、隐藏细节,还是仅仅因为觉得它“很酷”?
  2. 考虑社会成本 (Social Cost):编程是社会活动。这个抽象符合团队的习惯吗?引入它是否需要消耗大量的团队认知成本?(比如在 Go 里强推 Monad等函数式编程的范式)。
  3. 是否处于“上手状态” (Ready-to-hand):它能融入开发者的直觉吗?还是会成为注意力的绊脚石?
  4. 是否本质 (Essential):它是否捕捉到了问题的核心结构,能经得起未来的变化?
  5. 是否涌现 (Emergent):它是你从现有代码中“识别”出来的模式,还是你强加给代码的枷锁?

小结:保持怀疑,但别放弃好奇

Go 社区的“避免不必要的抽象”文化,本质上是对认知负担的防御。我们见过太多为了抽象而抽象的烂代码。但 John 提醒我们,不要因此走向另一个极端——恐惧抽象

正确且必要的抽象是强大的武器,它能让我们驾驭巨大的复杂性。只要我们能像海德格尔审视锤子那样审视我们的代码,区分“上手”与“在手”,区分“本质”与“巧合”,我们就能在 Go 的简约哲学中,找到属于自己的那条“正确”道路。

资料链接:https://www.youtube.com/watch?v=oP_-eHZSaqc


你的“锤子”顺手吗?

用海德格尔的视角审视代码,确实别有一番风味。在你现在的项目中,有哪些抽象是让你感觉“如臂使指”的(上手状态)?又有哪些抽象经常让你
“出戏”,迫使你不得不去研究它内部的构造(在手状态)?

欢迎在评论区分享你的“哲学思考”! 让我们一起寻找那个最本质的代码真理。

如果这篇文章带给你一次思维的“脑暴”,别忘了点个【赞】和【在看】,并转发给那些喜欢深究技术的伙伴!


还在为“复制粘贴喂AI”而烦恼?我的新专栏 AI原生开发工作流实战 将带你:

  • 告别低效,重塑开发范式
  • 驾驭AI Agent(Claude Code),实现工作流自动化
  • 从“AI使用者”进化为规范驱动开发的“工作流指挥家”

扫描下方二维码,开启你的AI原生开发之旅。


你的Go技能,是否也卡在了“熟练”到“精通”的瓶颈期?

  • 想写出更地道、更健壮的Go代码,却总在细节上踩坑?
  • 渴望提升软件设计能力,驾驭复杂Go项目却缺乏章法?
  • 想打造生产级的Go服务,却在工程化实践中屡屡受挫?

继《Go语言第一课》后,我的《Go语言进阶课》终于在极客时间与大家见面了!

我的全新极客时间专栏 《Tony Bai·Go语言进阶课》就是为这样的你量身打造!30+讲硬核内容,带你夯实语法认知,提升设计思维,锻造工程实践能力,更有实战项目串讲。

目标只有一个:助你完成从“Go熟练工”到“Go专家”的蜕变! 现在就加入,让你的Go技能再上一个新台阶!


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

© 2026, bigwhite. 版权所有.

内存去哪儿了?一个让大多数 Gopher 都无法清晰回答的问题

2026-01-15 08:21:39

本文永久链接 – https://tonybai.com/2026/01/15/where-did-the-memory-go-gopher-unanswered-question

大家好,我是Tony Bai。

“我的服务内存又在缓慢增长了,pprof 显示不出明显的泄漏点……内存到底去哪儿了?

这句午夜梦回的拷问,或许是许多 Go 开发者心中最深的恐惧。

这一切的根源,可能始于一个你自以为早已掌握的基础问题:“Go 的状态 (state) 存在哪里?” Go 开发者 Abhishek Singh之前断言:“我保证,一大半的 Go 开发者都无法清晰地回答这个问题。”

你的答案是什么?“在 goroutine 里”?“在栈上”?“由 Go runtime 管理”?

如果你的脑中闪过的是这些模糊的念头,那么你可能就找到了“内存失踪案”的“第一案发现场”。这个看似不起眼的认知模糊,正是导致无数生产环境中“内存缓慢泄露”、“goroutine 永不消亡”、“随机延迟飙升”等“灵异事件”的根源。

本文,将为你揭示这个问题的精确答案,并以此为起点,修复你关于 Go 内存管理的“心智模型”,让你从此能够清晰地回答:“内存,到底去哪儿了?”

揭晓答案与核心心智模型

首先,那个简单而重要的正确答案是:

Go 的状态,就是由 Go runtime 管理的内存,它要么在栈 (stack) 上,要么在堆 (heap) 上。

然而,知道这个答案只是第一步。真正关键的,是摒弃那个导致所有问题的错误直觉,转而建立如下正确的核心心智模型

Goroutine 不拥有内存,引用 (References) 才拥有。
一个 Goroutine 的退出,并不会释放内存。

当一个 goroutine 结束时,它仅仅是停止了执行。它所创建或引用的任何内存,只要仍然被其他东西持有着引用,就永远不会被垃圾回收器 (GC) 回收。

这些“其他东西”,就是你程序中的“内存锚点”,它们包括:

  • 一个全局变量
  • 一个 channel
  • 一个闭包
  • 一个 map
  • 一个被互斥锁保护的结构体
  • 一个未被取消的 context

这,就是几乎所有“Go 内存泄漏”的根本原因。 “内存去哪儿了?”——它被这些看不见的“锚点”,牢牢地拴在了堆上。

三大“内存锚点”——Goroutine 泄漏的元凶

Abhishek 将那些导致内存无法被回收的“引用持有者”,形象地称为“内存锚点”。其中,最常见、也最隐蔽的有三种。

“永生”的 Goroutine:被遗忘的循环

创建 goroutine 很廉价,但泄漏它们却极其昂贵。一个典型的“生命周期 Bug”:

// 经典错误:启动一个运行无限循环的 goroutine
go func() {
    for {
        work() // 假设 work() 会引用一些数据
    }
}()

这个 goroutine 永远不会退出。它会永久地持有 work() 函数所引用的任何数据,阻止 GC 回收它们。如果你在每个 HTTP 请求中都启动一个这样的“即发即忘”(fire-and-forget) 的 goroutine,你的服务内存将会线性增长,直至崩溃。

这不是内存泄漏,是你设计了一个“不朽的工作负载”。

Channel:不止传递数据,更持有引用

Channel 不仅仅是数据的搬运工,它们更是强力的引用持有者

ch := make(chan *BigStruct)
go func() {
    // 这个 goroutine 阻塞在这里,等待向 channel 发送数据
    ch <- &BigStruct{...}
}()

// 如果没有其他 goroutine 从 ch 中接收数据...

那么:

  • 那个 &BigStruct{…} 将永久地被 ch 持有。
  • 那个发送数据的 goroutine 将永久地阻塞。
  • GC 永远无法回收 BigStruct 和这个 goroutine 的栈。

这告诉我们:无缓冲或未被消费的 Channel,是缓慢的死亡。 它们会像“锚”一样,将数据和 goroutine 牢牢地钉在内存中。

context:被忽视的生命周期边界

context 包是 Go 中定义生命周期边界的“标准语言”。然而,一个常见的错误是,启动一个 goroutine 时,向其传递了一个永远不会被取消的 context。

错误模式

// 传递一个 background context,等于没有传递任何“停止信号”
go doWork(context.Background())

这个 doWork goroutine,一旦启动,就没有任何机制可以通知它停止。如果它内部是一个 for-select 循环,它就会永远运行下去。

正确的模式

// 从父 context 创建一个可取消的 context
ctx, cancel := context.WithCancel(parentCtx)
// 确保在函数退出时,无论如何都会调用 cancel
defer cancel() 

go doWork(ctx)

没有 cancel,就没有清理 (No cancel -> no cleanup)。context 不会“魔法般地”自己取消。

“不是 Bug,是生命周期”——如何诊断与思考

Abhishek 强调,我们习惯于称之为“泄漏”的许多问题,实际上并非 Go 语言的 Bug,而是我们自己设计的“生命周期 Bug”

诊断“三板斧”

  1. pprof (无可争议):这是你的第一、也是最重要的工具。通过 import _ “net/http/pprof” 引入它,并重点关注:

    • 堆内存增长 (heap profile)
    • 内存分配热点 (allocs profile)
    • goroutine 数量随时间的变化
  2. Goroutine Dumps: 通过 curl http://localhost:6060/debug/pprof/goroutine?debug=2 获取所有 goroutine 的详细堆栈信息。如果 goroutine 的数量只增不减,你就找到了泄漏的“犯罪现场”。

  3. 灵魂三问 (The Ownership Question):在审查任何一段持有状态的代码时,问自己三个问题:

    • 谁拥有这段内存?(Who owns this memory?)
    • 它应该在什么时候消亡?(When should it die?)
    • 是什么引用,让它得以存活?(What reference keeps it alive?)

那些我们不愿承认的“泄漏”

  • 即发即忘的 goroutine
  • 没有消费者的 channel
  • 永不取消的 context
  • 用作缓存却没有淘汰策略的 map
  • 捕获了巨大对象的闭包
  • 为每个请求启动的、永不退出的后台 worker

真正的教训 —— Go 奖励那些思考“责任”的工程师

Go 并没有隐藏内存,它暴露了责任。
GC 无法修复糟糕的所有权设计。

这是本篇最核心、也最深刻的结论。Go 的垃圾回收器,为你解决了“何时 free”的机械问题,但它将一个更高级、也更重要的责任,交还给了你——设计清晰的“所有权”和“生命周期”

Goroutine 不会自动清理自己,Channel 不会自动排空自己,Context 不会自动取消自己。这些都不是语言的缺陷,而是其设计哲学的体现。

Go 奖励那些能够思考以下问题的工程师:

  • 生命周期 (Lifetimes):这个 goroutine 应该在什么时候开始,什么时候结束?
  • 所有权 (Ownership):这份数据由谁创建,由谁负责,最终应该由谁来释放对其的最后一个引用?
  • 反压 (Backpressure):当消费者处理不过来时,生产者是否应该被阻塞?我的 channel 是否应该有界?

你不需要成为一名 Go 运行时专家,你只需要开始用“生命周期”的视角,去设计你的并发程序,并偶尔用 pprof 来验证你的设计。

这,就是修复 Go 内存问题“心智模型”的终极之道。

资料链接:https://x.com/0xlelouch_/status/2000485400884785320


你的“捉鬼”经历

内存泄漏就像幽灵,看不见摸不着却真实存在。在你的 Go 开发生涯中,是否也曾遇到过让你抓狂的内存泄漏或 Goroutine 暴涨?最终你是如何定位并解决的?

欢迎在评论区分享你的“捉鬼”故事和独门排查技巧! 让我们一起守护服务的稳定性。

如果这篇文章帮你修复了关于内存的心智模型,别忘了点个【赞】和【在看】,并转发给你的团队,让大家一起避坑!


还在为“复制粘贴喂AI”而烦恼?我的新专栏 AI原生开发工作流实战 将带你:

  • 告别低效,重塑开发范式
  • 驾驭AI Agent(Claude Code),实现工作流自动化
  • 从“AI使用者”进化为规范驱动开发的“工作流指挥家”

扫描下方二维码,开启你的AI原生开发之旅。


你的Go技能,是否也卡在了“熟练”到“精通”的瓶颈期?

  • 想写出更地道、更健壮的Go代码,却总在细节上踩坑?
  • 渴望提升软件设计能力,驾驭复杂Go项目却缺乏章法?
  • 想打造生产级的Go服务,却在工程化实践中屡屡受挫?

继《Go语言第一课》后,我的《Go语言进阶课》终于在极客时间与大家见面了!

我的全新极客时间专栏 《Tony Bai·Go语言进阶课》就是为这样的你量身打造!30+讲硬核内容,带你夯实语法认知,提升设计思维,锻造工程实践能力,更有实战项目串讲。

目标只有一个:助你完成从“Go熟练工”到“Go专家”的蜕变! 现在就加入,让你的Go技能再上一个新台阶!


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

© 2026, bigwhite. 版权所有.

当机器开始“剁手”:详解 Google UCP 与 Agentic Commerce 的架构革命

2026-01-14 15:56:12

本文永久链接 – https://tonybai.com/2026/01/14/google-ucp-agentic-commerce-architecture-revolution

大家好,我是Tony Bai。

想象一下,未来的某一天,你们公司的电商网站流量突然暴涨了 1000 倍。

但奇怪的是,后台数据显示 PageView(页面浏览量)几乎为零,热力图一片空白,也没有任何用户在点击你的促销弹窗。

这并不是遭受了 DDoS 攻击,而是你迎来了第一批“机器顾客”

我们正在从“人机交互”的电商时代,跨入“Agentic Commerce(智能体商业)”的新纪元。在这个时代,代替人类下单的,是运行在手机、云端或眼镜里的 AI Agent(智能体)

如果你是技术负责人,你可能会感到背脊发凉:

现有的这套为人类设计的、充满图片、广告和前端渲染的电商基建,对于“硅基生物”来说,效率低得令人发指。

为了迎接这场变革,Google 近期开源了 UCP (Universal Commerce Protocol),微软研究院在去年也前瞻性地发布了 Agentic Economy报告,Aqfer 提出了 AIO (AI Agent Optimization) 概念。

今天,我们就结合这三份重磅资料,从协议、基建、经济三个维度,深度剖析这场正在发生的架构革命。

第一性原理:为什么我们需要 Agentic Commerce?

根据微软研究院(Microsoft Research)的报告,Agentic Commerce 的爆发并非偶然,而是经济学第一性原理的必然推论。

传统的电商交易链路充满了“通信摩擦(Communication Frictions)”

  • 人类: 搜索 -> 筛选 -> 比价 -> 阅读评论 -> 填表 -> 支付。
  • 摩擦: 每一个环节都在消耗人类有限的注意力认知带宽

AI 智能体的出现,本质上是在消除这些摩擦

未来的购物模式将简化为:意图 -> 交易

用户只需说:“帮我买一个去日本旅游用的轻便行李箱,预算 500 元内,要耐摔的。”

接下来的搜索、比价、看评论、下单、支付,全部由 Assistant Agent(助理智能体) 和商家的 Service Agent(服务智能体) 在后台通过协议谈判完成。

这不仅是用户体验的升级,更是交易效率的指数级跃迁

技术基座:Google UCP 协议详解

然而,理想很丰满,现实很骨感。目前的 Agent 购物面临一个巨大的工程难题$N \times N$ 的集成灾难

每个商家都有自己的私有 API,Agent 不可能适配全天下所有的电商接口。

为了解决这个问题,Google 提出了 UCP (Universal Commerce Protocol,通用商业协议)

你可以把 UCP 理解为电商界的“USB 接口”。它定义了一套标准化的语言,让消费者智能体(Consumer Agent)和商家后端(Business Backend)能够直接对话。

UCP 的核心架构设计

  1. 标准化发现 (Discovery)
    类似于 robots.txt,商家只需在 .well-known/ucp 路径下发布一个 JSON 清单,声明:“我是卖花的,我支持搜索、加购和 Google Pay。” Agent 读到这个文件,就知道了交互规则。

  2. 原子化能力 (Capabilities)
    UCP 定义了一组标准的原语(Primitives),如 ProductDiscovery(商品发现)、Cart(购物车)、Checkout(结账)。这些原语是跨平台的,无论是 Amazon 还是独立站,语义都一样。

  3. 灵活的传输层 (Transport)
    UCP 不仅支持传统的 REST API,还原生支持 MCP (Model Context Protocol)
    这意味着,你的 UCP 服务可以直接作为一个 MCP Server 挂载到 Claude 或 Gemini 中,让大模型“天生”就具备操作你店铺的能力。

Agent 看到的不再是 HTML,而是干净的 JSON:

// UCP Checkout Response Example
{
  "ucp": {
    "version": "2026-01-11",
    "services": { "dev.ucp.shopping": { "version": "2026-01-11", "spec": "https://ucp.dev/specs/shopping", "rest": { "schema": "https://ucp.dev/services/shopping/openapi.json", "endpoint": "http://localhost:8182/" } } },
    "capabilities": [
      { "name": "dev.ucp.shopping.checkout", "version": "2026-01-11", "spec": "https://ucp.dev/specs/shopping/checkout", "schema": "https://ucp.dev/schemas/shopping/checkout.json" },
      { "name": "dev.ucp.shopping.discount", "version": "2026-01-11", "spec": "https://ucp.dev/specs/shopping/discount", "schema": "https://ucp.dev/schemas/shopping/discount.json", "extends": "dev.ucp.shopping.checkout" },
      { "name": "dev.ucp.shopping.fulfillment", "version": "2026-01-11", "spec": "https://ucp.dev/specs/shopping/fulfillment", "schema": "https://ucp.dev/schemas/shopping/fulfillment.json", "extends": "dev.ucp.shopping.checkout" }
    ]
  },
  "payment": {
    "handlers": [
      { "id": "shop_pay", "name": "com.shopify.shop_pay", "version": "2026-01-11", "spec": "https://shopify.dev/ucp/handlers/shop_pay", "config_schema": "https://shopify.dev/ucp/handlers/shop_pay/config.json", "instrument_schemas": [ "https://shopify.dev/ucp/handlers/shop_pay/instrument.json" ], "config": { "shop_id": "d124d01c-3386-4c58-bc58-671b705e19ff" } },
      { "id": "google_pay", "name": "google.pay", "version": "2026-01-11", "spec": "https://example.com/spec", "config_schema": "https://example.com/schema", "instrument_schemas": [  "https://ucp.dev/schemas/shopping/types/gpay_card_payment_instrument.json"
 ], "config": { "api_version": 2, "api_version_minor": 0, "merchant_info": { "merchant_name": "Flower Shop", "merchant_id": "TEST", "merchant_origin": "localhost" }, "allowed_payment_methods": [ { "type": "CARD", "parameters": { "allowedAuthMethods": [ "PAN_ONLY", "CRYPTOGRAM_3DS" ], "allowedCardNetworks": [ "VISA", "MASTERCARD" ] }, "tokenization_specification": [ { "type": "PAYMENT_GATEWAY", "parameters": [ { "gateway": "example", "gatewayMerchantId": "exampleGatewayMerchantId" } ] } ] } ] } },
      { "id": "mock_payment_handler", "name": "dev.ucp.mock_payment", "version": "2026-01-11", "spec": "https://ucp.dev/specs/mock", "config_schema": "https://ucp.dev/schemas/mock.json", "instrument_schemas": [ "https://ucp.dev/schemas/shopping/types/card_payment_instrument.json" ], "config": { "supported_tokens": [ "success_token", "fail_token" ] } }
    ]
  }
}

基础设施危机:“海啸级”查询与营销失效

当 Agent 能够读懂 UCP 协议后,商家的技术架构将面临前所未有的挑战。Aqfer 在其白皮书中发出了警告:你的基础设施准备好迎接“机器海啸”了吗?

流量的量级跃迁

人类逛淘宝,一分钟看 5 个商品就累了。

AI 智能体为了帮主人找到“最优解”,可能会在几毫秒内扫描 1000 个 SKU,实时比对全网价格和库存。

你的 Read API QPS 可能会暴涨 100倍 – 1000倍。传统的缓存策略可能失效,因为 Agent 需要毫秒级的实时库存(Real-time Inventory)准确性。

营销逻辑的崩塌

这是最让市场部绝望的一点:AI 智能体对“情绪”免疫。

你在详情页上精心设计的品牌故事、氛围感图片、促销倒计时,对于 LLM 来说只是无意义的 Token 噪音。

Agent 只关心:Data (数据)

  • 价格是多少?(精确数字)
  • 材质是什么?(结构化参数)
  • 物流几天到?(SLA 承诺)

从 SEO 到 AIO (AI Agent Optimization)

未来的流量入口不再是搜索引擎,而是 AI 智能体。

如果你想被 Agent 选中,你需要的不是 SEO(针对关键词优化),而是 AIO(针对智能体优化)

Data is the UI. 你的商品数据必须是清洁的、结构化的、向量友好的。如果你还在用图片存参数表,你的商品在 Agent 眼里就是隐形的。

未来推演:围墙花园 vs. 开放网络

微软研究院的报告指出了两种可能的终局:

  • 路径 A:Agentic Walled Gardens(智能体围墙花园)
    OpenAI、Google、Apple 建立自己的“智能体 App Store”。商家必须适配它们的私有协议才能被其 Agent 访问。这会形成新的垄断。

  • 路径 B:Web of Agents(智能体开放网络)
    基于 UCPMCP 这样的开放标准,任何商家的 Service Agent 都可以和消费者的 Assistant Agent 自由交易,无需经过中心化平台。

这就是为什么 Google 要急于开源 UCP标准协议。协议之争,将决定未来十年的互联网商业格局。

小结:为“机器客户”重构系统

Agentic Commerce 不仅仅是一个技术热词,它是一场生产关系的重构

作为架构师,你的使命正在发生变化:

从“为人类构建漂亮的 UI”,转变为“为机器构建健壮的 API”

不要等到你的竞争对手已经被 AI 智能体“自动下单”买空了库存,你还在研究 Landing Page 的按钮颜色。

拥抱协议,结构化数据,迎接那个“万物皆可被 Agent 调用”的未来。

参考资料

  • The Agentic Economy – https://arxiv.org/abs/2505.15799
  • Under the Hood: Universal Commerce Protocol (UCP) – https://developers.googleblog.com/under-the-hood-universal-commerce-protocol-ucp/
  • Universal Commerce Protocol官网 – https://ucp.dev/
  • The Age of Agentic Commerce: When Machines Become Your Customers – https://aqfer.com/wp-content/uploads/2025/09/AgenticCommerce_9.3.25_final_v1.pdf

你的“机器顾客”准备好了吗?

Agentic Commerce 的未来听起来既科幻又紧迫。如果你的应用突然迎来了一波 AI Agent 的访问,你的 API 扛得住吗?你认为未来的电商是会被巨头垄断,还是通过 UCP 走向开放?

欢迎在评论区分享你的脑洞或担忧! 让我们一起为即将到来的“机器时代”做好准备。

如果这篇文章让你对未来的电商架构有了全新的认识,别忘了点个【赞】和【在看】,并转发给你的产品经理和老板,告诉他们:变天了!


还在为“复制粘贴喂AI”而烦恼?我的新专栏 AI原生开发工作流实战 将带你:

  • 告别低效,重塑开发范式
  • 驾驭AI Agent(Claude Code),实现工作流自动化
  • 从“AI使用者”进化为规范驱动开发的“工作流指挥家”

扫描下方二维码,开启你的AI原生开发之旅。


你的Go技能,是否也卡在了“熟练”到“精通”的瓶颈期?

  • 想写出更地道、更健壮的Go代码,却总在细节上踩坑?
  • 渴望提升软件设计能力,驾驭复杂Go项目却缺乏章法?
  • 想打造生产级的Go服务,却在工程化实践中屡屡受挫?

继《Go语言第一课》后,我的《Go语言进阶课》终于在极客时间与大家见面了!

我的全新极客时间专栏 《Tony Bai·Go语言进阶课》就是为这样的你量身打造!30+讲硬核内容,带你夯实语法认知,提升设计思维,锻造工程实践能力,更有实战项目串讲。

目标只有一个:助你完成从“Go熟练工”到“Go专家”的蜕变! 现在就加入,让你的Go技能再上一个新台阶!


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

© 2026, bigwhite. 版权所有.

Go 的“显式哲学”为何在接口上“食言”了?—— 探秘隐式接口背后的设计智慧

2026-01-14 08:17:21

本文永久链接 – https://tonybai.com/2026/01/14/go-explicit-philosophy-implicit-interfaces-design-wisdom

大家好,我是Tony Bai。

“Go 倾向于显式、冗长的代码,而不是‘魔法’。那么,为什么接口实现却是隐式的呢?这让理解代码变得困难多了,简直让我抓狂。”

前不久,一位 Gopher 在 Reddit 上发出了这样的灵魂拷问。这不仅仅是一个新手的问题,它触及了 Go 语言设计中最有趣、也最常被误解的一个矛盾:在一个崇尚“显式”的语言里,为什么最核心的抽象机制(接口)却选择了极致的“隐式”?

相比于 Java 的 implements 或 Rust 的 impl for,Go 的这种“只要方法匹配,就自动实现”的 Duck Typing 风格,确实显得格格不入。

是 Go 的设计者们“双标”了吗?还是这背后隐藏着某种更深层的、我们尚未完全领悟的智慧?本文将带你深入 Go 的设计哲学,揭开这个“反直觉”设计背后的真相。

显式实现的“原罪”——被倒置的依赖

要理解 Go 为何选择隐式,我们首先要看看“显式实现”带来了什么问题。在 Java 或 C# 中,如果你想让你的类实现一个接口,你必须在定义类的时候就显式声明:

// Java
public class MyReaderImpl implements MyReaderIntf { ... }

这看起来很清晰,但它引入了一个致命的耦合:生产者(具体类型)必须知道消费者(接口)的存在。

这意味着:

  1. 你无法为第三方类型实现接口:如果你使用了一个第三方库的结构体,而你想让它实现你自己定义的接口,你做不到。因为你无法修改第三方库的源码去加上 implements MyInterface。
  2. “上帝接口”的诞生:为了规避第1点,库的设计者倾向于预定义庞大的、包罗万象的接口(如 IUser),强迫所有实现者都去依赖这个庞大的契约。这导致了接口定义的早产不必要的依赖

Go 的设计者们敏锐地捕捉到了这一点。他们认为,接口应当由消费者(Consumer)定义,而不是生产者(Producer)。

解耦的艺术——消费者定义的接口

Go 的隐式接口,彻底反转了这种依赖关系。

在 Go 中,具体的类型(如struct)不需要知道接口的存在。它只需要专注地实现它该有的方法。而接口的定义,可以发生在任何时间、任何地点,通常是在使用方(调用者)的代码中。

正如 Reddit 上高赞评论所言:

“Define interfaces at the receiving end.”(在接收端定义接口)

这带来了前所未有的灵活性:

  • 事后抽象:你可以先写具体的实现代码。等到某一天,你发现需要对这部分逻辑进行抽象或测试时,你可以在调用方就地定义一个接口,而无需修改原有的具体类型代码。
  • 小接口哲学:因为接口是消费者按需定义的,所以 Go 鼓励定义极小的接口(如 io.Reader 只有一个方法)。如果必须显式声明,开发者会倾向于定义大接口以减少声明的繁琐,而隐式接口则让 interface{ Read(…) } 这种微型契约变得轻量且自然。

这就是隐式的代价换来的价值:彻底的解耦。 它打破了“实现”与“抽象”之间的强绑定,让代码的演进变得更加自由。

测试与 Mock 的天堂:只 Mock 你关心的

在 Java 或 C# 这样的显式接口语言中,如果你要测试一个依赖了 Database 类的函数,你通常面临两个选择:

  1. 引入 Database 所在的庞大包。
  2. 为了测试,不得不为 Database 定义一个包含其所有方法的 IDatabase 接口,哪怕你只用了其中一个 Query 方法。这被称为“接口污染”。

而在 Go 中,隐式接口允许我们在“测试现场”定义接口。这被称为“最小化 Mock”

假设有这样一个场景:我们需要编写一个 WeatherReporter(天气播报员),它依赖一个庞大的第三方天气 SDK 来获取数据。

第三方库代码(我们无法修改,且很庞大):

// thirdparty/weather.go
type HeavyWeatherClient struct { ... } // 包含几百个方法
func (c *HeavyWeatherClient) GetTemp(city string) float64 { ... } // 我们只用这一个
func (c *HeavyWeatherClient) GetHumidity() float64 { ... }
func (c *HeavyWeatherClient) GetWindSpeed() float64 { ... }
// ... 还有几百个其他方法 ...

我们的业务代码:

// reporter.go
// 注意:这里我们直接接受具体的 HeavyWeatherClient,或者任何实现了 GetTemp 的东西
func ReportTemperature(client interface{ GetTemp(string) float64 }, city string) {
    temp := client.GetTemp(city)
    if temp > 30 {
        fmt.Println("It's hot!")
    }
}

我们的测试代码(Test 文件):

在测试中,我们完全不需要引入那个庞大的 thirdparty 包,也不需要 mock 那几百个无关的方法。我们只需要在测试文件里定义一个极小的接口:

// reporter_test.go

// 1. 定义一个只包含我们所用方法的“本地接口”
// 甚至都不需要给它起名字,匿名接口也可以
type mockFetcher struct{}

func (m *mockFetcher) GetTemp(city string) float64 {
    return 35.0 // 返回一个假数据
}

func TestReportTemperature(t *testing.T) {
    mock := &mockFetcher{}

    // 2. Go 的隐式特性发挥作用:
    // mockFetcher 并没有显式声明实现了任何接口,
    // 但它拥有 GetTemp 方法,所以它可以被传入 ReportTemperature!
    ReportTemperature(mock, "Beijing")

    // 验证逻辑...
}

注:关于 Mock 与 Stub 的严谨区分

细心的读者可能发现,严格来说,上例中的 mockFetcher 更像是一个 Stub (桩)——它只返回固定数据,不验证调用行为。但在 Go 社区的工程实践中,我们习惯将这类用于替换真实依赖的测试替身统称为 Mock。为了方便理解,本文沿用了这一通俗叫法。

这就是“天堂”的含义:你可以忽略对象 99% 的复杂性,只为你关心的那 1% 编写 Mock。这种按需定义 (Ad-hoc) 的能力,让 Go 的单元测试变得极其轻量和纯粹,彻底摆脱了对重型 Mock 框架的依赖。

警惕:不要为了测试而“预定义”接口

这里有一个新手常犯的错误:为了方便测试,在生产代码中为每一个 Struct 都配对写一个 Interface(例如 type UserServiceImpl struct 和 type UserService interface)。

这是一个反模式(Anti-pattern)。 Go 的哲学之一是不要在生产者(Producer)端定义接口,要在消费者(Consumer)端定义接口。如果你在生产代码中定义了一个只被自己实现的接口,你只是在增加代码的复杂度和阅读成本,而没有带来任何解耦的实际价值。

正确的做法

  • 如果 UserService 是你自己写的,且逻辑简单(纯逻辑,无 I/O),直接测试 Struct 本身即可,不需要接口
  • 如果 UserService 确实包含数据库操作,需要被 Mock,那么请在调用它的人那里(或者在测试文件里)定义接口,而不是在 UserService 旁边定义一个“没用”的接口。

记住:接口通过解耦来促进测试,但不要为了测试而强行制造接口。

如何应对“隐式”带来的困扰?

当然,提问者的困惑是真实的:“我怎么知道这个结构体实现了哪些接口?”

这种“不可知性”确实是隐式接口的副作用。但在 Go 的工程实践中,我们有成熟的应对方案:

  1. IDE 的力量:现代 IDE(如 GoLand, VS Code,甚至是安装了插件的Vim等)已经完美解决了这个问题。简单的“Find Usages”或“Go to Implementations”就能列出所有匹配的接口。工具弥补了人类肉眼的局限。
  2. 编译期断言:如果你是库的作者,你需要向用户保证你的类型(比如*MyStruct)实现了某个标准接口(例如 io.Writer),为了防止未来修改代码时不小心破坏了这个契约,你可以使用这行经典的“黑魔法”代码:
// 这是一道“编译期防线”
var _ io.Writer = (*MyStruct)(nil)

细心的读者可能会发现,这行代码强制 MyStruct 所在的文件 import 了 io 包。没错,这确实引入了依赖。

但与 Java 强制性的 implements 不同,Go 的这种耦合是可选的防御性的。

  • 它不是程序运行的必要条件,而是一个写在源码里的“编译期测试用例”
  • 它通常只用于向标准库或核心框架的稳定接口看齐。对于业务层那些灵活的、消费者定义的接口,我们通常不需要写这行代码,从而保持代码的纯净与解耦。

小结:显式的代码,隐式的契约

回到最初的问题:Go 违背了“显式”的哲学吗?

答案是:没有。Go 追求的是“行为”的显式,而非“类型分类”的显式。

Go 让你显式地编写方法,显式地处理错误,显式地进行类型转换。但在“谁实现了谁”这种元数据层面,Go 选择了隐式,因为它认为“鸭子类型” (If it walks like a duck…) 才是对软件组件交互最自然、最解耦的描述。

Go 的隐式接口,不是为了省去敲 implements 这几个字母的懒惰,而是一场关于软件架构解耦的深谋远虑。它赋予了 Go 语言一种独特的“结构化动态性”——既有静态语言的安全,又有动态语言的灵活。这,正是 Go 设计哲学的精妙所在。

资料链接:https://www.reddit.com/r/golang/comments/1pa6t2m/go_prefers_explicit_verbose_code_over_magic_so


你的接口设计习惯

Go 的隐式接口虽然灵活,但也给了开发者极大的自由度。在你的项目中,你是习惯先定义接口再写实现(顶层设计),还是先写实现再按需提取接口(事后抽象)?你是否也曾陷入过“接口定义泛滥”的陷阱?

欢迎在评论区分享你的设计心得或踩坑故事! 让我们一起探讨如何用好这把“双刃剑”。

如果这篇文章解开了你对 Go 接口的困惑,别忘了点个【赞】和【在看】,并转发给你的开发伙伴,一起感受 Go 的设计之美!


还在为“复制粘贴喂AI”而烦恼?我的新专栏 AI原生开发工作流实战 将带你:

  • 告别低效,重塑开发范式
  • 驾驭AI Agent(Claude Code),实现工作流自动化
  • 从“AI使用者”进化为规范驱动开发的“工作流指挥家”

扫描下方二维码,开启你的AI原生开发之旅。


你的Go技能,是否也卡在了“熟练”到“精通”的瓶颈期?

  • 想写出更地道、更健壮的Go代码,却总在细节上踩坑?
  • 渴望提升软件设计能力,驾驭复杂Go项目却缺乏章法?
  • 想打造生产级的Go服务,却在工程化实践中屡屡受挫?

继《Go语言第一课》后,我的《Go语言进阶课》终于在极客时间与大家见面了!

我的全新极客时间专栏 《Tony Bai·Go语言进阶课》就是为这样的你量身打造!30+讲硬核内容,带你夯实语法认知,提升设计思维,锻造工程实践能力,更有实战项目串讲。

目标只有一个:助你完成从“Go熟练工”到“Go专家”的蜕变! 现在就加入,让你的Go技能再上一个新台阶!


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

© 2026, bigwhite. 版权所有.

技术考古:Markdown 为何从博客工具演变成统治 AI 世界的“通用语”?

2026-01-13 13:21:54

本文永久链接 – https://tonybai.com/2026/01/13/how-markdown-took-over-the-world

大家好,我是Tony Bai。

在这个由科技巨头主导、充斥着复杂算法和封闭生态的数字世界里,有一种技术显得格格不入。它没有专利壁垒,没有复杂的构建流程,甚至不需要特定的软件就能阅读。

它是 Markdown

近期,知名科技博主 Anil Dash 发布了一篇题为《How Markdown Took Over the World》的长文。他在文中深情回顾了这一格式的诞生与崛起,并指出:在这个由科技巨头主导、充斥着封闭生态的数字世界里,Markdown 是一场属于普通人的胜利。

如今,从 GitHub 上的亿万代码仓库,到 ChatGPT等大模型 生成的每一个回答,再到你随手记下的 Apple Notes,Markdown 无处不在。它不仅成为了技术人员的“普通话”,更意外地成为了 AI 时代的“通用语”。

这一切,都始于 20 年前一位“固执”的苹果博主为了偷懒而写的一个小脚本。今天,让我们跟随 Anil Dash 的视角,回顾这段充满偶然与必然的技术传奇。

缘起:一个博主的“偷懒”计划

2002 年,John Gruber 做了一个在当时看来极其不理性的决定:全职运营一个只关注苹果公司动态的博客——Daring Fireball

在那个博客刚刚兴起的蛮荒时代,发布内容并不容易。你要么忍受简陋的输入框,要么得手写复杂的 HTML 标签。为了能在写文章时(比如加粗、插入链接)不被繁琐的 HTML 标记打断思路,John 决定为自己开发一套工具。

他的核心理念是:既然 HTML (HyperText Markup Language) 太复杂,那就叫它 Markdown 吧。

如果你想加粗,就用 **;想引用,就用 >;想列表,就用 -。这些符号并非凭空创造,而是深受电子邮件时代纯文本格式习惯的影响。John 的天才之处在于,他将这些约定俗成的习惯标准化,并写了一个 Perl 脚本将它们转换为合法的 HTML。

2004 年 3 月,在 Aaron Swartz(那位早逝的天才少年)的协助测试下,Markdown 正式发布。没有人预料到,这个小小的工具将改变互联网的未来。

统治世界:从程序员到 AI

Markdown 的崛起并非一夜之间,但它的生命力却异常顽强。

  1. 开发者的拥抱:GitHub 的出现是关键转折点。它将 README.md 设为项目标配,使得 Markdown 成为了开发者描述项目的标准格式。
  2. 应用的普及:从 Slack 到 Discord,从 Notion 到 Obsidian,现代生产力工具几乎全部内置了 Markdown 支持。哪怕是 Google Docs 和 Apple Notes 这样的大众软件,最终也向用户需求妥协,加入了 Markdown 支持。
  3. AI 的通用语:最令人意想不到的转折发生在当下。当最前沿的 LLM(大型语言模型)需要一种格式来输出结构化内容时,它们不约而同地选择了 Markdown。因为它既对人类可读,又对机器友好,且完全开放。

Anil Dash 在他的回顾文章中总结了 Markdown 成功的 10 个技术原因,其中几点尤为深刻:

  • 解决真实问题:它不是为了“发明一种新格式”,而是为了解决“手写 HTML 太痛苦”这个具体痛点。
  • 利用现有习惯:它没有强迫用户学习新符号,而是沿用了电子邮件时代的纯文本习惯(如 > 表示引用)。
  • 没有知识产权 (IP) 负担:John Gruber 从未试图将其商业化或申请专利,这种彻底的开放性消除了所有采用者的顾虑。
  • “查看源码”的哲学:Markdown 文件本身就是教程。你只需要看一眼源文件,就能立刻学会怎么写。

硬币的另一面:自由的代价

当然,Markdown 这种彻底的自由和缺乏中央控制,也带来了一个长期的副作用——碎片化

正因为 John Gruber 当年只给出了一个 Perl 脚本而没有定义极其严谨的规范,导致后来出现了各种“方言”。GitHub 有自己的 GitHub Flavored Markdown (GFM),Reddit 有自己的解析规则,Obsidian 和 Notion 也都添加了各自的私有语法(如双向链接 [[Link]])。

这导致了一个尴尬的现实:虽然 Markdown 到处都是,但你的 Markdown 文件未必能在所有地方都完美渲染。 表格的语法支持不一,数学公式的写法各异,甚至连换行符的处理都有微妙差别。

直到后来 CommonMark 等项目的出现,才试图事后诸葛亮式地去修补这种分裂。

但幸运的是,Markdown 的核心语法(标题、列表、粗体、引用、链接)已经足够稳固,成为了事实上的标准。正是这最基础的 80% 功能,支撑起了它在 AI 时代的通用性。对于大语言模型而言,这些细微的方言差异完全可以忽略不计——它只需要用最基础的语法,就能让全世界读懂。

这也再次印证了那个道理:在规模化面前,简单且“足够好”的方案,往往能战胜完美但复杂的方案。

启示:善良与开放的力量

Markdown 的故事,是对当代科技行业的一种温柔提醒。

真正的互联网基础设施,往往不是由拿了巨额风投的初创公司在董事会里规划出来的。它们往往源于像 John Gruber 或 Aaron Swartz 这样的人——他们有正职工作,但也充满热情;他们为了解决自己的问题而造轮子,然后慷慨地将其分享给世界。

在这个被“护城河”、“生态闭环”和“商业化变现”充斥的时代,Markdown 证明了:一个好的点子,加上一颗慷慨的心,依然可以改变世界。

下次当你用 ** 加粗文字,或者看着 ChatGPT 逐行吐出格式完美的回答时,请记得:这背后没有复杂的商业算计,只有一位在费城看球赛的博主,想让你打字时能稍微轻松一点。

资料链接:https://www.anildash.com/2026/01/09/how-markdown-took-over-the-world/


你的 Markdown 记忆

Markdown 已经陪伴了我们 20 年。你还记得自己第一次接触 Markdown 是在什么场景下吗?是写 GitHub README,还是做笔记?你最喜欢的 Markdown 编辑器又是哪一款?

欢迎在评论区分享你的 Markdown 故事和神器推荐! 让我们一起致敬这个简单而伟大的工具。

如果这篇文章让你对 Markdown 有了全新的认识,别忘了点个【赞】和【在看】,并转发给你的朋友,哪怕他只是个爱记笔记的非程序员!


还在为“复制粘贴喂AI”而烦恼?我的新专栏 AI原生开发工作流实战 将带你:

  • 告别低效,重塑开发范式
  • 驾驭AI Agent(Claude Code),实现工作流自动化
  • 从“AI使用者”进化为规范驱动开发的“工作流指挥家”

扫描下方二维码,开启你的AI原生开发之旅。


你的Go技能,是否也卡在了“熟练”到“精通”的瓶颈期?

  • 想写出更地道、更健壮的Go代码,却总在细节上踩坑?
  • 渴望提升软件设计能力,驾驭复杂Go项目却缺乏章法?
  • 想打造生产级的Go服务,却在工程化实践中屡屡受挫?

继《Go语言第一课》后,我的《Go语言进阶课》终于在极客时间与大家见面了!

我的全新极客时间专栏 《Tony Bai·Go语言进阶课》就是为这样的你量身打造!30+讲硬核内容,带你夯实语法认知,提升设计思维,锻造工程实践能力,更有实战项目串讲。

目标只有一个:助你完成从“Go熟练工”到“Go专家”的蜕变! 现在就加入,让你的Go技能再上一个新台阶!


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

© 2026, bigwhite. 版权所有.