高热度讨论(421 点)深入分析 AI 时代资深开发者的竞争力要素,超越纯编码技能。
大型语言模型如今已经能够嵌入 Cursor 等功能强大的 IDE,充当高效的编码助手,显著提升专业软件工程团队的产出和能力。
然而,要获得预期的功能性与非功能性成果,并达到可接受的代码质量,并不是一件自然而然就会实现的事。
在工作中,我和团队发现了一些能让编码助手大放异彩的实用场景,也总结出了一些实践和方法,它们可以显著帮助我们以足够高的质量达成预期结果。
事实证明,高级软件工程师最适合确保编码助手取得成功,因为那些看似在 AI 时代已经过时的软件工程经验、积累的专业知识以及项目管理最佳实践,恰恰是最高效地使用这些工具的关键。
下文将介绍一些非常适合编码助手的真实使用场景,并详细说明那些能够促成 AI 编程会话成功的配套实践。
你正在阅读的是原文经过大幅修订后的版本。新版本在清晰度和结构方面均有所改进。
原始版本仍可在 Github 上获取。
这篇文章还在 HackerNews 上引发了热烈讨论,同样值得一读:https://news.ycombinator.com/item?id=43573755
过去几个月里,我在 Joboo 的软件工程师团队中引入了 Cursor——一款基于 Visual Studio Code 的 AI 驱动型 IDE。
Cursor 封装了前沿的大型语言模型,例如 Anthropic 的 Claude Sonnet 3.7、Google 的 Gemini 2.5 和 OpenAI 的 GPT 4o;更关键的是,它以一种深入而广泛的方式将这些模型集成到了 IDE 的工作机制中。
这种集成不仅让模型能够“看到”整个代码库,还能直接对代码库采取操作,包括跨文件编辑,甚至执行命令行工具。
这种集成的广度和深度,使 AI 模型能够在团队中充当功能完备的编码助手。
这意味着,我们团队第一次可以让 AI 独立实现一项功能的大部分内容,有时甚至能独立完成整项功能。
虽然这种方式已经在多个案例中展现出巨大的生产力提升,但我们仍然有责任确保最终实现的功能正确、满足重要的非功能性需求,并保证整体代码质量不低于我们亲自开发并发布的水平,最好还能更高。
因此,在日常工作中,引导编码助手产出令人满意的结果正变得越来越重要。
到目前为止,我发现,要在 AI 辅助编程环境中成功开展工作,需要采取三项关键措施:
结构良好的需求
基于工具的护栏
基于文件的关键帧
接下来,我将解释这些措施各自包含什么,并结合日常工作中的真实案例,说明我们如何运用它们。
对于 AI 辅助编程环境中这些基于 LLM 的编程智能体,我还没有找到一个完美的比喻。目前,我采用的心智模型是:把它们看作“在编程知识方面绝对是高级工程师,但在你特定上下文中的架构把控方面绝对是初级工程师”。
由此可见,如果你希望它们为你节省大量工作,就需要投入一些策略性的精力。
而我的论点是:没有人比高级软件工程师更适合以正确的方式投入这些精力。
因为正如我们将会看到的,尽管我们面对的显然是全新的前沿技术,但真正让我们能够最高效地驾驭这种新能力的,恰恰是那些经过时间检验的传统实践和工具。
首先需要认识到的是:无论 AI 模型看起来多么无所不能,要让它的能力真正发挥作用,就必须为其指明方向,否则这种能力只会令人不知所措。
这是因为,在任何 AI 支持的工作流中,一条来自软件工程的关键洞见都会成为核心:
“六周的实现工作,轻而易举就能省掉两个小时的规划。”
这句讽刺的话凸显了一个基本事实:实现阶段是弥补规划不足代价最高的阶段。
AI 会进一步加剧这个问题,因为 AI(几乎)什么都能做,但我们需要它做的是我们想做的事。
因此,这三项措施——结构良好的需求、基于工具的护栏和基于文件的关键帧——归根结底都是约束手段。
任何成功的 AI 编程会话,其基础都是一组精确且完整的需求。
举例来说,下面是我几天前编写的一段真实 Cursor 提示词:
我需要你实现一个只读的 Web 用户界面,以类似表格的概览形式展示数据库中已经存在并持久化的数据。
相关数据来自一个名为“Contract”的 Doctrine 实体以及若干关联实体,涉及我们在平台中存储的订阅合同信息。
该功能需要在一个包含多个代码库的 monorepo 中实现。对于这项功能,有四个代码库会发挥作用:
位于“backend-app”中的 Symfony 5 应用
位于“backend-app”中的 Symfony 5 应用
位于“janus-christophorus”中的 Symfony 7 应用
位于“janus-christophorus”中的 Symfony 7 应用
由“janus-christophorus”使用、位于“janus-shared-bundle”文件夹中的 Symfony bundle
由“janus-christophorus”使用、位于“janus-shared-bundle”文件夹中的 Symfony bundle
另一个由“janus-christophorus”使用、位于“janus-webui-bundle”文件夹中的 Symfony bundle
另一个由“janus-christophorus”使用、位于“janus-webui-bundle”文件夹中的 Symfony bundle
各代码库的作用如下:
“backend-app”目前拥有相关数据,但没有提供展示这些数据的 UI
“backend-app”目前拥有相关数据,但没有提供展示这些数据的 UI
“janus-christophorus”需要提供一个用于展示合同数据的新 UI
“janus-christophorus”需要提供一个用于展示合同数据的新 UI
“janus-shared-bundle”负责承载由“backend-app”提供的 API 端点所对应的 API 客户端实现,以及其他一些内容;janus-christophorus 需要使用这些客户端从“backend-app”拉取数据
“janus-shared-bundle”负责承载由“backend-app”提供的 API 端点所对应的 API 客户端实现,以及其他一些内容;janus-christophorus 需要使用这些客户端从“backend-app”拉取数据
“janus-webui-bundle”包含适用于“janus-christophorus”所有 UI 的 Living Styleguide 所需的 Tailwind 配置、CSS 和 Twig 模板。
“janus-webui-bundle”包含适用于“janus-christophorus”所有 UI 的 Living Styleguide 所需的 Tailwind 配置、CSS 和 Twig 模板。
你现在的任务是:
在“backend-app”中实现所需的 API 端点,为“janus-christophorus”的 Web UI 提供需要展示的数据;该 API 还需要支持集成 API 的“演示模式”能力,因此必须在“backend-app”实现的测试框架层中提供一个演示数据服务,以便在 API 客户端提出请求时提供虚假数据
在“backend-app”中实现所需的 API 端点,为“janus-christophorus”的 Web UI 提供需要展示的数据;该 API 还需要支持集成 API 的“演示模式”能力,因此必须在“backend-app”实现的测试框架层中提供一个演示数据服务,以便在 API 客户端提出请求时提供虚假数据
在“janus-shared-bundle”中实现一个与这些新 API 端点匹配、可用于读取数据的 API 客户端
在“janus-shared-bundle”中实现一个与这些新 API 端点匹配、可用于读取数据的 API 客户端
在“janus-christophorus”中实现一个使用该新 API 客户端的表现层服务类
在“janus-christophorus”中实现一个使用该新 API 客户端的表现层服务类
实现一个表现层 Controller 和一个 Twig 模板,它们使用这个新的表现层服务,展示通过“janus-shared-bundle”和“backend-app”中的 API 集成所获取的信息
实现一个表现层 Controller 和一个 Twig 模板,它们使用这个新的表现层服务,展示通过“janus-shared-bundle”和“backend-app”中的 API 集成所获取的信息
请研究所提供的合同、用户和招聘方资料实体,以确定需要从 MariaDB 数据库中读取哪些数据,或者通过演示服务伪造哪些数据,才能在 API 中为 UI 提供有用的信息。
该功能的目标是提供一个 UI,让用户能够快速了解系统中可用合同的总体情况。
注意这里如何解释了与该任务相关的大量上下文,以及它如何隐含地设置了对功能不同部分的实现方式和位置的约束。
我还需要在新编码会话开始时向 Cursor 提示中输入一大批隐含的需求和约束:通过添加具有与需要创建的功能性质相似的现有实现文件,并向 Cursor 提及这些是"灵感来源(如需要)"——这样就为底层 AI 接近新实现的方式奠定了基调:
我还提供来自风格指南、UI 导航管理服务等的其他文件。也可以考虑我提供的其他现有功能中的文件作为指南和灵感。
现有"灵感"文件中的良好命名和命名空间结构也是很好的指导。
例如,在我们的代码库中,现有的功能可能是这样组织的:
SomeFeature
Api
Dto
Controller
Domain
Entity
Enum
Service
Presentation
Controller
Service
Resources
templates
Infrastructure
Command
Client
就像一个(细心的)人类初级开发者会说"嗯,这肯定能告诉我一些关于如何命名和组织新东西"一样,AI 也会很乐意遵循这些模式。
这与倾向于较长但非常描述性的类、方法和变量名相结合,有助于创建相当多的"隐含"指导。
最后,注意提示中没有描述的内容;我不会详细说明确切需要显示什么数据以及如何确切地显示它。
根据我的经验,在上述情况下,这可以作为一个很好的起点:可以安全地假设任何前沿的大语言模型在其训练过程中已经"看到"了无数"网页 UI 上的订阅合同数据演示",因此将"我需要这些数据在表格式概览中呈现"的一般概念与"这是我们的官方风格指南"相结合,通常会产生在视觉和信息方面都非常出色的首个用户界面。
虽然需求定义了目的地,但基于工具的护栏确保我们采取最直接的路线到达那个目的地。
考虑我们在开发中对实时反馈系统的重视。没有什么比在发布数周后通过客户服务投诉发现缺少空值检查更糟糕的了。
质量工具,如静态分析实用程序、linter 和软件测试,在开发过程中捕获问题是无价的——对 AI 智能体和人类同样重要。
对于像 Cursor 中那样的深度 AI 集成,AI 理解这些工具并有效地使用它们。例如,当代码更改破坏静态类型检查时,它会自动调整其实现以保持合规性。
我还确保 AI 可以验证其工作的功能方面。对于 API 实现,我提供 curl 命令,以便它可以直接测试其端点。
看着 AI 随后"使用"和优化自己的实现仍然是一个非凡的景象。这里是一个来自 Cursor 会话("Agent"模式)的小例子,AI 已经修复了它创建的一些 PHPStan 违规,并继续处理剩余的问题:
在 Cursor 提示中,为 AI 配备基于工具的护栏可能如下所示:
如前所述,打开的项目(位于 ~/projects/website)托管多个 PHP/Symfony 应用程序。我希望你定期使用下面列出的质量工具和测试运行命令来检查所有受影响的应用程序是否符合要求和正确性,并修复任何未解决的问题。
以下是不同应用程序代码库中可用于你的命令:
cd ~/projects/website/backend-app && make qualitycd ~/projects/website/backend-app && make tests:unit:only
cd ~/projects/website/janus-christophorus && make qualitycd ~/projects/website/janus-christophorus && make tests
cd ~/projects/website/janus-shared-bundle && make qualitycd ~/projects/website/janus-shared-bundle && make tests
cd ~/projects/website/janus-webui-bundle && make qualitycd ~/projects/website/janus-webui-bundle && make tests
此外,当在 backend-app 中的路由 GET /membership/contracts/ 处实现 API 端点时,你随时可以在本地请求此端点:
curl http://127.0.0.1/integration-api/membership/contracts/
如果你想从端点接收演示数据,使用
curl -H "X-DEMO-MODE: true" http://127.0.0.1/integration-api/membership/contracts/
正如现有代码库可能提供隐含需求一样,所选的技术栈也可以提供隐含的护栏:例如,具有强类型系统的编程语言为编码助手偏离正轨提供的机会要少得多。
如前所述,AI 智能体在创意问题解决方面表现出色,但我们需要约束这种创意,特别是关于代码组织方面——现在,我基本上从不让 Cursor 自己创建文件。这就是基于文件的关键帧技术发挥作用的地方。
该技术借鉴了动画工作室的工作流程,其中主动画师创建关键帧——动画序列中的关键时刻——而初级动画师填充中间帧。这种方法在优化资源使用的同时保持质量。
以同样的精神,当与 AI 合作时,我在 AI 编码开始之前在代码库中创建"空壳"文件。看我们的示例任务,AI 需要实现各种组件,例如:
一个 UI Controller 类
与其让 AI 决定文件位置和名称,或在提示中指定这些详细信息,我只是为这些创建一些最小的存根文件,像这样:
<?php
declare(strict_types=1);
namespace App\MembershipAdministration\Presentation\Service;
readonly class MembershipAdministrationPresentationService
{
}
对于控制器,我可能会包含一些基本的细节,如路由和方法名称:
<?php
declare(strict_types=1);
namespace App\MembershipAdministration\Presentation\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class ContractsOverviewController extends AbstractController
{
#[Route(
'/membership-administration/contracts/',
name: 'membership_administration.presentation.controller.show_contracts',
methods: ['GET']
)]
public function showContractsAction(): Response
{
}
}
这些存根看起来可能不多,但它们功能强大。它们再次为 AI 提供了关于以下几点的至关重要的上下文:
这里有相当多的隐含信息,根据我的经验,AI 总是急切地接受的:
是的,有一个专门的 Presentation Service,所以显然,我们不会把太多逻辑放入控制器。
处理 HTTP 请求的控制器方法显然以"Action"结尾。
该功能的命名空间是"MembershipAdministration",所以显然,Web UI 不是面向最终用户,而是面向需要消化和处理大量信息的高级用户。
PHP 属性(Attributes)明显优于注解(Annotations)。
永远记住:毕竟,Cursor 等工具背后的 AI 模型是大语言模型——它们在文本上工作,具有意义和意图的文本对于良好的编码体验至关重要。
在提示中,我有时甚至不提及存根文件,只是将它们添加到上下文中,AI 就会领会。如果我想确保 AI 真正遵守它们,我最多可能会添加这样一行:
为了准备此实现,我在不同代码库中创建了一些接近空的"存根"文件。
有了这三项措施,我们能够通过 Cursor 构建完整的功能,而无需自己编写代码。
到目前为止所展示的情况是一个特别富有成果的例子:将现有的后端实现和我们的 Living Styleguide 结合到 Cursor 提示上下文中,使 AI 能够快速为之前只有后端实现的功能实现专业外观的用户界面,即使这意味着 AI 还需要跨多个应用程序代码库实现基于 API 的数据集成。
在极端情况下,这为我们提供了有用的新前端,只需几分钟,而手工劳动意味着至少需要数小时的实现工作。
这是今天使用的最终合同概览功能的屏幕截图(显示演示数据):
如你所看到的,它还提供了一个"搜索"功能,该功能未包含在初始提示中。
这是在另一轮迭代中添加的。从工作流体验来看,在第一个可用实现的基础上继续构建确实非常容易:添加搜索功能时,实际上只需再输入一条提示词:“现在在页面顶部添加一个搜索框,用于筛选所有与特定合同 ID、客户 ID 或电子邮件地址匹配的合同。”大约 60 秒后,这项功能就从前端到后端完整实现了,包括新的 UI 区域、控制器层、API 客户端和 API 端点,而且开箱即用。
由于“根据搜索框中的输入筛选给定的项目列表”是一个如此常见、应用如此广泛的概念,因此不需要把大量底层细节一点一点喂给 AI——如果正确的实现方式显而易见,AI 就会采用这种显而易见的方式。
使用 Cursor 这样的 AI 工具,也让我们能够尝试实现舒适区之外的东西,例如使用并非我们专长领域的技术栈——通常情况下,令人望而却步的成本收益比会导致这些项目根本不会得到实现。
Problem Platform Monitoring 就是这样一个例子。这是一个 Python 应用程序,用于监控我们的 ELK-stack 配置,检测生产环境中的严重错误。
这个工具每小时都会扫描我们的 Elasticsearch 服务器以查找错误消息,并生成一份全面的电子邮件报告,如下所示:
虽然我对 Python 语言和生态系统的经验几乎为零,但我还是在几个小时内从零开始实现了这个应用程序。事实上,这个项目中的 Python 代码没有一行是我亲手写的,全部由 AI 完成。
“这三项措施”对于获得可靠且正确运行的解决方案帮助极大。
由于我们已将这个应用程序作为开源软件发布,因此可以在 Github 上获取完整代码:Platform Problem Monitoring。
其中包括结构良好的需求文档,我通过 REQUIREMENTS.md 文件将其提供给了 AI。
可以看到,这份文件并不能很快读完——但它有 371 行,为 AI 智能体朝着一个明确定义的最终状态开始工作提供了基础。
它遵循清晰的层级结构:
顶层:用一行文字描述核心需求
高层:用例和动机
中层:流程和工作机制
中层:架构、技术栈和约束
底层:详细的流程步骤
底层部分将应用程序的运行过程拆分为 12 个不同步骤,每一步都清晰定义了输入、输出和副作用。
当然,我也注意到不能在缺少一整套基于工具的护栏时,就直接让 AI 开始执行任务,项目的 Makefile 就很好地说明了这一点。它提供了:
使用 black 和 isort 格式化代码,
使用 mypy 进行类型检查,
使用 bandit 进行安全分析,
以及运行完整测试套件的方法。
最后,同样重要的是,本着基于文件的关键帧方法,我为需要实现的 12 个流程步骤创建了存根文件。
首先,在完全没有亲自编写任何代码的情况下从零创建一个非平凡的应用程序,这是一次很有意思的体验!
而且在这个具体案例中,这种新方法确实带来了决定性的差异,让我们的想法从“我是说,我们真的需要监控 ELK-stack 吗?”变成了“现在有了这个 ELK-stack 监控,不是挺好吗?”。
此外,从产品开发的角度来看,结果完全没问题!这个应用程序准确完成了它原本需要完成的工作,并且每天都在稳定顺畅地运行。
然而,从软件开发的角度来看,人们可能会认为结果有好有坏。正如用户 necovek 在本文上一版本对应的 HackerNews 讨论串中所评论的那样:
这个前提或许可能成立,但作为一名真正经验丰富的 Python 开发者,我查看了其中一个文件:utils.py
里面的一切都散发着一个(糟糕的)初级软件工程师的味道:从在顶部的模块级别配置 root logger(这依赖于模块导入缓存来避免重复应用配置),到不使用标准库的配置文件解析器而是自己构建一个,再到
load_json中的竞态问题——代码先用一个if检查文件是否存在,然后就继续执行,仿佛该文件肯定仍然存在……简而言之,如果其他部分也是这样,那它就是一坨垃圾。
总而言之,我认为,“采用未知技术栈的绿地项目”目前仍然是一个特殊案例。
尽管如此,通过采用结构化需求和全面护栏的方法,我还是以真正可控的投入得到了一个有用的成果。
随后在 Cursor 中进行的一次会话生动地证明了这一点。我将上述批评放入一条旨在提高代码质量的提示词中:
I have received the following feedback regarding this codebase:
(above comment from necovek)
I therefore ask you to thoroughly improve the code quality of the implementation in @src while staying in line with the requirements from @REQUIREMENTS.md, and while ensuring that the Quality Tools (see @makefile) won’t fail. Also, make sure that the tests in folder @tests don’t break.
See file @pyproject.toml for the general project setup. There is already a virtualenv at @venv.
我录制了随后 AI 智能体会话的前约 10 分钟。可以看到 Cursor 如何在修改代码库的过程中反复运行各项质量工具,以确保自己始终没有偏离正轨,这真的非常有意思:
通过提供结构良好的需求、借助 AI 智能体可使用的工具实现适当的护栏,并采用基于文件的“关键帧”方法,我们就能够在利用 AI 强大能力的同时,保持代码质量和架构完整性。
这些经受住时间考验的实践,尤其是人类在运用这些实践时历经磨炼所积累的经验,在 AI 辅助开发时代比以往任何时候都更加宝贵——远未过时。
因此,在开发我们的主要应用程序时,也就是在那些完全处于我们技术和架构舒适区内的项目中,使用 Cursor 等编码助手已经成为日常工作——因为本文描述的这些措施让我们能够有针对性地引导 AI,而且验证最终生成代码的质量也很直接。
在基于某种技术栈的绿地项目中使用相同的技术,