云加社区

3个月用Harness重写90万行旧代码:踩过的坑都在这

关注腾讯云开发者,一手技术干货提前解锁👇
Image
Image
Image

开发者公众号专属群聊

扫码加入获取更多一手教程、科技前沿报告

如今程序员越来越离不开AI Coding。当AI有足够能力很快把代码实现写出来,而开发人员在此过程中应该干什么?我们在开发过程中遇到的几个问题:这个需求到底要做成什么样?做完效果如何?最终产物质量够不够好?以前手搓代码时代这三件事裹在日常开发流程里,开发过程中就定好了,开发人员靠专业素养和工具守住软件工程的”规矩“。而如今换成模型来写代码、去跑工具,Harness Engineering把这几个关键点仍然留给开发人员这里把控,整个开发流程严格约束AI,不轻易让AI糊弄过去。开发人员要确保整个流程运行在正确的轨道上。

01

背景

我们目前在做一个内部视频在线剪辑类的重构项目,面对的用户是视频内容运营同学,日常音视频多轨道剪辑、预览、字幕编辑、合成发布。过去交接过好几个团队,今年交到我们手里,团队就2~3人全栈,加上AI一起把整套重写。3月份刚拿到项目后心态有点崩:

  • 代码体量失控:前后端代码大概90万行,多种风格且耦合严重,单个复杂组件超过6000行;

  • 相关文档和单测几乎没有;

  • 各种新需求又着急加入:任何一个改动都是牵一发而动全身,必须小心翼翼测试。

所以打定主意利用AI把它好好重构一下,设定的目标:在3个月内把旧版本还在用的功能做上去,替换掉旧系统,然后再迭新功能,并且能输出一套Harness开发模式可以复用到其他业务场景。

最初评估过几个重构方案:

Image

重构方案对比

通过快速简单的尝试,都觉得方案一和方案二不太可行,原因是旧项目结构乱、技术债重,代码量又大,搬过来很容易丢功能细节,而且人还得在不熟的旧代码反复确认逻辑和校对。

所以最后我们选的重构方案是方案三:旧项目当需求功能说明书,只借鉴产品功能,利用Harness将代码全部重新写。

6月底上了第一个试用版,随后转入功能持续迭新。截至8月底,重构版大约12万行代码、15万行文档、6000+测试用例,测试覆盖率86%。真正变了的是开发节奏:从旧项目半年只敢动不到3个需求或紧急Bug → 7、8两个月已经迭代了30+新功能需求。

Image

新旧项目数据对比

通过摸索搭建Harness工作流,AI全部负责需求和设计文档编写和机审、业务代码实现和CR、质量测试验证和文档更新;开发人员定需求、确认需求边界、取舍合理开发方案、技术高风险点确认、人工CR和最终验收。

02

Harness工作流体系

视频剪辑类产品既有前端剪辑预览,也有后端视频处理和信息增删改查,所以多数需求要前后端并行开发。工作流节点跟大家现在用的差不多,但无论什么流程,都必须先回答一个问题:当AI写完代码,工程质量应该如何可靠验证,才敢合入上线呢? 就拿“用户上传成功就能进剪辑”功能来说——AI两小时就能开发完成,单测全部通过,实际一看代码连覆盖率可能都它自己“圆”出来的。我们这套Harness工作流的目标就是希望把验证做可靠。

所以除了要有AI审查,我们还要求人对需求、设计文档和最终代码做交叉审查。整体思路就两条:

  • 工程质量越往前控,越可控。

  • 把运动员和裁判员分开,抵抗人性里的惰性。

为了方便讲解,我将工作流分为三个阶段:

Image

Harness流程总览

  • 立项设计阶段:主要处理执行写代码前相关工作,工作流节点包括:立项 → 需求澄清 → 需求审查 → 写验收标准 → 验收标准审查 → (前后端)代码架构设计 → 架构设计审查 → 人工需求/设计文档交叉审查。

  • 开发阶段:如果是跨端需求,前后端会并行同时执行:前后端分别代码实现 → 分别代码审查 → 后端冒烟测试或者前端链路自动化审查。

  • 开发人员验收和需求收尾工作:人工代码CR、验收、任务归档和发起代码MR等。

接下来详细聊聊,除了上面标准节点外,在工作流执行中,我们和AI分别具体做了什么来保证工程质量。

