程序员10年成长记:第3篇:你的代码,是“作品”还是“垃圾”?——代码规范与质量意识
第3篇:你的代码,是“作品”还是“垃圾”?——代码规范与质量意识
引言
2016年春天,一个爆炸性新闻占据了所有科技媒体的头条:Google的AlphaGo以4:1的比分战胜了世界围棋冠军李世石。一时间,人工智能、机器学习的讨论从专业圈子“破圈”进入了大众视野。启明科技的办公室里,程序员们午餐时的话题也从“哪个框架好用”变成了“机器会不会取代我们”。
技术负责人老李在一次技术分享会上半开玩笑地说:“在机器能完美理解人类意图之前,我们至少要先保证人类能看懂我们彼此写的代码。否则,我们不是被AI淘汰的,而是被自己写的‘烂代码’淘汰的。”
这句玩笑话背后,是日益严峻的现实。“凤凰项目”在成功上线购物车功能后,进入了快速迭代期。新功能、新需求如潮水般涌来,团队也从最初的几个人扩张到了十几人。代码库的体积在短短半年内翻了三倍。
随之而来的,是“成长的烦恼”:一个简单的需求,预估两天,结果改了五天还没改完;一个Bug修复了,却引发了三个新的Bug;新来的同事看着老代码,感觉像在解读天书……混乱,正在代码的字里行间悄然滋生。
老李决定,是时候来一场“代码清洁运动”了。而这场运动,将从一个看似简单的功能和一次“惨烈”的Code Review开始。
小故事:一行“猜你喜欢”引发的“血案”
产品团队提出了一个新需求:在用户个人中心页面增加一个“猜你喜欢”的模块,根据用户的浏览历史和购买记录,推荐5个相关商品。
这个任务被拆分给了几位新人:小葵负责核心推荐算法的初步实现,张三负责将推荐结果与商品信息整合,而小健则负责将最终的数据接口提供给前端。
1. “屎山”初现
张三在整合商品信息时,需要调用小健之前写的用户历史行为模块。他打开小健的UserActionService.java
文件,瞬间感觉血压飙升。一个名叫getData()
的方法,足足有300多行,里面是层层嵌套的for
循环和if-else
判断。变量名是这样的:map1
, list2
, temp_a
。张三花了整整两个小时,才勉强理清其中一小段逻辑,他忍不住在工作群里吐槽:“这谁写的代码,还能不能维护了?”
小健看到了,不服气地回复:“能跑就行了呗,那么多废话。我这儿忙着呢,别烦我。”
2. “炫技”的代价
轮到张三自己,他虽然鄙视小健的代码,但自己的风格也堪称“狂野”。他自诩对Java 8的新特性了如指掌,热衷于用Stream API写出各种“一行流”操作。为了从多个商品ID列表中查询数据,他写下了这样的代码:
// 张三的代码片段
List<ProductDTO> dtos = pids.stream().distinct().parallel()
.map(pid -> productService.findById(pid))
.filter(p -> p != null && p.getStatus() == 1)
.sorted(Comparator.comparing(Product::getCreateTime).reversed())
.limit(5)
.map(p -> { /* 一段复杂的转换逻辑 */ return dto; })
.collect(Collectors.toList());
他自鸣得意,感觉代码既简洁又高效。但当小葵需要在这段逻辑里增加一个“过滤已购商品”的判断时,她犯了难。这个链式调用太长了,想在中间加一步逻辑非常困难,而且parallel()
并行流在这里可能因为线程池竞争反而导致性能更差。她不得不找张三沟通,张三解释了半天,小葵才明白。
3. “清晰”的力量
与此同时,小葵在实现自己的推荐算法时,代码风格截然不同。她将复杂的逻辑拆分成了几个独立的小方法,每个方法只做一件事,并有清晰的命名。
// 小葵的代码片段
public List<Product> recommendProductsFor(User user) {
// 1. 获取用户最近的浏览记录
List<Long> viewedProductIds = historyService.getRecentViewedProductIds(user.getId(), 20);
// 2. 获取用户最近的购买记录
List<Long> purchasedProductIds = orderService.getRecentPurchasedProductIds(user.getId(), 10);
// 3. 合并ID并排除重复,同时排除用户已购买的商品
Set<Long> candidateIds = mergeAndExclude(viewedProductIds, purchasedProductIds);
candidateIds.removeAll(new HashSet<>(purchasedProductIds));
// 4. 基于候选ID,通过协同过滤算法计算推荐商品
List<Product> recommended = collaborativeFiltering.calculate(candidateIds, 5);
// 5. 如果算法推荐不足5个,使用默认热门商品作为补充
return fulfillWithDefaultHotProducts(recommended, 5);
}
她的代码几乎不需要注释就能读懂,每个方法的职责都非常清晰。更重要的是,她为每个公共方法都编写了单元测试。
4. 第一次正式Code Review
“猜你喜欢”功能在延期两天后,终于进入了提测阶段。老李以此为契机,在团队强制推行基于GitLab的Merge Request(MR)和Code Review(代码审查)流程。所有代码必须经过至少一位同事Review并Approve后,才能合入主干分支。
-
小健的MR: 第一个被审查的是小健的MR。结果,他的MR页面成了“重灾区”,被老李和另一位老同事标记了几十条评论:
-
“
getData()
这个方法名毫无意义,它到底‘get’的是什么‘data’?” -
“禁止使用魔法数字!
if (user.getStatus() == 2)
,数字2代表什么?请使用枚举或常量。” -
“这个嵌套了5层的
for
循环是什么?时间复杂度是O(n^5)吗?请重构。” -
“没有单元测试,打回!”
-
最终,小健的MR被直接关闭,并被要求根据新发布的《团队编码规范》从头重写。小健在工位上脸色铁青,感觉受到了奇耻大辱。
-
-
张三的MR: 张三的MR功能上没问题,但老李和小葵都在他的“一行流”代码下留了言。老李的评论很尖锐:“代码是写给人读的,不是写给编译器看的。 这种炫技式的代码,可读性和可维护性极差,等于是在给团队埋雷。请把它拆分成多个步骤,并加上注释说明。”张三虽然不情愿,但还是默默地修改了。
-
小葵的MR: 小葵的MR审查过程则异常顺利。大家对她清晰的方法命名和逻辑拆分赞不绝口。一位同事只提了一个小建议,希望能把一个常量提取到类的顶部。整个MR在15分钟内就被Approve了。
这次Code Review在团队内部引起了巨大震动。大家第一次如此直观地感受到,代码不仅仅是实现功能的工具,它更像一张“名片”,体现了工程师的专业素养、逻辑思维和协作精神。
核心要点:代码是团队的资产,质量是生命线
软件开发是一个团队协作的工程,而不是个人英雄主义的表演。你的代码在被写入后的生命周期里,被阅读的次数远远多于被修改的次数。
-
代码的可维护性至关重要: 一个难以维护的系统,会逐渐累积“技术债务”,最终导致迭代速度慢如蜗牛,Bug层出不穷,直到整个系统无法维护,只能推倒重来。小健写的,就是典型的“高利贷”式代码。
-
清晰胜于炫技: 简单、直接、清晰的代码,才是好代码。一段需要作者本人花10分钟才能解释清楚的代码,对团队来说就是负资产。张三的问题在于,他追求的是个人的“表达快感”,而牺牲了团队的“理解效率”。
-
专业主义的体现: 编写高质量的代码,就像外科医生仔细缝合伤口一样,是对自己职业的尊重。它不是额外的负担,而是一名专业工程师分内的工作。
关键技能:铸造高质量代码的“三板斧”
-
遵循团队编码规范 (Coding Conventions):
-
目的: 统一团队的编码风格,就像一个国家的官方语言,能极大降低沟通成本。大家阅读彼此的代码,就像在读同一个人写的。
-
核心内容:
-
命名 (Naming):
-
类名: 大驼峰,名词。如
ShoppingCart
,ProductService
。 -
方法名: 小驼峰,动词或动宾短语。如
getUserById()
,calculateTotalPrice()
。 -
变量名: 小驼峰,名词,清晰表达意图。避免使用
i, j, k, temp, data
等无意义的词。 -
常量名: 全大写,下划线分割。如
MAX_LOGIN_ATTEMPTS
。
-
-
格式化 (Formatting): 统一的缩进(通常是4个空格)、大括号的位置、空格的使用等。这些都可以通过IDE的格式化模板一键完成。
-
注释 (Comments):
-
什么要写: 注释的目的是解释“Why”(为什么这么写,有什么背景和坑),而不是“What”(代码在做什么)。好的代码本身就能说明“What”。
-
什么不要写: 不要写无意义的注释,如
// i加1
。不要用注释掉的代码,请直接用Git管理。
-
-
-
-
编写单元测试 (Unit Testing):
-
目的:
-
保证代码正确性: 验证你的代码在正常和异常情况下是否都按预期工作。
-
“安全网”: 当你或别人未来修改代码时,单元测试能立刻告诉你是否破坏了原有的功能,极大提升重构的信心。
-
“活文档”: 一个好的单元测试,本身就是一份展示代码如何使用的最佳示例。
-
-
基本原则 (AIR):
-
A (Automatic): 自动化,一键运行。
-
I (Independent): 独立,测试之间不应有依赖。
-
R (Repeatable): 可重复,在任何环境下运行结果都应一致。
-
-
示例 (JUnit 5):
class CalculatorTest { @Test void testAdd() { Calculator calculator = new Calculator(); // 验证 1 + 1 是否等于 2 assertEquals(2, calculator.add(1, 1), "1 + 1 should equal 2"); } @Test void testDivideByZero() { Calculator calculator = new Calculator(); // 验证当除以0时,是否会抛出预期的异常 assertThrows(IllegalArgumentException.class, () -> { calculator.divide(1, 0); }); } }
-
-
防御性编程 (Defensive Programming):
-
理念: “不信任任何外部输入”。你的方法随时可能被别人用错误的方式调用,你的系统随时可能收到脏数据。
-
常见实践:
-
参数校验: 在方法的开头,检查所有传入的参数是否合法(如是否为null,数值是否在范围内)。
-
空指针处理: 主动处理可能为null的对象,避免
NullPointerException
。可以使用Optional
类或Objects.requireNonNull()
。 -
异常处理: 预见可能发生的异常,并使用
try-catch-finally
妥善处理,而不是简单地抛出或忽略。 -
资源关闭: 对于文件、网络连接等资源,一定要在
finally
块或使用try-with-resources
语句确保其被关闭。
-
-
理论基础:无法忽视的“技术债务”
技术债务 (Technical Debt) 是一个绝佳的比喻,由敏捷宣言的创始人之一Ward Cunningham提出。
-
定义: 当我们为了追求短期目标(如快速上线)而选择了不那么完美的、临时的技术方案,就相当于欠下了一笔“技术债务”。
-
债务的类型:
-
有意的债务: “我们知道这样不好,但时间紧,先这么上,以后再重构。”
-
无意的债务: 由于能力不足或意识不到位,无意中写下了设计糟糕的代码(如小健的情况)。
-
-
利息: 技术债务和金融债务一样,是有利息的。这个“利息”就是你在未来维护、修改、扩展这些烂代码时所要付出的额外时间和精力。债务越高,利息越重,直到有一天,整个团队的所有时间都花在“还利息”(修Bug、处理线上问题)上,而没有任何精力去开发新功能。
-
管理技术债务: 技术债务无法完全避免,但必须被有效管理。
实战要点:如何进行一次健康的Code Review
Code Review的目的是共同提升代码质量,而不是批判个人。
-
作为审查者 (Reviewer):
-
对事不对人: 评论代码,而不是评论作者。用“这段逻辑可以优化一下”代替“你这里写得不好”。
-
提出建议,而非命令: 用“我建议这里可以封装成一个方法”代替“你必须把这里改成一个方法”。
-
解释“为什么”: 给出修改建议时,要说明理由,最好能附上相关文档或最佳实践的链接。
-
关注重点: 优先关注设计、逻辑、可读性、健壮性等大问题,而不是纠结于一个空格或拼写错误(这些应交给自动化工具)。
-
及时反馈: 不要让一个MR挂起好几天。
-
-
作为作者 (Author):
-
保持开放心态: 别人花时间看你的代码是来帮助你的。虚心接受所有建议,即使你不完全同意,也要先理解对方的出发点。
-
提交小的MR: 一次提交几千行代码的MR是对Reviewer的“折磨”。尽量将大的功能拆分成小的、独立的MR,让审查更容易。
-
做好准备: 在提交MR前,自己先Review一遍。确保代码能编译通过、测试能跑通、遵循了团队规范。
-
推荐书籍
《代码整洁之道》 (Clean Code: A Handbook of Agile Software Craftsmanship) - Robert C. Martin (Uncle Bob)
-
核心内容与思想:
-
何为整洁代码: 书中开篇引用了多位编程大师对“整洁代码”的定义,总结起来就是:简单直接、逻辑清晰、易于阅读、易于维护、经过测试。
-
专业主义与匠艺精神: 作者强调,编写整洁的代码是程序员专业素养的体现。这是一种纪律,一种对技艺(Craftsmanship)的追求。
-
具体的原则和实践:
-
有意义的命名: 变量、方法、类的命名是整洁代码的基石。
-
函数(方法): 函数应该短小,只做一件事,并把它做好。
-
注释: 大部分情况下,好的代码是自解释的,注释是“失败的表达”。只有在解释业务背景或复杂算法时才需要。
-
格式: 垂直和水平的格式排布,应该像报纸一样,让读者能轻松地浏览和理解。
-
对象和数据结构: 清晰地区分对象(隐藏数据,暴露行为)和数据结构(暴露数据,没有行为)。
-
错误处理: 使用异常而非返回错误码,将正常的业务逻辑与错误处理逻辑分离开。
-
单元测试与TDD: 整洁的代码离不开测试。测试是代码质量的保证,也是驱动设计优化的手段。
-
“代码坏味” (Code Smells): 书中列举了大量“坏味道”的例子,如重复代码、过长方法、过大类、过长的参数列表等,并给出了重构的建议。
-
-
童子军军规: “让营地比你来时更干净。” 这条规则引申到编程中,就是每次修改代码时,都顺手做一点小小的清理和重构,哪怕只是改一个变量名。久而久之,整个系统的代码质量就会持续提升。
-
这本书为整个软件开发行业提供了一套关于代码质量的通用语言和评判标准。读过它,你和你的团队在讨论代码时,就能从“我觉得这样好”的主观感受,上升到“根据整洁代码原则,这里应该……”的专业层面。
在启明科技,这场由Code Review引发的“清洁运动”虽然让一些人感到了阵痛,但它却为公司未来的高速发展,打下了坚实可靠的工程基础。小葵的职业素养再次得到认可,而小健和张三,则被迫开始重新审视自己对“写代码”这件事的理解。