从上线事故到代码审查:编程规范是团队的隐性底线

发布时间:2026/10/19 10:17:10
从上线事故到代码审查:编程规范是团队的隐性底线
被我们组新来的同事写的那段代码搞得差点上线事故当时就差没把键盘拍他脸上。我说你这也太不按套路出牌了他一脸无辜问能跑不就行了吗差那么点意思类似这种对话基本上每隔一段时间就会在各处上演一遍。写代码这件事跑通只是最底层的目标真正让一个团队稳定、高效、不互相埋雷的恰恰是那些看起来“不写业务逻辑也花时间”的规矩也就是编程规范。今天就从我这些年亲眼见过、自己踩过的坑出发把“没按编程规范来被骂惨了”这件事掰开揉碎了聊聊顺便也聊聊怎么让自己少挨点骂。1. 编程规范到底在约束什么很多人一提编程规范第一反应就是“代码风格”。包括缩进用几个空格、花括号换不换行、变量名用不用驼峰。说实话这些只是编程规范最表层的东西也是刚入门的人最容易纠结的部分。被骂惨通常不是因为风格本身而是因为没有遵守大家共同认可的规则导致别人接手、审查、调试时抓狂。1.1 规范的本质是统一预期我举个生活化的例子。一群人一起做饭有人把盐放在灶台左边有人放在右边有人搁在抽屉里做出来的味道可能没问题但如果中途换人接手新来的人光找盐就得找半天搞不好还会把孜然当成辣椒面。代码也一样一个项目少说几千个文件多则上万个文件如果每个人都按自己的习惯写那整个项目就像一座没有门牌号的迷宫看着每个房间都能住人但你真的要在里面维护东西的时候寸步难行。规范的本质就是统一预期。变量叫userName还是name函数能不能超过80行异常是该抛还是该吞这类问题看似主观但一旦团队定了标准它就成了铁律。原因很简单代码不是写给自己看的而是写给下一棒看的。你写完丢下跑了后面接手的人拿着你留下的烂摊子为了读懂一个变量的含义可能要在三四个文件里穿梭这种成本比你自己多花十秒钟起个好名字要高得多。1.2 被骂的根源往往不是风格是习惯我当年刚干这一行的时候有一次提交代码被当时的组长批了一顿不是因为变量名也不是缩进而是因为我把自己调试时候用的一个硬编码路径留在提交里了。组长原话大概意思是你留这么个东西在代码里别人跑你的代码连数据都读不到这个锅算谁的。那时候我就明白真正让人崩溃的通常不是“风格不好看”而是“行为习惯拉垮”。风格问题格式化工具一跑就解决反手就是一个format-on-save而习惯问题才是硬伤。比如日志不打、报错吞掉、边边角角的情况不考虑、命名全靠拼音首字母这些习惯靠工具救不回来只能靠规范意识和自我约束一点点扳过来。2. 命名规范第一个滑铁卢现场先聊最容易被忽视但也最影响阅读体验的命名。我见过一个支付项目的核心接口里有个变量叫m后面跟了一个布尔值f。你猜这两个字母什么意思答案分别是“金额”和“是否首单”。不夸张地说这种代码放出去当天就会被骂到怀疑人生。2.1 命名是给人看的不是给编译器看的代码的最终执行者是机器机器不在乎你变量叫a还是amount编译出来性能没区别。但代码的阅读者是人人在每天几百上千行的代码里翻找线索一个含义明确的命名能直接决定这段代码是“一眼看懂”还是“冥思苦想十分钟”。我之前重构过一个内部工具项目里面有一段函数叫doit()你根本看不出它是干什么的。拆开代码进去仔细看发现是发送异步提醒通知。我当时内心只有一个想法你叫sendAsyncNotification会掉块肉吗后来这个doit()被改成sendReminderAsync后整段逻辑的意图一下就清晰了不需要再看函数体内的一堆细节。具体操作上命名有个常用的小窍门名字要能回答三个问题——它是什么、用来做什么、和相邻代码有什么关系。比如price是“价格”这个数据calculateTotalPrice是“计算总价”这个行为。对方法而言用动词开头例如getUserInfo、sendEmail对变量而言优先用名词例如userList、retryCount对布尔值可以用is、has、can开头比如isActive、hasPermission。2.2 常见命名反面教材与纠正我把这些年碰到的反面案例整理了一个表看着挺好笑但每一行都是血泪原命名问题所在推荐改法a、b、c毫无信息量只能靠上下文猜用完整含义如leftOffset、rightOffsetdata、info等于没说任何东西都可以叫数据具体到对象如userProfile、orderDetailflag含义太泛谁都不确定它是做什么的标记带语义如isEnabled、isCompletedtemp临时变量但“临时”不说明它为什么存在按目的命名如filePath、recordIdstudent_name混用下划线团队规范用驼峰时这种混用会破坏一致性统一按团队规范如studentNamegetInfo2数字后缀意味着有旧版本别人不知道版本差别删除或提取用新语义命名如getLatestInfo还有一个容易挨骂的是用拼音首字母缩写命名。比如“商品库存”写成spkc本意是省事省编码时间结果阅读的人脑子里要做一道拼音翻译题。更麻烦的是缩写带有个人记忆换个人看同一个缩写推测出来的意思可能完全不一样。这类问题我建议尽早识别并及时清理别让缩写成为团队内的黑话。3. 代码可读性格式、结构与长短命名过关之后接下来是代码长得舒不舒服的问题。我见过几百行的大函数从头到尾全是状态变量的变化期间还有三个嵌套的if、两个for循环加一个try...catch整个阅读过程像在走一个看不清路标的地下迷宫每往下读十行就必须回头找前面的变量在哪赋的值。3.1 格式化只是一个底线在工具链已经非常成熟的今天代码格式化早就不应该用人肉解决了。主流IDE和编辑器基本都有“保存时自动格式化”的能力配合统一的配置文件团队里每个人写完代码格式都是整齐划一的。这样可以减少大量因为缩进、空格、引号风格这类琐碎问题引发的口水战因为这些争议本质上对业务毫无价值。但格式化解决不了“结构烂”的问题。就像你房间里东西都摆正了但东西本身乱糟糟地堆在一起那整齐也只是表面功夫。真正的可读性来自合理的结构一个函数只做一件事一个模块的依赖关系清晰一个文件不要塞进七八个各不相关的功能。3.2 函数长度与圈复杂度关于“函数多长算长”网上争论很多有的说不能超过50行有的说不能超过100行其实没有绝对的数字。更重要的是看圈复杂度简单说就是一个函数里独立路径多不多。if分支越多、循环越深、逻辑越绕就越难测试、越难排错阅读成本也越高。我给自己定的一个标准函数里的缩进层级尽量不超过三层。一旦超过三层基本意味着这段逻辑可以拆分了。举个很常见的例子有一段代码先遍历用户列表再判断用户是否为会员最后又叠加判断是不是活动期这时候逻辑堆在一起肉眼已经很难跟踪。正确的做法是把“判断会员可用状态”抽成一个独立函数把“计算活动折扣”再抽成一个独立函数主流程只负责编排调用关系。这样一来测试也好写后面出问题定位也快。你一段时间后再打开这个文件不需要重读所有细节光是函数名就能把主干逻辑顺下来。在实操中我自己有一个“三步拆函数法”先把能独立取名的代码块圈出来。然后把这个代码块搬到一个新函数里用它的意图命名。最后在原位置调用新函数并跑一遍全量测试看看行为有没有变化。这个过程听着简单但做起来需要一点勇气因为它意味着你的代码要经历一次“结构位移”。不过拆完之后你会明显感觉到整个文件读起来顺多了。3.3 避免“一次性代码”心态还有一种常见挨骂场景是“先这样以后再说”的一次性代码心态。比如写死一个配置文件路径不用环境变量也不放到配置文件里直接把本机路径拍进去。比如某个超时时间不提取成常量直接在代码里写5后面的人看到这个5都不知道是秒还是分更不用说为什么是5而不是3。这种“能跑就行”的思路短期内确实爽像是走了捷径但代价会在项目进入维护期之后加倍奉还。我后来养成一个习惯但凡超过一个地方用到的裸数值、字符串、配置项一律提取成常量或配置文件并加上必要的注释说明含义。这样不会让代码更复杂反而把魔法数字的大量猜测成本都消除了。4. 函数设计与模块边界为什么会被说“你要重构”有些人写代码很快但review的时候被批得最狠核心问题往往不是语法错误而是函数和模块的职责不清。原本应该只负责“获取订单”的函数偏偏把“发送通知”和“更新库存”都写进去了牵一发动全身改其中一个逻辑另外两个跟着出问题。4.1 单一职责原则的另一面大学里都学过单一职责原则但写代码时很容易不知不觉就违背了它。我自己的体会是判断函数是否职责单一可以看它的名字是否诚实。如果一个函数叫processOrder但里面还做了库存扣减、日志记录、通知发送、售后校验那这个名字其实挂羊头卖狗肉。一个职责单一的函数它的行为应该能用一句话说清楚而且不应该有太多“顺便”和“与此同时”。举个实际场景你在写一个结算功能代码里面顺带把会员等级升级了虽然跑起来没问题但结算模块和会员模块就产生了隐蔽耦合。后面会员规则一变你查结算问题的时候根本想不到是会员模块埋下的雷。4.2 重复代码与抽取时机重复代码是个老生常谈的问题但真正微妙的是抽取的时机。抽得太早可能抽出来的抽象并不正确反被复杂化绑架抽得太晚大量重复散落各处改一处忘一处隐患成堆。我的经验是同一个逻辑代码出现第三遍时再动手抽取成通用函数。第一遍、第二遍可以先不动到第三遍时基本可以确认这个逻辑是个重复模式了。抽取的时候有一点要特别小心不要一开始就想着做一个“万能抽象”。先把三个重复点的公共部分提取出来差异部分通过参数传入这就足够了。如果有人想把所有相似逻辑都统一成一个框架级的抽象除非你真的很有把握否则请谨慎因为过度抽象比重复代码更难维护而且不是难一点点。5. 注释与文档不是越多越好是恰到好处注释这件事经常走两个极端。一端是“打死不写注释”连私有函数干什么是干什么都不说全靠阅读者自己人肉理解另一端是“每行都加注释”变量名已经叫userName了还非要写一行“用户的名字”在旁边毫无信息增量。5.1 注释应该解释“为什么”而不是“是什么”注释的核心价值在于解释代码背后的约束、背景和决策原因。比如“这里不能用缓存因为要求强一致”或者“这个字段保留冗余信息是为了兼容老版本客户端”这类信息无法直接从代码本身看出来才最值得用注释记录下来。同样是写// 重试3次好的注释会补一句// 重试3次因为上游偶发超时超过3次仍失败则走降级逻辑。后者不仅告诉读者做什么还解释了为什么这么设计后续维护的人改起来就有谱了。我见过最难维护的一段代码里面几乎没有注释变量名也还算清楚但逻辑里有一段非常诡异的判断任何人都说不清它是干什么的。直到一个老同事在离职前才在文档里提了一句那是为了兼容十年前的某次历史数据修正。没有上下文后人只能盲目保留那段代码不敢删这种例子就是“注释写少了”的代价。5.2 不要用注释掩盖坏味道有一种情况比不写注释更让人头疼就是用大段注释解释一段糟糕的代码。如果你发现自己需要写长篇大论来讲清楚这段代码在干嘛大概率是这段代码本身太绕了。这时候优先选择的应该是重构把逻辑理顺让代码自己说话而不是靠注释来填坑。另外注释要有“更新纪律”。代码改了注释忘了同步这比没有注释更误导人。有些团队在review的时候会专门检查“注释与实现是否一致”我觉得这习惯很好。因为注释一旦和实现脱节阅读者就会开始怀疑所有注释的可信度最后谁都不信注释只能一行行硬看代码。6. 错误处理与边界思考接下来聊聊错误处理这可能是最容易被“凭感觉”写崩的地方也是被骂现场的高发区。很多人遇到异常时第一反应是catch然后打一个日志就完事甚至直接catch (Exception e) { // ignore }。这种写法在功能演示的时候可能看不出问题但一旦上了线上环境问题被无声吞掉排查起来如同大海捞针。6.1 不要吞异常不要空catch空catch在团队规范里基本属于红线级别。试想一下线上订单支付失败代码捕获异常后什么都没做用户端看到的是“支付转圈”后端日志一片空白这种情况下你怎么定位问题连问题发生的线索都没有。正确的做法是要么重新抛出异常要么在记录足够上下文后转换为可处理的业务异常要么至少先把日志打了。我经常给团队分享一个原则异常信息至少要回答三个问题——什么操作失败了、失败的原因是什么、失败时相关的业务参数、请求标识是什么。如果一条错误日志连这三个中的任何一个都没有那这条日志基本等于没有价值。6.2 错误码与业务异常的设计在稍微复杂的系统里光靠异常类型表达业务错误是不够的很多团队会引入错误码和业务异常体系。这里有个容易踩的坑错误码只写一个数字没有配套错误描述。比如你看到code: 50001不知道这是参数错误还是库存不足还得去翻文档对照表体验很差。我建议错误信息至少分成两部分对外可以暴露给用户的友好提示以及对内记录在日志里的详细上下文。对外提示不要太技术化比如“订单金额超出限制”远比“validator error”适合展示给用户对内日志则要足够详细方便定位。比如用户操作了哪个接口、传了哪些关键参数、服务端认为哪里不满足条件这些信息对排查问题非常重要。还有一个细节自定义异常类的命名要好辨识比如OrderCreateException、InventoryNotEnoughException不要什么都用一个BusinessException兜底然后在构造函数里塞一个字符串。字符串太自由了自由到很难统计和聚合。用明确的异常类型上层才能有针对性地处理也方便后续做监控告警。7. 团队协作中的规矩从提交信息到代码审查编程规范不只是“写代码时”的标尺在协作流程里同样有大量约定。这段最容易被新人忽略也最容易引发团队成员之间的火药味。7.1 Git提交信息是团队协作的“日常发言”很多人嫌弃提交信息麻烦随手就是一个fix、update或者提交。如果版本线上报问题要回滚你打开提交历史满屏的“update”“fix”根本不知道哪个提交改了什么东西这时候真的会非常绝望。好的提交信息简单说就是一句话让别人知道这次提交改变了什么。比如fix: 修复优惠券过期时间判断错误、feat: 新增订单导出功能有条件的在正文里再补充一下修改原因和影响范围。这类规范可以用现成的“约定式提交”模板不需要团队发明规则直接套用就行。我还建议在团队内约定不提交调试代码、不提交临时打印的日志、不提交无关格式变动。每当审查时看到一个提交里混着改功能和改格式的成百上千行变化我头都大了这种提交直接拒绝必须拆分。7.2 代码审查的标准代码审查Code Review在很多团队里是最容易“走形式”的一环。看代码的人如果只是确认“能跑”那和没看区别不大。真正有效的审查应该盯住几个要害命名是否表意、函数是否过大、是否有明显冗余、错误处理是否完善、边界条件是否覆盖、安全与性能有没有明显隐患。有些团队会把审查重点写成一张检查清单新人提交代码前自己先过一遍提交后再由老同事对照查一遍。这个过程刚开始有点繁琐但坚持下来你会发现线上的低级 bug 数量会明显下降。因为人的自觉总会有疏漏而审查机制是把“个人水平”变成“团队底线”的关键手段。这里有一个建议给被审查的人别人给出意见时不要急着辩解先搞清楚对方提的是规范问题还是设计问题。前者照单全收后者可以讨论。但无论如何态度上的开放很重要代码审查最忌讳的是“帮你挑刺你还急眼”。反过来给建议的时候也别居高临下描述问题比指责人要好得多比如“这个函数叫process不够具体改成processRefund更清晰”就比“你起的什么破名字”更容易被接受。8. 常见问题与踩坑实录最后把我在实践中反复见到的高频问题整理成一份“避坑手册”按场景归类哪个问题是什么表现、怎么排查、怎么预防一目了然。问题表现典型原因排查思路预防措施代码能跑但换个人就改不动函数过长、职责混乱拆函数画调用链用圈复杂度工具、review时卡函数长度变量名看懂靠猜缩写、拼音首字母全局搜索关联逻辑命名规范写进团队文档改了A模块B模块出问题模块边界不清晰、隐式耦合查B模块的依赖来源依赖方向约束、接口隔离线上异常无日志定位不到空catch、日志上下文不足补日志、上告警异常与日志规范review把关提交信息看不出改了什么提交信息太随意查代码diff看提交时间点提交信息模板、约定式提交重构经常拆到一半乱了缺乏测试保障先补核心用例再重构项目测试覆盖率基线大部分冲突发生在格式化上没有统一格式化工具跑一遍格式化再处理冲突保存时自动格式化、配置文件共享以上这些坑我几乎都踩过或者亲眼见过同事踩过。有些问题是刚入行时犯的有些是项目压力大赶进度时的无奈之举但最后都会汇聚成同一个结果被骂。被骂本身其实没那么可怕可怕的是不知道自己为什么被骂然后下个项目再重复一遍。9. 我的几点实际体会写了这些年代码我对编程规范的理解也在慢慢变化。最初觉得它是一堆教条后来发现它是团队协作的润滑剂再后来觉得它就是工程质量的底线。有些事情比如命名要表意、函数不要写太长、异常不能吞、提交信息要清楚并不是什么高深理论做起来也不难难的是每次都做到。我个人的体会是编程规范这件事最高效的做法不是靠自觉硬撑而是靠工具和习惯把大部分基础问题自动化解决。格式化交给IDE命名检查交给静态检查工具重复代码交给分析插件团队成员的精力留在更重要的设计讨论和业务逻辑上。等规范沉淀为肌肉记忆之后你写出来的代码自然就不太需要“被骂”了。另外我对刚接触编程的朋友特别想提一句代码审查挨批真的不用太玻璃心。每一次被指出来问题其实都是免费的成长机会。表面上是在说“你这段代码不符合规范”本质上是在告诉你“这个团队希望大家用更统一的方式协作”。想通这一点很多情绪问题就化解了。最后再分享一个坚持了挺久的小习惯每次提交代码之前我会先把自己的diff当成别人的代码重新读一遍专挑可疑的命名、含糊的提交信息、遗漏的边界条件下手像审别人一样审自己。这个方法效果立竿见影很多问题还没到同事面前就已经被自己拦下来了。你也不妨试试大概率能少挨几次骂。