Harness Engineering:驯服复杂的艺术

Harness Engineering

前两天跟朋友约了顿火锅,席间聊起各自最近的项目,大家的感慨出奇的一致:项目做大了以后,维护起来简直是噩梦。那种感觉就像是家里堆满了东西,想找个杯子都得翻箱倒柜半天。

这让我想起了OpenAI之前提到的一个词——Harness Engineering

这词儿可能你没听说过,直译过来是"马具工程"。你知道马具是干嘛的吗?就是套在马身上的各种装备——缰绳、马鞍、马镫、胸带。有了这些,人才能控制住这匹有自己想法的动物,让它朝着你想去的方向跑,而不是想往哪就往哪。

代码这东西,到了一定规模,就跟野马差不多。它有自己的一套逻辑,有时候跑着跑着就不知道去哪了。你以为只是改个bug,结果改出一串新的问题;你以为只是在A模块加了个功能,结果B模块莫名其妙地挂了。

这时候你需要的不是更多更聪明的人,而是一套像样的"马具"。

驯服之前

回想一下你刚接手一个新项目的日子。可能是别人留下的代码,也可能是自己几个月前写的但已经忘了是什么逻辑的代码。没有文档,或者文档是过时的;没有统一的代码风格,三个人能写出三种不同的写法;测试用例基本靠运气,有的模块甚至一个测试都没有;部署全靠一个人在终端里敲命令,每次都要翻翻之前的聊天记录看看步骤是什么。

我见过最离谱的一个项目,上线流程是这样的:开发人员在本机改好代码 → 打个包发到群里 → 运维人员手动下载 → 上传到服务器 → 手动执行一堆命令 → 然后祈祷一切正常。有一次运维人员不小心执行了旧版本的包,导致线上数据错乱了几个小时,最后还是靠翻日志才找到原因。

这种状态下,开发效率基本看脸。有时候一天能改完三个bug,有时候一个bug能改三天。不是能力问题,是环境问题。就像你想让一匹没套马具的马听指挥,那得靠运气和缘分。今天马心情好,你指东它就往东;明天马心情不好,你往东它偏要往西,还顺便把你掀翻在地。

更糟糕的是,这种状态下的人很容易陷入一种恶性循环:每次改动都小心翼翼,生怕碰到什么不该碰的东西;小心翼翼导致效率低下,任务越积越多;任务多了就更加匆忙,匆忙中更容易出错;出了问题又要花更多时间去修……久而久之,整个团队的士气都被磨没了。

套上马具

那"马具"具体是什么呢?我觉得大概有这么几样,每一件都有它的用处:

缰绳——CI/CD

缰绳的作用是什么?是引导方向,是控制速度。持续集成和持续部署就是你的缰绳。你给代码设定了一条路,它就得按着这条路走。每次提交代码自动跑测试,测试过了才能合并;合并了自动构建,构建成功了自动部署。这不是限制自由,这是让你省心。

以前你可能需要手动拉代码、运行测试、打包、部署,每一步都可能出错。现在这些事情都交给系统自动完成,你只需要关注代码本身。而且,当测试失败的时候,你能在第一时间知道问题在哪,而不是等到上线后才发现。

我推荐的一个实践是:把整个流程可视化。在Slack或者钉钉里建个频道,每次代码提交、测试结果、部署状态都自动发一条消息出来。这样团队里每个人都能看到项目的健康状况,出问题也能及时发现。

马鞍——代码规范

马鞍的作用是让人坐得稳,骑得舒服。统一代码风格就像马鞍,不管谁写的代码,看起来都差不多,读起来不会觉得跳戏。这里的关键不是什么风格更好,而是大家都用一样的。

我知道有些人会说:代码风格这种事为什么要统一?我写代码有自己的习惯,改起来不舒服。但问题是,代码不是写完就扔的,它会被人反复阅读和修改。当你习惯了某种风格,别人写的风格差异很大的代码读起来就很累。而当你习惯了统一的风格,阅读效率会高很多。

更重要的是,统一的代码风格能减少很多无谓的争论。你不用每次review的时候因为一个缩进的大小跟人吵半天,也不用因为变量命名风格不一样而犹豫不决。工具会帮你搞定这些,你只需要关注业务逻辑。

现在的工具已经很成熟了,Prettier、ESLint、Black、gofmt……几乎每种语言都有对应的工具。把这些工具配置好,并在提交前自动运行,就能保证所有代码都符合统一的风格。

马镫——工具链

马镫的作用是什么?是让你上下马更方便,骑得更稳。工具链也是一样,好的工具链能让重复劳动自动化,让你把精力放在更有价值的事情上。

举几个例子:

格式化代码——不用手动调整缩进、空格、换行,一个命令搞定;
生成API文档——不用手动维护一份和代码可能不一致的文档,从代码注释自动生成;
创建新模块——不用每次都手动创建一堆文件,一个命令生成完整的脚手架;
数据库迁移——不用手动写SQL脚本,通过版本控制的迁移文件自动执行。

这些工具看似小,但累积起来能省下大量的时间。更重要的是,它们能减少人为错误。当重复劳动被自动化后,犯错的空间就小了很多。

