设计模式之编程规范和重构

一、重构的几个概念

1.1 为什么要重构

代码可控,个人能力提升,也是我们学习的经典设计思想、原则、模式、编程规范等理论知识的练兵场。

1.2 重构什么

大致分为大规模高层次的重构和小规模低层次的重构。大规模高层次重构包括对代码分层、模块化、解耦、梳理类之间的交互关系、抽象复用组件等等。这部分工作利用的更多的是比较抽象、比较顶层的设计思想、原则、模式。小规模低层次的重构包括规范命名、注释、修正函数参数过多、消除超大类、提取重复代码等编程细节问题,主要是针对类、函数级别的重构。小规模低层次的重构更多的是利用编码规范这一理论知识。

1.3 什么时候重构

持续重构意识,把重构作为开发必不可少的部分融入到开发中,而不是等到代码出现很大问题的时候,再大刀阔斧地重构

1.4 如何重构

大规模高层次的重构难度比较大,需要有组织、有计划地进行,分阶段地小步快跑,时刻保持代码处于一个可运行的状态。而小规模低层次的重构,因为影响范围小,改动耗时短,所以,只要你愿意并且有时间,随时随地都可以去做。

二、单元测试

2.1 什么是单元测试?

单元测试是代码层面的测试,用于测试“自己”编写的代码的逻辑正确性。单元测试顾名思义是测试一个“单元”,这个“单元”一般是类或函数,而不是模块或者系统。

2.2 为什么要写单元测试?

单元测试能有效地发现代码中的 Bug、代码设计上的问题。写单元测试的过程本身就是代码重构的过程。单元测试是对集成测试的有力补充,能帮助我们快速熟悉代码,是 TDD 可落地执行的折中方案。

举个贴身的例子,很多公司写代码都在内网或者红黄区写,往往缺少测试环境,这时候单元测试就显得尤为重要,可以减少大量重复打包验证的操作。

2.3 如何编写单元测试?

写单元测试就是针对代码设计覆盖各种输入、异常、边界条件的测试用例,并将其翻译成代码的过程。我们可以利用一些测试框架来简化测试代码的编写。对于单元测试,我们需要建立以下正确的认知:

  • 编写单元测试尽管繁琐,但并不是太耗时;
  • 我们可以稍微放低单元测试的质量要求;
  • 覆盖率作为衡量单元测试好坏的唯一标准是不合理的;
  • 写单元测试一般不需要了解代码的实现逻辑;
  • 单元测试框架无法测试多半是代码的可测试性不好

三、代码的可测试性

3.1 什么是代码的可测试性?

粗略地讲,所谓代码的可测试性,就是针对代码编写单元测试的难易程度。对于一段代码,如果很难为其编写单元测试,或者单元测试写起来很费劲,需要依靠单元测试框架很高级的特性,那往往就意味着代码设计得不够合理,代码的可测试性不好。

3.2 编写可测试性代码的最有效手段

依赖注入是编写可测试性代码的最有效手段。通过依赖注入,我们在编写单元测试代码的时候,可以通过 mock 的方法将不可控的依赖变得可控,这也是我们在编写单元测试的过程中最有技术挑战的地方。除了 mock 方式,我们还可以利用二次封装来解决某些代码行为不可控的情况。

3.3 常见的 Anti-Patterns

典型的、常见的测试不友好的代码有下面这 5 种:

  • 代码中包含未决行为逻辑;
  • 滥用可变全局变量;
  • 滥用静态方法;
  • 使用复杂的继承关系;
  • 高度耦合的代码

四、大型重构:解耦

4.1 “解耦”为何如此重要?

过于复杂的代码往往在可读性、可维护性上都不友好。解耦,保证代码松耦合、高内聚,是控制代码复杂度的有效手段。如果代码高内聚、松耦合,也就是意味着,代码结构清晰、分层、模块化合理、依赖关系简单、模块或类之间的耦合小,那代码整体的质量就不会差。

4.2 代码是否需要“解耦”?

间接的衡量标准有很多,比如:改动一个模块或类的代码受影响的模块或类是否有很多、改动一个模块或者类的代码依赖的模块或者类是否需要改动、代码的可测试性是否好等等。直接的衡量标准是把模块与模块之间及其类与类之间的依赖关系画出来,根据依赖关系图的复杂性来判断是否需要解耦重构。

4.3 如何给代码“解耦”?

给代码解耦的方法有:封装与抽象、中间层、模块化,以及一些其他的设计思想与原则,比如:单一职责原则、基于接口而非实现编程、依赖注入、多用组合少用继承、迪米特法则。当然,还有一些设计模式,比如观察者模式。

五、小型重构:编码规范

5.1 命名与注释

  1. 命名的关键是能准确的达意。对于不同作用域的命名,我们可以适当的选择不同的长度,作用域小的命名,比如临时变量等,可以适当的选择短一些的命名方式。除此之外,命名中个也可以使用一些耳熟能详的缩写。
  2. 我们借助类的信息来简化属性、函数的命名,利用函数的信息来简化函数参数的命名。
  3. 命名要可读、可搜索。不要使用生僻的、不好读的英文单词来命名。除此之外,命名要符合项目的统一规范,也不要用些反直觉的命名。
  4. 接口有两种命名方式。一种是在接口中带前缀"I",另一种是在接口的实现类中带后缀“Impl”。两种命名方式都可以,关键是要在项目中统一。对于抽象类的命名,我们更倾向于带有前缀“Abstract”。
  5. 注释的目的就是让代码更容易看懂,只要符合这个要求,你就可以写。总结一下的话,注释主要包含这样三个方面的内容:做什么、为什么、怎么做。对于一些复杂的类和接口,我们可能还需要写明“如何用”。
  6. 注释本身有一定的维护成本,所以并非越多越好。类和函数一定要写注释,而且要写的尽可能全面详细些,而函数内部的注释会相对少一些,一般都是靠好的命名和提炼函数、解释性变量、总结性注释来做到代码易读。

5.2 代码风格

代码风格都没有对错和优劣之分,不同的编程语言风格都不太一样,只要能在团队、项目中统一即可,不过,最好能跟业内推荐的风格、开源项目的代码风格相一致

5.3 编程技巧

  1. 将复杂的逻辑提炼拆分成函数和类;
  2. 通过拆分成多个函数的方式来处理参数过多的情况;
  3. 通过将参数封装为对象来处理参数过多的情况;
  4. 函数中不要使用参数来做代码执行逻辑的控制;
  5. 移除过深的嵌套层次,方法包括:去掉多余的 if 或 else 语句,使用 continue、break、return关键字提前退出嵌套,调整执行顺序来减少嵌套,将部分嵌套逻辑抽象成函数;
  6. 用字面常量取代魔法数;
  7. 利用解释性变量来解释复杂表达式。

5.4 统一编码规范

统一编码规范除了细节的知识点之外,最后,还有一条非常重要的,那就是,项目、团队,甚至公司,一定要制定统一的编码规范,并且通过 Code Review 督促执行,这对提高代码质量有立竿见影的效果。

六、思维导图总结

设计模式之编程规范和重构

七、主要参考