ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

vibecoding黑盒代码改造:从“能跑”到“敢接手”的渐进重构指南

vibecoding黑盒代码改造:从“能跑”到“敢接手”的渐进重构指南 上个月公司让我接管一个vibecoding落地的订单发货系统。功能都跑着线上也有业务在走但没人敢碰——这是一个典型的黑盒12张表、4000多行代码挤在几个大而全的函数里、零测试、变量从a1排到a9。我花了差不多两周把它从一个“能跑但没人敢接手”的状态变成了一套组里新人也能上手维护的代码。这篇就把整个思路完整捋一遍vibecoding的黑盒为什么可怕、先做什么、再做什么、最后怎么防复犯。先说结论vibecoding本身不是原罪真正的问题是——让AI快速堆出来的代码天然默认你是唯一的维护者。当这个假设不再成立黑盒就出现了。接手这类项目最忌讳一上来就重构或重写正确顺序是先钉死行为、再看穿结构、然后渐进拆解最后把“为什么”还给后来人。这篇文章适合正在被类似项目折磨的技术负责人、被临时丢去救火的工程师以及那些用AI写项目但同时想对代码负责任的人。1. 先搞清楚vibecoding产物到底让人怕什么1.1 “能跑”和“敢接手”隔着三种不确定性我之前不只一次遇到“代码能跑但没人敢碰”的项目这类项目有个共同点作者还在的时候一切正常一旦作者去忙别的或离职整个模块就像是被施了沉默咒。接手消息刚放出来群里能沉默半小时。这种沉默不是大家懒而是每个人的大脑在飞速计算“如果我动了这笔单子出了事谁负责”。先别急着喷“代码写得烂”喷解决不了问题。真正的麻烦在于三种不确定性叠加在一起结构不确定性不知道业务逻辑被摊在哪个文件里、模块之间谁依赖谁。行为不确定性没有一个测试没有任何一份“改动前应该全绿的基线”改一行代码触发什么全凭运气。认知不确定性没人知道当初为什么用这个方案哪个分支是历史遗留哪个常量是被试出来的“魔法值”。这三种不确定性一旦同时存在再简单的代码也会显得像一个随时会爆炸的装置。你问团队“这个接口正确行为是什么”没人能答但你问“现在线上在跑的行为是什么”所有人都承认——就是当前代码的行为。可惜大多数人没有意识到后者才是接手时唯一可靠的抓手。1.2 黑盒的三个层次和它们的具体症状在动手之前我习惯把“黑盒”拆成三层因为每一层的处理手段完全不同。结构黑盒要用扫描和观测去打开行为黑盒要用特征测试去钉死认知黑盒要用决策记录去补全。层次核心问题典型表现接手人最直观感受结构黑盒模块边界和依赖关系不明一个文件2000行、路由直接写逻辑、跨模块互相读写数据库想改一个接口得先读半天文件行为黑盒没有任何行为基线和回归手段零测试、零mock连错误日志都只写console.log每提交一次都想烧香认知黑盒决策背景和业务语义丢失魔法数字遍地、没人能解释分支接手人只能靠猜和考古回到我的订单发货系统结构黑盒的典型是src/app.js塞满了全套业务一张路由表后面挂着十几个巨型函数行为黑盒是整个仓库一个测试文件都没有唯一的安全感来自线上“暂时还没出事”认知黑盒最经典的是发货模块里有一段if (count 3)这个3是从哪来的翻聊天记录才知道是“当天发货次数超过3次就给用户发提醒”而那已经是三版需求迭代之后的产物。这里有个关键判断三层不能乱着修。先解决行为不确定性再整理结构最后补认知。直接上来重构是最差选项因为你不具备“改坏了能立刻发现”的能力所有人只能靠祈祷。2. 别急着重构先用“行为快照”把黑盒钉死2.1 为什么第一件事是给代码装“行为锚”而不是重构在没有测试的仓库里做任何重构都等于让维修工在没断电的高压线上作业。vibecoding黑盒尤其如此它“能跑”这个事实本身就是唯一的规格说明书。你问“这个接口正确行为应该是什么”没人能答但“现在线上在跑的行为是什么”当前代码就是答案。所以接手的第一件事不是读懂代码而是把“当前行为”用测试快照的方式冻结下来。这类测试业内叫特征测试characterization test核心思路不是断言“应该怎样”而是断言“现在怎样以后也不能偷偷变成别的样”。它先接受现状再把它变成可以回归的基线。我习惯把这种测试叫做“行为快照”每个既有接口喂一组代表性的输入记录下真实输出、副作用写库、调外部API的签名然后固化成测试。后续每次修改只要这些快照有任何非预期diff系统就会报警。黑盒从这一刻起不再是“玄学”而是“可观测的灰盒”。提示特征测试不是业务验收测试它不判断对错只接受现状并防止无声改变。这是它和普通单元测试最大的区别。2.2 用最小测试基座给黑盒做体检不指望一步到位补全所有接口测试那样做不现实也没人愿意干。我的策略是先挑线上真实流量最高的三条链路——下单、发货、查物流——给它们建立基座。以发货链路为例我用 Node 自带的测试框架写了一个最原始的“录制型”快照行为如下// tests/snapshot-deliver.spec.js const { deliverOrder } require(../src/deliverOrder); test(发货行为快照给定输入输出结构和副作用保持稳定, async () { const req mockRequest({ orderId: A10086, address: xx市xx路1号 }); const res mockResponse(); await deliverOrder(req, res); expect(res.statusCode).toBe(200); expect(res.body).toMatchSnapshot(); // 第一次运行生成快照之后比对 expect(db.collection(shipments).inserted).toMatchSnapshot(); expect(logisticsApi.calls).toMatchSnapshot(); });第一次跑系统会生成一份“当前行为快照”之后只要代码行为有变化测试就会红。它完全不判断业务对错但非常诚实——它告诉你“你动过什么地方把原本稳定的输出动了”。除了这种函数级快照还有两个实用手段请求级快照在路由入口加一段透明的记录中间件把真实线上请求和响应录下来挑低峰期的流量存成JSON集合作为贴近生产的回归集。副作用探针在写库、外部API调用处包一层薄壳输出“谁在什么时候调了什么”后续改代码时能立刻看到牵动面。2.3 一个“黄金规则”任何改动先打全量快照行为快照构建完之后最重要的事情是立规矩。我规定从这一刻起任何人的任何改动提交前必须整包跑一遍快照测试全绿才能过。所有diff必须先被人工确认为两类之一——“我这次就是要改的行为”或“同步更新的预期快照”。一旦出现既没被人工预期、快照又红的情况立即定位原因不允许带病提交。黑盒最可怕的地方是“没有错误信号”。有信号之后重构和修bug就从“赌博”变成了“实验”。这一步听起来不起眼却是整个项目从“没人敢接手”走向“能正常维护”的第一个拐点。如果你实在连特征测试都没时间写那至少把线上每天的响应日志留好效果会打折但也能判断行为漂移有了自动化快照项目就等于多了一道成本极低但持续有效的护栏。3. 摸黑盒不用重写也能看穿整个系统结构3.1 静态地图找到被依赖最多的核心文件行为基线有了下一步要把“结构黑盒”打开灯看。别想着读一遍所有代码那是无底洞。正确姿势是先扫一张静态依赖图每个文件 import/require 了谁谁又被谁引用一眼圈住“被依赖最多的模块”。JavaScript/TypeScript项目可以试试npx madge --image deps.svg src/Python系可以生成import树即便不用现成工具用脚本统计一下每个文件被import或require的次数也能得到八九不离十的结论。我当时扫完有三个关键印象deliverOrder被12处调用orderService.js同时被controller、定时任务、消息队列消费数据库访问散落在至少9个不同文件里。这些信息直接说明要改订单状态或库存影响面是全系统而不是某一个模块。静态扫描不一定完美但它能快速画出“架构地中海”——所有人都依赖、但本身没有明确边界的部分。那通常是vibecoding对话式迭代的宿命AI为了响应新需求总倾向在旧函数里继续拍代码而不是重新设计边界。3.2 动态观测挑一条真实故事线走到底静态扫描告诉你“长什么样”动态调查告诉你“到底怎么运转”。我强烈推荐一个方法“用户故事追踪法”。挑一个最小、最常见的用户需求比如“修改订单收货地址”从入口开始一直顺着代码调用链走到数据库写操作把沿途涉及的每个函数、每张表、每个外部API全部列出来。这一步不需要看完整个项目只需要看懂一条链路但它能让你拿到整条可解释的“动脉”。具体操作上我通常在关键函数入口临时加几行日志打印出入参和耗时。不用上什么APM大件这个阶段的精度足够const start Date.now(); console.log([trace] deliverOrder entry, { orderId, address }); // ... 原有逻辑 console.log([trace] deliverOrder - deductStock, db.calls, Date.now() - start ms);跑一遍流程把日志拼起来你会第一次看到“黑盒内部的长相”。同时顺手记录哪个环节耗时最高、哪个分支永远没人走进这些信息后面重构时都会用到。我个人经验是不要贪多一轮跟踪只追一条故事线。追完三条你对整个系统的结构就会有一个非常具体的心智模型比读十遍代码都管用。3.3 用“复杂度 x 改动率”确定改造靶点摸清结构之后别着急动手。先用一个简单矩阵排优先级横轴是模块的圈复杂度看着越绕越难缠纵轴是近期改动频率用git log数一数这个文件被提交过多少次。落在“高复杂度 高改动率”区域的就是最痛的靶点也是重构后收益最大的位置。在我这边app.js名列第一——每次排期必改它每次改它必心惊胆战其次是散落各处的数据库访问代码它们让“库存扣减”从未有过统一边界。确定了这两个靶点后面两周的重构目标就非常清楚了。摸黑盒阶段的关键心态是你不是要把代码全部看懂你只需要找到“最小但最痛”的下手点然后把它从黑盒里抠出来。4. 从混沌到分层不重写也能拆出可维护的边界4.1 先把纯逻辑从副作用里剥出来渐进重构的第一刀永远是“把纯函数和副作用分开”。这是收益最大、风险最小的一刀。vibecoding生成的代码最常见的病态就是算价格、验证参数、扣库存、发通知、写日志全部糊在同一个函数里想复用任何一个环节只能连坐复制粘贴。我的拆法很简单凡是同一个输入永远得到同一个输出的计算——校验、计价、状态判断、格式转换——原样抽成纯函数凡是碰数据库、外部API、文件系统、时间的地方保持在一个薄薄的“副作用壳”里。改造前后对比如下// 改造前所有事都搅在 deliverOrder 里 async function deliverOrder(req, res) { // 参数校验 库存判断 扣减 调物流 写状态 发通知…… } // 改造后边界清晰每个动作都能单独测试 function validateAddress(address) { ... } // 纯函数 function createShipmentLabel(address) { ... } // 纯函数 async function deductStock(orderId) { ... } // 副作用壳 async function notifyCustomer(orderId, msg) { ... } // 副作用壳 async function deliverOrder(req, res) { const addr req.body.address; validateAddress(addr); const stock await deductStock(req.body.orderId); const label createShipmentLabel(addr); await notifyCustomer(req.body.orderId, label); res.json({ ok: true, label }); }这一步做完特征测试全绿是初级目标更重要的变化是你可以给validateAddress补真正的单元测试了因为它的行为完全确定再也不需要mock一整套数据库。纯函数一旦被抽出来后续所有调用点都会自然收缩到一条清晰路径上。4.2 收敛数据流让读写路径有唯一出口纯逻辑分离之后第二个大问题是“数据库访问点太散”。在vibecoding生成的项目里经常出现 A 模块直接写 B 模块的表过阵子 B 模块重构A 模块莫名爆炸。我采用的做法是给每一类业务对象建立一个唯一的存储接口存储层Facade订单、库存、物流、通知各一个仓库文件。所有读写都必须经由对应仓库跨模块的数据库操作一律在仓库内部完成上层不要直接碰表名。这一步不改任何业务行为只是把“物理上的访问路径”统一起来让依赖方向从“乱成一团”变成“业务→仓库→数据源”的单向箭头。动刀方法依然一样每收敛一个文件的数据库访问就整包跑一遍快照。出现数据差异立刻回退那一次提取不硬扛。收敛数据流不能一次性大面积铺开一次一个文件一周内把它们全部收进仓库层就很理想。这个阶段最忌讳的是“顺手优化”其它东西跑着跑着就变成重构项目了。4.3 用“防腐层”模式把旧代码留在原地渐进重构最大的敌人是你觉得旧代码太丑了忍不住想重写。我要特别强调黑盒里那些又乱又老又没人看懂的旧代码不是垃圾是资产。它们承载着大量线上已验证的行为和业务妥协的痕迹直接删掉重写等于把“已经跑通”抛掉去赌“新的能跑通”。推荐的做法是给旧代码包一层防腐层Facade / Anticorruption Layer新代码只面向新接口编程旧代码暂时保留原样但通过适配器暴露给外部。这样你可以一边在新路径上建设整洁结构一边让旧路径继续承担存量流量直到某一天旧函数内部被替换到无可替换。拿我的发货链路来说把跨文件的数据库访问收敛到orderRepo.js之后deliverOrder函数体一下子瘦了大半那几个“不要动会炸”的分支终于有人敢看了。整个过程没有一次“爆改式重写”每动一步都回到特征测试上验证所以系统一天都没有停摆过。这样的拆解比大重构慢但慢得值每一步都是可解释、可回滚、可交付的。5. 把“为什么”还给后来人文档不是注释是决策透传5.1 vibecoding真正丢失的是决策上下文不是代码注释很多团队在交接受阻时会想“要不我们把注释补一补”我认为这是最低效的一步。vibecoding代码缺的不是“这个变量什么意思”而是“为什么当初这样做”。注释可以告诉你count 3是“超过三次提醒”但它不会告诉你为什么是3不是5为什么不在前端做提醒而要在后端做更不会告诉你当时客户场景是什么。这些“为什么”在vibecoding工作流里丢失得尤其快因为生成代码的过程中每个选择都来自对话上下文里的约束一旦会话结束约束就消失了。对一个接手的工程师而言理解这些约束才是真正敢放手改动的前提。所以我们要抢救的不是注释而是决策记录。5.2 轻量ADR一条决策一张卡片我推荐团队在docs/adr/下建一个极轻量的决策记录目录。每条记录只要四段话状态、背景、决策、后果。模板可以这样# ADR-001: 订单号生成改用 Redis 自增 状态已生效 背景初版使用数据库自增主键作为订单号后续接入多实例部署后存在撞号风险。 决策订单号改为 Redis INCR结合日期前缀生成唯一编号。 后果订单号生成 TPS 提高但依赖 Redis 可用性运维侧需要补充监控。不需要写成几十页的正式架构文档每条控制在半页以内。它的作用是后来人看代码之前先花两分钟读一遍卡片就能理解“代码为什么长这样”而不是把“为什么”猜成“它就是烂”。我在梳理发货系统时把当初的聊天记录翻了一遍找出四个关键决策——为什么用Redis自增、为什么发货提醒放后端、为什么某些字段做冗余存储、为什么库存扣减不放在下单时统一做——各写了一张卡片。两个小时后后来人会觉得这个系统“有作者了”。5.3 运行地图和数据字典给接手人一页纸导航决策记录之外另一个性价比极高的文档策略是“运行地图”一页纸说明系统入口在哪、核心链路是哪几条、依赖了哪些外部服务和数据表、当前哪些区域最痛。我做了一张表格贴在README最前面入口核心调用链依赖当前风险点POST /api/ordersorderController → orderRepo → orderDB物流API、短信API库存扣减逻辑散落数据字典则负责记录表/字段的含义。对vibecoding项目尤其重要因为很多表结构是AI根据prompt现场拍出来的字段名含义经常只有聊天记录才知道。比如orders.flag字面意思根本看不出是“是否加急”翻记录才知道。把这类信息补上后来人改需求时就不必再考古。你会不会觉得这些文档工作很花时间我的体验恰好相反越是在混沌项目里这些文档越省钱。一张ADR五到十分钟一条字段说明三十秒但它们在接盘者那里的价值是用“天”来计量的。黑盒之所以可怕很大程度上是因为代码库“不解释自己”一旦这些解释补上接手便从“解密”变成了“查资料”。6. 让人敢接手的另一半技术解药治不好信任问题6.1 恐惧不只是技术问题更是责任归属问题我原以为把代码收拾干净、测试补上、文档写好大家就会抢着接手。实际情况是测试补上之后主动提 MR 的人确实多了但真正愿意“负责”这个模块的人仍然寥寥。后来我发现恐惧的来源有两层。表层是“怕看不懂”深层的其实是“怕出事了没人替我兜底”。接手一个vibecoding黑盒等价于承诺“以后线上一切异常我来背锅”而黑盒之前的作者早已不在场。没有任何一个工程师愿意在没有同行理解、没有评审支持、没有安全特权的状态下接这种锅。这才是“敢不敢接手”的核心。6.2 用三种机制重建信任场光靠文档是不够的要让接手者感受到组织的支持。我给当时团队做了三件事结对接手每一项修改任务业务最熟的人 新接管人一起看代码、一起提MR。前两周不追求“独立负责”追求“知道出了问题有人在旁边”。重构预算每周固定三个小时只允许做“安全重构”不允许夹带新需求。让整理代码成为正式排期上被认可的工作而不是志愿者行为。评审保护条款约定任何改动只要碰了某个模块评审人必须包含该模块当前owner。保证“我改了别人的地盘”不再是孤军奋战所有动静都有人先盯。这些机制等于给新owner发了一张“安全网凭证”你动黑盒不是你一个人站在悬崖边上而是一组人站在你背后。慢慢地大家从“我不敢碰”变成“我们有人懂”。6.3 让“第一次成功提交”成为破冰仪式所有群体性恐惧都需要一个具体的破冰事件。我当时做了一件小事挑出一个低风险、小范围、预期可见的修改任务把某个模块里一个讨厌的switch换成策略表专门交给那个最怕黑盒的同事并在动手前跟他逐条过了一遍特征测试和回滚预案。他提交的时候手在抖但第二个MR他就开始主动找更难的“拆弹任务”了。破冰之后整个团队对黑盒的态度会有肉眼可见的变化群聊里不再说“这个别动”而是“这个现在是安全的可以小心改”。这一点对vibecoding项目尤为重要——技术方案的完备只解决“客观上能不能”协作机制和安全感解决“主观上敢不敢”两者缺一个黑盒都算不上真正变成了可维护代码。7. 防复犯给vibecoding画边界而不是禁用它7.1 目标不是“AI别写代码”而是“AI写出来的代码必须可以被接管”处理完这一次黑盒我最大的体会是别因为一次痛就否定vibecoding。AI辅助编程已经是日常工作的一部分真正值得认真对待的问题是如何在享受AI速度的同时不把“可维护性”从交付物清单里删掉。我给团队定的规矩是vibecoding可以随便用来做原型、一次性脚本、demo但凡是进主干的代码至少要满足四个“被接管”条件。这不是限制AI而是钳制我们自己的惰性。vibecoding最危险的地方不是AI写得差而是人类在AI产出的过程中失去了表达维护意图的动力——觉得AI写的没人在乎。7.2 四个检查点AI代码进入主干前的最低门槛如果你也在用vibecoding方式交付项目可以参考下面这套门槛检查点具体标准验收方式有行为测试每个新功能至少有一条快照或单元测试CI全绿才算完成无全局状态扩散不允许越过模块边界改数据库、改全局变量Review时重点看副作用落点模块边界可解释一个文件一个职责接口能一句话说清写不出来就回去继续拆决策记录有一句话在ADR或MR描述里写清“为什么”代码评审时顺手检查这些条件看着简单落地效果出乎意料大家开始下意识地对AI提更高要求prompt也从“写一个登录接口”升级成“写一个登录接口模块边界按已有分层走不要直接碰其他仓库”。AI产出的结构明显更收敛接手成本骤降。7.3 和AI协作时值得养成的三个习惯最后分享几个我自己提炼出来的、和AI协作写生产代码的习惯。第一先让AI写接口定义再写实现。我会在prompt里说“先输出这个模块的函数签名和输入输出等我确认边界你再写函数体。”这样至少能把结构复杂度压下来。第二定期让AI解释“这个模块为什么这么设计”。把解释写进ADR而不是留在聊天记录里。聊天记录迟早会被清理而文档会留在代码库旁边。第三给vibecoding一个“禁止越过模块边界”的系统级约束。无论是system prompt还是代码评审时的提醒都要让AI明确感知到你可以写得不完美但不能没有边界你可以迭代得快但不能不接受测试。7.4 黑盒蒸馏把AI产物的“手感”沉淀成规则热搜上有个词叫“黑盒蒸馏”本意是让黑盒模型的高能力迁移到更小的模型上。我在做完整个项目后倒觉得它在软件工程上也能成立把vibecoding黑盒里那些“跑得对、但没人理解”的行为通过测试、ADR、数据字典重新提取成可理解、可传播的规则这个动作本身就是在做“维护性蒸馏”。黑盒不可怕可怕的是我们只享受了AI的速度却没有把它的产出翻译成人类能负责的东西。我现在的态度是vibecoding完全可以继续用但我会把它当成一个“写初稿非常快的实习生”——实习生的产出必须经过review、必须有测试、必须能解释自己的方案。一旦用这样的标准去要求它vibecoding生产出来的代码就从“能跑”逐步变成“能接手、能演进、能交付”。
返回列表