我建议每个团队都花点时间去评估现有的工具链,看看哪些地方可以改进。不要因为"我们一直这么做"就拒绝改变,也许一个简单的工具就能让整个流程顺畅很多。

缰扣——版本控制策略

缰扣的作用是连接和控制,让整个系统成为一个整体。版本控制策略就是你的缰扣,它定义了大家怎么协同工作。

怎么分支、怎么合并、怎么发布,这些得有明确的规定。不是死板的流程,而是让大家有共识。这样就不会有人不知道该往哪个分支提交代码,也不会因为搞错分支导致事故。

常见的一种做法是采用Git Flow:master分支只存放稳定版本;develop分支用于日常开发;每个新功能从develop拉一个feature分支,开发完成后合并回develop;发布时从develop拉一个release分支,测试通过后合并回master和develop。

当然,不是所有团队都需要这么复杂的流程。小团队可能只需要一个master分支就够了。关键是大家要有一致的认知,并且把这种认知固化下来,写成文档,让新加入的人也能快速理解。

设计哲学

聊完具体的"马具",我想再聊聊背后的设计哲学。因为工具和方法都是死的,但让这些工具真正发挥作用的,是一套正确的思维方式。

简约,而非繁复

好的马具不会给马增加负担,反而会让它跑得更轻松。工程实践也一样。

我见过一些团队,为了追求"正规化",搞了一堆复杂的流程:各种审批、多层code review、强制要求每个测试覆盖率必须达到100%。结果呢?大家把更多精力花在应付流程上,而不是写代码。就像给一匹马套上过重的马具,它根本跑不动。

简约的意思是:每一条规范、每一个工具,都要回答一个问题——它真的让事情变简单了吗?如果不能,就删掉。宁可少一点,也要精一点。

约束,带来自由

这听起来可能有点矛盾。马具不就是一套套在马身上的约束吗?缰绳限制它想往哪跑,马鞍限制它怎么坐。但恰恰是这些约束,让马和骑手能够协作,共同完成任务。

代码也一样。你可能会觉得,统一的代码风格是限制个人发挥;自动化测试是限制快速迭代;严格的发布流程是限制快速响应。但想想看,如果没有这些"约束",每次改bug都要担心牵一发而动全身,每次上线都要祈祷不出问题,这真的是"自由"吗?

真正的自由,是在边界之内,你可以放心地做任何事情。你知道只要遵守规则,系统就会稳定地运行。这种安全感,才是工程师最需要的自由。

契合,而非套用

马具要适配马的体型、性格,甚至用途。一匹用于长途旅行的马和一匹用于赛马的马,需要完全不同的装备。

工程实践也是一样,不能生搬硬套。大厂的方法论固然有参考价值,但不一定适合你的团队和项目。一个十人的初创团队,如果照搬几百人团队的那套复杂流程,只会把自己累死。

你需要问自己:我的团队规模多大?项目的技术栈是什么?业务的变化速度如何?团队的成熟度如何?根据这些来设计适合你的"马具"。

契合的另一个层面是渐进。不要试图一次性给项目套上全套马具,那会让马受惊的。一次加一件,让团队适应,让项目消化。就像驯马师驯马一样,一点点建立信任,一点点让马习惯被控制。

爱马仕的启示

Harness Engineering

说到马具,就不得不提 Hermès——那个大家熟知的奢侈品牌。可能很多人不知道,爱马仕最早就是做马具起家的。

19世纪的巴黎,马车是贵族的主要交通工具。蒂埃里·爱马仕(Thierry Hermès)最初就是为贵族制作高品质的马鞍和马具。他们的马具之所以能出名,不是因为装饰华丽、花里胡哨,而是因为——好用。每一件马具都是为具体的马、具体的用途量身定做,贴合到极致。

后来汽车取代了马车,爱马仕并没有死守马具不放,而是转型做起了皮具和配饰,把那种"精益求精、为用户量身打造"的精神带到了新的领域。今天,当你看到一个爱马仕的包,它背后依然是那种"极致契合"的哲学。

我们的工程实践不也应该这样吗?不是为了套用而套用,不是为了看起来"高大上"而折腾。每一项规范、每一个工具,都应该像爱马仕的马具一样——为你的项目量身打造,贴合到极致。

当然,我不是说你要花几个月去打磨你的"马具"。但至少要有这种心态:去想清楚什么是真正适合你的,而不是别人用什么你也用什么。

人,而非工具

最后,也是最重要的一点:马具最终是为人服务的。不管是骑手还是马,最终目标都是让它们配合得更好。工程实践也一样,它的目的是为了让团队协作更顺畅,让每个人都能更专注地做有价值的工作。

工具和方法不是目的,人才是。不要为了用某个工具而用某个工具,不要为了追求"最佳实践"而忽视团队的实际情况。

我见过一个团队,花了三个月搭建了一套完美的CI/CD系统,结果没人会用,最后成了摆设。另一个团队,只是写了一个简单的checklist,每次上线前照着打勾,反而效果很好。原因很简单——后者符合他们的工作习惯,前者则成了负担。

持续进化

