关于 Agentic Coding 的一些个人想法
关于这段时间对agentic coding的一些个人想法,字多。可以当厕所读物。
我用了大概3个月,我单纯依靠它写了6.4K行代码(不含unit test), 30多个文件的py项目。还在过程中。在这个过程中有几个感想
- 如果你不懂某种语言,你可以通过AI快速得到一个可运行的Demo。但这个代码是不可维护的。 原因很简单,一个一定规模以上的代码是需要约束条件的,这个条件具体来说是AI的Context。但不仅仅是Context。它包含非常多的内容。包括但不限于Functional Spec的具体描述 (单纯Spec这一点就就并不容易做到,我见过太多日常需求都说不利索的情况了,更不要说面向AI编程时要求更高的结构化思维)
更容易被忽略的是编程的约定,例如py项目是用uv还是poetry? 用unittest还是pytest?用black还是ruff?等等。光是这一个约定的Context文件我就有600多行,从文件目录结构,到git commit前做ruff和mypy检查,到pytest要求,cov要求,到git commit格式等等。
话说到这里,就回到我一开始说的问题了,如果不懂一个语言和框架,是难以写出这样的约定文件的。即使可以让AI自己制定这份约定,但问题的本质并没有变化 – 不懂就是不懂,无法判断AI给出的约定是否合理。问题只是从 “I dont know why it works, I dont know why it wont works” 进化到 “I dont know why it works” 而已。
#赚不到认知以外的钱这句话的含金量还在提高 #但含金量提高并不能让我赚到更多钱
- Context文件并不仅仅是这些。我日常在使用AI生成代码的过程中至少常驻5个ctx文件。除了上面提到编程约定,还需要有前面提到的Func Spec。缺少这个东西让我吃足了💩。
我经历了一个module组件返工了至少3次。一个功能需求如果仅仅是用十几行Prompt描述是远远不够的,这会导致AI上来哐哐一顿输出,能运行,然后后面就发现各种问题了,不合理的抽象,不完整的设计等等问题(这里的问题比想象的要复杂,后述)。
所以现在我通常会使用讨论 - 形成文档 - 将文档变成ctx注入prompt - 开始写代码实现的过程。我会要求AI把它"看到"和"理解"的实现完整说出来,review后再动手。避免代码实现"短路",AI可能会选择结果一致,但实现不对的方式去完成,让它先输出一轮类似一种CoT的中间结果,甚至多次讨论并更新完善设计,最后再做代码实现。这样有2个好处,一个是确保每次中间思考的过程是可review的,发现错误时可以及早介入更正,另一个好处是后面提到的,如果会话中断是可恢复的,避免之前的讨论和决策的细节随着会话结束而消失。
- 关于ctx的部分还没完👿 。它还需要一个"草稿纸",用于记录各种进度。目前这应该是Agentic Coding的标配了,产品会自带plan和跟踪已完成未完成的事项,但在Claude Code还没有今天这么火之前,我用的产品还没有这个功能。需要我手动实现。
另一方面,这个草稿纸并不仅限于todo list。之所以需要这个草稿纸,是因为AI不可避免的会碰到token limit需要对上下文压缩,在压缩过程中一定会丢失一些细节,例如现在改到哪个文件了?在实现功能还是修复bug? 因此它需要这么个东西来记录进度和一些在需要中断的场景下持久化讨论的细节。避免在对信息压缩后丢失细节。
另一个side effect是,在碰到一些重启的场景下,例如添加了新的mcp,或者碰到产品异常需要重启会话的情况下,这个机制可以很好的避免之前的工作被浪费了。
- ctx还在输出。最后一部分实际是编程约定的部分。但我想有必要单独拿出来说,这一点非常反直觉 – 限制AI每次产生的代码行数。这是人肉编程和AI编程最大的差异点。AI可以轻松一次性生成几百上千行代码。但是这样的结果是人肉无法review。人肉一次性review 100多行代码可能就是极限了。在AI一次性吐出的几百行代码中,很难发现一些设计上和逻辑上的问题 – 上下文跨度对人肉而言太大了。
另一个问题会进一步加剧这个问题。就是代码库的规模增长。在代码量比较少的情况下,AI基本上可以把握全局关系输出代码,而代码量一旦超过某个阈值,AI会丢失代码之间的逻辑关系,代码质量会极速下降。让AI一次性拉的越多,代码腐化程度就好急剧增加。
这也是我前面说到反复返工的问题的核心原因。当我的代码量增长到目前6.3K, 33个文件和不同module目录的情况下,AI会逐渐难以生成正确的代码。这也是为什么我需要这么多ctx文件来给AI提供足够的上下文信息。
为防止AI在我的代码里拉屎,我采用的方式是在ctx中限制AI一次性输出大致60到70行代码,不需要很精确,只要是可review的就好。同时,要求AI在每次生成一段新的代码后,必须生成对应的pytest的unit test,来保证每段代码都有一定的unit test覆盖。当AI改动代码并提交前,必须保证test suite 100%通过,以防止AI在上下文不完整的情况下引入了错误的实现。目前大致有460个左右的unit test和83%的code coverage.
#一旦让它在大量生成代码就回不去了 #什么赛博电灯泡
- 另一个问题就是代码库增长导致的上下文丢失的问题,这个问题出现的比预期要快。越是模块化设计的语言这个问题会越明显,因为不同的类,方法,函数实现,helper函数会散落在不同文件中,每次当开始一个AI会话session时,它几乎不可能把整个代码库都读入再进行操作,这时候如果需要查看一些symbol的定义,引用,就需要花不少时间去查找和读取文件。为了解决这个问题,我临时让AI写了一个基于lsp实现的mcp,通过lsp快速获取整个项目的symbol和line numbrr赖快速导航。
虽然是个很简陋的lsp的mcp,但效率可以极大提升,AI可以通过lsp快速定位到某个class的实现和引用。但可惜是它并不完美,AI还是不时用默认自带的工具去search或者整段读取文件,大概是这些工具是在system prompt种,它使用自己添加的tools的优先级并不太高。没有太好的办法。
#这里的LSP不是你想的那个LSP的意思
- 总结一下ctx部分。我目前日常需要4/5个ctx文件常驻。1个是全局的编程约定,剩下的都是每个branch下单独跟当前任务有关的,1个design doc或者说是func spec,1个task progress, 1个"草稿纸"。
ctx并不是越多越好,这些ctx日常就会占用掉6到8K左右的token,而且ctx太多,并不能指望让AI记住每一个细节,只是在需要提醒时不需要重复太多说明。
AI: 测试通过了我要git commit下班了
我:你给我等一下,提交前的检查别忘记做
AI : 哦对我忘了我还需要运行pytest, ruff format, mypy...
(然后发现了一堆test fail,又回去吭哧一顿改)
但要防止它乱改,如之前提到的,因为在代码量超过一定程度后,它无法记住之前的一些上下文,例如我实现了一个Protocal,为后面的某个函数的静态检查使用,但可能隔了2天再去改的时候,它可能就不知道存在这么个Protocal,导致完全绕过了这个静态检查。虽然代码可运行,pytest也没问题,但实际上已经背离了设计方案了。
- 正当我以为解决了这些问题就解决了最大的boss时,它开始转二阶段了。最后这个问题比较棘手。
我将其称之为,在同一性认知框架下导致的一致性错误。说人话就是,AI如果生成了错误实现的代码,那么,它会用同样错误的逻辑去实现测试用例。
这是因为AI始终维持在一个一致的上下文环境中,它的逻辑是自洽的,如果代码实现是错误的,那么它生成的单元测试也必然是符合其代码逻辑的,也是错误的。为此,我一度付出了修正一处代码逻辑,引发了100多个单元测试失败的惨痛代价,我花了几天的时间去重新更正这些测试案例,但,这些测试案例里面还隐藏了多少这类错误,不好说。现在总共460多个测试案例,现在还没有闲到有空去一一review。
这个问题是AI的结构性问题,不好解决。我现在想到一个可行的方案是通过多agent去解决,刻意去屏蔽一些上下文信息让不同agent避免处于同一认知框架下。具体而言,在我的设想中,至少需要3个agent,他们分别是:
- designer, 它负责将需要转化为高层级的代码设计,例如class之间的交互关系,class有哪些属性,方法,参数和返回是什么,但不负责具体代码实现。它负责what to do
- coder, 它根据designer提供的框架性约束,完成具体的代码实现。它不管设计是否合理,只是完成实现,具体上下文由designer给出。它负责how to do
- tester, 它根据designer提供的设计,去编写测试用例并执行测试。
只有coder和tester是不行的,因为如果完全屏蔽了两者之间的信息,tester无法知道coder实现了什么样的类名方法名,无法编写测试用例。而如果将coder实现暴露给tester,那么两者又将再次处于同一认知框架下,又将回到代码实现和测试用例一对全对,一错全错的状况下。
所以我认为至少需要这3者共同协作,并相互屏蔽一定上下文信息才能更好的解决这个问题。designer也可以将其理解为agent的coordinator。如果是3个agent,那么designer既负责设计,也负责协调。如果是4个agent,那么还会有一个独立agent负责协调和tracking进度(pm是吧) 最后,在多agent的模式下,可以使用TDD的开发模式,这可能是一个比较有意思的点,但还没有尝试过。
- 快写完了。接下来的问题是一味顺从的AI。我有点蚌不住我每说一句话AI都会说"You are absolutely right."或许本意是让AI有比较强的指令遵循的能力,但事实上会变成了对用户的盲从。在我需要跟它讨论设计时因为这一点出现过很多问题,会导致"用户胡说八道都是对的"。
举一个实际例子,我之前在配置一个数据同步CDC的任务,配置完成运行前我例行让它检查源/目标的连通性,此时AI发现源端mysql连不上timeout, 此时我忘记它又做了什么检查,最后给出的结论是源端配置错了要我用postgresql的方式配置,我没仔细想我说源端就是mysql怎么会是pg呢?AI给我整一句你说的对,源是mysql,可能是我运行方式的错。结果运行起来不出意外的出意外了,排查后发现AI是对的,我复制粘贴地址时把源/目标搞反了,导致的确源端提供的是pg的地址,修正成mysql地址就没问题了。
类似的情况还有很多,盲从的问题会把我个人错误的设计给掩盖起来。我不得不在每次跟它讨论都要反复强调"我说的不一定对我是半桶水你给我好好想想",(虽然也加到了ctx了,但是整体ctx太多了它经常无视这句话)。让他避免盲从我的说法。有趣的是前两天看到一篇公众号文章说需要一个愤怒的AI队友会跟你在线互喷的,核心思路是一致的,都是在绕开AI盲从的问题。
- 虽然我建立了这么一套复杂的玩意。但实际情况怎么样?
我个人感觉是,基于这套编程约定和足够高的单元测试和代码覆盖率,目前的代码质量还算不错。但效率反而是下降了,可能是现在我花了很多时间在完善这个体系,后续可能会上升? 现在还不得而知,总体而言,因为需要防止AI在代码里拉屎,需要每一步都花很多时间去讨论设计,更新文档,实现代码,实现测试。速度上是不如我自己写,但从规范角度看,它的质量和扎实的程度"看起来"比我自己写要高很多。
- 最后总结一下,agentic coding是个强大的工具,就像拥有了大圣的72变的能力和撒毛成兵。但这里存在一个思维范式的转变,跟inline suggestion的使用不同,agentic coding本质上是从单点的问题上升到团队管理了,所以需要要求有很强的工程化流程管理,AI能力很强,他可以一边写代码一边搜索,写完还能完善文档和测试案例,并且自己排查和修复发现的问题,这需要一个框架约束其行为受控,不能让它放飞自己。这本质上跟团队管理没太大两样,团队管理是在让水平能力经验不同的团队成员互相协助在同一约束框架下工作,如果让个别成员随意放飞自己,那么同样的问题也会出现。AI上述的种种问题都可以在现实中找到对应的模式。
回过头看我自己做的这些工作,本质上是对AI能力的一种"驯化"和"框架化",这与团队管理有着惊人的相似之处。在实际的人肉IT工作中,也会人为创造认知差异来防止错误的一致性传播,例如独立的测试团队。
这也是我为什么前面一开始就说,如果我不懂某种技术,我是无法让它产生可控的代码的。正如那句话所说,我赚不到认知以外的钱。通过AI也是一样。
前面说了很多不足之处,但总体上agentic coding的这些问题正在通过更为工程化的方式磨平使用上的鸿沟。这篇小作文想写很久了,但一直懒得写,但我司的Kiro IDE的出现打乱了我的节奏,老实说我还没有深度使用,从介绍中的spec和hooks功能可以看出这是基于工程化实践得出的方式,也跟我上述提到的我个人的一些实践高度契合。后续如果还有机会我再聊下Kiro。
我不反对使用agentic coding生成demo代码做poc,但我坚决反对使用它生成自己也不知道在干啥的代码。以前,各种语言,框架存在天然的门槛,不懂的话连一个正常可运行的代码都不一定能得到,只能在自己认知范围内造屎山代码。但现在AI技术的发展具备了"我不懂但我可以通过AI得到一个可运行的像模像样的代码"。这下好了,现在可以超出自己认知范围造屎山了。很难说这些超出作者认知范围的屎山会对未来代码生态造成多大的影响。
这让我想起有一种说法,现在海量的mcp中充斥着大量通过AI生成的mcp, 同时又基于其他mcp提供了能力得以完成这个任务 – 等等,我之前的lsp的mcp不就是这么干的?
#好家伙原汤化原食是吧
AI是否会导致更多人跳过必要的学习过程,直接获得"看似正确"的结果?这对软件生态是祝福还是隐患? 大概就是这些,一些个人的想法,后面想到什么更多的再说。