AI 时代,程序员还要修什么
古董级程序员,大厂出来后一直在创业公司,现在仍在一线写代码、做产品、和 AI 编程工具较劲。更完整的更新会放在微信公众号「字与码」:工作经历、新技术观察、AI 开发实践,以及这些年踩过的坑,都会陆续写在那里。
《AI时代的程序员修养》写到第十二篇,差不多该收束一下。
这个专栏从“程序 = 算法 + 数据结构”讲起,经过进程、线程、协程、模块通信、接口契约、数据库、缓存、队列、高并发、测试、可观测性和重构,绕了一圈,其实是在说同一件事:AI 可以帮你写代码,但它不会替你承担系统后果。
程序员长期要修的,不是背更多框架名字,而是形成稳定的工程判断。知道问题怎么拆,状态怎么流,边界怎么画,成本怎么算,结果怎么验证,风险怎么收住。
不要把 AI 当成新手程序员
很多人把 AI 当成一个写代码很快的新手程序员。这个比喻有一半对,一半错。
对的是:AI 确实需要清楚的任务、上下文和验收标准。你给一句含糊需求,它就会按概率补全。你给明确边界,它会稳定很多。
错的是:新手程序员会在项目里慢慢形成责任感,知道某段代码上线后会影响谁。AI 没有这种责任感。它不知道线上报警半夜响起来是什么感觉,也不知道一次错误扣费要怎么补偿用户。
所以程序员的角色不是“让 AI 替我写完”,而是“把问题组织到 AI 可以正确工作的形状”。
这个形状通常包含:
明确的输入输出
稳定的数据结构
清楚的状态机
可验证的测试样例
受控的副作用
可观测的运行证据
可回滚的上线方式
这些东西听起来普通,但它们就是工程。
长期能力一:问题建模
写代码之前,先把问题建模。
建模不是画漂亮图,而是把现实里的混乱概念压成程序可以处理的对象、关系、状态和约束。
比如“用户提交一个任务,系统异步执行并返回结果”。这句话太粗。真正建模时要问:
任务有哪些状态?
状态之间怎么转移?
任务是否可以取消?
重复提交怎么处理?
执行失败是终态还是可重试?
结果是否有版本?
用户是否能看到别人的任务?
任务执行是否产生扣费?
扣费发生在提交时、执行前还是成功后?
很快,你会得到一个状态机:
created -> queued -> running -> succeeded
├── failed_retryable -> queued
├── failed_final
└── cancelled
这个状态机比一句需求有用得多。
它能指导数据库字段、API 错误码、worker 幂等、测试样例、前端展示和告警规则。
让 AI 写代码前,先让它和你一起建模:
先不要写代码。
请把这个需求建模成实体、字段、状态机、状态转移约束和失败场景。
请指出哪些状态转移需要事务保护,哪些操作必须幂等。
会建模的人,AI 才能帮得上忙。
长期能力二:复杂度判断
复杂度不是算法课里的符号游戏。
实际系统里,复杂度决定一个功能能不能撑住真实数据。
看一段 AI 写的代码:
for user in users:
orders = await db.get_orders(user.id)
for order in orders:
items = await db.get_items(order.id)
result.append(build_row(user, order, items))
它看起来很自然,但很可能是灾难。
如果有 1 万个用户,每个用户 20 个订单,每个订单 5 个商品,这段代码会发起大量数据库查询。问题不在 Python 语法,而在复杂度。
需要转换成批量思路:
一次取用户集合
一次按 user_id 批量取订单
一次按 order_id 批量取商品
在内存里用哈希表关联
伪代码:
orders_by_user = group_by(await db.get_orders_by_user_ids(user_ids), key="user_id")
items_by_order = group_by(await db.get_items_by_order_ids(order_ids), key="order_id")
for user in users:
for order in orders_by_user.get(user.id, []):
items = items_by_order.get(order.id, [])
result.append(build_row(user, order, items))
这里用到的知识很基础:哈希表、批量查询、时间复杂度、I/O 次数。
AI 可以写出第二版,但前提是你知道第一版哪里不对。
长期能力三:状态敏感
很多线上 bug,本质都是状态问题。
重复请求、并发提交、任务重试、缓存过期、事务回滚、消息重复投递,最后都会落到“系统现在处于什么状态”。
AI 写代码时经常只写 happy path:
job = await repository.get_job(job_id)
result = await provider.call(job.payload)
await repository.save_result(job_id, result)
await repository.mark_succeeded(job_id)
工程里要补的是状态保护:
job = await repository.lock_job(job_id)
if job.status not in {"queued", "failed_retryable"}:
return SkipReason("invalid_status")
await repository.mark_running(job_id)
try:
result = await provider.call(job.payload)
except TimeoutError:
await repository.mark_retryable_failed(job_id)
raise
await repository.save_result_once(job_id, result)
await repository.mark_succeeded(job_id)
这里有事务、锁、幂等、状态转移。
状态敏感的人,看代码时会自然问:
这个操作能不能重复执行?
两个人同时点会怎样?
worker 重启在中间步骤会怎样?
外部接口成功了,但本地写库失败会怎样?
消息队列重复投递会怎样?
这些问题问出来,AI 生成代码才会从 demo 接近生产。
长期能力四:边界感
边界感是程序员非常重要的修养。
一个模块应该知道什么,不应该知道什么;一个函数应该承担什么,不应该承担什么;一个接口应该暴露什么,不应该暴露什么。
没有边界感,系统会变成这样:
API 层直接拼 SQL
数据库层判断用户权限
工具调用层顺手扣费
日志里塞完整用户输入
前端依赖后端临时字段
脚本直接改线上状态
这些代码短期都能跑,长期都会还债。
边界感不是追求形式上的分层,而是让变化有地方可去。
权限规则变了,只改权限模块。第三方 provider 换了,只改 provider client。扣费策略变了,只改 billing。前端展示变了,不影响数据库事务。
给 AI 的指令要具体:
API handler 只负责请求解析、调用 use case、返回响应。
业务规则放在 domain 层,尽量写成纯函数。
数据库访问通过 repository,不要在 handler 里拼查询。
外部服务调用通过 provider client,必须设置超时和错误映射。
扣费、审计、埋点是独立副作用,不要散落在业务分支里。
你越有边界感,AI 生成的代码越不容易乱长。
长期能力五:反馈回路
工程不是写完代码就结束。
代码上线后,用户怎么用,系统哪里慢,错误在哪里发生,哪类输入最多,哪个功能没人碰,这些都要回到下一轮设计里。
反馈回路包括:
测试反馈:代码是否符合预期。
构建反馈:依赖、类型、格式是否稳定。
线上反馈:错误率、延迟、资源消耗是否正常。
业务反馈:用户是否真的完成目标。
团队反馈:别人是否能理解和维护。
AI 编程会让产出速度变快,但如果没有反馈回路,错误也会更快地堆起来。
一个很实用的节奏是:
小步生成
小步测试
小步 review
小步上线
看指标再继续
不要一次让 AI 生成一整套大系统,然后再试图从 5000 行代码里找问题。那不是效率,是把风险延期。
长期能力六:产品判断
程序员不能只判断技术可行。
AI 时代,做一个功能的成本更低了,错误功能的数量也会变多。以前因为开发成本高,很多需求会被自然过滤。现在只要一句 prompt,就能生成一个看似完整的功能,反而更需要产品判断。
产品判断不是“我要不要转产品经理”。它是工程决策的一部分。
你要问:
这个功能解决谁的问题?
用户完成任务的最短路径是什么?
这个功能会不会增加维护成本?
有没有更小的版本可以先验证?
数据是否足够准确,用户是否会误信?
失败时用户能不能理解发生了什么?
很多技术债,源头不是代码写差了,而是需求没想清楚。
AI 能降低开发成本,但不能自动证明需求有价值。会写代码的人如果没有产品判断,会更快地制造无用系统。
长期能力七:表达和协作
AI 编程让表达能力变得更重要。
以前你写给人看的需求、设计、PR 描述,现在同时也是写给 AI 的上下文。表达越清楚,AI 越容易做对;表达越含糊,AI 越会替你脑补。
好的表达不是长,而是结构清楚:
背景:为什么做。
目标:做到什么算完成。
非目标:这次不做什么。
输入输出:数据从哪里来,到哪里去。
约束:性能、权限、兼容性、成本。
失败场景:哪些情况必须处理。
验收:测试、指标、示例和回滚方式。
团队协作也是一样。
AI 生成代码以后,更要写清楚为什么这么做。不要把一大坨生成代码丢给同事 review,然后说“AI 写的”。代码进仓库以后,就是团队资产,责任仍然在人。
长期能力八:知道什么时候不用 AI
AI 很强,但不是所有场景都适合直接让它写。
这些场景要特别谨慎:
权限和安全边界
扣费、结算、财务记录
数据删除和不可逆操作
数据库迁移
高并发核心路径
复杂并发控制
隐私和合规逻辑
跨系统状态修复脚本
不是说 AI 不能参与,而是不能无审查地生成和执行。
更合适的用法是:
让 AI 列风险点。
让 AI 生成测试。
让 AI 解释迁移影响。
让 AI 做只读分析。
让 AI 写草稿,人逐行审查。
能力越强的工具,越需要边界。
一份长期练习清单
如果只保留一份清单,我会建议长期练这些:
1. 看到需求先画状态机。
2. 看到循环先估数据量和复杂度。
3. 看到数据库先问索引、事务和锁。
4. 看到外部调用先问超时、重试和幂等。
5. 看到接口先问 schema、错误码和兼容性。
6. 看到异步任务先问重复投递和恢复点。
7. 看到缓存先问失效、一致性和穿透。
8. 看到 AI 生成代码先问测试和观测。
9. 看到大改动先问灰度、回滚和数据修复。
10. 看到新功能先问是否真的有人需要。
这些不是某个框架的知识。它们是跨语言、跨平台、跨时代的工程习惯。
给 AI 的总提示词
这个专栏的最后,可以把一段提示词留在这里:
你不是只负责写代码。
在实现前,请先帮助我完成工程设计:
1. 把需求拆成实体、状态、输入输出和约束。
2. 说明核心数据结构和算法复杂度。
3. 说明模块边界、依赖方向和副作用位置。
4. 说明数据库表、索引、事务和锁的设计。
5. 说明接口 schema、错误码、幂等和版本兼容。
6. 说明缓存、队列、异步任务和失败恢复。
7. 说明测试方案:单测、集成测试、契约测试、回归测试。
8. 说明可观测性:日志、指标、Trace、审计和业务埋点。
9. 说明上线策略:灰度、回滚、数据修复和监控。
10. 标出你不确定的地方,不要自行假设。
确认设计后,再分步骤生成代码。每一步都要说明修改范围和验证方式。
这段提示词并不能保证 AI 永远正确,但它能强迫对话回到工程本身。
程序员没有过时,只是门槛换了
AI 时代,写几行代码的门槛会越来越低。
但系统的门槛没有消失。状态还是会错,事务还是会冲突,缓存还是会不一致,队列还是会重复投递,接口还是会兼容,用户还是会误操作,线上还是会报警。
区别在于,过去程序员花很多时间敲代码。现在,更多时间会花在定义问题、组织上下文、审查结果、验证行为和承担后果。
这不是退步。某种意义上,这是程序员工作里更核心的部分终于浮出来了。
长期看,真正有价值的程序员,不是比 AI 多记几个 API,也不是和 AI 比谁写得快。
而是知道什么该生成,什么该手写,什么必须验证,什么不能上线,什么需要删除,什么值得长期维护。
AI 会放大程序员的能力,也会放大程序员的盲区。
所以修养这件事,反而更重要了。
微信公众号
欢迎关注「字与码」
如果这篇文章对你有用,也欢迎在微信里继续关注后续更新。
X / Twitter
关注 @ax2_zicode
更即时的技术观察、新文章提醒和一些短想法会发在 X 上。