高级程序员的代码质量应当达到什么水准
不断思考总结提炼,持续迭代优化前进。
工作十余年,对代码质量有过一段孜孜不倦的追求,同时也在实战中有一些亲身体会,总结下对代码质量的观点和经验。
关于代码质量,基本都总结在我的博客上:代码修行。也写过一本小小的电子书,再分享一次,https://pan.baidu.com/s/1noGsrkJto_CoAPxjXEmMSA?pwd=yvu6 。一年有效期。
概述
那么,在我看来,高级程序员的代码质量应当达到什么要求呢?兼顾时间成本,我认为适中即可。达到如下代码质量要求是可以的:
- 功能的深度正确实现
- 清晰性和可维护性
- 良好的健壮性
- 必要的日志记录
- 核心方法的充分单测
- 大数据量,考虑性能
- 大流量,考虑稳定性
- 适度的通用性
- 适度的可扩展性
- 代码安全性
要达到代码质量要求,程序员需要掌握代码重构技能,需要熟悉各种代码错误并有效规避。踩过的雷和坑越多,越有意识地规避,才能避免重犯错误。可阅:代码问题及对策。
代码质量
功能的深度正确实现
代码质量的最起码要求是功能的正确实现。
要能完成程序功能的基本正确性,对于程序员来说是不难的。不过说到深度正确,何为深度正确呢?在我的工作经历里,有以下几种情况需要考虑:
- 变更处理。比如你的表从其它APP冗余了几个字段(比如主机名、主机业务组等),当这些字段变更时,需要监听消息去更新它们;当主机卸载时,需要删除对应的记录;当租户变更时,需要插入新的初始化记录。
- 关联处理。多个功能相互影响,比如告警加白、归并和响应。归并在加白流程之前,如果归并了,告警就不能加白,可能与用户预期不符(因为用户设置了白名单,归并却是用户并不感知的系统内置机制)。而告警归并会导致后面被归并的告警的元素无法响应;此外,多个告警会共享同一个元素,一个元素在一个告警里响应了,也会展示在其它告警里,导致其它告警展示响应信息,但是告警列表的告警却没有响应信息,可能导致客户困惑。
- 大数据量处理。一个主机封禁一个IP好解决,十万台主机封禁上千个IP呢?
- 并发处理。引入OpenAPI之后,元素响应接口调用频次就高了很多。对于元素响应接口来说,高频重复发送同一个元素的同一个操作(或者相近操作),操作可能落到不同机器上,你的程序能正确应对么?如果第一次成功,后续失败,会不会出现异常?
- 错误处理。一个普通的主机响应操作,如果主机不在线怎么办?如果数据库操作失败怎么办?如果主机在线但是处于空跑状态无法响应任何请求怎么办?如果中间件异常怎么办?一个耗时操作分为同步和异步两部分,如果异步流程出错了,怎么办?
如果这些都不能考虑到,那就等着被测试同学提的BUG轰炸吧!我也是从各种BUG中提炼出这些场景的。
清晰性和可维护性
可维护性可以说是程序员耳熟能详的关于代码质量的词眼了。可维护性涉及多个方面,这里不一一赘述。可以去知乎上搜索下,或者阅读各种代码质量相关的书籍。比如《编程珠玑》、 《 Effective Java 》、《代码整洁之道》、 《Writing solid code》、《编写可读代码的艺术》、《重构》、《敏捷技能修炼》。
清晰性是一个很重要的目标,但是却不那么容易达到。我们都写过类似的特殊处理的代码,通常是为了绕过某些检测或者适配某种流程的条件。但这种代码是很不清晰的,很容易留坑。如果没有注释的话,即使当初写这段代码的人也很容易忘记为啥会有这段代码。
因此,要达到清晰性,要尽量避免这种原子性的判断。用有语义的函数替代,并辅以必要的注释。
// 反例
if shop_id != "" && shop_type == "plain" && other condition {
head_shop_id = ""
}
// 也不算好的正例
if isPlain(shop_id, shop_type) != "" {
// 为了适配某种流程,需要设置总店ID为空
head_shop_id = ""
}
func isPlain(shop_id, shop_type) bool {
return shop_id != "" && shop_type == "plain" && other condition
}
良好的健壮性
实现功能之后,健壮性是第一个必要的基本要求。要想线上不出各种奇奇怪怪的问题,健壮性一定要做好。实现健壮性的基本理念是防御式编程和契约式编程。
健壮性检查通常包括:
- 参数校验:比如特定信息(IP,端口,邮件等)的格式。
- NPE (nil point reference) 处理:对象为空。
- 数值溢出。比如有些 IPv6 地址转成整数会超出 int64 能表示的最大数值。
- 数组列表越界:数组列表越界,尤其是使用 split 方法之后提取字段的时候。
- JSON 解析出错:json 格式不对、json 内容不全。
- 错误处理:立即失败、重试、重续、根据条件判断、抛出并捕获异常。
- 资源和锁管理:确保资源和锁及时被释放,不会释放错误的资源和锁。
- 并发安全:确保多线程处理同一个共享可变对象加了同步措施。
以上所列,只是部分。更详细的可以问ChatGPT。对于一般功能,能达到功能实现正确、适度的健壮性,就已经足够了。易测、清晰、健壮,是我认为代码质量的基本要求。可阅:写代码的指导思想:如何写出易测、清晰、健壮的牢固代码。
必要的日志记录
线上运行之后,一定会出现一些问题,需要进行问题排查。问题排查的基本思路就是线索+逻辑推理。线索从哪里来,从日志记录和数据库里来。
记录关键变量、关键路径。由于日志量不宜过多(影响性能、磁盘存储空间),因此往往会倾向于把业务ID及业务ID 关联关系记录下来,然后根据业务ID去数据库里查询详情。对于流程,最好能有一个业务ID或唯一键,能够贯穿整个流程,这样,根据业务ID或唯一键就能看到整个流程,以及看到流程在哪里终止了。
对于并发问题,一定要留意时间线索。可阅:软件调试与问题排查的修炼之路与实战经验 。
必要的核心方法的单测
单测必不可少。程序员往往嫌单测麻烦而不愿写单测。实际上,单测是一个减少时间成本的保障措施。通过单测,确保核心逻辑没有问题,就可以怀疑是流程中的参数不合理或者是其它地方有问题,而不至于一遍遍调试,结果发现是核心方法的一个地方没有覆盖到或者没有处理好。短链路调试显然比长链路调试要省时。
关于单测编写,可阅: 深入探究单元测试编写,使用Groovy+Spock轻松写出更简洁的单测,使用Java函数接口及lambda表达式隔离和模拟外部依赖更容易滴单测,改善代码可测性的若干技巧 。
现在,代码和单测都可以通过 AI 生成了,进一步便利了程序员。所以,代码完成后,千万不要拉下单测啦!
性能和稳定性
有一定业务规模的互联网公司,往往对性能和稳定性也有要求。
处理大数据量或长流程时,接口往往会比较慢。解决性能的五件套:选择适合的数据结构和算法、索引、缓存、并发、异步非阻塞。此外还有精简流程。做方案时,养成评估数据量的好习惯。无论数据量多少,都做个评估。数据量,可能是瞬时大数据量,可能是基于较大日增量的累积大数据量。多数情况下要考虑累积数据量。可阅:应用层性能优化思路及方法。
稳定性通常涉及到对系统全局的理解和掌控。涉及大流量时,需要考虑对系统的冲击,即系统在大流量时不至于直接崩溃导致服务不可用。此时要考虑限流、降级,评估对核心 API 调用和对中间件访问的高频程度,避免对核心API和中间件造成冲击,尤其要避免雪崩。毕竟,核心API和中间件挂了,那就不只是一个服务出问题了。可阅:增强系统稳定性的基本方法 。
性能和稳定性考量,最考验一个高级程序员的综合技术素养。对互联网常用服务端技术有一个整体概览,对理解性能和解决问题也很有助益。正如修习内功也有助于修炼高阶武学。可阅:互联网应用服务端的常用技术思想与机制纲要 。
适度的复用性
复用性也是程序员非常看重的代码特性。写一次代码,多次复用,省时省力,何乐而不为?
代码复用性,涉及多个层面:代码级(函数、接口、类、对象)、模式级、API级、模块级、框架级、中间件级、服务级。初级程序员通常能做到代码级复用,高级程序员应该能做到针对一类需求或模式级或API级的可复用设计,一流程序员能够做到模块级、框架级、中间级、服务级。
泛型和函数式编程,是达成代码可复用目的的两大编程技巧。可阅:“写出可复用代码的基本思想与实践”, 函数式+泛型编程:编写简洁可复用的代码,再谈函数式编程:释放编程创造力。
复用性最考验程序员的抽象思维能力。抽象度越高,复用性越强。抽象能力强的人,通常是能写出一个好用框架的。
为什么说适度的复用性呢?因为代码的高复用性可能会导致代码清晰度下降或者有性能损耗,或者理解起来有一定成本,需要有所权衡。
适度的可扩展性
大多数公司对代码可扩展性并不作要求,更多是对系统架构设计有可扩展要求。不过,程序员为了省事省力,达到一定的代码可扩展性是有助益的。
达到代码可扩展性通常基于接口和插件编程,要求对设计模式有一定掌握。比如策略模式、组合模式、装饰模式、桥接模式、访问器模式、观察者模式等。可阅:实现可扩展代码的四步曲、由一次重构引发的对可扩展性的思考。
达成系统可扩展性,则需要具备更多技能和经验:【整理】系统可扩展性的设计与实现 。
为什么说适度的可扩展性呢?过度的可扩展性往往意味着过度设计,一开始考虑了很多,结果基本上用不上,反而导致系统实现复杂,维护成本增大,得不偿失。
代码安全性
代码安全性放在最后,但并非不重要。高级程序员往往也会忽视代码安全性。
- 敏感信息泄露:比如密码、配置等打印在日志里(或者作为调试语句打印上线却忘了删除)。
- 权限控制不当:低权限的人访问到其未授权的资源。
- SQL注入:在数据库相关操作中,可以让用户填入过于灵活的查询条件却不加检查。
- 远程代码执行:在可以让用户输入语句进行的应用里,没有对恶意代码进行检测或过滤。
- 反序列化问题:fastjson 的事情大家都知道了。即使大厂程序员都不能避免,其它人更不谈了。
- 文件路径漏洞:一般体现在文件上传上,不过在任何可以让用户输入路径的地方,都可能存在文件路径漏洞。
代码重构
代码重构,是提升代码质量技能的最有效的手段。
经过代码重构,代码能达到一种更精练更优雅的境界,阅读起来也是很有成就感的。可阅:精练代码:一次Java函数式编程的重构之旅 ,一次重复代码重构的思考及探索。
代码重构涉及程序员的综合技能素养:
- 简单的代码删减和挪动,涉及对分层结构的深入理解;
- 较复杂的代码结构改造,涉及对业务流程结构和设计模式的理解和应用;
- 更复杂的系统级重构,涉及对系统设计理论和新技术栈的学习和应用。
只有第一种是相对安全的,后两种在完成之后,都要仔细检查是否对现有逻辑有改动,或者需要回归测试下。
进阶磨炼
迭代前进
不断思考总结提炼,持续迭代优化前进。
在熟练驾驭代码质量,且能够有效利用 AI 生成代码之后,就可以向更上层进阶了。
如何作出良好的设计方案,是下一步的目标。因为代码已经可以由AI 生成,你所需要做的是定好整体方案和规划,而不是沉迷于具体细节。可阅:如何做出一个好的设计方案 。
在方案之上,程序员还有更高的追求。那就是架构设计,全局视野。可阅:从系统整体观思考系统构建 。实际上,系统整体观,不仅仅应用于技术性系统,也可以应用于社会系统,机构组织等。这时候,你的视野不再局限于技术和代码,而是放眼于组织、社会、商业等。
思考力
程序员最核心的竞争力是什么?强大的抽象和逻辑思维,高质量的思考力。
强大的抽象和逻辑思维,清晰透彻缜密的思考力,像激光一样具有穿透力的思考力。这是从事软件开发磨炼出的特有的能力,这种能力可以轻易地理解现实社会的规则(尽管未必会屈从某些规则),理解很多比较复杂的关联关系。
小结
本文小结了代码质量相关的知识和技能。可以看到,代码质量看上去只是代码层面,实际上反映的是程序员的综合素养,包括程序员的逻辑和抽象思维、程序员的系统设计能力、程序员的编码和细节处理能力等。要写出高质量程序,可不那么容易。
每一次总结提炼,都是对自身的一次升华。