马具需要定期检查、调整。马长大了,可能需要换个更大的马鞍;马的状态变了,可能需要调整缰绳的松紧。

工程实践也是如此。项目在变,团队在变,技术在变。一套三年前完美的方案,今天可能已经不适用了。定期审视你们的"马具":哪些还在发挥作用?哪些已经成了累赘?有哪些新的工具值得尝试?

保持开放的心态,保持改进的动力。这不是说每次都要大改,而是小步迭代,持续优化。

设计不是一开始就追求完美,而是在不断的试错中找到平衡。马具如此,工程实践亦然。

实际操作

说这些可能有点虚,我讲个真事儿。

半年前我接手了一个项目,是个电商平台的订单系统。代码量不算特别大,大概十万行左右,但维护起来真的头疼。没有自动化测试,每次上线前都要人工跑一遍核心功能;部署脚本写在一台个人电脑上,只有那台电脑能正常部署;代码风格五花八门,有的人用驼峰,有的人用下划线,还有的人混着用;几乎没有文档,想了解一个模块的功能只能读代码。

最尴尬的一次,客户在周五晚上打电话来说支付功能不工作了。我们排查了半天,发现是一个月前改的一个配置导致支付回调接口的签名验证失败。而那个改动,当时没有做任何测试,也没有做代码review。

后来我花了点时间,给这个项目套上了"马具"。整个过程大概持续了三个月,我们一点点改进:

第一个月:CI流水线

首先把部署脚本迁到了GitHub上,并且配置了GitHub Actions。每次push代码到master分支,自动运行测试、构建、部署到测试环境。测试环境验证通过后,再手动触发部署到生产环境。

刚开始测试覆盖率只有15%,很多模块根本没有测试。我们没有追求一下子把所有模块都加上测试,而是先从核心模块开始——订单创建、支付、发货这些最关键的流程。等到这些流程有了足够的测试覆盖,我们才慢慢扩展到其他模块。

第二个月:代码规范

团队里有几个人对代码风格很执着,经常在review的时候因为一个小问题争论不休。于是我统一配置了Prettier和ESLint,并且在pre-commit hook里加上自动格式化。

刚开始大家有点不习惯,觉得工具改的格式不符合自己的审美。但过了一段时间,大家就发现了一个好处:review的时候不再浪费时间在格式问题上,而且改别人的代码变得容易多了——因为所有代码的风格都一样。

第三个月:文档和工具

我们做了一个硬性规定:每个新功能都得配上简单的README,说明这个功能是干嘛的、怎么用、怎么测试。不用写得多完美,但得让后来的人能看懂。

同时也整理了一些常用工具:数据库迁移脚本、日志查看工具、调试脚本。把这些工具放在一个统一的目录里,并且写清楚怎么用。

结果呢?

上线事故从一个月两三次降到半年一次。改bug的时间少了一半,大家能花更多时间做新功能。最开心的是,周末不用再担心生产环境出问题——因为很多事情在开发阶段就发现了,而且即使出了问题,我们也能通过日志和监控快速定位。

团队氛围也变好了。以前大家上班总是提心吊胆,生怕哪个地方又出了问题;现在大家都更有信心去做新的尝试,因为知道即使搞砸了也有兜底的办法。

不是一蹴而就

说到这儿可能有人会问:"我项目已经很乱了,现在搞这些还来得及吗?"

我觉得什么时候都不晚。当然,如果能在项目一开始就建立这些规范是最理想的。但现实是,大多数项目都是一边写一边补课。没关系,慢慢来。

你可以先从最痛的地方开始。如果测试覆盖太低导致上线经常出问题,那就先加测试;如果部署太复杂经常出错,那就先搞自动化部署;如果代码风格太乱影响review,那就先统一规范。不必什么都一起搞,一个一个来,每次解决一个问题就好。

这里有个实用的优先级排序方法:按影响范围和紧急程度来排。影响大又紧急的,优先解决;影响小又不紧急的,往后排。这样你就能在有限的时间内做出最大的改进。

还有一个建议:不要指望一次性就能做到完美。工程实践是一个持续改进的过程,今天做到70分就先接受70分,明天再想办法提高到75分。重要的是保持前进,而不是追求一步到位。

最后的话

Harness Engineering不是什么高深的技术,它更像是一种态度。承认代码会变复杂,承认人的精力有限,承认需要工具来辅助。这不是偷懒,这是聪明地工作。

就像老话说得好,磨刀不误砍柴工。把时间花在建立良好的工程实践上,看起来像是"非生产时间",但长远来看,这是回报率最高的投资。

其实这篇文章讲的都不是什么新鲜东西,这些都是软件工程领域几十年来的经验和教训。但我发现,很多人知道这些道理,但在实际项目中却很难坚持。原因有很多:工期紧张、团队不配合、领导不理解……但我想说的是,不管环境多么困难,总能找到一些可以改进的地方。哪怕只是配置一个自动格式化工具,哪怕只是写一份简单的文档,这些小改进累积起来,就能让整个项目变得越来越好。

希望这篇文章能给你一点启发。如果你有更好的工程实践想法,欢迎来交流。毕竟,驾驭代码这匹野马,我们需要彼此的经验。