在 AI 编程工具引领潮流的当下,许多开发者仍在观望,疑惑这些“AI 辅助编码”工具究竟有多大实用价值,是否值得为高昂的订阅费用买单。本篇文章中,从事编程工作已有 36 年的开发者 Tom Yedwab 将以 Cursor 为例,分享他对这款工具的实际体验与见解,希望能为仍在犹豫的读者提供有价值的参考。
在 AI 编程的论坛中,我经常会看到有软件开发人员提出一个常见的问题:有人从 Cursor 这样的工具中获得价值吗?为此付费订阅是否值得?
于是,在将 Cursor 作为我个人和工作项目中日常工具使用了几个月后,我有一些观点想和大家分享,以此来讨论这究竟是一个“必备”工具,还是仅仅昙花一现的流行产品。同时,我还会介绍一些快速获得最大化收益的策略,希望能帮助那些想尝试它的人。可能有些人试过 Cursor,觉得效果不尽人意,而这些建议也许能激励你再给它一个机会。
我没有受到 Cursor 的赞助,也不是产品评测者。我既不是要推崇它,也不是要贬低它,只是想分享我个人的使用体验。
我是谁?
首先,先向大家介绍一下我自己。我是有一名有着 36 年编程经验的开发者,熟练掌握多种语言,主要专注于 C 语言为主的游戏引擎开发,以及 Go、Python、JavaScript 的网页开发。我假设这篇文章的读者同样能够在大型代码库中自如地工作,能用自己擅长的语言编写和调试代码等。对于那些希望借助 AI 学习编程概念或想让 AI 替他们写超出自身水平的代码的初学者,我会提供完全不同的建议!
对我来说,AI 助手的吸引力在于帮我处理模板化和重复性的工作,这样我可以集中精力在每个问题的核心逻辑上。我对自动生成大量代码并不特别感兴趣,对“写了多少行代码”作为效率指标持高度怀疑态度。我更希望在花费更少时间写同样多代码的情况下,有更多时间去思考边界情况、可维护性等问题。
那么,言归正传:
什么是 Cursor?
Cursor 是基于 Visual Studio Code(VS Code)开发的一个分支版本,内置了大模型(LLM)支持的功能,集成在核心界面中。这是一个专有产品,开发商提供了免费和订阅两种方案;不过,价格表并没有明确说明订阅用户的具体权益,以及与其他竞品的区别。我会结合自己的理解,在后续功能介绍中尽量说明这一点,以下是简要总结:
Tab 补全:这是一组专有的微调模型,提供代码补全功能,并可以通过 Tab 键跳转到下一个推荐操作。该功能仅对订阅用户开放。
内联编辑(Inline editing):这是一个基于聊天界面的编辑工具,可以对选定代码进行修改,并通过简单的差异视图显示更改,采用基础模型如 GPT 或 Claude。此功能对免费和付费用户均开放。
聊天侧边栏:同样是基于聊天的编辑界面,允许在侧边栏进行更大范围的修改,支持更长的讨论、多文件代码建议等,采用基础模型如 GPT 或 Claude。此功能对免费和付费用户均开放。
Composer:这是专门用于跨代码库大规模重构的聊天界面,生成多个文件的差异视图供用户逐一查看和批准,使用基础模型如 GPT 或 Claude。此功能对免费和付费用户均开放。
Tab 补全
虽然其他由 LLM 驱动的编码工具更侧重于聊天体验,但在我使用 Cursor 的过程中,Tab 补全功能才是最符合我日常编码习惯并能节省最多时间的功能。显然,这个功能背后投入了大量的思考和技术研究,因此它不仅可以建议一行、多行,甚至整个函数的代码补全,还可以建议下一个需要编辑的行。这样一来,只需不断按下 Tab 键,就能在整个文件中自动完成相关的修改。
使用它的一种用法是将它当作一个“增强版”代码重构工具。例如,假设我有一段代码块,其中的变量名是 under_score,而我希望将它们改为 camelCase。我只需重命名一个变量的一个实例,然后逐行按 Tab 键,就能更新所有需要更改的行,包括其他相关变量。许多繁琐、容易出错的任务都可以通过这种方式自动完成,而无需额外编写脚本:
有时 Tab 补全甚至会独立发现并提出修复 bug 的建议。很多时候,当我在 Python 或 Go 中添加依赖项时,它会自动建议导入相应的模块。如果我将字符串用引号给括起来,它会适当地转义内容。与其他工具类似,它还可以根据函数签名和可选的文档字符串生成整个函数代码:
总体而言,这个工具感觉就像在读我的心思,能预测我接下来的操作,让我能更少关注代码细节,而更多专注于构建的整体架构。
值得一提的是,补全速度极快,几乎是在我停止输入的瞬间,代码建议就弹出来了,完全没有等待的感觉。如果这个过程拖延太久,那对我来说肯定会是个大缺点。
那么,我对 Tab 补全的抱怨是什么呢?第一个问题比较小:有时候我没及时看到补全建议就继续输入了,结果建议消失了。而一旦消失,似乎没办法让它重新出现,只有重新输入一部分再试。
另一个问题刚好相反:有时补全的建议完全不合适,我会故意忽略它。然而,偶尔我选择了一个不同的补全,但之前忽略的建议却被悄悄应用了。这种情况虽然不多,但已经引发了一些难以察觉的 bug,因为我并未意识到不对的逻辑被误用。这虽然不至于影响 Tab 补全带来的效率提升,但确实稍微影响了体验。
内联编辑、聊天侧边栏和 Composer
据我所知,这些功能在与基础模型的交互上非常相似——我几乎只用 Claude 3.5 Sonnet——主要区别在于用户界面。
内联编辑可以通过选中代码并按下 Ctrl-K/Cmd-K 启动。我输入想要的更改后,文件里会显示一个清晰的差异视图,让我选择接受或拒绝。我主要用它来添加小段代码或做些小范围的重构。
一个典型的用法是,当我有一个任务循环并想把它并行化时,就可以用这个功能来处理:
然后,你可以通过 Ctrl+L/Cmd+L 来打开聊天侧边栏,它为多轮对话提供了更大的操作空间。不过,我对当前测试过的 LLM 模型有个小抱怨:它们总是先返回代码,而不是在遇到歧义时先询问澄清。建议的代码会有一个“应用”按钮,点击后可以在当前选中的文件中生成差异视图。这对于单文件的大规模重构,或基于当前打开的文件创建新文件非常有用。如果涉及其他文件,它们可以手动添加到上下文中,不过 Cursor 会根据查询和后台生成的索引来猜测相关文件。
这里有一个示例:将应用程序的数据库 API 转换为 REST API,并提供参数验证和正确的 HTTP 状态码,然后再编写一个客户端库来访问这个 REST API:
另一个示例是将这个客户端库从 Python 转换为 Go 语言,注意松散类型的 Python 代码如何被转换为结构明确的结构体类型,且符合 Go 惯用的错误处理风格!这并非简单的 1:1 重写。
最后,Composer 专门用于跨文件的重构,这是我最少使用的功能,但它提供了一次性查看多个文件差异的良好体验。
.cursorrules 文件
我最初并不知道这个功能的存在,直到在(我认为过于简略的)文档中偶然发现它,那时我看到各种聊天模式始终会包含位于工作区根目录的 .cursorrules 文件的内容,以提供额外的上下文。我目前在尝试利用它来告知 LLM 有关代码库的编码规范、常用包和其他文档信息。
这个功能可能有助于解决我在使用 Cursor 时遇到的一个大难题:它无法遵循代码风格和模式,除非这些风格已经存在于你正在编辑的文件中。例如,在 Khan Academy,我们使用一个专有的 Go 库来在函数间传递上下文,用于日志记录、HTTP 请求等场景,所以 LLM 需要能够使用这个库。过去这一直很困难,但或许一个写得好的 .cursorrules 文件可以作为改进的第一步。
目前的限制是每个工作区只能有一个 .cursorrules 文件,因此像我们这样包含多种语言代码的 monorepo 将比包含一个小型代码库(代码风格一致)要复杂得多且更难设置。
另外,文档提到 .cursorrules 文件仅在聊天模式中使用,而不适用于 Tab 补全。不过,我尝试将该文件固定在工作区的一个标签页中,确认可以让它至少部分参与到 Tab 补全的上下文中。
对我的工作流程的改变
像 Cursor 这样的工具最令人兴奋的地方并不是我可以更快地编写代码,毕竟写代码本身并不是瓶颈;事实上,我常常需要放慢速度,以免过分关注代码本身而忽视要解决的高层次问题。真正的价值在于它改变了我的编码方式。
虽然这种技术还处于早期阶段,但到目前为止,我的工作方式已经发生了以下变化,我也期待在不久的将来看到更多改变:
我现在在编码时不太倾向于引入新的库或框架。并不是说我自己会去写加密库,而是对于小型工具而言,直接让 LLM 按我的需求生成定制代码比引入一个通用库更简单。这些通用库通常在刚推出时小巧且轻便,但因为是开放的并且被越来越多的人使用,所以它们慢慢会积累我不需要的功能和选项。
很多库存在的意义只是为了减少样板代码,过去我会觉得这很值,因为有了这些库可以帮我省下了写和维护代码的时间。可现在,LLM 可以帮我完成这些重复性的工作,所以我觉得使用库的成本就显得有些不划算了。而且,这个成本可能更高——你试过让一个写了一年甚至更久的 Node.js 项目重新跑起来吗?不过借助 LLM 干脆从头再写一遍。
我也不再担心在自己的代码中是否遵循 DRY(不要重复自己)。过早地定义抽象会导致后期的技术债增加,因此能够参考其他代码快速生成相似代码,而不将其抽象为函数或类,为我提供了更大的灵活性。如果需要在未来将重复的逻辑重构出来,LLM 也能提供帮助。
我对不熟悉的语言或框架的接受度也更高了。例如,多年来我一直在使用 R 语言,尤其是将它用于数据可视化。但说实话,我对它并不熟练,我对 dplyr 的理解不深,总觉得有很多不同的方法可以完成同样的任务。现在我只需描述想要的可视化效果,LLM 就会自动生成相应的数据操作和 ggplot 可视化。以前花一小时的任务,现在五分钟就能完成,所以我更不容易放弃转而用 Python 来做了。
也许有一天我甚至会用 Rust 写点什么。或许吧。
我现在会先对一些小模块做快速调整,然后再把它们整合到大项目里面。这样做的部分原因是因为 LLM 处理大项目有些吃力,另一方面也让我发现了一些以前没想到的工作方法。比如,我可以先用 Python 这种灵活的语言来做原型,解决好技术细节后,再迅速转成类型严格的 Go 代码,直接在 Web 应用中。我还可以让 LLM 自动生成测试数据,或者模拟一个后端来对前端进行开发。既然一个想法还没完全确定下来,为什么要一下子投入在成熟的大项目里付出高昂的成本呢?
总结
我不确定几年后我是否还会继续使用 Cursor,亦或者转向其他工具。但可以肯定的是,在我撰写本文时,我觉得 Cursor 是 LLM 编程助手的最佳工具。如果你想探索这类工具可能带来的价值,我建议你试试 Cursor。