MoreRSS

site iconTony Bai | 白明修改

《Go语言精进之路》作者,Go C程序员,架构师,技术讲师、撰稿人。先后贡献了lcut、cbehave、buildc多个工具框架。
请复制 RSS 到你的阅读器,或快速订阅到 :

Inoreader Feedly Follow Feedbin Local Reader

Tony Bai | 白明的 RSS 预览

别再无脑 go get @latest 了!你的服务器可能下一秒就被黑客接管

2026-03-19 07:30:58

本文永久链接 – https://tonybai.com/2026/03/19/proposal-support-dependency-cooldown-in-go-tooling

大家好,我是Tony Bai。

试想一个极其真实的“黑色星期五”场景:

下班前一小时,你为了修复一个无关紧要的小 Bug,或者只是心血来潮想把项目里的依赖库清理一下,于是你顺手在终端里敲下了极其熟练的几个字符:

go get -u

或者 

go get github.com/xxx/yyy@latest

看着屏幕上飞速滚动的下载进度条,一排排依赖被成功升级到带有 v1.x.x 的最新版本,你的心里涌起了一阵莫名的舒适与安全感。毕竟,在绝大多数程序员的潜意识里:“最新版 = 修复了所有已知漏洞 = 性能更强 = 最安全”。

但如果我今天告诉你,你敲下的那个 @latest,其实是黑客精心为你准备的“夺命接引符”呢?

这绝不是危言耸听。就在不久前,Go 官方 GitHub 仓库中出现了一个引发核心开发团队激烈讨论的提案:Issue #76485(在 Go 工具链中支持依赖冷却期)。

这个提案的提出,暴露出我们在面对一种名为“供应链投毒”的高级攻击时,防御体系有多么脆弱。

今天,我们就来硬核扒开这个提案背后的深层技术逻辑,看看 Go 官方打算如何拯救我们的依赖树。

你以为的最新版,其实是黑客的“盲区红利”

近几年来,在 NPM、PyPI 乃至 Rust 的 Crates.io 生态中,“开源供应链投毒”早就不是什么新鲜事了。黑客们的攻击手段已经从早期的“暴力破解服务器”,演变成了极其阴险的“社会工程学与自动化投毒”。

他们的套路简单粗暴,但杀伤力惊人:

黑客会去盗取某个高星级开源库作者的 GitHub 账号,或者利用极具迷惑性的“拼写错误(Typosquatting,比如把 mongodb 拼成 mogodb)”发布一个恶意包。在这个包的 init() 函数里,他们悄悄塞进一段挖矿脚本、一段窃取服务器环境变量(包含 AWS Key 或数据库密码)的后门代码,然后打上一个闪亮的最新版本号,比如 v1.9.9。

这个时候,谁最先更新依赖,谁就最先成为黑客刀下的韭菜。

在网络安全界,有一个极其残酷的定律:恶意代码从发布到被发现,是存在一个“致命时间差”的。

当一个投毒包被发布到全世界的代理镜像(Proxy)上,到它被安全社区的白帽子发现、逆向分析、并最终拉黑(报 CVE 漏洞),通常需要几天到几周的时间。

在这段无人察觉的“安全盲区”里,你对“最新版”的盲目狂热,恰恰成了黑客最喜欢的传播加速器。你在帮黑客做大范围的灰度测试,而你的生产服务器,就是那只可怜的小白鼠。

Go 的三道防线:MVS 与 SumDB 的极限,以及最后的防守漏洞

很多 Go 开发者看到这里可能会不服气:“Tony 老师,你说的都是 Node.js 和 Python 那边的事儿。我们 Go 语言的依赖管理系统可是业界公认最安全的!”

没错,Go 语言在设计模块系统(Go Modules)时,确实比其他语言多长了几个心眼。我们目前拥有两道底层防线:

第一道防线:MVS(最小版本选择,Minimal Version Selection)。

当你安装一个依赖时,NPM 默认会去寻找符合语义化版本(SemVer)的“最新兼容版本”。但 Go 的 MVS 算法极其保守,它只会选择能满足所有依赖要求的最老版本(即最小版本)。这意味着,即使黑客发布了一个带毒的 v1.2.9,只要你的项目依赖树只要求 v1.2.0,Go 就绝对不会自作多情地帮你自动升级到最新版。MVS 直接掐断了黑客通过“传递依赖”悄悄感染你的路径。

第二道防线:SumDB(校验和数据库)。

如果你在本地偷偷篡改了某个版本的代码,Go 会在构建时大声报错。因为 Go 引入了一个基于密码学的透明日志系统 sum.golang.org。每一个包的版本只要一经发布,它的哈希值就会被永久记录在这个不可篡改的账本上。黑客无法“悄悄替换”一个已经存在的历史版本。

既然有了 MVS 和 SumDB,我们是不是就绝对安全了?

错!这两道防线有一个致命的盲点:它们防不住“开发者手贱”。

如果黑客发布了一个全新的带毒版本 v2.0.0,而你为了追求新特性,或者仅仅是强迫症发作,主动在终端里敲下了 go get -u,或者 go get xxx@latest,那么 MVS 的保护伞将瞬间失效。你主动把门禁打开,把伪装成“最新版”的木马迎进了核心机房。

终极杀招:Go 社区的建议——“让子弹飞一会儿”

既然传统的静态代码扫描防不住这种零日投毒,既然开发者总是管不住手想要升级最新版,那该怎么办?

Go 社区在提案中给出了一种解法:“既然投毒被发现需要时间,那我们就用魔法打败魔法——给依赖强行加一个物理隔离的冷却期(Cooldown)。”

在这个代号为 #76485 的提案中,开发者提出引入一个全新的环境变量来掌控全局:

GOCOOLDOWN=15d go mod tidy

这句话的底层指令是:“Go 工具链请注意,在帮我拉取或更新依赖时,请自动屏蔽掉所有发布时间少于 15 天的包。哪怕它的版本号再高、特性再诱人,只要它太年轻,一律当它不存在。”

这个设计的底层逻辑简直绝妙:绝大多数开源投毒攻击,在极度活跃的头几天内就会被安全专家揪出来。只要你忍住不当全网第一批“小白鼠”,等这个包在开源世界里被成千上万的其他语言开发者“趟过雷”,冷却了 15 天依然安然无恙,那么它大概率就是真正安全的。

这就是传说中的:只要我跑得足够慢,黑客的镰刀就永远割不到我。

如何骗过时间?Go 底层的极度严谨

看到这里,有经验的高级架构师肯定会抛出一个极其尖锐的质疑:

“等等!如果黑客在发布恶意包的时候,直接篡改 Git 的 Tag 时间,把今天的发布时间伪造成三个月前,这所谓的冷却期不就成了一个毫无防备的摆设了吗?”

如果你能想到这层,说明你已经具备了极强的黑客攻防思维。但在提案的深度讨论中,Go 密码学包主要维护者 FiloSottile 等核心开发者,早就把黑客的这条退路给焊死了。

在 Go 团队的设计构想中,冷却期的计算,绝对不依赖于容易被任意篡改的 Git Tag 或包作者自己声称的发布时间。

相反,Go 将调用我们前面提到的那套坚如磐石的基础设施——SumDB

当全球代理(如 proxy.golang.org)第一次看到并抓取某个包的全新版本时,SumDB 会在它的密码学叶子节点上,不可撤销地打上一个“首次观测时间戳(First-observed timestamp)”

这就像是去典当行抵押物品。小偷可以随意把手表的出厂日期磨掉改成十年前,但他绝对无法欺骗典当行头顶那带时间戳的监控录像。只要 SumDB 的日志显示这块表是昨天刚拿进来的,那么 GOCOOLDOWN 就会无情地将其拦截在门外。

至此,Go 语言的供应链防线形成了完美的逻辑闭环:

  • MVS 确保了你不会被动卷入升级;
  • SumDB 确保了历史包的绝对不可篡改;
  • 而全新的 Cooldown(冷却期),则补齐了你主动拉取最新版时的最后一块安全护盾。

小结:在特性落地前,我们该怎么保护自己?