2.1 立项设计阶段

Image

立项设计阶段

此阶段目的写代码之前先把需求、验收标准、设计方案确定下来。

如图可以看到,我们在工作流(上图红和绿色方块)中间插了好几次审查,包括人审和AI审查,似乎多余浪费人的精力和Token的设计。但我们其实是在初期踩过坑之后才加上的,为的是确保需求、代码设计符合要求。

所以需求澄清、验收标准、设计文档,要求开发同学都要自己看一遍,也需要对当前架构了熟于胸,以便做出一个程序员在以前手搓时代做过的必要判断。同时AI把产生的文档里能扫出来的文字类、一致性和设计类问题扫出来,人把注意力留在“这个需求合不合理、符不符合产品要求”。

同时,我们也要求确认好可执行的需求、设计等相关文档在此阶段应该及时提前提出MR,要求非此需求负责人纳入执行review,尽可能将问题在设计阶段暴露。

此步骤总的目的很简单:尽量在源头减少 AI 产生的目标漂移。

需求审查

需求审查是通过人工校对和AI检查对需求文档是否能得上立项时沟通的要求,确保方向正确。

所以开发人员必须把需求文档看明白,尤其是在需求比较大时,一旦有一些细节没有沟通清楚(可能是没有想到的细节点),AI很容易自己脑补它认为正确的常见做法。比如,在我们视频剪辑项目中:转码参数细节、超时了错误处理、老数据缺字段算不算坏数据等边界情况,部分情况AI并不会停下来跟人来确认,或者问了人没仔细看图快点了推荐确认,最终做出来的需求可能跟我们预想大相径庭。

AI 独立审查是把人看着累不容易发现、机器扫得快的问题找出来:如禁止含糊词(如:适当、尽量)、代词不知道指谁、章节或者链接引用是死链、补充缺失信息。而人把重点精力放在判断当前需求文档合不合理、符不符合产品要求。

验收标准审查

这里审查的是最终验收目标有没有写对。因为代码实现、审查、测试,AI都按这份文档当最终标准。一旦写得不清楚,或者只写“用户上传成功”这种大而不够细节的结果,AI一偷懒就容易把结果”做“通过。所以把什么算通过,什么算不过都写清楚,实现和测试才会覆盖到这些条件。同时也要把量化的尽量把验收数字也确定下来。

架构设计审查

此步骤重点审查技术方案合理性、可行性和扩展性:当前目标变更代码设计是否合理、有没有复用或扩展现有已经类似功能代码、前后端协议是否对齐、字段表前后一致、后续扩展性、错误路径问题等等。

例如,在我们工作流搭建初期阶段,代码质量扫描工具扫描重复代码出现概率高了很多。分析发现AI的工作原理带来的,它写的局部代码比较优,但对于全局是未必的。因为每次新需求对它都是一个新的开始,随着项目代码量的越来越大,它需要了解的内容和遵守的准则越来越多,如果检索词太窄或者根本没搜类似的实现代码,就着急开始自己着手实现。同时我们还发现一旦有”屎山“前兆,不加以控制,会导致问题快速堆积:例如,即使AI发现同一功能已经有多种实现时,它也不会自己收敛,反而去不停打各种奇怪的补丁,从而项目质量进一步恶化。

行业观察GitClear对过去大量代码的质量研究里,发现AI Coding逐步普及后,代码的重复率从8.3% 提升到了12.3%。所以我们设定AI架构设计审查环节,在设计文档阶段必须审视当前架构合理性。同时要求人也要对当前架构了解熟悉,能从设计文档上裁定新增或变更的代码能不能复用,架构合不合理,这样做性能够不够好。

2.2 开发阶段

Image

开发阶段

进入开发阶段,跨端需求会分前后端两条链路并行进实现。按照设计文档使用TDD先写契约测试再实现,同时通过Rules和宪章让AI遵守好的代码习惯。但即便如此,AI还是会出现写出烂代码的情况,因为这些在我们看起来应该是限制文件,但对于AI来说更像是叮嘱式的建议,随着Context使用越来越多时,信息被稀释,AI往往会忽略曾经的约定,所以代码自动化CR是必不可少的。

