用 Cursor 快速搭建软件原型
Cursor 是一款使用 AI 能力加持的代码编辑器,或者说得更大一点,文本编辑器。正如它在官网所说的那样 Built to make you extraordinarily productive, Cursor is the best way to code with AI.
使用它能够让在编程时更加高效,几倍地提升编码效率,将工程师从平常繁琐、简单、机械、重复的代码编写工作中解放出来,更加专注于分析、建模和解决关键的问题。
但需要说在开头的是,Cursor 无法让零基础的用户变成专精的软件工程师,以能够解决所编写代码中发生的各种疑难杂症,但能让他们轻松搭建出一些有意思的应用,例如简单的网页,便捷的脚本等,真正遇到问题需要定位和调试的时候,往往是需要人类提供指导方案的。Cursor 能够做的是让有一定编程基础的用户更加高效地编写代码,让专业的软件工程师更加专注于解决问题本身。简而言之,让零基础的人玩得起来,让有基础的人更加高效。
本文介绍了我如何使用 Cursor 在业余时间独立完成了一个软件系统的原型搭建、后续演化以及最终部署上线的。
Cursor 真香定律的应验
作为使用了两年以上 Github Copilot 和各类聊天机器人来补充搜索引擎的用户,我自我感觉自己在编码时已经足够高效了,所以在最初听说 Cursor 时自己不以为然,以为就是类似 Github Copilot 的编辑器而已,我现在的编码流程已经足够高效了,不需要再接触 Cursor 这样新的代码编辑器。后来在多方渠道的反复熏陶之下,我决定尝试一下 Cursor,看看它到底是不是真如海外网友说的那样惊艳。
和 Github Copilot 等一众 VS Code 的插件不一样,Cursor 其实是从 VS Code 分叉出来的一款独立的编辑器,它的界面与核心功能都与 VS Code 非常相似,但是它在编辑器的底层加入了一些 AI 能力,使得它能够更好地理解用户输入的要求,提供更加智能的代码提示和补全,以及更加智能的代码生成能力。Cursor 除了支持 Github Copilot 一直以来所提供的行内代码补全功能之外,还支持全局范围地修改代码,例如当前光标在第 80 行修改了函数的返回值,根据你的编辑记录,它能够推测出你下一步会修改函数的签名,并且自动地提示并且生成出来,让你能够通过 Tab
键快速地接受这个建议完成修改。Cursor 早期版本的这个功能就已经让我觉得比 Github Copilot 的代码补全功能强大了很多,因为 Github Copilot 的追加写生成的代码很多时候并不符合要求,反而打乱我的心流,而 Cursor 的追加写提示则相对收敛和谨慎,更加符合我的预期。
新版本的 Cursor 中加入了 Composer
模块,它与 Chat
模块平行,是一个独立的模块。它对当前打开的代码项目整体掌控能力更强,能够更好地理解项目的结构和代码的关系,我们与之对话的过程中可以使用 @
提及我们需要修改的文件、目录甚至是整个项目,Composer 会根据我们提及的文件和我们的指令来生成代码,每次生成完都会直接在编辑器中展示修改的 Diff,我们作为用户可以选择部分接受或者是全部接受。每次代码修改的交互完成后,Cursor 都会记录检查点 CheckPoint,如果某次修改出现了问题,我们可以选择让 Cursor 修改当前的代码,也可以回滚到上一个检查点,调整代码编写指令让 Cursor 重新生成一次。
Composer 最强的一点是它能够跳出单一文件去理解整个项目的代码结构、去理解文件与文件之间的关系,能够根据需要在任意文件的任意位置新增或者删除代码,甚至是创建新的文件。这个功能在我后续的项目中发挥了很大的作用,让我能够快速地搭建出一个完整的软件原型,而不是仅仅是一个简单的代码片段。自此我完全被 Cursor 的功能所吸引,我为我初期对 Cursor 的忽视感到遗憾,并认为自己应该更早地接触 Cursor。
从这件事情也让我清楚了一点,人是有路径依赖的,尤其是在自己从事已久并自认为足够熟练和出色的领域,很容易陷入自己的舒适区,不愿意尝试新的事物。Cursor 除了打开我新的编码方式,也让我意识到自己的这种路径依赖,在未来我应该秉持更加开放的态度来对待新的技术和新的事物。
项目背景
我过去一段时间里在公司做了一套知识库 RAG 系统,用来响应内外部用户针对公司产品相关特性的咨询。该系统的核心是文档检索和回答的生成,对外提供类似论坛、群聊机器人这样形式的调用接口。从八月初灰度发布到现在已经稳定运行了三个多月,内部的同事和外部的用户使用频率都很高,但是在使用过程中也发现了一些问题,例如:
- 作为检索数据的文档本身内容有所欠缺,导致有些用户的问题无法找到相应的参考资料;
- 系统的运行数据无法直观展示给运维人员(也就是我),无法针对性的优化系统的表现;
- 文档检索过程难以调试,需要额外编写代码,结果展现也不够直观;
- 系统对接的系统较多,接口异构导致无法对系统生成的回答效果进行准确的评估。
针对这些问题,我决定设计和开发一个 RAG 系统的管理后台,用来管理系统的知识库,展示系统运行数据,提供调试的功能(例如提供对话界面、文档召回测试等)。正好皈依了 Cursor 的怀抱,我决定用 Cursor 来搭建这个系统的原型。
系统设计
系统功能
根据运行期间的问题和需求,我整理了系统的功能需求如下:
- 知识库管理:提供对知识库的增删改查功能,支持对知识库内的文档进行增删改查;
- 系统数据展示:提供系统的运行数据展示,例如总计回答次数、用户采纳次数、用户点赞次数、每天提问量等,同时系统分析指标需进行缓存,防止多表联查的 SQL 执行过于频繁;
- 调试功能:提供对话界面,用来测试系统端到端的回答效果;提供文档召回测试功能,用来测试系统的文档检索效果;
- 系统管理:提供用户及角色的管理功能,提供用户登录、登出、修改密码等功能;同时提供系统本身的审计功能,记录用户的“写”操作日志;
- 回答评估:提供对系统回答效果的评估功能,支持手动设置某个回答的评分,例如用户的满意度、回答的准确率等,用以后续优化系统的回答效果。
系统架构
为了跨平台性和可维护性,我很快就决定使用 B/S 架构来完成这套系统,一方面是 B/S 架构对我而言足够熟悉,另一方面是我希望这套系统能够在任何设备上都能够访问,不受限于操作系统。系统的架构如下:
- 前端:使用 React + Next.js + TypeScript + Shadcn UI 来完成,前端的主要功能是展示系统的数据和提供用户交互界面。同时使用 OpenAPI Generator 来生成前端的 API 客户端代码;
- 后端:使用 Python + FastAPI + SQLAlchemy + Alembic 来完成,后端的主要功能是处理前端的请求,调用数据库来获取数据,同时提供一些系统的管理功能;FastAPI 提供了 OpenAPI 的支持,与 OpenAPI Generator 配合使用可以快速生成前端的 API 客户端代码;
- 数据库:因为 RAG 系统本身使用 OceanBase 作为数据库(OceanBase 是我司的主要产品,是一款分布式关系型数据库,4.3.3 版本以来支持在关系表中存储向量类型的数据,故也可作为向量数据库使用),所以管理后台自然也是使用 OceanBase 作为数据库;
- 缓存:使用 Redis 做缓存,主要用来弥补 Python 中加锁、定时过期等功能的不便之处。
系统原型搭建
项目初始化
因为技术栈已经选定,接下来真正编码的步骤就比较快了。前端方面,因为之前在学校时写过很多的 React 项目,所以对 React 的开发环境和生态都比较熟悉,Next.js 是 React 生态里很成熟的开发框架,提供了很多便利的功能和模板市场。我在其中找了一个管理后台启动模板当做了项目的启动代码,下载到本地用 Cursor 打开之后就开始了项目的开发。
Cursor 的使用
首先我将 Cursor 的规则设置成为“Always respond in 中文, write code and comments in English, especially UI text.”也就是让 Cursor 的回答都是中文,而生成的代码和注释都是英文,尤其是 UI 上的文本。然后就开始了面向聊天的开发过程。
节选一段我和 Cursor 的对话:
1 | Q: @page.tsx 给这个 form 增加 username 和 password 两个表单字段 |
在这段对话里,我让 Cursor 修改了登录页面的代码,添加了用户名和密码两个表单字段,还提醒我要修改 lib/auth.ts
中的 signIn
函数,确保它能够处理用户名和密码的情况。而我在界面上点击登录之后页面没有反应,于是我直接追问它:
1 | Q: 看起来用户名和密码的表单和 form 元素没有绑定到一起,点击登录按钮之后没有获取到两个的值 |
Cursor 不会一下子把所有的代码都生成出来,而是会根据我的反馈来逐步生成代码,它生成的代码也会出错,但是可以通过在代码文件中或者是在命令行报错信息旁点击 Fix
来修复错误。这样的交互方式让我觉得很舒服,我可以在开发的过程中随时提出自己的需求,然后查看生成的代码是否满足要求,如果不满足要求,我可以直接反馈给 Cursor,让它继续修改。
因为使用的是 Claude 3.5 Sonnet 这个模型,它的代码编写能力是业内领先的,所以据我观察它生成的代码质量还是足够高的。为了满足一些我的需求,它甚至会提出引入一些新的依赖,例如使用 echarts 来绘制图表等,所以在对谈开发的过程中我也能顺便学习到一些新的技术和工具。
例如创建新的页面我也是通过下面这样的命令来完成的:
1 | 增加一个注册按钮放在 @page.tsx 页面里,点击之后进入注册页面,输入 username 和两次 password 之后并确认两次密码相同之后即可提交,使用 register 接口进行注册。 |
生成效果
在原型搭建阶段,Cursor 给我带来的震撼是足够大的,因为我不用自己写多少行代码就能得到一个多页的、美观的、鲁棒的 Web 应用,远远超出了我之前认为的代码生成工具的能力。鉴于试用阶段它表现出的如此强大的能力,我决定继续使用 Cursor 来完成这个项目的后续开发,最终推动该项目的上线。
系统演化上线
系统想要正式推出使用,只有前端是不够的,还需要后端的支持。
后端开发
后端方面,我前段时间在公司的工作过程中接触到了 FastAPI,这是一个 Python 的后端开发框架,类似于 Flask,但是提供了更加便捷的 API 开发方式,支持 OpenAPI、异步 I/O、依赖注入,配合 Pydantic 可以实现方便的数据校验和转换。我在 Cursor 中新建了一个 Python 项目,然后安装了 FastAPI 和 SQLAlchemy,开始了后端的开发。
除了用户、角色等新增数据库表的定义需要自己写,其他的部分也基本上是通过 Cursor 来完成的,我会先定义要实现哪些接口方法以及这些接口的输入输出类型之后,让 Cursor 先自己实现这些接口,例如 帮我把第 258 行之后的涉及角色和用户的方法都实现了
,它也都能像模像样地生成出来,然后我再根据生成的代码来进行调整和优化,最终逐步迭代出一个完整的后端项目。
项目整合
因为 FastAPI 提供了 OpenAPI 支持,我选用 OpenAPI Generator 来通过接口文档自动生成前端所需要的 API 客户端代码,这些代码的参数和返回值类型与后端定义的接口是一致的,这样就避免了前后端接口定义不一致的问题。在对接过程中我也是让 Cursor 来生成引入对应的依赖和命令,例如:
1 | 我想要用 openapi-generator 中的 fetch 生成器针对 127.0.0.1:8000/openapi.json 文件进行 api 客户端代码的生成,你能帮我完成吗? |
我希望使用 SWR 来包装生成出来的 API 客户端方法,也是通过 Cursor 来完成的:
1 | 用 swr 帮我封装 @apiClient.ts 中的 client,在 @page.tsx 中获取知识库信息,并且用表格展示出来 |
通过对谈的方式,我能够快速查看到应用开发的效果,而不是想法来临时要自己再花一部分时间实现,这样心流就不会被打断,几乎是以和我在脑海里头脑风暴一样快的速度完成了原型的实现和演进。粗略估计,我实际编写的代码量只占项目总体的 10%~20%,通过将编码的时间用于评审和测试验证,系统的整体的迭代速度是极快的。
项目部署
项目开发完成后我打算将其在内部部署。我采用的是传统的部署方式,也就是使用 Nginx 作为 Web 服务器,背后对接 Uvicorn 作为 FastAPI 的 ASGI 服务器,同时对接 Next.js 的前端服务。对外,使用阿里云的负载均衡器和 Nginx 的 IP 过滤器共同保证访问来源的可靠性。部署完成之后,经过几位同事验证,系统的功能性和稳定性都得到了认可,于是我将系统正式推广,供内部同事使用,日后用来共同维护和优化 RAG 系统。
体会与总结
给需求工程带来了什么
软件工程中的需求工程是一个非常重要的环节,它决定了软件的功能和性能,也决定了软件的质量和用户体验。在需求工程的过程中,我们需要对用户的需求进行分析和整理,然后将这些需求转化为软件的功能和性能要求,最终形成软件的需求规格说明书。对于难以沟通描述的需求,项目团队往往会根据初期理解来搭建原型,然后通过原型的演示来跟用户进一步沟通需求,而原型也会根据实际需要被直接抛弃或是逐步演进。
Cursor 的出现让原型的搭建变得更加容易,它能够根据用户提供的文字需求来生成代码,让用户能够更加直观地看到软件的功能和性能,从而更好地理解需求。在我这个项目中,我通过 Cursor 来搭建了一个管理后台的原型,然后评估认为这个原型能够解决或是缓解当前遇到的问题,于是在业余时间里继续使用 Cursor 来完成了这个项目的开发,既是对业内工作的补充,也是兴趣爱好的延续。最终能够顺利完成该项目并且投入使用,我是非常高兴的。
Cursor 的遗憾
通篇都在说 Cursor 的好处,但是 Cursor 也有一些遗憾的地方,例如:
- Cursor 的代码生成能力虽然强大,但是对于一些复杂的逻辑,例如需要调用外部接口、需要进行数据库事务操作等,它的生成能力就会显得有些力不从心,这时候就需要我自己来完成这些逻辑的编写;
- Cursor 只能让本来就懂的用户更加高效,对于零基础的用户来说,“使用什么样的工具和框架来完成系统的开发”这件事情包含的信息差本身也是一个很大的挑战,Cursor 无法帮助他们解决这个问题;
- 遇到问题的调试能力有限,Cursor 无法帮助我解决一些复杂的问题,例如操作系统本身的问题、网络问题、依赖安装失败等,这些问题都需要我自己来解决,需要在该领域有一定的开发经验和技术积累才能顺畅地解决。
展望
使用 Cursor 对我而言算是一个重要节点,它标志着我个人在编程这个行为上将有一个新的开始。在过去的几年里,我一直在使用各种代码编辑器和 IDE,它们借助 Copilot 只能提供一些基本的代码补全和格式化功能,对于一些复杂的代码生成和重构功能,它们的支持就显得有些力不从心。而 Cursor 的出现填补了这个空白,它能够根据用户的需求来自由地生成代码,让我能够更加专注于解决问题本身,而不是在重复代码的编写和调试上浪费太多的时间,让我更加专注于宏观的方法论、技术栈、系统架构和业务目标。这无疑让我感到兴奋,这也是为什么我每天下班回家继续用它写代码的原因——它让我感受到了变革的气息。
Cursor 的出现极大提高了我的效率,“十倍程序员”或许对我并不遥远,而与此同时我想做的也并不仅仅是编码本身了。
用 Cursor 快速搭建软件原型