虽然目前 #76485 依然在激烈的 Proposal Review(提案评审)阶段,甚至可能最终会演变成一个外部的轻量级过滤代理工具,但它透露出的底层工程哲学,值得每一位后端开发者立刻应用到日常的高并发架构中:

  1. 立刻戒掉 @latest 的瘾:在生产环境中,尽量不要使用 go get -u 去盲目追新。稳定运行了几个月的依赖树,如果没有极其严重的 Bug 或报出的 CVE 安全漏洞,绝对不要去动它。
  2. 拥抱自动化的“安全缓冲期”:如果你在公司内部使用了 Renovate 或 Dependabot 这样的自动依赖更新机器人,立刻去后台把“最小发布年龄(Minimum Release Age)”配置项打开,设置为 7 天或 15 天。让机器替你踩刹车。
  3. 敬畏时间,建立护城河:软件工程不是追星买首发。让别人不重要的边缘业务先去帮这个开源库的最新版“踩坑”,这是一个能够扛起千万级 QPS 的资深架构师应有的沉稳与克制。

在险象环生的网络世界里,时间不仅是解药,更是我们最强大的防火墙。期待 GOCOOLDOWN 的防守理念早日普及,让我们彻底告别每天提心吊胆更新依赖的日子。

资料链接:https://github.com/golang/go/issues/76485


今日互动探讨

你在公司里,遇到过因为同事“手贱升级了最新依赖”而导致生产环境崩溃,或者遭遇供应链投毒的血泪史吗?

欢迎在评论区疯狂吐槽与分享


认知跃迁:读懂底层机制,才能看透系统架构的本质

从保守的 MVS,到密码学级别的 SumDB,再到今天探讨的反直觉的 GOCOOLDOWN,你会发现,Go 团队在设计这门语言的工具链时,处处透着一种对工程稳定性、安全性的极致追求和克制。

然而,令人遗憾的是,很多开发者写了五六年的 Go 代码,却依然只停留在“会用 Gin 写写 CRUD 接口”的表层。他们对 Go 工具链底层的设计哲学、并发调度的本质、内存模型的安全逻辑一无所知。一旦线上的高并发系统出现复杂的性能瓶颈,或是遭遇底层的安全漏洞,往往束手无策,只能靠瞎猜。

如果你渴望突破这种“熟练调包侠”的瓶颈,想要像顶级大厂架构师一样,看透 Go 语言背后的系统级设计思维,建立起坚不可摧的技术护城河——

我的极客时间专栏 Tony Bai·Go语言进阶课 正是为你量身定制。

在这 30+ 讲极其硬核的内容中,我不仅带你剥开语法糖,深挖 Goroutine 调度、Channel 哲学、内存逃逸;更会带你全面吃透 Go 的工程化实践,把构建、依赖管理背后的深层逻辑一次性讲透。

目标只有一个:助你完成从“Go 熟练工”到“能做顶级架构决策的 Go 专家”的蜕变!

扫描下方二维码,加入专栏。不要用战术上的勤奋,掩盖战略上的懒惰。让我们一起用架构师的视角,重新认识 Go 语言。


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

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

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


原「Gopher部落」已重装升级为「Go & AI 精进营」知识星球,快来加入星球,开启你的技术跃迁之旅吧!

我们致力于打造一个高品质的 Go 语言深度学习AI 应用探索 平台。在这里,你将获得:

  • 体系化 Go 核心进阶内容: 深入「Go原理课」、「Go进阶课」、「Go避坑课」等独家深度专栏,夯实你的 Go 内功。
  • 前沿 Go+AI 实战赋能: 紧跟时代步伐,学习「Go+AI应用实战」、「Agent开发实战课」、「Agentic软件工程课」、「Claude Code开发工作流实战课」、「OpenClaw实战分享」等,掌握 AI 时代新技能。
  • 星主 Tony Bai 亲自答疑: 遇到难题?星主第一时间为你深度解析,扫清学习障碍。
  • 高活跃 Gopher 交流圈: 与众多优秀 Gopher 分享心得、讨论技术,碰撞思想火花。
  • 独家资源与内容首发: 技术文章、课程更新、精选资源,第一时间触达。

衷心希望「Go & AI 精进营」能成为你学习、进步、交流的港湾。让我们在此相聚,享受技术精进的快乐!欢迎你的加入!

img{512x368}


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

© 2026, bigwhite. 版权所有.

为什么你的 AI Agent 总是像个智障?来自 Manus 大佬的 2 年血泪避坑指南

2026-03-18 20:21:04

本文永久链接 – https://tonybai.com/2026/03/18/why-ai-agents-act-stupid-manus-expert-pitfall-guide

大家好,我是Tony Bai。

如果你在过去一年里跟风写过 AI Agent(智能体),你大概率经历过这样的绝望时刻:

你兴致勃勃地给大模型挂载了二三十个精心编写的 Function Calling(函数调用)工具,比如 read_file, search_web, execute_python……你期待它能像钢铁侠的贾维斯一样运筹帷幄。

结果呢?面对稍微复杂一点的任务,你的 Agent 瞬间退化成一个“智障”。

它要么在几十个工具里疯狂迷失,选错了参数导致系统报错;要么陷入无限死循环,把你的 Token 烧个精光,最后无辜地吐出一句:“抱歉,我无法完成该任务。”

我们总以为是自己的 Prompt 没写对,或者是大模型还不够聪明。

直到前些日子,一位名叫 MorroHsu 的顶级实战派大佬(在被 Meta 收购前,他是现象级 AI 产品 Manus 的后端技术负责人)在 Reddit 上抛出了一篇长文

在过去两年里,他以后端负责人的身份参与构建了包括 Manus、agent-clip 等在内的多个顶尖 Agent。在被大模型的各种奇葩幻觉折磨了无数遍之后,他得出了一个极其震撼、甚至有些反直觉的血泪结论:

别再瞎折腾繁琐的 Typed Function Calls(类型化函数调用)了!给大模型一堆乱七八糟的 API,就是它变“智障”的罪魁祸首。大模型最需要的,仅仅是 50 年前的 Linux 命令行(CLI)。

今天,我们就来看看这位 Manus 前后端大佬的 2 年避坑心法。看看为什么最前沿的 AI,反而需要最古老的 Unix 哲学来拯救。

为什么给 AI 几百个工具,它反而成了“智障”?

目前主流的 Agent 框架(如 LangChain),都在教我们怎么给大模型塞满工具箱。你塞的工具越多,系统看起来越庞大。

但 MorroHsu 指出了这背后的致命逻辑错误:工具选择的认知过载(Cognitive Load)。

大模型每次行动前,都要在几十个有着不同数据结构(Schemas)的工具中艰难地做选择题:“我到底该用哪一个?参数填什么?” 上下文的注意力被极大地分散了,准确率直线断崖式下跌。

大佬的解法粗暴且优雅:废弃所有花里胡哨的工具,只给大模型提供唯一的一个函数:run(command=”…”)。

为什么?因为大模型天生就是个 Linux 高手!

大模型的训练语料库里,充斥着 GitHub 上数十亿行的代码、README.md 中的安装指南、以及 Stack Overflow 上的报错日志。这些语料中,密密麻麻全是 CLI 命令行。

如果你让它去调用你发明的 read_log_file(path) API,它还要去猜测你的参数定义;但如果你让它去找日志里的错误,它会凭着肌肉记忆毫不犹豫地写出:

run(command=”cat /var/log/app.log | grep ‘ERROR’ | tail -n 20″)

你看,CLI 本身就是大模型最熟悉的母语。不要发明新的轮子去教大模型做事,直接把它最熟悉的世界交给它。

50年前的“管道”魔法,完美解决了 Agent 编排难题

如果只有一个 run 命令,AI 遇到复杂任务怎么办?

这就引出了 50 年前 Unix 操作系统的伟大设计哲学:一切皆文件。

Unix 的先驱们设计了大量只做一件事的小工具(cat, grep, sort),然后通过管道(Pipe |)将它们串联成无比强大的工作流。

而这,完美契合了大模型的核心本质——大模型只能理解文本输入和文本输出!

在传统的 Function Calling 中,为了完成“下载数据 -> 过滤错误 -> 排序前 10 条”这个任务,你的 Agent 可能需要连续调用 3 个不同的自定义函数,经历 3 轮耗时极长的 LLM 推理,中间稍微错一步就满盘皆输。

但在 CLI 模式下,AI 只需要通过一次组合调用就能秒杀:

run(command=”curl -sL $URL | grep ’500′ | sort | head 10″)

这种强大的“组合编排能力(Composition)”,不是什么 AI 领域的最新黑科技,而是 Unix 管道原生自带的降维打击。

把大模型当人看,设计“防智障”导航系统

