引言
随着大模型能力的提升,AI 编程的效果越来越好。GPT 4 时代我只会给粘贴某些代码片段让 AI 分析,或者是生成一些简单的工具函数。如今的 AI 使用起来更加便捷,功能也愈加强大,不仅能理解整个仓库的代码,还能自动编辑文件,使用 MCP 工具,完成更大、更系统的需求。
笔者从去年12月开始使用 Windsurf,到今年高强度使用组内自研的 Agent 工具,发现 AI 确实能提高效率。在此和大家交流一些个人浅显的心得。
使用 AI 编程的模式主要分为两种:1.创造者模式;2.迭代者模式。
创造者模式就是使用 AI 以最小的成本和最短的时间推出一个具备基本功能的产品版本,实现从0到1的创造。不同于 bolt.new 可以一句话生成 app,在业务中产品形态和设计图都是确定的,在此可以引申为从零开始实现某个页面的 MVP 版本。
迭代者模式,顾名思义就是进行日常的迭代,例如重构、或是添加新的功能点。
创造者模式:从 0 到 MVP
设计图转代码
以 Claude 为代表的大模型支持以图片作为输入,不妨先尝试图片生成页面:
在 index.tsx 中,还原这个页面:
继续向 AI 补充信息,例如 XXX 模块可以复用已有组件,需要调用哪些接口,接口的入参和出参如何定义。
完善 AI 的修改计划后,开始执行:
执行的时间还蛮久的,因为 AI 还是会写出错误代码,不断修复的过程很花时间。大约20分钟后,得到了这么个页面。
整体的样式和设计图完全对不上,或许可以说明目前 Claude 解析图片的能力还存在进步空间?
总之给 AI 扔一张设计图,提效比较有限:
- 首先在样式上,并不能还原得很好。
- 其次在代码逻辑上,人类也不能很好地把控,常常需要自己补充,或者是花很多时间调整。
详细描述生成页面
既然直接上传图片的效果不好,那么退而求其次,直接口述描述一个页面呢?
以这个页面为例:
为了让生成的页面尽量符合预期,在页面结构、布局和业务逻辑上都有比较详细的描述,虽然打字花时间,但是可以一边写一边理清思路,也能减少后面调整 AI 代码的时间成本。这一步不需要说得事无巨细,只需大致符合设计图。
细节差的还是比较多,但是整体框架出来了,基本上符合预期。然后就可以转为迭代者模式。相比只传设计图,这种方式减少了调整的成本,不用花太多时间,就能改成这样:
接下来再慢慢和服务端联调就行了。
该方式虽然需要输入较多提示词来引导 AI 生成符合预期的页面,前期要花一定时间,显得比较笨,但是生成的代码更可控,后期调整的成本显著降低。
迭代者模式:日常开发
比起直接生成页面,对局部进行调整的场景更加频繁。在迭代者模式下,我认为以下两个 case 有参考价值。
具体的功能点,辅以文档
在某需求里,需要用 iframe 接入兄弟团队提供的播放器。由于笔者 iframe 接触得不多,就想到了求助 AI,只需大致描述下背景,再把文档复制过去:
在 generateList 里通过 iframe 加载了其他页面,请你阅读以下内容,完成主页面和 iframe 之间的通信。
iframe 嵌入的页面,通过 postMessage 来进行消息传递。 1. 当页面载入完成后,会发送以下消息,通知页面已载入 window.parent.postMessage({ action: 'iframe_ready', namespace: 'cara', payload: {}, }, ''); 2. 当页面接到上述消息,就可以向预览 iframe 页面发送 json 数据,消息格式如下 { action: 'player_init', template: {} } 3. 预览 iframe 页面接到上述消息后,会尝试载入数据。若载入成功,则用户可预览创意;若载入失败,会发送以下消息通知 window.parent?.postMessage({ action: 'iframe_error', namespace: 'cara', payload: { code: 101, error: '创建或渲染播放器失败' } }, '')
没花多少时间 AI 就生成了可用代码,靠笔者自己 Google 研究肯定是要花更多时间的。
useEffect(() => {
window.addEventListener('message', handleMessage);
return () => {
window.removeEventListener('message', handleMessage);
};
}, [handleMessage]);
const handleMessage = useMemoizedFn((event: MessageEvent) => {
const { data, source } = event;
if (data?.namespace !== 'cara') return;
switch (data.action) {
case 'iframe_ready': {
const iframe = iframeRefs.current[iframeIndex];
if (iframe?.contentWindow) {
iframe.contentWindow.postMessage(
{
action: 'player_init',
template: videoList[iframeIndex].mixedEditTemplate,
},
'*',
);
}
break;
}
}
});
<iframe
key={`${latestTask.id}-${video.updateTimestamp}`}
ref={el => {
if (el) {
iframeRefs.current[index] = el;
}
}}
src={iframeUrl}
className={styles.iframe}
/>
以防万一,再查下 MDN,确保代码正确性。
核心是把握任务的颗粒度。这种做法适用于某一明确的场景,大体功能框架都已经确定,只是针对某个小点进行改造,可以让 AI 协助你攻克最后的问题。文档仅起到辅助的功能。
仿照已有模块的实现
如果已经实现过模块A,希望改一改得到模块B,这种场景也很适合交给 AI。
例如已经有视频预览组件 VideoPreview,现在希望把里面用到的 <video>
标签换成上述 iframe,需要写一个 VideoPreviewIframe。不妨让 AI 完成:
VideoPreview 中实现了视频预览,现在需要在 VideoPreviewIframe 里实现一套新的,其他逻辑都一样,只是中间的视频播放器不能使用 video 标签,而是要使用 iframe,相关代码请参考 XXX 里面 iframe 的相关实现。 不要修改 VideoPreview 和 XXX 的代码。
于是就直接得到了一个可用的新组件:
总结
AI 适合确定性强的工作
AI 适合做确定性较强的工作,尤其是存在参考信息的时候,AI 可以为人类省下不少重复工作的时间。参考信息可以是具体描述、现有代码、文档等。
- 根据描述,实现某个页面的大体框架。
- 参考现有代码,接入已有模块。或者是仿照 A 模块实现 B 模块。
- 按照文档,实现某个具体的功能。日常开发中可以把问题转化为一个个孤立的、定义明确的任务。对于比较大的功能,进行逐步构建。
一旦缺少参考信息,或信息过少,确定性不足的时候,AI 就会开始放飞自我,反而浪费时间。不建议一次性让 AI 写太多代码,一来等待时间太久消磨人的耐心,二来写得越多犯错概率越高,AI 左右脑互博导致更多时间被浪费,最后大概率还要直接 git restore 恢复原状。
最好不要完全依赖 AI
把 AI 作为辅助工具进行提效并非全盘接纳,要带着批判的眼光看待 AI 代码,在理解的基础上思考有没有更优的实现方案,使用人工智能来加速而不是取代人的判断。
AI 编码存在 70% 问题,即 AI 工具能够帮助快速完成70%的工作,但最后的30%却变成了收益递减的过程。如果在使用 AI 实现某功能的时候出现了卡壳,不妨停下来重新审视一下问题,暂时放弃依赖 AI,相信自己的经验。