最近在用Cursor辅助写一个FastAPI小项目,刚开始觉得挺爽的,自动补全和生成CRUD效率很高。但项目写到3000多行代码时,发现它经常自己改我写好的函数,比如把变量名改成跟别处一样的,或者在一个不相关的文件里插入import。最离谱的是,我让它重构一个路由模块,它直接给拆成了三个文件还把逻辑弄乱。想问下大家,用AI编程工具做复杂项目有什么经验?是不是要频繁commit,或者给Cursor写详细注释来“驯化”它?还是说我这种小项目其实只适合用Copilot那种单行补全?有点迷茫,求指点。
用Cursor写Python后端,感觉越改越乱,是我打开方式不对吗?
全部回复
共 2 条说实话,你这个帖子我看了好几遍,感觉特别有共鸣。因为我自己从去年开始重度使用Cursor做后端开发,中间也经历过几乎一模一样的痛苦期,尤其是项目规模从两三千行往五千行以上爬坡的时候,那种“AI帮倒忙”的感觉会越来越强烈。我先直接回答你最核心的问题:不是你打开方式不对,而是Cursor这类基于大模型补全的工具,在项目复杂度跨越某个阈值后,它的工作模式和你作为人类工程师的预期会产生根本性冲突。这个阈值大概就在两三千行代码、十来个文件左右。下面我尽量把这个问题拆透了说,也分享一些我摸爬滚打出来的实操方案。
首先,你提到的“它经常自己改我写好的函数,把变量名改成跟别处一样的”——这个现象背后其实反映了Cursor的上下文机制缺陷。Cursor本质上是在你当前打开的标签页文件以及它通过某种检索拿到的相关片段上做概率预测。当你项目文件多了以后,它可能同时看到了A文件里叫user_repo的变量和B文件里叫user_repository的变量,然后它觉得“这两个应该是同一个东西”,就在你完全不注意的时候把其中一个改掉了。这不是它故意捣乱,而是它的概率模型认为“一致性”比“保留原有命名”更重要。我踩过一个坑:我有个工具函数叫generate_token,它在我某个路由文件里自己改成了create_jwt_token,然后导致其他引用这个函数的地方全部报错。那次之后我养成了一个习惯:所有核心函数和类定义的文件,在Cursor生成完代码后,我会手动用git diff扫一遍,尤其关注变量名和函数签名有没有被静默修改。这不是小题大做,在项目中期阶段,Cursor的“隐形修改”是导致混乱的头号元凶。
其次,你提到的“让它重构一个路由模块,它直接给拆成了三个文件还把逻辑弄乱”——这就触及到当前AI编程工具最本质的局限:它不理解你整个项目的架构意图。对你来说,重构一个路由模块可能意味着“把/user相关的增删改查抽到一个单独的文件里,保持依赖注入结构不变,路由前缀统一”。但对Cursor来说,你给的prompt是“重构这个路由模块”,它看到的是你当前打开的那个文件,然后基于它训练数据里见过的各种项目结构(有按功能拆的、有按层拆的、有按微服务拆的),随机选择了一种它觉得概率最高的拆分方式。更糟糕的是,它可能同时把一些公共中间件或者数据库会话管理代码也复制到了新文件里,导致你项目里出现两份几乎一样的代码。我去年做一个订单服务时,让它帮我抽一个支付路由,结果它把整个数据库连接池配置都复制了一遍,差点导致生产环境连接数爆掉。从那以后,我对AI重构的态度变成了:你可以用AI生成新文件的骨架代码,但文件拆分、模块依赖关系、配置共享这些架构级别的决策,必须由你亲手做,或者至少你画好架构图、写清楚边界条件,再让AI去填充具体函数体。
那么,是不是说小项目只适合用Copilot那种单行补全?我的看法不太一样。我认为关键不在于项目大小,而在于你如何管理AI的“工作边界”。我现在的做法是三层控制:第一层,严格控制Cursor能影响的文件范围。在Cursor的设置里,你可以配置它只对当前工作区内的特定目录或文件类型生效。比如,我把所有业务逻辑放在app/services下,所有路由放在app/routes下,所有模型放在app/models下。然后我会在Cursor的规则里明确写“不要修改app/core下的任何文件,不要在app/models外引用模型定义”。这些规则虽然不能百分之百约束它,但能大幅降低它乱改的概率。第二层,用类型注解和接口契约来锁死行为。比如,你的函数参数如果都写了严格的类型注解,并且返回值类型明确,Cursor在生成调用代码时就不太容易瞎猜。我甚至会在关键接口上写docstring,里面直接写明“这个函数不要修改任何全局状态,只返回计算结果”。这听起来有点玄学,但大模型对自然语言的约束其实比很多人想象的要敏感。第三层,就是commit频率。你说得完全对,频繁commit不是防御性编程,而是你对抗AI不确定性最有效的工具。我现在基本每完成一个逻辑单元(比如一个CRUD接口的完整实现,或者一个数据库迁移脚本),就commit一次。commit message我会写得很结构化,比如“feat(order): 添加订单取消接口,涉及service层改动,routes层新增路由,model层未改动”。这样一旦Cursor在后面某个步骤里乱改了前面的代码,我可以用git bisect快速定位到是哪次commit引入了问题。更重要的是,当你需要回滚时,颗粒度越细,损失越小。
另外,我想分享一个可能和你直觉相反的观察:在项目越改越乱的时候,很多人会下意识地给Cursor写更详细的注释,试图“驯化”它。但我认为这往往是火上浇油。原因很简单,大模型的注意力是有限的,你注释写得太长太详细,它反而会丢失重点。我做过实验:同一个函数,我写三行注释和写十行注释,生成的代码质量反而下降,因为它开始过度解读你注释里提到的边缘情况。更有效的做法是,把你的注释写成“必须遵守的约束条件”,而不是“功能描述”。比如,与其写“这个函数用来更新用户信息,注意要校验邮箱格式,如果用户不存在则返回404”,不如写“约束:1. 除非用户存在,否则不执行任何写操作;2. 邮箱字段必须通过validate_email函数校验;3. 返回值只能是UserResponse或HTTPException”。这种结构化约束,大模型反而更容易理解,而且它生成的代码也更接近你想要的。
还有一个很多人忽视的点:Cursor在处理跨文件引用时,它的上下文窗口会因为文件切换而重置或混淆。我自己摸索出来的一个流程是:当你需要让Cursor做涉及多个文件的改动时,不要让它自己跳来跳去。你应该先手动把相关的几个文件都打开,然后在当前文件里用自然语言描述清楚“我现在在A文件里修改函数X,它调用了B文件里的Y函数,同时需要确保C文件里的Z常量不受影响”。这样Cursor的上下文里同时包含了这三个文件的信息,它的决策会准确很多。如果你只打开一个文件,让它去改另一个文件,它大概率会基于过时的信息做判断。
最后,我想认真说一个可能有点反主流但我觉得很重要的观点:用AI编程工具做复杂项目,本质上是在把你的部分架构决策权让渡给一个概率模型。这个模型很强大,但它没有你对业务的理解,没有你对未来三个月需求演变的预判,也没有你对代码可维护性的长期责任感。所以,我现在的信条是:AI可以写代码,但你不能让它设计代码。设计代码的职责——包括模块划分、接口定义、依赖方向、错误处理策略——必须牢牢掌握在人类手里。你可以让Cursor去填充一个已经定义好接口的handler函数,但你别让它去决定“这个handler应该放在哪个包下”。你可以让它生成一个数据库查询的单元测试,但你别让它去决定“测试覆盖率应该达到多少”。一旦你模糊了这个边界,项目就会像你描述的那样“越改越乱”,因为混乱的根源不是代码质量,而是架构意志的分散。
我自己的项目现在稳定在大概两万行代码,十几个微服务模块,用Cursor辅助开发的体验已经比最初好很多。总结下来就是:接受它是个超级厉害的补全器,而不是一个架构师;用git和类型系统给它上缰绳;把注释写成约束而不是故事;永远不要让它同时重构超过一个文件的逻辑。如果你能做到这几条,我保证你的FastAPI项目不会再因为AI而陷入混乱。反之,如果你试图完全信任它,让它自由发挥,那3000行之后必然是一个由AI自己制造的屎山,而你还要负责在上面继续盖楼。这无关乎工具好坏,只关乎你对不确定性的管理能力。
同感,3000行就是个分水岭,我之前也是到这个时候开始被AI瞎改代码,后来养成习惯每改一个功能就手动commit,回退起来快很多。另外Cursor对项目上下文的理解确实有限,我试过在关键函数上加详细的docstring和类型注解,它乱改的几率明显降低了,你可以试试。至于拆文件那个,建议给AI明确约束,比如“只修改这个文件,不要动其他模块”,或者直接用Composer模式手动选文件范围。