当然,光把命令行扔给大模型,它依然会因为瞎猜而犯错。MorroHsu 总结了三个极其硬核的实战设计技巧,教你如何打造一个“防智障”的 Agent 导航系统:

绝招 1:渐进式发现(Progressive Discovery)

不要一开始就把所有命令的长篇大论全塞给大模型,那会瞬间撑爆它的上下文窗口。

只要告诉大模型:“你可以运行 run(“command”)。遇到不懂的,运行 command –help”

大模型其实非常懂得自我探索。当它发现报错时,它会自动去查阅说明书。这种“按需发现”的能力,极大地节省了宝贵的 Token。

绝招 2:把报错变成“向导”

这是最具启发性的一点!当大模型敲错命令时,千万别只返回一个冷冰冰的 exit code 127 或者 command not found。大模型无法像人类那样去 Google 搜索错误原因,它只会陷入瞎猜的死循环。

你必须在 stderr(标准错误输出)里加上向导信息。

传统报错:cat: photo.png: binary file

给 AI 的防智障报错:[Error] cat: photo.png is a binary image. Use ‘see photo.png’ instead.

不要试图阻止大模型犯错,而是要让它的每一次犯错,都成为指向正确道路的路标。

绝招 3:双层架构(物理隔离幻觉)

大模型的上下文是极其脆弱的。MorroHsu 分享了一个惨痛的真实案例:

一个用户上传了一张系统架构图,Agent 试图用 cat 命令读取它。结果 182KB 的乱码二进制字节流瞬间冲入了大模型的上下文。大模型当场“失了智”,开始不停地胡言乱语、重试、陷入死循环……足足浪费了 20 次推理的钱。

为了解决这个问题,必须在底层 Unix 执行和大模型展示层之间,建立一道“二进制守卫(Binary Guard)”“截断溢出守卫(Overflow Mode)”

当探测到命令输出超过 200 行,或者包含二进制乱码时,系统绝不把原数据返回给大模型,而是强制拦截并返回提示:

“— 输出已截断。请使用 grep 或 tail 命令进行搜索。—”

这就像给大模型戴上了一副防护眼镜,彻底杜绝了上下文被垃圾数据污染、导致智力下降的可能。

小结:化繁为简,才是架构的最高境界

目前,全网依然在乐此不疲地比拼谁的 Agent 框架更庞大、谁支持的 Tool Call 种类更多。但 原 Manus 大佬的这套“返璞归真”的血泪总结,给我们狠狠敲响了警钟。

最前沿的 AI,其实最需要最古老的系统智慧。

将 Unix 哲学的精髓(文本流、组合管道、小而美)与大模型的文本处理能力完美结合,放弃给 AI 制造复杂的隔离层和几十个脆弱的 API 接口,这才是真正属于“顶级工程师”的架构审美。

正如他在文末所言:“CLI 并非银弹,对于强类型校验和高安全性要求极高的场景,Typed API 依然不可或缺。但在广袤的智能体自主探索宇宙中,命令行,就是大模型所需要的全部。

资料链接:https://www.reddit.com/r/LocalLLaMA/comments/1rrisqn/i_was_backend_lead_at_manus_after_building_agents


今日互动探讨:

你在写 Agent 时,是喜欢用框架提供的一大堆 Tool Calls,还是像这位大神一样,直接让大模型写代码/写命令去执行?在实战中你的 AI 发生过哪些最搞笑的“智障/幻觉”行为?

欢迎在评论区分享你的血泪避坑史!


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

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

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


原「Gopher部落」已重装升级为「Go & AI 精进营」知识星球,快来加入星球,开启你的技术跃迁之旅吧!

我们致力于打造一个高品质的 Go 语言深度学习AI 应用探索 平台。在这里,你将获得:

  • 体系化 Go 核心进阶内容: 深入「Go原理课」、「Go进阶课」、「Go避坑课」等独家深度专栏,夯实你的 Go 内功。
  • 前沿 Go+AI 实战赋能: 紧跟时代步伐,学习「Go+AI应用实战」、「Agent开发实战课」、「Agentic软件工程课」、「Claude Code开发工作流实战课」、「OpenClaw实战分享」等,掌握 AI 时代新技能。
  • 星主 Tony Bai 亲自答疑: 遇到难题?星主第一时间为你深度解析,扫清学习障碍。
  • 高活跃 Gopher 交流圈: 与众多优秀 Gopher 分享心得、讨论技术,碰撞思想火花。
  • 独家资源与内容首发: 技术文章、课程更新、精选资源,第一时间触达。

衷心希望「Go & AI 精进营」能成为你学习、进步、交流的港湾。让我们在此相聚,享受技术精进的快乐!欢迎你的加入!

img{512x368}


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

© 2026, bigwhite. 版权所有.

手工作坊的终结:为什么你必须把 Agent Skills 开发,变成严谨的软件工程?

2026-03-18 08:01:11

本文永久链接 – https://tonybai.com/2026/03/18/building-industrial-grade-agent-skills

大家好,我是Tony Bai。

我是你的老朋友,一个正在被 AI 疯狂“内卷”的程序员。

如果你最近几个月一直在使用 Cursor、Claude Code 或者其他各种 AI 编程助手,你大概率会经历一个情绪的“过山车”:

第一天:“卧槽,太牛了!这代码它自己就写完了!”

第一周:“等等,这段逻辑有点怪,我得去修一下它的 Bug。”

第一个月:“崩溃了……我给它写了 500 行的 Prompt,它还是会在同一个地方犯错。而且,它昨天明明写对了,今天稍微换个问法,它又按老套路瞎编了一遍!”

这就是我们当前面临的真实困境。

我们正处在一个尴尬的过渡期:AI 写代码的速度远远超过了我们人类 Review 和兜底的速度。当我们试图用更长、更复杂的 Prompt 去控制 AI 时,我们发现自己变成了一个“疲于奔命的手工作坊老板”

你精心雕琢的 Prompt,就像是一本厚厚的员工手册。你把它塞给一个记忆力只有 7 秒、充满迷之自信、速度极快的“初级天才开发”。结果就是,他偶尔能超常发挥,但大多数时候,他会把事情搞砸,而且你根本不知道他为什么搞砸。

靠“玄学 Prompt”来驱动 AI 的时代,已经一去不复返了。

为什么你觉得无力?因为你在用“黑盒”对抗“黑盒”

为了解决这个问题,业界开始推出各种“技能(Skill)”或者“智能体(Agent)”框架。你可以把一套工作流、最佳实践、甚至是工具库打包成一个 Skill,让 AI 在需要的时候调用。

这听起来很完美,对吧?

于是,你开始尝试用一些自动化工具(比如 Anthropic 的 skill-creator 或者各种自研的 Agent 平台)来帮你写 Skill。你输入一句“帮我写一个分析日志的技能”,工具咔咔咔一顿输出,生成了一堆配置文件和 Markdown。

你测试了一下,好像能用。

但当你把它投入真实的生产环境时,灾难开始了:

  • 触发率成迷: 用户明明说了“帮我看看日志”,AI 却死活不加载这个 Skill。
  • 指令“漂移”,输出不稳定: 面对结构稍微不同的日志,它就开始胡编乱造。
  • 薛定谔的复现率: 同一个任务,昨天它完美执行,今天你稍微换了个问法,它就彻底无视了整个 Skill 的存在,开始自由发挥。
  • 难以迭代: 你想加个新功能,结果旧功能莫名其妙就退化了。

面对这些自动生成的代码和配置,你感到一种深深的无力感。因为对你来说,这个 Skill 是一个“黑盒”,而生成它的那个工具,是另一个“黑盒”

当系统出问题时,你甚至不知道该修改哪一行字。

打破黑盒:把 AI 技能开发,变成严谨的软件工程

如果我们要真正驾驭 AI,让它成为我们可靠的队友,而不是一颗随时会爆的定时炸弹,我们就必须抛弃“调包侠”和“按键猴子”的心态。

我们需要将 AI 技能(Agent Skill)的开发,视为一项严肃的软件工程

这也是我策划这门微专栏的初衷。我将它命名为:《打破黑盒:用工程思维构建工业级 Agent Skill》

在这门专栏中,我不会教你那些几个月后就会失效的“Prompt 奇技淫巧”。相反,我将带你深入底层,拆解一个高质量工业级 Skill 诞生的全生命周期。