而在AI代码CR阶段,我们给各个SubAgent赋予架构、安全、契约、性能、接口契约、运行时、代码规范、测试几个维度的审查角色。这样设计的目的,是希望Agent专职审查同一类型的问题,确保注意力不会分散。前几天正好看到行业大佬Uncle Bob的一个访谈,他在Harness时会把实现、清理、强化、验收拆开,目的还是跟我们一样,就是给AI任务够细够聚焦,信息才不容易丢失。

接着就是传统工程质量检查:单元测试、冒烟测试、E2E测试、测试覆盖率、质量指标。加入这些传统自动化工程质量检查的好处是能确认可靠的质量,提升我们对代码质量的信心,同时又不占 Context,AI只需要看结果是否通过即可。但跑下来我们仍然发现两个大的问题:

AI会作弊

例如,测试覆盖率阈值如果只写成“覆盖通过”,没写清超过多少覆盖率,甚至它为了过关会偷偷改条件而通过质量检查。所以现在相关阈值我们不放在脚本默认值里,放进单独的红线配置。改动会触发门禁,工作流会停下来等人工确认。

测试用例的质量问题

AI 很容易把测试覆盖率写得很“感人”,部分代码轻轻松松能达到90%+,但测试覆盖率这项指标只能说明覆盖到没有,说明不了测试质量情况。当同一模型既写函数又写测试时,很容易出现定制化断言,错的行为或者参数也能通过测试,这时的测试用例覆盖率其实是没有什么说服力的。

所以我们在工作流里加入了变异测试环节,目的是通过得出变异测试得分来评估单元测试有效性和质量情况。它的原理是自动修改源码,故意把大于改成大于等于、把条件反转,再跑测试。每修改一次,它就会运行一遍完整的测试,通过把代码故意改坏,来预期测试用例应该失败。如果测试用例如果没有失败,就说明这个断言其实不够严谨,需要打回修改更正。

从搜索到行业观察数据来看,AI生成测试的变异测试得分常在20~40分,而人写一般能得到60~80(搜索的参考数据,不一定完全正确,随着AI发展可能数据会变化,我实测下来一般在50~60)。

如果你也想在自己工作流上加入变异测试,我认为有一个点还需要注意:假设原本跑完一次全量单测测试用例要1分钟,引入变异测试后,需要反反复复的修改源码来跑几百遍全量测试用例,全量跑完测试时间可能变成30分钟或者更久。如果你有时间效率要求,又想加入变异测试,建议对增量或者修改的单测跑。至于过去的旧单测,可以当个技术债需求来收就好。

2.3 人工验收和收尾

Image

人工验证和收尾阶段

工作流走到最后阶段,除了归置对应的工作流文档和开发功能自测外,我想聊一下我认为非常重要但目前容易忽略的最后一道门禁——人工CR。但是随着AI写代码能力越来越强,越来越快,人工CR存在的必要性?如果要读,应该怎么读,读多少?甚至是不是应该放心全部交给AI来干CR?

”我“的责任心要求”我“对代码负责就必须理解它,但是AI代码产出快,而且量又大,“我”根本看不过来,整个工作流效率的瓶颈变成”我“读懂才能放心合入。

我们目前的做法是"验证需求和体系设计 + 问题分歧裁决 + 重要代码精读"(不一定是最优,但看上去是适合我们目前的较优解法,如果有更好方法欢迎大家分享)。工作流搭建各种审查、角色检验等目标不是为了不读代码,而是把逐行阅读代码效率从机械性的内容中解放出来,所以我们集中阅读几个核心读物:

  • 需求文档:这个需求内容是什么,是不是我们或者产品同学需要的功能;

  • 设计文档:当前技术方案是什么,是不是应该这样设计;

  • 重要代码:重点关注的关键路径核心代码;

  • 验收报告:确认设计到代码实现是否符合要求。

我个人认为,人工CR还是非常有价值的:

一、团队对当前代码该不该合入而负责,应该具备应有的系统判断;

二、CR对于个人是一种成长,通过优秀代码学习,提升专业素养的途径。

业界里跟我们这个思路比较接近的,谷歌大佬Addy Osmani的文章《Agentic Code Review》中提到:程序员不是退出审查代码,是上移一层——抽查代码、审计方案,把有限的注意力花在更重要的地方。

