> For the complete documentation index, see [llms.txt](https://yunzhao.gitbook.io/notes/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://yunzhao.gitbook.io/notes/computer-science/design-patterns/refactoring-and-specification.md).

# Refactoring & Specification

## 重构

**定义**：重构是一种对软件内部结构的改善，目的是在**不改变软件的可见行为**的情况下，使其更易理解，修改成本更低。

**为什么要重构？**

* 时刻保证代码质量。随着代码不停的堆砌，更多的人参与项目，代码会越来越乱，最后重构成本比重新开发还高。
* 优秀的代码都不能一开始就完全设计好，都是迭代出来的，随着系统的演进，重构是不可避免的。
* 避免过度设计。在迭代过程中，真正遇到问题的时候再对代码重构，避免前期投入太多精力。
* 帮助工程师技术成长。

**重构什么？**

* **大规模高层次重构**：对顶层代码设计重构，包括系统、模块、代码结构、类与类之间的关系，手段有分层、模块化、解耦、抽象可复用组件等。主要利用**设计思想、原则、模式**。影响面较大、难度较大、耗时较长、引入 BUG 风险较大。
* **小规模低层次重构**：对代码细节的重构，包括类、函数、变量等，如规范命名、规范注释、消除超大类等。主要利用**编码规范**。

**何时重构**：持续重构。平时无事时可以做一下小规模重构；在做功能开发时，遇到不规范的代码，也顺手改了。

### 解耦

过于复杂的代码可读性、可维护性都不友好。解耦是保证代码松耦合、高内聚、控制代码复杂度的有效手段。

根据模块、类与类之间的关系图的复杂性来判断是否需要解耦。

封装、抽象、中间层、模块化以及一些设计原则与思想（单一职责、基于接口而非实现、依赖注入、多用组合少用继承、迪米特等）是常用的解耦方法。

## 单元测试

单元测试（Unit Testing）是保证重构不出错的有效手段。单元测试是研发工程师自己编写，用于测试代码的正确性。相对于集成测试（Integration Testing）粒度更小。

**写单元测试的好处：**

* 能帮你发现代码中的 bug。
* 能帮你发现代码设计上的问题。代码的可测试性是一个重要的质量标准，若代码很难被测试，那么意味着代码设计不合理。
* 对集成测试的有力补充。集成测试很难覆盖全面。
* 写单元测试的过程本来就是代码重构的过程。
* 阅读单元测试能帮你快速熟悉代码。

尽管单元测试的代码量可能是被测代码的 1\~2 倍，但并不耗时，因为代码逻辑简单、重复很多。单元测试不会在线上运行，所以代码质量可以降低。不能用覆盖率作为单元测试质量的唯一标准。写单元测试不需要了解代码实现逻辑。

代码的可测试性就是编写单元测试的难易程度。依赖注入是提高可测试性的有效手段。

## 规范

### 命名

* **长短**：在足够表达含义的情况下，命名越短越好。对于常用的词，可以用缩写，如 str(string), num(number) 等。作用域较小的变量，可以用相对较短的命名。
* **利用上下文简化命名**。如 User 类中的成员变量 userName 可以简化为 name。
* **可读、可搜索**。可读（容易发音）是为了在项目沟通时方便。可搜索是为了方便在 IDE 中搜索，如大家都用 selectXXX，而你用 queryXXX，那么在 IDE 中药搜索所有查询相关的代码就不方便。
* **接口和抽象类**：接口两种方式都可以，一种 IUserService 和 UserService，另一种 UserService 和 UserServiceImpl。抽象类前缀 Abstract。

### 注释

* 注释的目的是让代码更加容易看懂。注释一般包含：做什么、为什么、怎么做，一些复杂的类和接口还要写如何用。
* 注释也有维护成本，并非越多越好。一般类和函数要写注释，而函数内部注释相对较少。

### 代码风格

* 函数的行数：不要超过一个屏幕的大小。
* 类的行数：间接判断标准，类读起来头大、实现某个功能不知道用哪个函数、用一个小功能需要整个函数（这个类包含很多无用的函数）时，说明这个类行数过多。
* 一行代码的长度：不要超过 IDE 显示的宽度。
* 善用空行分割代码。
* 缩进：推荐两格，因为可以节省空间。永远不要用 tab 缩进。
* 大括号推荐不另起一行，因为节省空间。另起一行的好处是左右括号可以对齐。
* 类中成员的排列顺序：先成员变量后函数，先静态后普通，先 public、再 protected、最后 private。

### 编程技巧

* 将复杂的逻辑提炼拆分成函数和类。
* 避免参数过多。可以拆分成多个函数或将参数封装成类的方式。
* 不要使用参数来做代码执行逻辑的控制。包括 null 的情况。
* 函数设计要职责单一。
* 移除过深的嵌套。方法有：去掉多余的 if 或 else；使用 continue、break、return 提前退出嵌套；调整执行顺序；将部分嵌套逻辑抽象成函数。
* 字面量取代魔法数。
* 解释性变量替代复杂表达式。
