学习并整理一下个人的理解:什么样的代码是高质量的代码?

代码的评价指标

这部分内容主要参考知乎上的回答:什么样的代码是高质量的代码? - 王争的回答 - 知乎

简洁性

KISS原则:“Keep It Simple,Stupid”

我们需要通过尽可能简单的代码来完成目标,避免炫技地引入复杂的结构和设计模式。

典型的反例就是Java,完全的面向对象导致了代码架构的过度复杂化。例如程序员需要点一杯咖啡,Java 风格的代码如下

1
CoffeeFactoryBuilderSingletonManagerProvider.getCoffeeFactoryBuilderSingleton().getCoffeeBuilder().addMilk().addSugar().build();

当然了,单纯的批判也是不对的,这种复杂设计和规范对于项目的可维护性还是很有必要的,它可以保证不同的Java码农产出同一套风格的代码。(与之不同的是,C++程序员在实现同一套逻辑时可以给出 N 种风格的代码)

可读性

Any fool can write code that a computer can understand. Good programmers write code that humans can understand.

我们需要编写提供易读、易理解的代码,让机器理解代码是容易的,困难的是让其他人也容易读懂代码,这方面有很多细节需要考虑:

  • 编码是否规范(缩进和空格风格是否一致等)
  • 命名是否规范
  • 注释是否详尽
  • 函数体的长度是否合适
  • 函数参数是否过多,过于混乱
  • 模块划分是否清晰
  • ...

没有人喜欢在代码中加上没什么用的注释,但是又不得不加上一点,就好像打扫厕所——都不想干这个活,但是它确实有用。

有几条关于注释的建议:

  • 注释并不是越多越好,在较难理解的位置添加说明即可;
  • 使用良好命名和语法的代码本身就能解释 what,额外的注释应该只出现在必要的位置,注释是用来解释 why 和 how。

当然,有时候为了程序的性能提升也可以牺牲一部分代码可读性:代码丑到这个样子,性能一定快到飞起吧!

可复用性

DRY原则:“Don't Repeat Yourself”

我们需要在代码中尽量减少重复片段的编写,复用已有的代码是更好的选择。 如果代码中存在多处重复片段,不仅仅是代码量太大,还意味着改动时必须同步更改所有位置,否则很容易导致问题。

可扩展性

可扩展性表示代码应对未来需求变化的能力,对于具有可扩展性的代码,我们可以在不修改或少量修改原有代码的情况下,通过扩展的方式添加新的功能。 更简单地说,代码自身已经预留了一些功能扩展点,我们可以把新功能代码直接插到扩展点上,而不需要因为要添加一个功能而大动干戈地改动大量的原始代码。

可维护性

对代码的维护主要是修改bug、修改旧代码、添加新代码等。可维护性的直观含义为:

  • 对于易维护的代码,我们可以在不破坏原有代码设计、不引入新的bug的情况下,快速地修改或者添加代码。
  • 对于不易维护的代码,修改或者添加代码需要冒着极大的引入新bug的风险,我们可能需要花费很长的时间才能完成。

可维护性通常可以由前面的几个指标保证,例如可读性、可复用性、可扩展性等,有时还需要加上可测试性:代码本身是充分解耦的,使我们容易编写单元测试。

程序员的美德

参考为什么一些程序员很傲慢? - 方应杭的回答 - 知乎

  • 懒惰: 懒惰使得你花大力气去偷懒。它敦促你写出节省体力的程序,同时别人也能利用它们。为此你会写出完善的文档,以免别人问你太多问题。

  • 急躁: 当你发现计算机懒洋洋地不给出结果,会感到愤怒。于是你写出更优秀的代码,能尽快真正的解决问题。至少看上去是这样。

  • 傲慢: 有极度的信心写出别人挑不出毛病的完美程序。

补充

关于代码优化

对于一个分支判断过于复杂的函数,使用“卫语句”是一个很好的习惯:优先检测非法情况,对于非法情况提前返回,避免把函数的主要部分放在多层if语句中。

在编程中,除了保持一些良好的编程习惯,更重要的是不要过早地对代码进行一些“自以为是”的优化,需要写出对人类,对编译器都友好的代码。 对于大部分的简单情况,编译器在了解了代码的意图时会自行进行相关优化。 只有在完成了主体功能,经过测试找到性能瓶颈之后,手动进行的针对性优化才是有意义的。

优化的前置步骤是性能测试,优化的目标代码是性能瓶颈部分,对于细枝末节的优化并不会产生显然的性能提升,反而可能增加出错的风险。

关于参数检查

在编程中,参数的合法性检查到底是调用者负责,还是由被调用者负责? 通常有如下几种做法:

  • 调用者负责:适合性能敏感的底层库、内部接口、数学运算、算法实现等,此时只需在文档或注释中说明参数要求即可。
  • 被调用者负责:适合公共 API、用户输入处理、安全敏感模块。
  • 兼而有之:在调试模式下被调用者开启参数检查,但是在发布模式下关闭参数检查,以减少性能开销。

关于命名

  • 命名风格通常有大驼峰,小驼峰,下划线(蛇形)三种,使用类型缩写作为前缀的匈牙利命名法已经过时了。
  • 某些语言严格规定了命名风格,遵循规定即可。
  • 有时为了和语言的标准库/内置函数区分,也可以反其道行之,使用另一种风格进行命名。
  • 变量名应清晰描述变量的含义,大范围生效的变量应避免使用 a, b, c, i, j, k 等无语义名称,小范围或者循环变量则可以接受(没办法,科学计算如果每一个变量名称都很长,计算公式就过于复杂了)
  • 布尔变量通常以 is_has_should_ 开头,不要以否定形式命名,否则逻辑有点绕。
  • 函数名称通常是动名词形式,类的名称通常是名称形式,类的方法与函数的要求相同。
  • 常量通常使用全大写加下划线的形式,即使某些语言(python,MATLAB)没有直接支持常量(除非使用额外的封装技巧),也最好遵循这个习惯。

什么是糟糕的代码

什么样的代码是好的?这可能很难回答,但是不妨反过来思考——什么样的代码是糟糕的? 这个问题的答案至少包括:

  • 使用简短无意义的变量名
  • 命名风格混搭
  • 代码格式化混乱,缩进不统一
  • 不写注释
  • 使用非英文进行命名或注释
  • 消极处理错误或异常
  • 广泛使用全局变量
  • 在动态语言中尽量少地明确变量类型
  • 使用多层嵌套语句
  • 函数体过长
  • 存在未使用的变量,以及不会执行的死代码
  • 不使用代码测试