AI编程时代,为什么还要手动撸码?
1. 当“AI编程”成为默认选项我为什么还在手敲每一行函数最近在几个技术群和社区里刷到的高频画面是有人贴出一段用Copilot生成的300行代码配文“十分钟搞定需求”有人晒出Cursor里自动补全的整套微服务架构连Dockerfile和CI脚本都带注释还有人发截图显示本地大模型正把PRD文档实时转成TypeScript接口定义——而我坐在工位上刚删掉第三遍重写的useDebounceEffectHook手指悬在键盘上方盯着编辑器里那行手动敲出来的const [value, setValue] useState()突然觉得这行字像一枚生锈的螺丝钉被拧进了高速旋转的涡轮机里。这不是怀旧也不是对抗。我清楚知道Copilot能在我输入// fetch user profile后精准补全带错误重试、缓存策略和类型守卫的async函数我也试过让Claude分析一段崩溃日志它三秒内就定位到是某个第三方SDK在iOS 16.4下对SharedWorker的非标准调用引发的竞态。这些能力真实、高效、不可逆。但问题在于当“生成”变成默认动作谁来定义“生成什么”谁来判断“生成得对不对”谁在模型幻觉把Math.random()写成Math.rand()时第一时间发现并修复——这些事恰恰发生在AI无法落笔的缝隙里需求模糊时的追问、边界条件的穷举、异常路径的预设、性能拐点的直觉、甚至是一段注释里藏着的业务隐喻。我坚持手动撸码不是拒绝工具而是守住“意图锚点”。就像老木匠不会因为有了电动刨就放弃目测木纹走向——AI是更锋利的刨刀但木纹方向得人眼来认。过去三个月我刻意把工作流切成“AI辅助区”和“人脑主控区”API契约设计、单元测试用例生成、重复性CRUD模板、日志格式标准化全部交给模型但状态流转逻辑、副作用触发时机、内存泄漏防护点、以及所有涉及“用户没说但必须做”的隐性需求一律手写且强制要求每段核心逻辑旁白式注释不是// 设置状态而是// 此处需同步更新localForage缓存否则离线重进时profile头像丢失——见2023Q4用户投诉#A782。这种割裂感起初很别扭现在却成了我的质量防火墙。上周上线的订单状态机AI生成的初版漏掉了“支付超时自动取消”与“客服人工干预”的并发冲突正是我在手写状态跃迁表时用纸笔画出17种时序组合才揪出来的。模型擅长填空人得负责出题。提示不要用“让AI写完再人工审”代替“人在关键节点深度介入”。审阅是滞后防御介入是前置建模。真正的风险不在AI写错而在人放弃定义“什么算对”。2. 手动撸码的现代生存法则从“写代码”到“编排意图流”十年前一个资深开发者的核心竞争力是算法复杂度分析和JVM调优今天同等资历的人花在“意图翻译”上的时间可能超过编码本身。我最近重构的搜索推荐模块典型工作流是这样的2.1 需求解构把模糊描述锻造成可执行契约产品经理说“首页搜索要更懂用户”。这不行。我拉他坐下来用白板拆解“更懂”指什么是点击率提升还是长尾词曝光增加“用户”是谁新客的冷启动需求和老客的个性化偏好策略完全不同“首页搜索”包含哪些触点搜索框输入建议、结果页相关搜索、甚至搜索失败后的智能兜底——每个触点的数据源、延迟容忍、降级方案都不同。最终产出的不是PRD文档而是一张意图契约表包含字段触点ID、用户分群、核心指标、数据源SLA、兜底策略、可观测性埋点。这张表才是后续所有AI生成任务的唯一输入源。我试过直接把“首页搜索要更懂用户”喂给模型它生成的代码完美实现了“根据历史点击加权排序”却完全忽略了新客零行为数据的冷启动问题——因为“新客”这个词根本没出现在原始需求里。手动解构本质是把自然语言里的歧义、省略、假设全部显性化、结构化、可验证化。2.2 工具链重铸让AI成为“高级胶水”而非“黑盒产线”我现在的开发环境像一个精密实验室上游用Obsidian管理意图契约库每个需求卡片关联历史决策记录比如“为什么不用Elasticsearch而选Meilisearch因实时增量索引延迟50ms见20240315压测报告”中游VS Code里配置了自定义Copilot指令集例如输入/api-contract user-profile自动注入当前契约表中该接口的全部约束字段必填性、枚举值范围、敏感数据脱敏规则下游所有AI生成的代码必须通过本地预检流水线先跑eslint --fix再用自定义脚本校验是否包含契约表要求的埋点字段最后强制插入// AI-GEN: [timestamp] [prompt-hash]水印注释。这套流程的关键在于AI不接触原始需求只处理已被人类提炼、标注、验证过的结构化意图。它像一台高精度CNC机床但图纸必须由老师傅亲手绘制。上周有同事跳过契约表直接让AI基于Figma设计稿生成React组件结果生成的SearchBar /里搜索图标用了SVG内联导致SSR时hydration失败——因为设计稿没标注“需支持服务端渲染”。而我的版本契约表里明确写了renderMode: SSR-compatibleAI生成的组件天然包含dangerouslySetInnerHTML的安全封装。2.3 质量门禁用“人肉测试矩阵”对抗模型幻觉AI生成的代码最危险的不是语法错误而是逻辑正确性幻觉。它能写出完美的try...catch却可能在catch块里静默吞掉网络超时错误。我的应对方式是建立三阶验证矩阵契约层验证检查生成代码是否100%覆盖契约表中的所有分支条件如“用户未登录时搜索框应显示‘请先登录’提示并禁用提交按钮”边界层验证手动编写极端用例比如输入 纯空格、a.repeat(10000)超长字符串、null故意传空对象观察是否触发预期降级时序层验证用performance.now()在关键路径打点确认AI生成的防抖逻辑实际延迟是否真在300ms±10ms内而非它声称的“优化了性能”。这个过程很慢但每次都能挖出惊喜。上个月AI生成的WebSocket心跳保活逻辑契约要求“断连后3秒内重连”它确实写了setTimeout(reconnect, 3000)但没处理reconnect函数自身失败时的指数退避——这是我在时序验证时故意拔网线后连续触发5次断连才发现的。模型会计算单次延迟但不会模拟系统级故障链。3. 手动撸码的隐性资产那些AI暂时学不会的“手感”在键盘上敲击十年以上的人会形成一种难以言传的“手感”知道什么时候该拆分函数不是因为ESLint报错而是当滚动条需要下拉三次才能看到函数结尾时直觉告诉你“这里呼吸感窒息了”能从console.log输出的毫秒级时间戳里一眼识别出0.034和34.217之间的本质差异——前者是JS引擎内部调度后者意味着主线程被阻塞在Git diff里看到 const data await api.fetch()后面紧跟着 if (data?.items?.length) {立刻警觉data?.items可能为undefined但?.length在null时返回undefined而非0这个条件判断实际永远为false。这些能力源于数万小时与JavaScript引擎、V8垃圾回收器、浏览器渲染管线的“肉搏”。AI可以学习语法树但学不会在Chrome DevTools里看到Layout阶段耗时突增时脊椎发凉的本能反应。我最近维护的一个老项目核心渲染逻辑里有一段requestIdleCallback包裹的DOM操作AI生成的优化建议是“改用setTimeout(fn, 0)提升响应速度”。这建议语法完美逻辑却致命——requestIdleCallback的本意是利用空闲时间避免卡顿而setTimeout(fn, 0)会抢占下一个事件循环反而加剧渲染压力。只有亲手调教过几十个FPS掉帧现场的人才懂idle和immediate之间隔着一条用户体验的生死线。这种手感还体现在对“技术债气味”的嗅觉上。当AI生成的代码里频繁出现// ts-ignore、any类型、或document.getElementById(xxx)这类反模式时我不会立刻修改而是先查Git Blame这段代码最初是谁写的当时为什么选择这个方案是时间压力还是技术限制上个月发现一个ts-ignore标记追溯到2019年当时团队刚迁移到TypeScriptfetch的Response类型定义还不完善。现在ts-ignore早已多余但直接删除会导致编译失败——因为下游有三个模块依赖这个any返回值做动态属性访问。真正的解法不是删注释而是用as unknown as MyResponseType做渐进式迁移并给每个下游模块发重构通知。AI能删掉ts-ignore但不会理解这个注释背后三年的技术演进史。注意手感无法速成但可刻意训练。我的方法是每周选一段AI生成的“优质代码”用老式调试法断点、console.time、Performance面板逐行验证其实际行为而不是相信它的注释或类型声明。4. 手动撸码人的新战场从代码实现者升级为“AI训练教练”当编码不再是核心瓶颈开发者的价值重心必然上移。我现在70%的时间花在三件事上Prompt工程不是写“写一个登录接口”而是构建包含上下文、约束、示例、拒答规则的完整提示包。比如要求AI生成JWT验证中间件时我的Prompt包含【角色】你是一个有5年Node.js安全审计经验的工程师 【约束】必须使用jsonwebtoken v9禁用{ algorithms: [none] }密钥必须从环境变量读取 【示例】参考auth.middleware.ts第12-18行的错误处理风格 【拒答】若请求头无Authorization字段返回401且不记录日志防暴力探测这比单纯写代码难十倍因为它要求你同时理解框架机制、安全规范、团队约定和模型能力边界。反馈闭环建设所有AI生成的代码上线后我强制要求监控系统捕获两类数据一是运行时异常如TypeError: Cannot read property id of undefined二是业务指标偏移如搜索转化率下降0.5%。这些数据不是丢给运维而是反向注入到我的Prompt库中形成“负样本集”。当AI再次生成类似代码时我会在Prompt里加入“历史数据显示此类optional chaining用法在iOS Safari 15.6下有5%概率返回undefined而非null请改用data data.items data.items.length 0”。知识晶体化把个人经验转化为AI可消化的结构化知识。我维护一个dev-knowledge.md文件里面没有长篇大论只有原子化条目## [React] useEffect依赖数组陷阱 - 场景监听props.userId变化重新获取用户数据 - 错误写法useEffect(() { fetchUser(userId) }, [userId]) - 根因userId可能为null或undefined导致无限请求 - 正确写法useEffect(() { if (userId) fetchUser(userId) }, [userId]) - 验证方式在DevTools中修改userId为null观察Network标签页是否发起请求这些条目直接作为AI的参考文档比任何教程都管用——因为它们来自真实的血泪教训。这种转型不是选择而是生存必需。当初级开发者能用AI完成90%的CRUD时“会写代码”就从稀缺技能变成了基础配置。真正稀缺的是能定义AI该做什么、能判断AI做得好不好、能在AI失效时立刻接管的“意图指挥官”。我最近面试一个候选人让他用AI生成一个防抖Hook。他很快交出代码但当我问“如果用户连续快速点击10次第5次点击时网络请求超时第6次点击会触发新请求吗为什么”——他愣住了。这个问题不考语法考的是对异步状态机的肌肉记忆。而这种记忆只能来自亲手撸过上百个防抖、节流、竞态取消的深夜。5. 未来已来但方向盘还在人手里上个月我参与了一个内部AI编码大赛两组人用相同需求文档一组纯AI生成一组“人主导AI辅助”。结果很有趣AI组代码量多出40%单元测试覆盖率高15%但上线后第一周人主导组的P0故障数为0AI组有3起——全是“逻辑正确但业务错误”比如搜索推荐把“儿童玩具”优先推给35岁以上用户因为模型从历史数据中学到了“35岁用户购买力强”却忽略了“购买者”和“使用者”的身份分离。这让我想起机械时代的故事当第一台自动织布机问世老师傅们没去砸机器而是转身成了织机校准师、花色设计师、故障诊断专家。今天的手动撸码人正在经历同样的职业升维。我们不再比谁敲键盘更快而比谁定义问题更准、谁校准AI更稳、谁在系统崩塌时重建得更牢。我书桌抽屉里还放着大学时买的《算法导论》第一版书页泛黄边角卷起。最近一次翻开是在调试一个图遍历算法时发现AI生成的BFS实现漏掉了环路检测。我指着书上第543页的伪代码对实习生说“看这里if not visited[v]的判断不只是为了性能更是为了防止无限循环——就像我们做人有些边界明知绕过去更快也必须亲手标出来。”这大概就是手动撸码人在AI时代的终极价值不是对抗浪潮而是成为浪潮里那块礁石——不阻止水流但让水流学会转弯。