你可能忽视的细节-代码整洁之道(中)

1,737 阅读8分钟

最近被领导安排,参加了几次codeReview,发现了不少不大不小的问题。因为是另一个小组的,所以我基本上没怎么关注业务逻辑,主要重点放在了对代码风格、抽象层次等的关注,随之发现了一堆问题。

正好我最近在看《代码整洁之道》这本书,因此我有了整理这方面的一些想法。

本系列将分为上中下三篇,分别从纯理论方向实践层面来对代码整洁之道进行个人的一些见解说明。

方向

上一篇,我们大概介绍了一下关于代码整洁程度的重要性、如何保持代码的整洁等几个宏观的方面,本篇我们将会着重梳理以下几个方面来使你的代码保持整洁。

  • 命名
  • 函数
  • 注释
  • 代码格式
  • 错误处理
  • 边界
  • 单元测试
  • 迭进设计
  • 逐步改进

命名

很多代码复杂度提升的原因,不在于代码的简介程度,而在于代码的模糊程度,即上下文在代码中未被明确体现的程度。

因此我们需要尽可能的使用更能完整表达我们的含义的命名,不要害怕因为想一个名称浪费的那一点点时间,这一点点时间将会为后来的维护带来更多的价值。

关于命名,我们应该尽量做到:

  • 提防使用外形相似度较高的名称
  • 使用可以读的出来的名称,而不是某种简写
  • 使用可以搜索的名称,比如定义常量

函数

在大部分语言中,函数都是占比相当大的一个组成,并且存在Lisp这种的函数式编程语言,足以见得函数的重要程度,于此同时,函数的规范、简洁程度也应该被人们重视。

javascript中,函数是一等公民,因此拥有极高的灵活性,但是也因此更不容易被优美的使用,无用的函数、超长的函数等等层出不穷,为后续的迭代、维护带来莫大的负担。

因此,我们应该重视函数的简洁、规范,让我们的代码变得更为健壮:

  • 函数应该尽量短小
  • 函数应该遵循单一职责原则,也就是函数应该只做一件事,做好这件事,并且只做这一件事
  • 不要害怕长名称,长而具有描述性的名称,要比短而令人费解的短名称好,要比描述性的长注释好
  • 函数的复杂度往往与参数的个数成正比,因此函数参数应该尽可能的减少,不要多于三个,多余三个应当改为使用对象传参或者考虑是否应该拆分
  • 尽量使用纯函数,纯函数具有更好的可维护性,更健壮

注释

每个语言中,都存在注释,注释存在的意义就是为了向后来者阐述自己的思路以及想法,因此注释的好坏,往往也会影响一个程序的维护。

注释的恰当用法是弥补我们在用代码表达意图时遭遇的失败。

好的注释会直观的向程序员描述一个程序的意图,然而好的注释可能会随着程序功能的迭代变得不再适用,因此程序员需要将注释保护在可维护、有关联、精确的高度。否则,如果程序员不能坚持维护注释,那么遗留的错误注释将会成为拖累程序可维护性的一个毒瘤。

对于大多数注释,我们可以将其划分为两类注释:好的注释和坏的注释

好的注释(或必不可少的注释)

  • 法律信息
  • 提供信息的注释
  • 对意图的解释
  • 帮助某些无法修改的库进行阐释的注释
  • 用于警示可能会出现的风险的注释
  • TODO
  • 用于放大某些看似不合理的代码的重要性的注释
  • jsdoc

坏的注释(或没有意义的注释)

  • 自说自话,通篇废话,没有主题的描述
  • 多余的注释
  • 误导性的注释(比如未随着程序一起迭代更新的老旧注释)
  • 为了注释而注释(比如声明了一个无关紧要的变量也进行注释)
  • 日志式的注释
  • 废话注释
  • 位置标记
  • 归属与署名
  • 注释掉的代码
  • js注释中使用html来进行注释
  • 非当前程序的注释
  • 信息过多的注释

代码格式

代码格式因为现在已经存在了类似eslintprettier这些风格类工具,因此可以较为简单的说明一下。

代码格式推荐:

  • 代码块之间留行分割
  • 关系密切的概念应该互相靠近
  • 变量声明尽可能靠近其使用位置
  • 若某个函数调用了另一个,就应该把他们放到一起,而且调用者应尽可能放在被调用者上面
  • 使用短行代码(最多80列或120列)
  • 运算符之间应该存在空格
  • 应该使用统一的缩进
  • 团队应该使用统一的代码风格

错误处理

错误处理是任何一个程序都离不开的话题,从程序自身的语法错误、类型错误,到网络请求的各种响应错误,再到程序间通信的自定义错误,错误处理的方式,影响着程序整体的规整程度以及后续的扩展性。

因此,对于错误处理:

  • 使用统一的错误处理模块
  • 对错误进行分类处理
  • 对各种错误信息应该有着详尽的描述

边界

边界是指一些平时可能不会注意到,但是又会切入程序本身的一些地方,比如我们使用的第三方模块。

对于第三方模块这种边界,应当:

  • 编写测试用例来遍览以及理解,确保在版本变化中不会因为第三方模块的破坏式更新造成影响

单元测试

对于一个功能完善的程序来说,单元测试必不可少,他能够提供足够的使用场景供你来对你的程序进行测试,并可以生成测试覆盖率,来帮助你完善各种边缘case。

单元测试一般常用于测试纯函数、纯组件,纯应用保证在同样的使用场景下,使用会保证得到相同的结果。

关于单元测试,我们应该尽量做到:

  • 编写好的单元测试,随着功能的修改而及时跟进,需要保持整洁以及有效,防止造成不可维护的测试代码
  • 保证单元测试代码的健壮、可维护性,它将会让你的代码可扩展、可维护、可复用
  • 保证足够的测试覆盖率
  • pre-commit钩子进行单元测试的执行,通过之后才可进行继续提交
  • 单元测试的代码应该足够明确、简洁,并有足够的表达力
  • 单元测试的代码应该按照颗粒度进行划分,保证功能单一性,每个测试一个断言
  • 要进行单元测试的代码应该保证足够的纯度

单元测试消除了对清理代码就会破坏代码的恐惧

迭进设计

迭进设计是指先实现一个近似的模型,然后收集新的参数,继续实现一个更近似的模型,如此反复。

我们可以通过迭进设计的方式,来进行系统的构建:

  • 递增式的完善代码
  • 便于修改
  • 留有足够的空间对系统进行更优设计
  • 可以逐步编写单元测试

逐步改进

要编写整洁代码、必须先写肮脏代码,然后再清理它

我们在进行代码重构的时候,可以使用逐步改进的方法:

  • 逐步减少重复代码
  • 尽可能减少类和方法的数量
  • 可以使用有关优秀软件设计的一切知识,如:
    • 提升内聚性
    • 降低耦合度
    • 切分关注面
    • 模块化系统性关注面
    • 缩小函数和类的尺寸
    • 选用更好的名称等增加表达力
    • 编写更为优秀的单元测试
  • 采用测试驱动的规则,在保证测试用例始终可以正确运行的前提下,进行渐进式重构
  • 重构前整理好要修改的原因,在重构的时候可以进行针对性的优化

小结

本篇从多个方向介绍了如何保证代码的简洁,可以说,这是《代码整洁之道》一书的精华所在。

下一篇,我们将从一些具体的小例子,来对此进行更详实的描述。

希望看完这三篇,对你有所帮助。