我的核心观点只有两个:

  1. 不要逆势而为,必须“用 AI 制造 AI”。 面对复杂的上下文和多步推理,人类的手写能力已经触及天花板。我们必须学会熟练使用类似 skill-creator 这样的自动化工具,利用多智能体协作(Multi-Agent Collaboration)来帮我们生成、测试和优化 Skill。
  2. 绝不接受“黑盒”。 我们必须站在“上帝视角”,深刻理解这些自动化工具内部的运行机制。我们需要知道:
    • AI 是如何“阅读”和“加载”一个技能规范(Spec)的?
    • 在自动化测试中,那个负责打分的“裁判智能体(Grader)”是按照什么标准来评判好坏的?
    • 当需要评估两个版本哪个更好时,那个“盲测智能体(Blind Comparator)”是如何排除偏见,给出量化数据的?
    • 最后,那个负责迭代的“分析师智能体(Analyzer)”是如何通过分析执行轨迹(Transcript),找出失败的根本原因,并给出改进建议的?

只有看懂了裁判的打分规则,你才能写出满分的卷子。只有理解了系统底层的齿轮是如何咬合的,你才能在遇到触发率低、输出不稳定等问题时,精准地进行降维打击,而不是像无头苍蝇一样乱改 Prompt。

你将在这个微专栏中获得什么?

这门专栏共 7 讲,每一讲都是一次认知升级和实战演练:

  • 第 1 讲 | 开篇:手工作坊的终结,为什么你必须学会“用 AI 制造 AI”? (就是你现在看到的这篇)
  • 第 2 讲 | 拆解 Skill Spec:揭秘 AI “理解”与“按需加载”技能的底层逻辑
  • 第 3 讲 | 启动引擎:从“模糊意图”到“高潜草稿”的自动化生成之路
  • 第 4 讲 | 拒绝玄学:构建可量化的 Eval 断言与全自动测试流水线
  • 第 5 讲 | 盲测与进化:让 AI 裁判自己证明“新版本比老版本强”
  • 第 6 讲 | 榨干最后 1% 精度:用数据驱动的 Benchmark 彻底解决触发难题
  • 第 7 讲 | 交付与升华:从打包部署到构建“人机混合”的新一代研发体系

我希望,通过这门专栏的学习,你能完成从“被 AI 牵着鼻子走的打字员”到“能够指挥一支硅基研发车队的超级架构师”的蜕变。

在这个全新的时代,代码的生成速度不再是壁垒,如何定义规范、如何编写断言(Assertions)、如何设计基准测试(Benchmark)、如何建立评估体系(Eval),才是工程师真正的护城河。

准备好打破黑盒,迎接挑战了吗?

立即点击此处订阅专栏,或者扫描下方二维码,让我们一起,用工程思维,重新定义 AI 时代的开发范式!


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

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

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


原「Gopher部落」已重装升级为「Go & AI 精进营」知识星球,快来加入星球,开启你的技术跃迁之旅吧!

我们致力于打造一个高品质的 Go 语言深度学习AI 应用探索 平台。在这里,你将获得:

  • 体系化 Go 核心进阶内容: 深入「Go原理课」、「Go进阶课」、「Go避坑课」等独家深度专栏,夯实你的 Go 内功。
  • 前沿 Go+AI 实战赋能: 紧跟时代步伐,学习「Go+AI应用实战」、「Agent开发实战课」、「Agentic软件工程课」、「Claude Code开发工作流实战课」、「OpenClaw实战分享」等,掌握 AI 时代新技能。
  • 星主 Tony Bai 亲自答疑: 遇到难题?星主第一时间为你深度解析,扫清学习障碍。
  • 高活跃 Gopher 交流圈: 与众多优秀 Gopher 分享心得、讨论技术,碰撞思想火花。
  • 独家资源与内容首发: 技术文章、课程更新、精选资源,第一时间触达。

衷心希望「Go & AI 精进营」能成为你学习、进步、交流的港湾。让我们在此相聚,享受技术精进的快乐!欢迎你的加入!

img{512x368}


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

© 2026, bigwhite. 版权所有.

泡沫消退后的冷思考:2026年,AI 工程师的真实生存图景

2026-03-17 17:45:58

本文永久链接 – https://tonybai.com/2026/03/17/ai-engineer-survival-2026-post-hype

大家好,我是Tony Bai。

在过去几年里,“AI工程师”几乎成为了科技界最耀眼的标签。各大研报和媒体都在不遗余力地描绘一个人才奇缺、薪资突破天际的黄金时代。无数开发者涌入各大在线课程,试图为自己的简历贴上“大模型”、“Agent”、“RAG”等金字招牌。

然而,当我们把视线从光鲜亮丽的科技新闻转向一线招聘市场和开发者社区时,却看到了截然不同的景象。

近日,知名技术社区 r/aiengineering 的版主发布了一篇万字长文,以其作为一线招聘者和资深从业者的视角,揭开了当前 AI 工程领域的另一面。这篇文章没有迎合铺天盖地的炒作,而是抛出了几个令人深思的残酷事实:

  • 所谓“广泛的 AI 人才缺口”可能是一种错觉;
  • 过度依赖 AI 正在削弱开发者的核心竞争力;
  • 而科技行业的“知识折旧率”,正达到前所未有的高度。

当狂热的泡沫开始消退,我们有必要重新审视:在 2026 年,AI 工程师的真实生存环境究竟如何?未来的技术护城河又究竟在哪里?

被倒置的供需关系:从“拉动型”到“推动型”市场

关于 AI 领域存在巨大人才缺口的论调,在招聘市场的一线数据面前显得苍白无力。

该社区版主直言不讳地指出:“目前并没有‘广泛的’ AI 工程岗位需求。如果你发布一个真实的 AI 工程师职位,一天之内就会收到 300 到 500 份简历。” 这并非个例,整个科技就业市场目前都呈现出类似的拥挤态势。

为了更好地理解当前的处境,我们不妨借用经济学中的概念,将其与过去的科技红利期进行对比:

  • 过去的“拉动型(Pull)”市场:大约 12 年前,当数据工程和自动化 ETL 刚刚兴起时,行业面临着真正的结构性短缺。企业处于“拉动”状态:只要你展现出一定的逻辑能力和对数据的敏感度,哪怕经验不足,公司也愿意花钱、花时间去培养你。技能是可以习得的,潜力才是被争夺的稀缺资源。
  • 现在的“推动型(Push)”市场:如今的 AI 领域则完全相反。企业期望求职者自带极其完备的技术栈(甚至要求精通那些刚发布几个月的框架,甚至是刚发布几周的超热门工具,比如openclaw等),却极少愿意承担培训成本。数以百计的候选人为了一个岗位而拼命“推销”自己,内卷成为常态。

更具讽刺意味的是,当我们跳出科技圈,看看传统的蓝领技术工种(如高级焊接)。在这些领域,雇主依然愿意支付高昂的起薪,甚至在培训期间就支付报酬。这是因为蓝领技能基于稳定的物理定律,一旦掌握便能长期受用;而软件工程的技能,却在框架和工具的不断更迭中迅速贬值。

技能折旧与“伪效率”的陷阱

在生成式 AI 的加持下,代码生成的门槛被史无前例地降低。几句 Prompt 就能生成一个完整的 SaaS 应用原型,这让许多人产生了一种“掌握了魔法”的错觉。

但这种错觉背后,隐藏着巨大的技能危机。

大语言模型(LLM)的本质,是基于海量人类输入数据的概率预测。它擅长寻找“最短路径”,但缺乏基于物理世界常识的真正理解力和创造力。它不是在思考,而是在反刍

当开发者习惯了将一切问题抛给 AI,危险便悄然而至。

  • 丧失第一性原理的思考能力:当你不再亲自设计数据流、不再一行行推敲边界条件,而是依赖 AI 直接输出结果时,你实际上是在放弃对系统底层逻辑的掌控。如果遇到复杂架构中牵一发而动全身的非标问题,或者 AI 生成的代码出现了隐蔽的竞态条件,习惯了“一键生成”的开发者将束手无策。
  • 工具异化为“拐杖”:AI 是极其出色的工具(如强大的代码格式化器、高级字典、语法补全器和代码生成器),但它绝不能替代人类的批判性思维。如果一个开发者连撰写一段清晰的需求说明,或者理解一个基础的报错日志都需要求助于 LLM,那么他正在将自己的核心竞争力“外包”。

正如版主所警示的:“想象力是创造力的前置条件。当你过度依赖外部的‘搜索引擎’(或现在的 LLM),你实际上是在限制自己想象和构建复杂系统的能力。”

