AI时代,代码可读性成为软件工程新分水岭
1. 代码一夜之间变“白菜价”工程师的账本也要重算1.1 “写”这个环节的成本到底降了多少先说一个我自己的直观感受。早几年写一段批量数据处理脚本从需求到能跑通少说也得折腾半天查接口文档、试参数、处理边界条件、压测调优。现在呢把需求描述清楚扔给大模型几分钟就能出一版能跑的代码。别说普通脚本了就算是一个完整的小型服务AI 生成基础骨架加核心逻辑也就是一杯咖啡的时间。毫不夸张地说代码生成这个环节的成本已经被打到了地板价。我团队里之前接手过一个内部工具需要对接三套老系统做数据同步。放在过去这种“胶水代码”最磨人每个系统的字段含义不一样、接口风格不一样、异常处理习惯也不一样。我们花了整整一周理文档、写适配层、调试边界。现在让 AI 先做一轮粗活把字段映射、接口调用模板、异常分支全部铺开人只需要做校验和兜底一两天就能收工。省下来的不是“写”的时间而是“查资料 梳理关系”的时间这才是 AI 真正值钱的地方。但是成本大幅下降的另一面是代码总量以肉眼可见的速度膨胀。原来一个人可能一天写几百行现在让 AI 打底一天产出上千行很轻松。这就带来一个此前被忽略的问题代码的“生产”从稀缺资源变成了过剩资源而真正稀缺的东西变成了谁能把这些代码读懂、改对、维护好。换句话说软件工程的分水岭已经移动了从前是“谁能写得出来”现在越来越像“谁能接得住、读得懂”。1.2 需求里最贵的从来不是“生产”而是“维护”很多人聊软件生命周期都喜欢引用那个经典比例——一个系统从上线到报废真正花在“写代码”上的时间可能只占两成剩下八成全在维护修 bug、改需求、适配新环境、处理历史债务。而维护的第一步动作永远是“读代码”。你连这段代码在干什么都搞不清楚谈何修改更扎心的是软件工程里有个残酷现实大部分代码不是你写的但迟早要由你来改。我在几家公司待过接手过别人留下的模块、别人写的工具、别人搭的框架每一回都在“读代码”上付出过惨痛代价。有一个老项目变量名全部是a、b、temp函数几百行不拆关键逻辑藏在不为人知的全局状态里。我光是想搞清楚“这个接口为什么返回这个值”就在代码海洋里泡了两天。最后发现是某个模块在初始化时悄悄修改了一个全局配置而完全没有任何注释提示。当 AI 把写代码变成“对话框里输入需求”的时候这种维护压力被成倍放大。因为你现在面对的海量代码很可能不是你精心构思写出来的而是 AI 生成的“半成品流水线产品”。如果一个团队只追求“能跑”不追求“读得懂”那每个成员积累的其实不是资产而是债务。等债务大到一定程度系统就会进入“动哪里哪里炸”的状态——这比任何技术债务都可怕。2. “读得懂”为什么成了新的分水岭三个真实场景2.1 接手历史代码读不懂就改不动我印象最深的一次是接手一个跑了两年的数据清洗任务。需求很简单数据源里多了一个字段需要加一层转换逻辑。听起来半小时的活结果我在代码里翻了三个小时。为什么这个任务的主函数嵌套了六层循环中间还有两个标志位控制分支整个流程像迷宫。每个变量名都短得像暗号完全没有注释也没有任何单元测试可以帮我兜底。我当时的感觉就是这段代码不是让我阅读的是让我考古的。每一个分支为什么存在、为什么这个字段要这样处理、那个临时变量是什么含义全靠猜。最后我不敢直接改只能顺着调用链条一个一个验证确认了十几个调用方的输入输出格式后才小心翼翼动手。一个明明半小时就能完成的修改硬生生变成了半天的考古工作。这个场景在 AI 时代会越来越多。以前代码至少是人写的写的人多少有思路轨迹可寻现在 AI 生成代码很多时候连“作者”自己都不知道自己写了什么——因为那是模型基于概率统计拼出来的。历史代码的维护难度已经从“读不懂别人的思路”升级成“读不懂任何人的思路”你就只能靠代码本身的信息密度去反推。如果你的代码不注重可读性后面的接手人就是在给一个没有施工图的废墟做改造。2.2 AI 生成代码的“外包感”看着能跑细看不敢动AI 生成代码有一个典型特征表面规整内里松散。它会很自然地写出processData(data)这种听起来不错但内容毫无节制的函数会引入你没要求过的魔法数字和神秘常量会在一些莫名其妙的地方做防御性判断却在真正关键的边界上漏掉处理。我把这种代码称为“外包感代码”——它像极了你把需求发给一个水平一般的远程外包交付物能跑、能出结果但你看不到推导过程摸不清设计意图更别提扩展了。看过的人都有体会这种代码是最没有安全感的因为它看起来好像没问题但任何风吹草动你都不敢担保。你只能在上面叠新的补丁结果补丁越来越多代码越来越厚最后谁也不敢动。用 AI 写代码的时候尤其容易踩这个坑。因为你的心态是“它写完了我验收下”而不是“我写完了我要负责”。两者对代码质量的把控标准完全不同。如果心态是验收那你大概率只看“能不能跑通”不会细致到去抠“这段逻辑为什么这样写”。于是 AI 生成的代码就被原样合进主干隐性问题藏得更深。真正可靠的做法是把 AI 当成一个打字很快的初级同事它的产出必须经过可读性审查和重构而不是直接当成最终交付物。2.3 多人协作的“共识机器”软件工程的本质是多人协作而代码正是团队之间传递共识的载体。你写的每一行代码不只是告诉机器做什么更是在告诉你的同事这里我是怎么想的边界在哪里为什么做出这个取舍。如果一段代码只有机器能读懂人读不懂那它就不是一个好的协作产物。我在评审别人的代码时最怕看到的就是“逻辑没错但我完全无法评价”的状态。明明一段代码能跑可你要它改一个参数你根本不知道该从哪儿下手你说要给它加个功能你完全不确定它会不会影响旁边那个模块。这种“一人写全组猜”的代码就是典型的可读性失败。反过来可读性好的代码是什么感觉你拿到手里像读一篇结构清楚的文章而不是面对一堆需要拆解的零件。新人能快速上手评审人敢拍板排障时能顺着思路一路追下去。这种代码在协作中几乎是透明的——大家不需要在开会时反复解释“当时为什么这么写”因为代码本身已经把答案写出来了。团队共识成本越低迭代速度就越快这个账越往后算越明显。3. 可读性不是玄学拆开看它有哪几个抓手3.1 命名是为调用方服务的契约很多程序员觉得命名是小事随便起个名能用就行。但我想告诉你命名是整个可读性的地基。变量名、函数名、类名是最直接的“意图表达容器”。读代码的人第一眼看到的不是逻辑细节而是名字。名字起好了读者能顺着意图走名字起砸了读者只能在语义迷雾里横冲直撞。举两个例子。一个函数叫handle签名是def handle(a, b)你完全不知道它处理什么、两个参数分别代表什么。而如果改成def generate_invoice_pdf(order_id, include_tax)读的人立刻就能建立心理模型这是一个根据订单生成发票 PDF 的函数第一个参数是订单号第二个参数控制是否含税。同样是代码前者像在猜谜后者像在读说明书。那用 AI 的时候怎么控制命名质量我的经验是在 prompt 里就把命名规则定死。比如明确要求“变量名必须表达业务含义禁止使用 a、b、temp、flag 这类无意义命名”“函数名使用动词开头体现动作意图”“参数名必须自解释”。AI 是很听话的你给它框定标准它就能产出符合标准的命名你不管它它就会按概率输出最通用的名字而最通用的往往是最没信息量的。提示我习惯在项目的代码规范文档里单独写一节“命名词表”把高频概念固定成术语。比如统一叫user_id而不是一会儿uid一会儿userId然后把这个词表直接拼进 AI 的上下文。效果非常明显AI 生成的代码风格会和团队融为一体而不是一看就是“外来产物”。3.2 控制流与复杂度的“体力活”可读性的第二根支柱是控制流设计。一段代码好不好读很大程度上取决于它的“叙事节奏”——有没有层层嵌套把人绕晕有没有太多分支让人摸不着头脑有没有过长的循环体让人迷失方向。我见过最典型的反面教材就是十几层 if 嵌套加上三四个标志位控制的“重型状态机”。你读这种代码时大脑实际上是在同时维护多条执行路径每一条都互相关联等于让人肉做一次深度搜索。正常人读三遍都未必能理清更不用说后续修改了。要提高可读性关键在于“降维”。具体来说有这么几个常用手段早返回。把非法条件、前置校验提前拦住减少嵌套层级主流程保持一条清晰主线。很多人不习惯早返回总喜欢把校验写在最后结果就是代码开头一个 if结尾一个 if中间全被包进深层括号里。提取函数。一旦发现一个函数里出现了“一个完整的小逻辑单元”就把它拆出去单独命名。比如“计算折扣”和“生成财务报表”是两件事就不该混在同一个大函数里。限制函数长度。我不追求极端的“函数不能超过 10 行”但我有一条红线一段代码如果让我在心里读三遍都还没理解透那它就该拆。一个函数最好能在“一屏之内”把主线讲完这种体量最容易让人建立心智模型。AI 生成代码的时候尤其要关注复杂度。因为 AI 是“文本概率生成器”它在深层嵌套上的把控能力远不如人类——你让它写复杂业务它真的会给你写出一坨比头发还密的逻辑。所以我在用 AI 产出代码后一定会做一轮“控制流体检”有没有过深嵌套有没有该拆没拆的长函数有没有含义不明的标志位该重构就重构不要心疼 AI 生成的内容它本来就是你的草稿。3.3 边界、依赖和模块的“地基层”可读性不止体现在单段代码还体现在“结构”层面模块边界清不清晰依赖关系是不是一目了然运行时到底有多少隐藏状态。如果说命名是台词、控制流是叙事那边界和依赖就是整个舞台的地基。地基稀烂的话台词再漂亮也是空中楼阁。我最怕的一种代码就是隐藏依赖。比如某个模块内部直接访问全局数据库连接、读写临时文件、修改共享配置。你单看一个函数的实现也许没毛病但放在整个系统里它的行为会随着环境、时间、调用顺序而变化。这种代码在读的时候特别费劲因为你无法从函数本身看到全部影响因素得到处翻上下文。改善方法也很朴素让依赖显式化。需要什么参数就通过参数传进来需要什么外部服务就通过接口注入不要在函数内部偷偷摸摸访问全局状态。这样读代码的人一眼就能看出“这个函数和外界有什么关系”而不用去猜它还有多少隐形触手。模块边界的道理类似。好的模块边界应该让读者一眼知道“这块管什么、不管什么”。比如订单模块就不要顺手处理用户积分数据解析模块就不要顺带写日志文件。这种“顺手”在写代码时很爽但在读代码时会带来巨大的认知负担——每个模块都不再是单一职责读者得同时记住它“名义上的职责”和“实际上的副作用”。AI 生成代码时最容易犯的毛病就是“往一个函数里塞太多职责”。因为它会把需求里所有相关的动作都平铺出来不做取舍。这时候人要做的事情就是“切”把 AI 生成的代码按职责拆开每个函数只干一件事每个模块只管一块业务。虽然多花了一点时间但后面所有人都会感谢你。3.4 注释、文档与测试把意图打包带走可读性最后一道防线是注释、文档和测试。但这里我想先泼一盆冷水大多数人写的注释是“无效注释”。什么叫无效就是注释在复述代码本身。例如# 循环遍历用户列表 for user in user_list: ...这种注释对读者毫无帮助因为读者能直接从代码里看到“在遍历”。真正有价值的注释是在解释“为什么”# 这里不能直接用 user.name因为部分历史数据缺失会返回 None所以统一走 get_display_name display_name get_display_name(user)读完这段注释你就知道这个“间接调用”不是多余的而是有历史背景的。这种“为什么”信息代码本身根本表达不出来只有当时的创作者知道。如果你不写下来后面所有人都会疑惑甚至有人自作聪明地“优化”掉它然后引发故障。文档也是同理。我特别推崇“可运行文档”——也就是文档里的每个示例代码读者可以直接复制运行验证而不是那种“示意性质”的伪代码。一个能跑的示例胜过千言万语。这里其实也是 AI 的强项让 AI 为你的函数生成 docstring、生成示例调用、生成参数说明然后人做校验。你只需要确保它写的例子是真实可运行的而不是它幻觉出来的“看起来合理但一跑就挂”的代码。至于测试我把它当作另一种形式的文档而且是“活文档”。一套好的测试用例简直是把函数的输入输出和边界条件一一列给你看。读完测试你基本就明白了这段代码的“行为契约”。AI 在生成单元测试这件事上相当靠谱很多时候它比一部分程序员更能穷举边界。但前提还是那句老话人要读、人要审、人要修不能直接信任。4. AI 时代保持代码可读性的实操方法4.1 从 prompt 开始约束可读性很多人的 AI 使用习惯是“直接甩需求”然后代码出来跟预期差很远回头再反复调教。这种做法很低效而且容易让 AI 输出的风格漂移不定。我的做法是反过来先给约束再给需求。具体来说我会在系统提示词里写清楚团队的项目背景、技术栈和代码规范要点然后附上“生成代码时必须遵守的规则列表”。比如你是项目的资深开发工程师。生成代码时必须遵守以下规则 1. 变量名、函数名必须表达业务含义禁止使用 a/b/temp/flag 等无意义命名。 2. 禁止魔法数字必须定义常量并给出含义。 3. 函数长度控制在 50 行以内超过必须拆分为多个子函数。 4. 每个函数必须包含 docstring注明参数、返回值、异常。 5. 不修改外部全局状态所有依赖通过参数显式传入。 6. 生成代码的同时必须生成对应的单元测试。你也许觉得“写这么长的提示词很麻烦”但实际上一劳永逸。这串提示词可以保存成模板每次写代码都复用。AI 一旦被这样的约束框住产出的质量会显著比“裸写”高因为你从一开始就把它拉进了你们团队的编码规范。我甚至会把团队 code review 的高频问题翻译成 prompt 规则。比如经常发现“函数名和实现不一致”“有未使用参数”“异常被静默吞掉”就把它加到规则里。AI 学得很快你纠正几次之后它的产出就会朝团队期望的方向靠拢。4.2 把可读性放进自动化审查光有 prompt 约束还不够因为 AI 的“服从能力”有上限尤其是遇到复杂需求时它可能为了完成任务而偏离规范。所以必须用自动化工具守住底线。我现在的团队做法是三层防线第一层格式统一。用格式化工具统一代码风格比如 Python 的 black、JavaScript 的 prettier。这一层解决的是“肉眼可见的乱”保证所有代码长一个样。第二层静态检查。用静态分析工具抓“逻辑性可读性问题”比如未使用变量、不可达分支、函数圈复杂度、重复代码块。把这些规则写进 CI任何一次提交都会自动跑一遍不满足就被拦下来。第三层人工评审清单。格式和静态检查只能兜底一部分真正的思路级可读性还是得靠人看。评审的时候拿着清单逐项过命名有没有诚意控制流有没有绕模块边界有没有模糊注释有没有解释为什么有了自动化兜底人的注意力就能集中在更重要的“结构层”。不会出现“一地格式错误把评审人累死结果没人发现逻辑乱成一锅粥”的情况。这也是我在 AI 时代特别推荐的做法让机器先去筛低级问题让人去解决高级问题。还有一个很实用的技巧新代码合入主干前用 AI 反向翻译一遍。具体操作是让 AI 读取刚写完的代码用自然语言描述它“认为这段代码在做什么”。如果 AI 的描述和你最初的需求对不上或者读起来含糊不清那说明这段代码的“自我解释”能力出了问题。连 AI 都读不懂的代码人类读者大概率会更困惑。4.3 让 AI 反过来帮你“读”说完了“用 AI 写人要读”我还要提一个被很多人忽略的方向AI 也是一个极其高效的“阅读加速器”。当你面对一大坨历史代码时与其自己硬啃不如先让 AI 帮你做一轮“导读”。我经常这样用把一段历史代码贴给 AI要求它“用一份文档解释这段代码的功能、输入输出、边界条件和潜在风险用便于评审的语言组织”。AI 的输出不一定完全准确但它能提供一条很好的“线索路径”。你可以顺着它指出的关键词再看代码本身效率比从头盲猜高太多。再进一步让 AI 给历史代码生成“可读性改造建议”。比如你让它阅读一个 300 行的老函数问它“有哪些地方改成什么样子会让它更清晰”。它通常能指出“这里有很多重复分支可以合并”“这个函数职责太多可以拆成三步”。有了建议清单你再动手重构方向感会明确很多。还有一个我强烈推荐的场景新人 onboarding。新成员加入项目时直接丢给他大量代码很容易让他沉进去出不来。我现在会让 AI 先生成一份“模块导读文档”每个模块的职责是什么、入口函数是哪个、和其他模块的依赖关系如何、运行时有哪些地址和账号。新人拿着这份导读再去看代码路径感会非常强。当然AI 生成的文档还只是“带路地图”最后的准确性要求人校验否则就是拿着一份错得离谱的地图去走未知丛林。5. 常见问题与排查技巧实录5.1 典型翻车案例盲目合并 AI 代码导致线上故障讲一个我自己踩过的坑。有一次用 AI 写了一个订单状态转换的判断逻辑AI 生成了一段代码里面带着一个魔数status 5。我当时赶进度想着“反正它写的测试能过应该没问题”就直接合进去了。结果上线后一部分订单状态怎么都推不动客服反馈一个接一个。查到最后才发现5在旧系统里代表的是“已退款”而新系统的状态枚举里5是“已取消”。AI 从某段训练数据里“学”到了这个魔数但在我们的系统里它毫无意义甚至是个埋雷。这件事给我的教训很深刻AI 生成的代码越是看着正常越要警惕它背后有没有你不知道的隐含假设。尤其是魔法数字、硬编码路径、隐式转换这类东西一定要揪出来问一句“为什么是它”。如果你无法解释那就说明代码有隐藏风险。现在我的审查清单里永远有一条代码里出现的每一个魔法数字、每一个未知来源的常量都必须有明确的注释。5.2 把握“过度抽象”与“裸奔代码”的平衡AI 时代的另一个常见难题是抽象力度的失控。你会发现 AI 特别喜欢“过度设计”明明三个方法就能搞定的事它能给你抽象出接口、基类、工厂、策略模式整整齐齐一大套。乍一看好像很“面向对象”仔细一想纯属多余徒增阅读负担。反过来也有另一种极端AI 生成了一批“裸奔代码”所有逻辑平铺直叙没有任何封装两百行代码里塞满重复逻辑。这种代码读起来也不轻松因为信息密度太低读者要在大量重复中提炼真正的变化点。我的平衡标准其实很朴素抽象是否服务于读者。如果提取出一个接口能让调用方更容易理解和替换实现那就值得做如果只是为了让代码“看起来架构漂亮”但读者反而要多跳好几层才能找到真正逻辑那就别做。可读性永远是第一位的设计模式是手段不是目的。注意在让 AI 写代码前我会明确告诉它“避免不必要的抽象优先使用简单直接的方式”。如果生成结果里出现了大量设计模式我会先问一句这个模式带来了什么实际收益如果答不上来直接删掉重写。5.3 AI 帮你读代码时要注意“一本正经胡说八道”最后一点也是我认为 AI 时代最需要警惕的风险AI 在解释代码时可能会一本正经地胡说八道。它不像人类读者那样有“读不懂的羞耻感”它会用流畅的语句给你编造一个看似合理、实则完全错误的解释。你如果不加验证很容易被带进沟里。我记得有一次让 AI 解释一个状态机的跳转逻辑它给出的回答逻辑自洽、条理清晰我差点就信了。结果对着代码一查它把两个分支的判定条件搞反了整个解释都是在错误的上下文中“圆”出来的。要是照着它的思路去改代码后果不堪设想。所以我的原则是把 AI 的解释当成“线索”而不是“答案”。它给你一个方向你顺着方向去读代码、跑测试、验证假设。最终结论一定要建立在你自己查证过的事实上而不是模型生成的“听起来很对”的文字上。这也呼应了全文的核心观点AI 让“写”变便宜了但让“读”变成了新的能力分水岭——因为如果没有准确读懂的能力AI 给你的一切都只是加速犯错的方式。