但也搜到一些更极端的看法,Redis之父Antirez最近在文章《Control the ideas, not the code》中写到:他现在基本不读代码,只控制AI的想法和设计。他的理由跟我们痛点一样:AI一天能产出成千上万行代码,逐行理解根本看不完,所以他选择不看。

但他说能这样做的前提是能看懂AI的设计描述,能判断对不对、哪里有问题、这个方向对不对,同时有可靠的检测机制。同时他也提了一个有意思的问题,如果没有经验的年轻工程师看不懂设计和没有发现问题的能力应该怎么办,他自己也没有答案。

关于CR的最佳实践方案,我们也还在摸索中,团队中有人认为提升专业素养途径,与其CR AI产出可能的垃圾代码,更好的提升途径应该是认认真真花时间手搓代码学习和积累经验。如果大家有更好的做法,欢迎留言。

03

工作流里一些其它值得提的点

3.1 多 Agent 协同

在开发阶段AI CR时提到我们拆了好几个审查角色,这里说下为什么要这么拆。

刚开始我们让Agent自己写完代码后,换个角色合约让Agent自己再CR一遍。很多时候CR报告都是通过状态,但发现还是会有遗漏。本质上还是AI自己审查容易陷入过度自信导致,觉得自己写的东西没问题。所以目前不管是CR还是方案审查,都有必要拆分SubAgent来独立执行验证角色 + 可程序化的验证命令。

这样做还能有2个好处:

  • 任务能拆得足够细、足够聚焦:每个专职SubAgent有自己的角色合约和审查清单,注意力能关注在一个维度上。

  • SubAgent可以从创建 → 执行任务 → 消失,执行完就关掉,面对的是干净的上下文,不会被以前的内容所干扰。

3.2 工作流状态机和防AI篡改

整个工作流进度由一个Python CLI管理,配合Hook守卫防篡改。

核心思路:进度存文件,AI 只能通过命令推进,不能手动篡改。这样做目的是防止AI偷偷标记"已完成"跳过某些步骤。同时也可以记录当前执行需求每一步骤信息,用于冷启动时恢复上下文。

相关工作流数据(每个步骤完成时间、由谁触发、相关报告等)还能用于任务审计。

3.3 Token消耗的经济性设计

当时设计此工作流时,很多国产AI的开发能力还没有完全起来,国外的又比较贵,而且Token预算有限,想用尽量少的Token做更多的事情。同时也想避免Context膨胀会导致AI注意力稀释和幻觉率上升。所以我们对工作流做了一些经济性设计:

  • Context分层加载:将各类工作流资产(包括Rules\Skills\角色合约文档等)设计为3档(L1/L2/L3)分时加载,具体如下:

    Image
  • 模型按需分配:可以通过配置按阶段指定使用不同的模型,一些步骤如文档同步、冒烟验证用弱推理,设计、CR、开发用强推理,简单任务不占用强模型配额,把好钢用在刀刃上。

  • 工作流冷启动重开:当对话轮数过多、AI响应明显变慢,Context 接近上限时可以利用工作流冷启动机制主动重启用于有效清除多余的对话上下文。但不能无差别使用,因为过早重启反而丢失已建立的理解成本,得不偿失。

04

最后

如果你也有历史技术债很重并且还需要持续维护的旧仓库,靠手搓重构可能要花半年甚至一年时间,希望本文能给你带来一些借鉴意义。要是回到3月我们刚刚接手项目时,我个人认为工作流最值得的投入和走过的弯路是: 

Image

最值得的投入和走过的弯路

Harness Engineering不是一次性工程,初步体系搭好只是开始,会跟着工程问题的出现或者AI模型能力的提升持续进化。而开发者充当的角色也在过程中逐渐发生变化,例如前后端的边界在淡化,从过去专精某一端的实现者到现在变成对整个需求全栈负责。

随着AI越来越智能化,下一步开发者角色会变成什么样?我也说不准,但目前能确定的是:如今AI的代码产出越来越快,写代码的门槛也越来越低,能把需求想清楚,在开发过程中守住工程判断,把控好工程质量,同时把最终结果验清楚——做好这几件事,可能才是程序员的护城河。

-End-
原创作者|胡皓
感谢你读到这里,不如关注一下?👇
图片
📢📢来抢开发者限席名额!点击下方图片直达👇
Image
Image
扫码领取腾讯云开发者专属服务器代金券!
图片
Image
Image
Image
Image
Image