在未来,真正有价值的工程师,绝不是那些只会熟练复制粘贴 Prompt 的人,而是那些能够深刻理解业务痛点、具备严密系统思维,并将 AI 作为提效“手术刀”的架构师。

软件工程的重塑:回归本质与防御性思考

如果我们承认 AI 只是一种强大的工具,那么在 2026 年乃至于更远的未来,软件工程的发展方向将发生怎样的变化?

数据治理与所有权的回归

AI 的上限取决于其训练数据的质量。随着企业越来越意识到数据的核心资产价值,“数据保护主义”正在抬头。聪明的企业将不再盲目地将核心业务数据和架构信息投喂给公有云的 LLM,而是转向构建私有化部署、安全可控的小模型或混合架构。这意味着,理解如何在企业安全边界内有效利用 AI(如本地 RAG、数据清洗脱敏),将比单纯的 Prompt 技巧更有市场。

重拾物理世界的基石

数字世界是脆弱的,极易被复制、攻击甚至完全由机器生成。在未来,能够产生真正不可替代价值的技术创新,将不可避免地与物理世界产生更深度的融合——例如机器人技术、能源革命(核聚变、新型材料开发)以及量子计算等。这些领域无法仅靠敲击键盘和调用 API 来完成,它们需要扎实的工程物理基础和试错成本。

对技术银弹的祛魅

我们需要接受一个现实:就像过去 20 年的互联网繁荣并没有从根本上解决住房、医疗等生活成本上升的问题一样,AI 也不是解决所有结构性问题的万能药。它提高了软件生产的效率,但这部分效率的红利最终会流向何处(是转化为开发者的工资,还是变成了资本的利润),还有待时间的检验。

小结

历史的车轮滚滚向前,AI 技术的浪潮不可逆转。但作为身处浪潮之中的工程师,保持清醒的认知比盲目追逐风口更为重要

不要被短期的喧嚣蒙蔽了双眼。去培养那些 AI 无法轻易替代的能力:对复杂系统的架构设计能力、对模糊业务需求的洞察力、以及在真实物理世界中解决问题的韧性。

AI 是我们手中的剑,不要让它斩断了我们自己的思考之路。

资料链接:https://www.reddit.com/r/aiengineering/comments/1rf7myh/the_actual_state_of_ai_engineering_in_2026/


你感到“钝”了吗?

习惯了 AI 这个“认知轮椅”后,你是否也曾有过“离开 AI 不会写代码”的瞬间?在你看来,2026 年最值钱的技能是哪个?是能写出完美的 Prompt,还是能看透 AI 看不透的复杂系统?

欢迎在评论区分享你的生存感悟!


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

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

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


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

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

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

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

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


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

© 2026, bigwhite. 版权所有.

被嘲笑比 Python 还慢?扒开 Go 正则表达式的底层,看看它为了防范“系统猝死”付出了什么

2026-03-17 07:28:31

本文永久链接 – https://tonybai.com/2026/03/17/why-is-go-regex-so-slow

大家好,我是Tony Bai。

如果有人问你:在处理纯 CPU 密集型的文本匹配时,Go 和 Python 哪个快?

相信 99% 的 Go 开发者会毫不犹豫地把票投给 Go。毕竟,一门编译型的静态语言,怎么可能输给拖着 GIL 锁的解释型脚本语言?

但现实往往比小说更魔幻。

最近,在 Reddit 的 r/golang 论坛上,一张残酷的 Benchmark 跑分图引发了整个 Go 社区的剧烈震荡。一位开发者,使用极其常见的日志解析正则表达式(提取 IP、时间、URI 等),对各大语言进行了一次横评。

结果令人大跌眼镜:同样的数据集,Rust 跑了 3.9 秒,Zig 跑了 1.3 秒,而 Go 居然跑了整整 38.1 秒!整整比第一名 Zig 慢了接近 30 倍!

如果你再去翻看 Go 官方的 Issue #26623,会看到更绝望的数据:早在2018年的一次正则基准测试中,Go 不仅被 C++ 和 Rust 碾压,甚至连 Python 3、PHP 和 Javascript 都能在正则上把 Go 按在地上摩擦。

一时间,无数 Gopher 信仰崩塌:“为什么 Go 的标准库 regexp 这么慢?”、“连简单的正则都做不好,Go 凭什么做云原生霸主?”

今天,我们就来硬核扒开 Go 语言 regexp 包的底层设计和实现。你会发现,这不是 Go 团队的技术拉跨,而是一场关于“性能、安全与工程哲学”的博弈。

原罪:你以为的慢,其实是替 CGO 负重前行

面对“为什么 Go 的正则比 Python 还慢”的灵魂拷问,Go 核心团队成员 Ian Lance Taylor 给出了第一层解释。

在 Python、PHP 甚至 Node.js 中,你以为你是在运行脚本,其实它们底层都在悄悄“作弊”。这些语言的正则表达式引擎,几乎全部是用高度优化的 C 语言库(主要是 PCRE,Perl Compatible Regular Expressions)编写的。

当你在 Python 里调用 re.match() 时,它瞬间就穿透到了 C 语言的底层,享受着现代 CPU 指令集的极致加速。

那 Go 为什么不用 C?因为 Go 是一门有着“极度洁癖”的语言。

如果 Go 的标准库引入了 C 语言的 PCRE,就必须通过 CGO 来调用。而 CGO 的上下文切换成本极高,更致命的是,它会彻底破坏 Go 引以为傲的“跨平台交叉编译”能力。你再也不能在一个简单的 go build 后,把二进制文件无痛丢到任何 Alpine 容器里了。

因此,Go 团队做出了第一个艰难的决定:完全使用纯 Go 语言,从零手写一个正则表达式引擎。

脱离了 C 语言几十年的底层优化积累,用原生代码去硬刚别人的 C 引擎,这是 Go 看起来“慢”的表层原因。

但这,仅仅是冰山一角。

路线之争:为了防止系统“猝死”,Go 抛弃了速度

真正让 Go 正则变得“慢”的,是算法架构上的降维选择。这牵扯到 Go 语言的缔造者之一、大神 Russ Cox (rsc) 的一段往事。

在正则表达式的底层世界里,存在着两大流派:

  1. 基于回溯(Backtracking)的 NFA 引擎:代表人物是 PCRE(被 Python、Java、PHP 广泛使用)。
  2. 基于 Thompson NFA / DFA 的引擎:代表人物是 RE2(被 Go、Rust 采用)。

PCRE 引擎极快,它支持各种花里胡哨的语法(如前瞻断言 Lookaround、反向引用 Backreferences)。它的算法逻辑是“不撞南墙不回头”的深度优先搜索(DFS)。在匹配正常字符串时,它快如闪电。

但它有一个极其致命的死穴:ReDoS(正则表达式拒绝服务攻击)。

想象一下你写了一个看似无害的正则:

^([a-zA-Z0-9]+\s?)+$

如果黑客故意传入一个极其恶意的字符串:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!(注意最后的感叹号)。

PCRE 引擎会陷入可怕的“灾难性回溯”。它会尝试所有可能的组合,时间复杂度瞬间飙升到 O(2^n) 级。短短几十个字符的输入,能让单核 CPU 满载运行几年都算不出结果!

2019 年,互联网巨头 Cloudflare 就因为在 WAF 防火墙中写错了一个极其简单的正则表达式,CPU资源瞬间耗尽,导致全球80% 的通过 Cloudflare 代理的网站受到影响,陷入瘫痪长达 27 分钟。这就是 PCRE 回溯引擎的恐怖破坏力。

Russ Cox 在设计 Go 的 regexp 包时,定下了一条铁律:系统安全与可预测性,绝对高于单次请求的极限性能。

因此,Go 彻底抛弃了危险的回溯引擎,选择了基于 Thompson NFA 的算法(源自他之前在Google主导设计的 C++ RE2 引擎)。这种算法保证了匹配时间永远是线性复杂度 O(n)

无论黑客传入多么恶意的字符串,Go 的正则引擎绝对不会发生灾难性回溯。它牺牲了在美好情况下的极致快感,换取了在极端恶劣环境下的金身不坏。

这算是 Go 团队最顶级的“克制”吧。

硬核剖析:Go 的正则,时间到底去哪了?

既然算法是 O(n) 的,为什么 Go 依然比同样采用 RE2/DFA 思想的 Rust 慢那么多呢?

