这两年,我用AI辅助开发了好几个Web应用,有上线跑了几千个小团队用户的内容工具,也有试水后直接砍掉的内部后台。我的结论很直接:AI确实能把一个Web应用从0搭到80分,但想把它推到95分以上,光靠“AI写代码”远远不够。高品质不是靠某一个提示词蹦出来的,而是靠一整条从选型、建模、编码到测试和审查的流水线。
这篇文章我会拿一个实例贯穿始终:一个面向内容运营同学的“AI加工作台”Web应用,目标是帮运营批量生成选题、起草初稿、自动生成配图建议。前后端都用AI辅助搭建。说实话,这个项目在过程中踩了不少坑,但也沉淀出了一套可以复用的工作流。无论你是刚接触AI编程的学生,还是已经在用Cursor、GitHub Copilot干活的全栈工程师,只要想把AI真正用进Web应用开发,而不是停留在“让AI写个脚本”的阶段,这篇文章都值得往下看。
1. 用AI做技术选型:先让它把方案冲突暴露出来
1.1 为什么选型阶段就该让AI介入,而不是直接让它写代码
我见过很多团队拿到需求后的第一个动作,就是打开AI聊天框,说“帮我做一个内容管理系统”。结果AI真的给你输出一套可以跑起来的代码,目录结构、数据库表、登录注册全都有。但等你往里面加业务逻辑时,会发现扩展一个字段要改七八个文件,AI又不敢大改,最后项目变成一团浆糊。
问题出在让AI写代码之前,没有让它参与决策。技术选型不是一个“问答案”的过程,而是一个“摆约束”的过程。我们可以把项目背景直接丢给AI,让它扮演一个技术顾问,输出多套候选方案。
我当时给AI的约束是:团队会写Python和TypeScript,业务场景在中后台,需要用户上传图片、调用大模型接口生成文案、支持多人协作。AI给了三套主流组合:Python系的Django5全家桶、FastAPI加独立前端、以及Spring AI那套面向Java团队的方案。它还顺手给了一个我没想到的选项,即Next.js全栈方案,理由是“既然重度依赖大模型接口,服务端渲染加API Route可以减少一台后端服务器”。
这些信息单独查文档也能得到,但AI的价值是把冲突点提前摆到桌面上来。比如它会提示:如果选Django5,后台管理可以直接复用Admin,省掉很多重复建设,但对前端同学的TypeScript类型推导不太友好;如果选FastAPI加独立前端,接口文档自动生成,前后端并行开发很舒服,但要多维护一套前端工程。
1.2 让多个AI互相评审同一个架构方案
选型这件事,单一AI的建议存在知识新鲜度和偏好的问题。生产环境中我习惯让两个不同的大模型“吵一架”。具体做法是,把初步方案投给另一个AI,让它扮演一位有十年经验的架构师,专门挑毛病。
我在“AI加工作台”项目里让Claude给出了一套基于FastAPI的设计,随后把方案原文丢给另一个模型评审。它挑出了几个真实存在的问题:第一,AI生成任务对耗时要求高,如果继续用同步HTTP请求,前端会长时间白屏;第二,文件上传如果直接存本地,部署扩容时会丢数据;第三,AI接口的密钥如果写死在配置里,迟早会被人提交到仓库。
这几条建议看着简单,但当时我确实没在第一时间想到异步任务队列和对象存储的问题。AI之间的“互评”本质上是让不同的知识盲区互相补位,这部分比让单个AI自我反思可靠得多。
在选型阶段,我的习惯是先让AI出至少两套可对比的表格,包含语言生态、部署成本、团队上手难度、扩展性、与AI能力的集成方式这几个维度。表只要不跑偏,决策就快了。
| 方案 | 适合场景 | 扩展性 | 与AI能力集成难度 |
|---|---|---|---|
| Django5全家桶 | 内容管理、后台密集、想省掉Admin开发 | 中,单体重也可模块化扩展 | 中,Async支持虽有但圈复杂度高 |
| FastAPI + React/Vue | 前后端分离、接口繁杂、需要高性能异步 | 高 | 低,原生async直接调用大模型接口友好 |
| Spring AI系列 | Java技术栈团队统一 | 高 | 中,Java生态成熟但样板代码多 |
| Next.js全栈 | 产品页面需要SEO、团队偏前端 | 中 | 低,Server Action直接封装LLM服务 |
最终我定了FastAPI + PostgreSQL + React的方案。原因不是它比Django5更好,而是团队对类型安全、接口文档和前后端并行开发的需求优先级更高。技术选型从来不是找“最好的”,而是找“最不难受的”。
2. 从零手写不如让AI先生成“产品级数据模型”
2.1 先和AI一起把需求拆成用户故事,再谈数据表
不少开发者直接让AI“生成一个选题管理的数据库”,AI会快速建出articles表、users表。这没错,但往往忽略了业务核心:AI生成过程是异步的,需要记录任务状态;用户可能对一个选题反复生成多次,需要保留版本;运营人员之间要能分配任务。这些隐含需求,用户故事比字段描述更容易暴露。
我一般会让AI扮演产品经理,先输出一份用户故事清单。例如:
- 作为运营编辑,我希望提交一个内容方向后能获得多个选题建议,并确认其中一个。
- 作为运营编辑,我希望看到AI生成任务的进度,避免重复提交。
- 作为团队负责人,我希望查看每位成员的选题处理量,做周报统计。
AI会把这些故事反推成实体关系,然后给出候选的字段。这一步很流畅,因为大模型确实见过了大量典型业务模型。但真正值钱的是它随后追加的表设计说明。比如对任务表,它会建议用waiting / running / succeeded / failed 四态,而不是简单放一个布尔字段,这样以后做重试、回调、统计都有依据。
2.2 让AI产出带约束的迁移脚本,必须检查索引和软删除
拿到字段清单后,我让AI按SQLAlchemy模型生成迁移脚本。先放一段生成结果的简化版本:
from sqlalchemy import Column, BigInteger, String, Text, DateTime, ForeignKey, func, Index from sqlalchemy.orm import DeclarativeBase, relationship class Base(DeclarativeBase): pass class User(Base): __tablename__ = "users" id = Column(BigInteger, primary_key=True) email = Column(String(255), unique=True, nullable=False, index=True) nickname = Column(String(64), nullable=False) created_at = Column(DateTime(timezone=True), server_default=func.now()) class Article(Base): __tablename__ = "articles" id = Column(BigInteger, primary_key=True) user_id = Column(BigInteger, ForeignKey("users.id"), nullable=False) title = Column(String(255), nullable=False) content = Column(Text, nullable=False) status = Column(String(16), nullable=False, server_default="draft") created_at = Column(DateTime(timezone=True), server_default=func.now()) updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now()) __table_args__ = ( Index("ix_articles_user_created", "user_id", "created_at"), )这段代码并不惊艳,但有一个细节很关键:AI自动给“按用户查最近文章”的查询场景设计了联合索引。如果不提前说明查询模式,让它裸写,很多AI不会主动加联合索引。数据量小的时候没感觉,等一个用户攒了几万篇文章,性能差距就出来了。
更需要人工介入的是软删除。AI初版把所有表都设计成了硬删除,删除操作意味着历史数据永久丢失。对于内部工作台这种有审计需求的场景,我会在生成结束后补一句:所有业务表增加is_deleted字段,默认False,查询统一过滤。AI能快速覆盖,但触发它生成这个设计的人必须是你。
2.3 生成式AI最容易漏掉的坑:循环外键和状态机
这个项目里踩过一个很实在的坑。第一次让AI生成User和Article模型时,它为了保证“谁创建了谁”的关系,在两边都加了外键。结果启动迁移时数据库直接报“循环外键约束”。原因是我提示词里写“双向关联”,AI就在users表里加了last_article_id,同时又让articles表关联user_id。
循环外键不是技术上完全不能存在,但它会让删除、迁移和ORM序列化都很难受。处理方式很简单:表中只保留外键的“子侧”,即Article持有user_id,User侧如果要查最近文章,靠联合索引和ORM关系即可。
另一个生成式AI容易出现的问题是状态枚举之间缺少迁移路径。我用AI建模生成任务状态时,它的初版表把status定义成了普通的字符串,值可以是任意内容。后来任务从“成功”进入“已撤回”状态时,代码里到处要判断字符串对不对。正确的是把状态定义成枚举,在代码里用常量约束:
from enum import Enum class TaskStatus(str, Enum): queued = "queued" running = "running" succeeded = "succeeded" failed = "failed" canceled = "canceled"AI能生成状态枚举,但它默认不会想清楚状态之间的流转条件。例如“canceled”只能从“queued”和“running”进入,“succeeded”不能直接被取消。这些状态机约束,必须由懂业务的人补充到提示词或代码里。让AI代写逻辑没问题,但业务规则的解释权不能全交给它。
3. 前端实战:从设计稿到可用界面的AI链路
3.1 用截图和草稿让AI生成初版界面,而不是靠文字盲打
做Web应用最耗时间的是前端界面。以前从Figma设计稿到React组件动辄要数天,现在的AI工具链已经可以把这个周期压缩到小时级别。我在“AI加工作台”里实践过一套流程:先用Figma拉一个简单的运营后台布局,导出PNG后丢给Cursor的视觉输入能力,让它基于截图生成页面。
初版的代码水平相当能打。它会自动补齐侧边栏导航、表格数据展示、状态标签颜色这类基础组件。如果用v0或类似的AI前端生成工具,代码还会自带Tailwind的原子类,几乎可以直接并入现有工程。关键在于要让AI知道你的组件库约定。提示词里写清楚“使用React + TypeScript + Tailwind + shadcn/ui风格”,生成结果和团队代码风格会比较统一。
但直接生成的页面仍然有强烈的“AI味”。它擅长把理想状态的界面画得像模像样,一旦涉及真实数据的边界情况就露怯。空数据、加载中、接口失败三种状态,AI默认不会主动生成。
3.2 让界面从60分到90分:必须显式要求三态和反馈
我总结了一条前端提示词铁律:任何AI生成的列表页或详情页,都要补一句“为loading、empty、error三种状态分别设计UI,并完善交互反馈”。
以选题列表页为例,没有加载态时,用户点击刷新只能看到白屏,以为卡死了;没有空态时,新团队进去看到一张空白表格,以为功能坏了;没有错误态时,后端接口超时后前端只留下一句控制台报错,用户根本不知道发生了什么。这些场景不是AI能力不足,而是它训练数据里的代码片段往往只截取了核心渲染逻辑,不会带上完整的边界处理。我们只要主动要求,它通常能补得不错:
function ArticleList() { const [tasks, setTasks] = useState<TaskItem[]>([]); const [loading, setLoading] = useState(true); const [error, setError] = useState<string | null>(null); useEffect(() => { fetchTasks() .then((data) => { setTasks(data); setLoading(false); }) .catch((err) => { setError(err.message); setLoading(false); }); }, []); if (loading) return <SkeletonTable rows={5} />; if (error) return <EmptyStatus type="error" text={`加载失败:${error}`} />; if (tasks.length === 0) return <EmptyStatus type="empty" text="还没有选题任务,点击右上角新建" />; return <DataTable rows={tasks} />; }这段代码逻辑很简单,但AI“默认不会给你”。你要求了,它才给。
另一个高频改进点是“可逆操作”。AI生成的删除按钮通常点击后直接从列表里移除数据,但真实产品需要二次确认弹窗和取消入口。把这些要求写进设计提示里,会让产品体验提升非常明显。
3.3 让AI改交互逻辑时,别用一句话需求
用AI改前端代码,最常见的翻车现场是:你说“帮我改一下这个按钮”,它噼里啪啦输出了一整个新文件,结果你发现它把另一个组件的逻辑也带偏了。问题不在于AI笨,而是它缺少上下文。
我后来强制自己在提问时给足四个信息:文件路径、现状代码、期望行为、约束条件。比如:
在 src/features/generate-task/components/GenerateButton.tsx 中,现在按钮点击后会直接调用api.generateArticle()。请改成点击后先进入 loading 状态,按钮禁用并显示“生成中...”,等接口成功后再恢复,失败时在按钮下方展示错误信息。注意不要改变按钮的现有样式逻辑,不要引入新的状态管理库。
这样出来的修改通常能一次到位。如果只是一句“让生成按钮带上loading”,AI可能会改按钮,也可能给你包一层高阶组件,甚至还会顺手把按钮事件改成onMouseDown,后续追查起来很痛苦。
4. 后端工程化:AI生成的代码要过四道关
4.1 让AI生成完整工程骨架,不要在单文件里堆逻辑
AI擅长写单个接口的单点逻辑,不擅长主动设计分层。让它直接实现“AI生成选题接口”,它可能把HTTP请求解析、数据库查询、大模型调用、异常处理全部塞进一个端点函数里。开发时爽,测试时崩溃,后期维护更是地狱。
我让AI按“路由层-服务层-仓储层”结构生成FastAPI工程。路由层只负责参数校验和返回格式;服务层承载核心业务,比如调AI接口、组装上下文;仓储层封装所有数据库操作。同时加上了pydantic-settings管理环境变量,统一异常处理中间件,以及结构化日志。
第一版AI生成时大概率会漏掉日志的可追踪性。Web应用一旦遇到线上问题,没有trace_id的日志查起来就像大海捞针。我补了一个中间件,请求进来时生成UUID写入request.state,日志里统一带上trace_id。这个习惯建议在项目第一天就做好。
补充后的结构示意:
app/ main.py # FastAPI实例、注册路由 core/ # 配置、安全、异常 routers/ # 路由定义,只做参数校验 services/ # 业务逻辑,如调用AI大模型 repositories/ # 数据访问,ORM查询 models/ # 数据模型 schemas/ # Pydantic输入输出模型AI能够生成这个目录结构,前提是提示词里写清楚,“这是一个生产级FastAPI项目,请按分层架构创建完整骨架”。另外要求列出每个文件的职责说明,方便团队成员对照。
4.2 数据库事务与并发更新:AI最容易想当然的地方
在后端代码中,AI生成最不稳的部分是“有副作用的写操作”。
一个典型场景是团队积分功能:用户每次成功生成一篇文章,系统给对应账号加积分并写入流水。AI初版代码是这样的:
article = await repository.create_article(data) user.points += 10 await repository.update_user(user) await repository.create_points_log(user.id, 10)表面看没问题,但如果create_points_log执行失败,积分已经加了;如果两个请求同时给同一个用户加分,后一个更新会把前一个覆盖。AI不会主动告诉你这里需要事务和锁,因为它的训练样本里大量代码都是教学性质的“顺序执行”。
我会让AI先导出代码,再单独追问一句:“这个函数在并发场景下是否安全?是否需要放在数据库事务里并加行锁?”让AI基于追问生成改进版:
async with db.session.begin(): user = await repository.get_user_for_update(user_id) user.points += 10 await repository.update_user(user) await repository.create_points_log(user.id, 10, "generate_article")问题在于AI并不清楚你的业务并发量级。如果你不做任何澄清,它给的是普通代码;你只要多问一句,它才会切换到生产环境的思维。这个“追问”的动作,本质上是在模拟一个资深工程师对初级开发者的Code Review。
4.3 把AI生成的代码接入CI,让质量问题自动暴露
无论AI写得多顺,进入主干分支前都要过自动化质量门禁。我会让AI生成一份GitHub Actions配置,在每次Push和Pull Request时执行:lint检查、单元测试、构建镜像、安全扫描。
name: ci on: pull_request: push: branches: [main] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: "3.12" - run: pip install -r requirements-dev.txt - run: ruff check app tests - run: pytest --cov=app --cov-fail-under=80把AI代码交给CI之后,它的能力边界变得很清楚:如果AI生成了带语法错误的代码、引入了未使用的依赖、测试覆盖过低,流水线会直接标红。这样,在代码被合并之前就把大部分低级问题拦截住了。品质不是靠人盯着代码逐行读出来的,而是靠流程卡出来的。
我个人的经验是:AI生成后端代码的速度很快,但代码合并的速度要人为放慢。先跑一遍lint,跑一遍测试,再让AI自己解释“这段逻辑为什么这么写”。如果解释不清楚,那这个实现很可能是错的。
5. AI Agent编码实战:把“它自己改文件”变成可控流程
5.1 Agent模式比普通对话强在能跨文件动手,但更容易跑偏
现在的AI编程工具早就超越了“聊天窗口复制代码”的阶段。Cursor、GitHub Copilot的Agent模式可以让AI自己读取项目文件、列出目录结构、定位到具体函数、执行修改,甚至运行测试命令。刚用这类功能时确实很震撼,尤其是面对一个几千文件的老项目,普通的补全模式连项目上下文都拉不完,Agent却能一步步翻找。
但方便意味着风险。Agent在自主模式下有时会一次改太多文件。我遇到过一次让它修“任务列表排序问题”,它把筛选逻辑也重写了,还顺手升级了依赖库,导致另外几个测试失败。如果你对项目不熟,甚至很难一眼从diff里看出它动了哪些地方。
解决办法是限制工作半径。许多Agent工具都允许自定义权限,我默认禁止Agent修改测试文件以外的核心配置,比如数据库连接、鉴权逻辑、部署脚本。每次让它执行重构时,先用“plan模式”先生成一个修改计划,由人确认后,再切换到执行模式。
5.2 给Agent写任务清单,像给新同事布置活一样拆解
Agent能不能干好活,很大程度上取决于任务描述是不是“动词加范围加验收标准”。我习惯给Agent一个结构化的任务单,比如:
任务:为 article_gen_task 增加重试功能。 背景:任务失败时,用户可以在详情页点击“重新生成”。 改动范围:只允许修改 app/services/task_runner.py 和前端 task-detail-page.tsx。 实现要求: 1. task_runner.py 中新增 retry_task(task_id) 函数,重新将任务状态置为 queued。 2. 同一任务只允许在 failed/canceled 状态下重试,否则返回 400。 3. 前端在失败状态展现时增加“重新生成”按钮,点击后调用新接口。 4. 禁止修改数据库迁移文件或表结构。 验收:运行 pytest tests/test_task_retry.py 全部通过;前端跑通一次手动点击用例。任务范围越小,Agent的执行准确率越高。我试过把所有需求塞在一个提示词里让Agent一口气实现,结果经常是前两个功能写好,后面两三个功能因为上下文长度问题被忽略。把大任务拆成多个能独立提交的小批次,每批都跑过测试后再进行下一批,收益远大于一次“全家桶”式生成。
5.3 Agent改完代码,先用命令确认再信任它的汇报
Agent会汇报“我已经完成任务”,但它的判断可能只是单纯的“代码已写入”。我要求自己执行三个确认动作:看diff、跑测试、检查关键文件。
git diff --stat git diff app/services/task_runner.py pytest tests/test_task_retry.py -q如果diff范围超出预期,立刻用版本回退,再重新缩小任务边界。如果测试没过,先把失败测试贴回给Agent,让它解释根因。这套流程看起来“繁琐”,但它恰恰是AI编程和“失控”之间的护栏。Agent是一个极其高效的执行者,但不是一个可靠的项目经理,项目管理者始终是人。
6. AI辅助测试与安全检查:让质量防线自动化
6.1 让AI生成有意义的单元测试,而不是堆覆盖率
让AI写单测很容易,让它写“能抓住真实回归”的单测很难。AI默认生成的测试往往是happy path:构造一个对象,调用函数,断言返回值等于预期。这类测试跑一遍全绿,但代码回归时一点忙都帮不上。
我改进了提示词,让AI基于“异常路径和边界条件”设计测试用例。比如给任务重试函数写单测时,要求覆盖这样几个场景:
async def test_retry_failed_task(): # 预置一条 failed 状态任务,调用重试后状态变为 queued pass async def test_retry_succeeded_task_returns_400(): # 预置一条 succeeded 状态任务,重试接口应该返回 400 pass async def test_retry_running_task_returns_400(): # 预置一条 running 状态任务,重试接口应该返回 400,防止重复执行 pass之前我以为让AI写这类测试很困难,结果是它生成得又快又稳,前提是我明确告诉它“被测函数应该有哪些状态约束”。又一次证实了AI不是不行,而是需要人被逼着把逻辑想清楚。
6.2 端到端测试:AI能写用户视角的脚本
单元测试覆盖了函数逻辑,但Web应用真正的品质问题经常跨模块出现。前端按钮调了错误接口、登录态丢失、后端数据格式升级但前端没适配,这些问题单测发现不了。我让AI用Playwright生成端到端脚本,覆盖核心用户旅程:登录、打开选题页、提交生成、等待任务完成、确认生成结果。
从AI生成的脚本形态来看,它天然适合这种“口语化描述转代码”的工作。但需要注意,AI在写端到端测试时会过度依赖固定文案定位元素。前端一旦把按钮文字从“重新生成”改成“再生成一次”,测试就挂了。我让AI改用data-testid定位,并要求在关键操作之间加上显式等待,不用全局的固定等待时间,测试稳定性会好很多。
6.3 安全测试提前做,不要在出事后补
Web应用的安全测试应该渗透到开发的每个阶段,而不是发布前的某一天突然做一遍。AI能帮上忙的地方,是扮演“攻击者”对代码做代码审视。
在“AI加工作台”里,我拿着AI生成的登录注册代码,让它以安全工程师的角色重新审查一遍。它很快找出了注册登录接口缺少频率限制、用户输入文案被直接渲染到前端有存储型XSS风险、文件上传没有校验文件类型这三个问题。
这些判断不完全依赖ChatGPT类大模型,也需要你有一点Web安全的基础认知。以前看CTF里web应用的攻防题目时积累的敏感度很有帮助。安全问题的思路往往是反直觉的:你不仅要确定“正常用户怎么用”,还要想“恶意用户怎么滥用”。AI能检查和提示常见漏洞,但不会主动建立安全测试常态化机制。真正让大家不放心的不是AI不够强,而是没人在流程里给AI安排安全审查这个角色。
对AI生成的接口代码,我要求团队在合并前做一轮基础payload检查,尤其是在登录、搜索、评论这类用户输入密集的功能上。把安全策略写进CI:用gitleaks检查硬编码密钥,用bandit检查Python代码的常见危险调用。
7. 打磨“高品质”:AI代码评审与发布前的最后检查
7.1 让AI扮演不同角色,对同一份代码做多轮审查
代码写完、测试通过,距离“高品质”还差最后一步:评审。这一步不应该只靠人,也不应该只靠AI,而是两者合作。
我的做法是,把待审查的代码贴给AI,分别指定角色。第一轮让它扮演“用户”,关注功能是否简单直接:这个页面我要花几步能完成任务?第二轮让它扮演“性能专家”,检查有没有N+1查询、有没有不必要的循环调用外部API。第三轮让它扮演“数据安全负责人”,检查日志是否打印了用户敏感信息、权限判断是否失效。
以一次真实体验为例,AI在扮演“用户”时发现选题详情页要跳转两个页面才能重新生成,建议把操作收敛到详情页内;在扮演“数据安全负责人”时,它发现调试日志里把用户邮箱明文打出来了。这些问题是团队内部Code Review时未必能当场注意到的,让AI换角色扫一遍,成本低,产出却直接。
7.2 人工必须掌控的部分:权限模型、密钥和不可逆操作
AI能做的事情很多,但有三类环节我会牢牢握在自己手里。
第一类是权限模型。AI很容易生成“登录后都能访问所有功能”的代码,但真实产品通常有普通成员、管理员、团队负责人三类角色。每一个接口的权限判断,都需要业务负责人明确指认,不能交给AI猜。第二类是密钥管理。AI生成的示例代码经常包含明文密钥,比如api_key = "sk-xxx"。这个习惯带进生产仓库就出大事。第三类是涉及真金白银或不可逆代价的操作,比如批量删除、自动扣费、对外发布。这些操作建议加人工确认二次弹窗,AI写的代码默认信任太危险。
我在发布前有一个固定清单:用gitleaks扫一遍仓库密钥、检查环境变量是否全部走配置中心、手工跑一遍权限冒烟测试、审查所有“删除”接口是否有软删除或二次确认。AI能帮我把这个清单落地成脚本,但制定清单和签字确认的那个人,必须是人。
7.3 一点个人习惯:宁可让AI多产,代码合并要小步走
这几年下来,我对“用AI打造高品质Web应用”有了另一个层面的理解:高品质不是指代码毫无瑕疵,而是整个团队对代码的“可控感”。AI能生成大量代码块,但真正的产品价值永远来自清晰的架构和稳定的流程。
我在实际操作中的体会是,把所有AI产出的内容按批次提交,不要一次开一个十天不合并的长分支。每次提交都保证diff尽量小,测试尽量全,评审尽量快。这样即使AI在某一步产生了问题,回溯到上一版本的成本也极低。AI替你写一万行代码很轻松,但你要是能在出错后的十分钟内稳定回滚,那这个项目才算真正具备了可持续交付的底气。
有时候我会把AI当成团队里一个永远不下班、学习速度极快、但对业务一无所知的初级工程师。给它清晰的输入、给它明确的边界、在关键时刻校方向。这样协作出来的Web应用,既有AI带来的速度,又保持了人能掌控的品质。