高级程序员的代码质量应当达到什么水准

不断思考总结提炼,持续迭代优化前进。

工作十余年,对代码质量有过一段孜孜不倦的追求,同时也在实战中有一些亲身体会,总结下对代码质量的观点和经验。

关于代码质量,基本都总结在我的博客上:代码修行。也写过一本小小的电子书,再分享一次,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 生成,你所需要做的是定好整体方案和规划,而不是沉迷于具体细节。可阅:如何做出一个好的设计方案

在方案之上,程序员还有更高的追求。那就是架构设计,全局视野。可阅:从系统整体观思考系统构建 。实际上,系统整体观,不仅仅应用于技术性系统,也可以应用于社会系统,机构组织等。这时候,你的视野不再局限于技术和代码,而是放眼于组织、社会、商业等。

思考力

程序员最核心的竞争力是什么?强大的抽象和逻辑思维,高质量的思考力。

强大的抽象和逻辑思维,清晰透彻缜密的思考力,像激光一样具有穿透力的思考力。这是从事软件开发磨炼出的特有的能力,这种能力可以轻易地理解现实社会的规则(尽管未必会屈从某些规则),理解很多比较复杂的关联关系。

小结

本文小结了代码质量相关的知识和技能。可以看到,代码质量看上去只是代码层面,实际上反映的是程序员的综合素养,包括程序员的逻辑和抽象思维、程序员的系统设计能力、程序员的编码和细节处理能力等。要写出高质量程序,可不那么容易。

每一次总结提炼,都是对自身的一次升华。

posted @ 2024-11-12 07:33  琴水玉  阅读(57)  评论(0编辑  收藏  举报