如果你去追踪 Go 官方的 Issue #19629Issue #11646,通过 pprof 分析 Go 正则匹配的 CPU 耗时,你会看到几个令人头疼的瓶颈:

1. 沉重的 UTF-8 解析税

Rust 和 C 的很多正则引擎,底层是直接在“字节(Byte)”级别游走的。而 Go 为了贯彻它对 Unicode 的原生支持,regexp 包在内部极其频繁地将输入流解码为 Rune(Go 的 Unicode 字符单位)。这种逐个解析 Rune 的操作,带来了巨大的计算开销。

2. NFA 虚拟线程的内存震荡

在 Go 的底层源码中,你可以看到耗时最高的两个函数是 (machine).add 和 (machine).step。

Go 是通过维护两个“状态队列(稀疏集)”来模拟 NFA 的并行推进的。每读取一个字符,引擎就要把所有可能的状态添加到下一个队列中。这导致了海量的内存重分配(Allocation)和切片拷贝。哪怕是匹配一个简单的长字符串,底层都在疯狂地挪动内存。

既然这么慢,为什么不把 C++ RE2 里那个极速的 DFA(确定性有限状态自动机)移植到 Go 里呢?

Issue #11646 记录了这次尝试。开发者 Michael Matloob 曾经试图将 RE2 的 DFA 移植过来,但被 Russ Cox 拦下了。原因很直接:DFA 虽然快,但它在运行时会动态生成大量的状态,如果不加以严格限制,极易引发内存耗尽(OOM)。在 Go 带有 GC 的内存模型下,频繁创建和销毁庞大的 DFA 状态缓存,会让垃圾回收器不堪重负。

于是,Go 的标准库在“安全、内存、性能”的三角博弈中,选择了妥协于现状。

社区的探索:SIMD 降维打击与 100倍加速的 coregex

官方的克制固然令人敬佩,但对于身处一线的业务开发者来说,由于正则太慢导致的 CPU 告警,是实实在在的痛点。

“既然官方不愿意改,那我们就自己造轮子!”

在近期的 Issue #26623 中,一位名为 kolkov 的开发者带着他的开源库 coregex 杀入了战场,向 Go 标准库发起了直接的挑战。

coregex 是一个完全用纯 Go 编写的正则库,它的出现直接将 Go 的正则性能拉到了与 Rust 并驾齐驱,甚至在某些场景下超越 Rust 的境地。

它是怎么做到的?它在底层祭出了几个大杀器:

  1. SIMD 预过滤(Prefilters):它使用了手写的汇编代码(AVX2/SSSE3 指令集),将正则中的静态字符串提取出来,利用 CPU 的向量化指令,一次性对比 32 个字节。像匹配 .*.txt 这种正则,速度直接飙升了 1500倍
  2. 带缓存的 Lazy DFA:它绕过了标准库每次都重算 NFA 的毛病,在运行时动态构建 DFA 缓存,大幅消除了内存分配。
  3. 写时复制(COW)的捕获组:标准库在处理提取子串时会疯狂分配切片。coregex 通过切片状态共享,让内存分配直接减少了 50%。

在 kolkov 提供的 CI 跑分中,在 6MB 的输入下,coregex 处理邮箱、URI 的耗时仅为 1.5 毫秒,而标准库耗时高达 260 毫秒。足足快了 170 倍!

然而,这段极其硬核的改进,依然很难入Go团队法眼,更不用谈在短期内被合并进 Go 的标准库。

一方面,Go 官方目前正在推进自己的内建 SIMD 方案(Issue #73787),不想接入手写的汇编代码;另一方面,社区大牛 Ben Hoyt 在使用 coregex 时发现,如果开启 Longest() 模式(最长匹配模式),这个库的性能会发生严重退化。

这再次印证了标准库开发的残酷:在某几个特定场景下跑到全宇宙第一很容易,但要在一套 API 里无死角地兜底全世界所有的奇葩正则输入,难如登天。

在 Go 中写正则的正确姿势

大致了解了底层原理,回到日常开发中,我们该如何应对 Go 正则的性能瓶颈?作为高级 Go 开发者,请务必将以下三条军规刻在脑子里:

第一条:能不用正则,就坚决不用

如果你只是想检查字符串是否包含子串,或者进行简单的前后缀匹配,永远优先使用 strings.Contains()、strings.HasPrefix() 等内置函数。 它们底层有优化的实现,在这样简单场景下,速度是 regexp 包不可比拟的。

第二条:将编译前置,远离循环

如果你翻看新手代码,最常见的低级错误就是在 for 循环或者每次 HTTP 请求里调用 regexp.Compile()。

正则的编译过程(生成 NFA 字节码)极其消耗 CPU。请永远在全局变量或 init() 函数中使用 regexp.MustCompile(),将其编译好并复用。Go 的 Regexp 对象是并发安全的,随便多 Goroutine 调用。

第三条:在极端性能要求下,打破“洁癖”

如果你的核心业务(比如高频日志清洗、海量数据 ETL)确实被 regexp 卡住了脖子,不要硬抗。

你可以选择引入通过 CGO 调用 PCRE的Go binding库(比如https://github.com/GRbit/go-pcre),但要注意防范 ReDoS 攻击,或google/re2的Go binding(比如https://github.com/wasilibs/go-re2),又或是在业务侧尝试社区的野路子 coregex。在生存面前,架构的“洁癖”是可以适当妥协的。

小结

“为什么 Go 的正则这么慢?”

这并非一个简单的工程失误。它是一道分水岭,隔开了“追求跑分好看的玩具代码”与“守护千万级并发集群的生产级设计”。

Russ Cox 宁愿忍受整个开源界的群嘲,也没有为了刷榜而去引入危险的回溯引擎。这或许就是 Go 语言能够成为云原生时代头部语言的原因:不盲目追求上限的巅峰,而是死死守住安全下限。

参考资料

  • https://www.reddit.com/r/golang/comments/1rr2evh/why_is_gos_regex_so_slow/
  • https://github.com/golang/go/issues/26623
  • https://github.com/golang/go/issues/19629
  • https://github.com/golang/go/issues/11646
  • https://swtch.com/~rsc/regexp/

今日互动探讨:

在你的日常开发中,有没有被由于“写了糟糕的正则表达式”而导致 CPU 飙升 100% 的惨痛经历?你又是如何排查和优化的?

欢迎在评论区分享你的血泪史


认知跃迁:读懂底层机制,才能看透系统架构的本质

从放弃 CGO 选择纯 Go 实现,到防范 ReDoS 采用 NFA,再到社区为了榨干 CPU 性能而引入 SIMD。Go 语言的每一个看似“不合理”的设计背后,都隐藏着深邃的系统级考量。

然而,令人遗憾的是,很多开发者写了五六年的 Go 代码,遇到性能瓶颈依然只能靠“瞎猜”和“重启”。他们对 Go 的内存逃逸、Goroutine 调度机制以及标准库的底层数据结构一无所知。

如果你渴望突破“熟练调包侠”的瓶颈,想要像 Russ Cox 这样的顶级大厂架构师一样,看透 Go 语言背后的底层逻辑,建立起自己坚不可摧的技术护城河——

我的极客时间专栏 Tony Bai·Go语言进阶课 正是为你量身定制。

在这 30+ 讲极其硬核的内容中,我不仅带你剥开语法糖,深挖 Goroutine 调度、Channel 哲学;更会带你全面吃透 Go 的工程化实践,把底层性能调优背后的逻辑一次性讲透。

目标只有一个:助你完成从“Go 熟练工”到“能做顶级架构决策的 Go 专家”的蜕变!

扫描下方二维码,加入专栏。不要用战术上的勤奋,掩盖战略上的懒惰。让我们一起用架构师的视角,重新认识 Go 语言。


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

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

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


原「Gopher部落」已重装升级为「Go & AI 精进营」知识星球,快来加入星球,开启你的技术跃迁之旅吧!

我们致力于打造一个高品质的 Go 语言深度学习AI 应用探索 平台。在这里,你将获得:

  • 体系化 Go 核心进阶内容: 深入「Go原理课」、「Go进阶课」、「Go避坑课」等独家深度专栏,夯实你的 Go 内功。
  • 前沿 Go+AI 实战赋能: 紧跟时代步伐,学习「Go+AI应用实战」、「Agent开发实战课」、「Agentic软件工程课」、「Claude Code开发工作流实战课」、「OpenClaw实战分享」等,掌握 AI 时代新技能。
  • 星主 Tony Bai 亲自答疑: 遇到难题?星主第一时间为你深度解析,扫清学习障碍。
  • 高活跃 Gopher 交流圈: 与众多优秀 Gopher 分享心得、讨论技术,碰撞思想火花。
  • 独家资源与内容首发: 技术文章、课程更新、精选资源,第一时间触达。

衷心希望「Go & AI 精进营」能成为你学习、进步、交流的港湾。让我们在此相聚,享受技术精进的快乐!欢迎你的加入!

img{512x368}


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

© 2026, bigwhite. 版权所有.

真相调查:Go 语言真的消灭了 Undefined Behavior 吗?

2026-03-16 08:27:35

本文永久链接 – https://tonybai.com/2026/03/16/go-language-eliminated-undefined-behavior-truth-investigation

大家好,我是Tony Bai。

在系统编程的古老传说中,流传着一个关于“鼻恶魔”(Nasal Demons)的笑话。

这个梗源自 comp.std.c 新闻组,它是对 C/C++ 语言中“未定义行为”(Undefined Behavior,以下简称 UB)最生动也最恐怖的诠释。根据 ISO C++ 标准,如果你的代码触犯了 UB(例如数组越界、有符号整数溢出、空指针解引用),编译器可以“为所欲为”。

这种“为所欲为”不仅包括程序崩溃,还包括产生错误的结果、损坏数据,甚至——虽然只是笑话——让恶魔从你的鼻孔里飞出来。换句话说,一旦触碰 UB,程序的所有保证瞬间失效。

2009 年,Go 语言横空出世,高举“云原生时代系统语言”的旗帜,承诺提供比 C++ 更高的安全性、更快的编译速度和更简单的并发模型。Go 的拥趸们津津乐道于它的内存安全特性,仿佛 Go 已经彻底终结了 UB 的噩梦。

但真相果真如此吗?

近日,我翻阅了一份珍贵的历史资料——2013 年发生在 golang-nuts 邮件组的一场深度辩论。对话的一方是 Go 语言曾经的顶级贡献者 Dave Cheney,另一方是 Go 核心团队成员、gccgo 的作者 Ian Lance Taylor。

这场发生在这个语言童年时期的对话,揭示了一个令人背脊发凉又引人深思的事实:Go 并没有完全消灭未定义行为,它只是将 UB 赶进了一个更隐秘、更危险的角落——并发。

本文将带你层层剥开 Go 语言规范的表皮,调查“未定义行为”在 Go 中的真实生存状态,并探讨这对我们编写高质量代码意味着什么。

用“定义”换取“安全”——Go 的显式哲学

要理解 Go 做了什么,我们首先得明白 C/C++ 为什么保留 UB。Ian Lance Taylor 指出,C/C++ 保留 UB 本质上是为了性能——允许编译器假设“坏事永远不会发生”,从而进行激进的优化。

Dave Cheney 的疑问直击灵魂:“Go 规范中几乎看不到‘undefined’这个词,这种设计如何影响了 Go 的安全性与性能?”

答案是:Go 选择了一条确定性(Determinism)优先的道路。Go 语言规范以一种近乎偏执的态度,将绝大多数在 C/C++ 中属于 UB 的行为,都进行了严格的“定义”。即便是在错误场景下,Go 也要保证行为是可预测的

整数溢出的“确定性”承诺

在 C 语言中,有符号整数(Signed Integer)的溢出是经典的 UB。编译器有权假设溢出永远不会发生,从而将 x + 1 > x 优化为恒真(Always True),这曾导致过无数的安全漏洞。

但在 Go 语言规范中,对此有着截然不同的定义:

无符号整数:运算结果严格按照 2^n 取模。这意味着高位被丢弃,程序可以依赖这种“回绕(Wrap-around)”行为。

有符号整数:运算可以合法地溢出(legally overflow)。结果由有符号整数的表示方式(通常是补码)、运算类型和操作数确定性地定义。溢出不会导致运行时 Panic。

最关键的是,Go 规范明确禁止编译器进行危险的假设:“编译器不得假设溢出不会发生。例如,它不得假设 x < x + 1 总是为真。”

代码实证:

// https://go.dev/play/p/5CZVVU-SITX
package main

import "fmt"

func main() {
    // 1. 有符号整数溢出 (Signed Overflow)
    var a int8 = 127
    // 在 C 语言中这是 UB,但在 Go 中这是明确定义的
    b := a + 1
    fmt.Printf("int8: %d + 1 = %d\n", a, b)
    // 输出: 127 + 1 = -128 (确定性的回绕)

    // 2. 编译器禁止做的优化
    // 如果编译器假设溢出不发生,它会把这个判断优化掉
    if b < a {
        fmt.Println("发生溢出:b 确实小于 a")
    } else {
        fmt.Println("未发生溢出逻辑(Go 中不会走到这里)")
    }

    // 3. 无符号整数溢出 (Unsigned Overflow)
    var c uint8 = 255
    d := c + 1
    fmt.Printf("uint8: %d + 1 = %d\n", c, d)
    // 输出: 255 + 1 = 0 (严格的 Modulo 2^n)
}

Go这么做的代价是Go 编译器失去了一些数学优化机会(例如不能简单地消除某些循环边界检查)。但也消除了因编译器“自作聪明”而导致的逻辑崩塌,保证了不同平台下的行为一致性。

数组越界的“必杀令”

缓冲区溢出(Buffer Overflow)是网络安全史上最大的杀手。C/C++ 将越界访问视为 UB,允许攻击者通过越界读取敏感内存或覆盖返回地址,进而控制系统。

Go 对此零容忍:越界必须触发 Panic。

无论是在栈上分配的数组,还是在堆上分配的切片,Go 编译器都会在每一次访问操作前(除非能静态证明安全)插入一段 Bounds Check(边界检查)指令。一旦越界,程序立即停止,绝不含糊。

代码实证:

// https://go.dev/play/p/-CqDpIDr0BC
package main

import "fmt"

func main() {
    // 定义一个长度为 3 的切片
    s := []int{1, 2, 3}

    // 模拟一个动态索引(避免编译器在编译期直接报错)
    index := getIndex() 

    fmt.Println("尝试访问索引:", index)

    // 这里会触发 Runtime Panic
    // 错误信息明确:runtime error: index out of range [3] with length 3
    val := s[index] 

    fmt.Println("这行代码永远不会执行", val)
}

func getIndex() int {
    return 3
}

这种边界检查是在运行时(Runtime)介入,抛出 Panic,打印堆栈信息。因此会带来运行时性能损耗。虽然现代 Go 编译器引入了 BCA(边界检查消除)技术,但在无法静态分析的场景下,这就是必须缴纳的“安全税”。

空指针的“硬着陆”

在 C 语言中,解引用一个空指针是 UB。编译器有时会优化掉判空逻辑,因为它认为“既然你解引用了,那指针肯定不为空”,导致后续的安全检查失效。

Go 规定:解引用 nil 指针必须触发 Panic。

这通常是通过 CPU 的硬件异常(SIGSEGV)来捕获的。Go 运行时会接管这个硬件信号,并将其转化为一个可恢复的 Go Panic,而不是让进程直接 Core Dump 或进入不可预测的僵死状态。

代码实证:

// https://go.dev/play/p/hlyZks1dGRf
package main

import "fmt"

type User struct {
    Name string
}

func main() {
    var u *User // u 默认为 nil

    fmt.Println("准备访问 nil 指针...")

    // 在 C 中这是 UB,可能导致程序崩溃或更糟的情况
    // 在 Go 中,这不仅会 Panic,还可以被 Recover 捕获
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("捕获到恐慌:", r)
            // 输出: runtime error: invalid memory address or nil pointer dereference
        }
    }()

    // 触发 Panic
    fmt.Println(u.Name)
}

综上,我们可知:在单线程维度,Go 确实几乎消灭了 Undefined Behavior。它通过强制规定行为(Wrapping, Panicking),将“未定义”变成了“定义明确的错误”。即使程序写错了,它的错误方式也是确定的,而非随机的。

房间里的大象——数据竞争

如果文章到这里结束,那么 Go 就是一个完美的、绝对安全的语言。

但 Ian Lance Taylor 随后抛出了一个重磅炸弹:

“However, Go does have undefined behavior: if your program has a race condition, the behaviour is undefined.”
(然而,Go 确实存在未定义行为:如果你的程序存在数据竞争,那么行为就是未定义的。)

这就是 Go 语言安全神话中最大的裂痕。

在 Rust 中,编译器借用检查器(Borrow Checker)会在编译期阻止数据竞争,因此 Rust 可以自豪地宣称“无数据竞争”。但 Go 选择了更简单的并发模型,允许 Goroutine 共享内存。

一旦发生数据竞争(Data Race),即多个 Goroutine 同时访问同一块内存且至少有一个是写操作,Go 就不再提供任何保证。

为什么数据竞争是真正的 UB?

很多 Gopher 认为数据竞争只是“读到了旧数据”或者“计数器少加了 1”。这是一种极其危险的误解。在多核 CPU 和现代编译器优化的加持下,数据竞争在 Go 中可能导致内存安全破坏

这主要源于 Go 的多字数据结构(Multi-word Data Structures)

接口(Interface)的“撕裂”

Go 的 interface 在底层是由两个机器字组成的:{type_ptr, data_ptr}。

  • type_ptr 指向具体类型的元数据(如方法表)。
  • data_ptr 指向具体的数据值。

假设我们有一个全局接口变量 var i interface{},以及两个实现类型 type A 和 type B。

  • Goroutine 1 试图将 i 赋值为 A{}。
  • Goroutine 2 试图将 i 赋值为 B{}。

如果没有加锁,Goroutine 3 可能会读到一个“弗兰肯斯坦”般的怪物接口:它的 type_ptr 来自 A,但 data_ptr 却指向 B 的数据!

当你调用这个接口的方法时,程序会尝试用 A 的方法表去操作 B 的内存布局。这会导致什么?

如果运气好,你会得到Panic(类型断言失败或非法内存访问)。

反之,如果运气不好,那远程代码执行(RCE)的攻击者可以精心构造内存布局,利用这种类型混淆(Type Confusion)来劫持控制流。

切片(Slice)的“越界”

切片由 {ptr, len, cap} 三个字组成。数据竞争可能导致你读到了新的 len(变得很大),但 ptr 还是旧的(指向一个小数组)。结果是你拥有了一个长度远超底层数组容量的切片,这让你能够读取甚至修改不属于该切片的任意内存——这正是 C 语言缓冲区溢出的翻版。

这,就是 Go 中的 Undefined Behavior。 它不是“鼻恶魔”,但它是真实存在的安全黑洞。

那些“未指明”的灰色地带

除了致命的 UB,讨论中还涉及了 Go 语言规范中的另一种存在:未指明行为(Unspecified Behavior)实现定义行为(Implementation-Defined Behavior)

这些行为虽然不会导致内存破坏,但同样破坏了程序的“确定性”。

Map 的迭代顺序

在 Go 中,for k, v := range m 的顺序是故意未定义的。

Ian 解释说,这是为了防止开发者依赖某种特定的哈希实现顺序。Go 运行时甚至在每次迭代开始时引入了随机种子(迭代器会在map bucket 数组中随机选取一个起始位置向后遍历),强制让顺序变得不可预测。

这是一个非常有智慧的设计:通过强制随机化,逼迫开发者编写不依赖顺序的健壮代码。

表达式求值顺序:在“确定”与“未指明”之间

在 C/C++ 中,f(g(), h()) 中 g() 和 h() 谁先执行是未定义的(Undefined Behavior 或 Unspecified Behavior),这取决于编译器实现。

Go 语言规范对此做了更严格的规定,但依然保留了一块微妙的“灰色地带”。

确定的部分(Defined):

Go 规定,在求值表达式的操作数、赋值语句或返回语句时,所有的函数调用、方法调用和通信操作(Channel receive)都必须按照词法上从左到右的顺序执行。

例如,在赋值语句 y[f()], ok = g(h(), i()+x[j()], <-c), k() 中,函数调用和通信的发生顺序被严格锁定为:

f() -> h() -> i() -> j() -> g() -> k()。

未指明的部分(Unspecified):

然而,规范同时也指出:并没有规定上述事件与表达式求值、索引操作、以及变量 y 的求值之间的顺序。

这意味着,虽然函数调用的相对顺序是固定的,但涉及副作用(Side Effects)的变量读写顺序可能是不确定的。来看 Spec 中的经典反例:

a := 1
f := func() int { a++; return a }

// x 可能是 [1, 2] 也可能是 [2, 2]
// 因为 a 的求值与 f() 的执行顺序未定义
x := []int{a, f()}
println(a, x)

// --- 示例:map 字面量中 key/value 的求值顺序未定义 ---
b := 1
g := func() int { b++; return b } // g() 会修改 b

// 若 b 先被求值:key=1, value=2  → m = {1: 2}
// 若 g() 先被执行:key=2, value=2 → m = {2: 2}
// Go 规范不保证 key 表达式与 value 表达式谁先求值
m2 := map[int]int{b: g()}
println(b, m2[b])

虽然 Go 比 C/C++ 确定得多,但在编写依赖于求值顺序的副作用代码(例如在参数列表中修改全局变量)时,依然可能会掉进“未指明行为”的陷阱。因此,最好不要在单行表达式中依赖复杂的副作用顺序。

浮点数转换的幽灵

讨论中有开发者 提到了 float64 转换为 uint8 的行为。在早期的 Go 版本中,对于溢出值的处理可能依赖于底层硬件指令(x86 vs ARM),从而表现出不一致。

虽然 Go 正在逐步收紧这些规范,例如 #76264 提案(尚未落地)正试图统一浮点转整数的饱和行为,但这提醒我们:即使是强类型语言,在跨平台移植时也可能遇到底层架构带来的“方言”差异。

如何在充满 UB 的世界里生存?

既然 Go 没有彻底消灭 UB,作为开发者,我们该如何自保?

视 -race 为生命线

Ian Lance Taylor 的警告应该被打印在每个 Go 开发者的工位上。

建议

  • 单元测试必须开启 -race 标志运行。
  • 在 CI/CD 流水线中,竞态检测是不可跳过的阻断性步骤。
  • 不要相信“我的并发逻辑很简单,不会出错”,人脑无法模拟现代 CPU 的乱序执行。

敬畏 unsafe

Go 的 unsafe 包是通往 C 语言 UB 世界的后门。使用 unsafe.Pointer 进行类型转换时,你实际上是在对编译器说:“我知道我在做什么,出了事我负责。”

除非你是编写底层运行时或极致性能库的专家,否则在业务代码中绝对禁止使用 unsafe。一旦使用,你必须熟读《Go 内存模型》和《垃圾回收器写屏障规则》。

理解“实现定义”与“未定义”的区别

  • 未定义(UB):可能导致 Crash、数据损坏、安全漏洞(如数据竞争)。零容忍。
  • 未指明/实现定义:不同版本或平台可能表现不同(如 Map 顺序)。不要依赖它。
  • 已定义:Go 承诺的行为(如整数回绕)。可以依赖,但需知晓代价。

小结:完美的幻象与工程的现实

通过这次“真相调查”,我们得出的结论可能有些令人沮丧,但也足够清醒:

Go 语言并没有彻底消灭 Undefined Behavior。它只是通过牺牲一部分性能和增加运行时检查,将 UB 的“攻击范围”从 C/C++ 的“随处可见”缩小到了“并发数据竞争”和“不安全代码”这两个特定的领域。

这是一种极其成功的工程权衡。它让 Go 在保持高性能的同时,为 99% 的日常编码提供了坚实的安全保障。

然而,作为 Gopher,我们不能沉浸在“绝对安全”的幻象中。我们必须意识到,当我们敲下 go func() 的那一刻,当我们试图共享一个指针的那一刻,我们正行走在悬崖的边缘。

Go 给了我们围栏(定义明确的行为),但也给了我们梯子(并发与 Unsafe)。能否不跌入 UB 的深渊,最终取决于我们是否遵守工程的纪律。

资料链接:https://groups.google.com/g/golang-nuts/c/MB1QmhDd_Rk


你遇到过“鼻恶魔”吗?

哪怕是 Go 这样严谨的语言,在并发面前也会露出锋利的牙齿。在你的开发生涯中,是否遇到过那种因为没开 -race 而在生产环境产生的“灵异事件”?你对 Go 这种“用性能换确定性”的哲学怎么看?

欢迎在评论区分享你的“探案”心得!


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

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

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


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

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

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

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

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


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

© 2026, bigwhite. 版权所有.