最近正在重构项目,并且正在看《重构》,在实践的同时总结了一些点,或许能给你一些重构或者写代码上的一些思考。

clean-code

我一直认为代码结构是一个因人而异的事情,很多时候我们其实判断一个代码的好坏往往是通过主观判断,比如同样是实现一个功能,100 行的代码并非一定比 50 行的差;我们没有一个合理的标杆去评判。

但是,最近我的想法变了,发现有些代码一定是毒药,早点发现他们,往往会对于我们以后需求的修改有莫大的帮助。

命名

如果把整个项目代码比作是房屋建造,命名就是砖头,命名的好坏直接决定了你代码 50% 的可读性。绝大部分的情况下,读者应该可以通过你函数的命名,直接了解到你这个函数的功能。

要求

  1. 命名要描述具体意图而非模糊的操作
  2. 功能命名不要出现技术名词
  3. 整个项目统一命名

命名要描述具体意图

不好的比如:process、modify…
通常我们需要使用一个动词+宾语,比如 modifyUsername,processFile
而一个类(对象)的命名,通常使用名词

功能命名不要出现技术名词

不好的比如:UserRoleMap
显然 Map 是一个技术名词,而这个对象的类型其实往往已经可以清楚的标识这个是个 map 类型。
这里想要表示的是一个用户角色名称的映射关系,个人习惯会通常命名为: UserRoleMapping 理解为映射关系

整个项目统一命名

最好在项目建立数据库的时候就统一命名,特别是针对一些专有名词的命名,可以建立一个表格。

并且很多英语单词的非常考究的,小技巧:当你使用翻译软件翻译成一个英文单词之后,将这个英文单词再放到搜索引擎里面再去搜一遍,或者搜索对应图片,就能知道这个单词是否真的是你想要的

函数

控制函数长度

书本上有一句话经常被人提到就是:写的代码越少,bug 越少,所以要减少函数长度

这句话我是不认可的,有的时候代码极具的减少,可能会带来一些意外的操作,因为长度的减少有些时候并不是 等值替换 特别是 python 这种经常可以一行搞定的情况。

但是我认可要控制函数长度,函数长度越短,功能点越集中,阅读代码速度越快。很多函数我看一眼命名就知道要完成的功能是什么,然后测试的时候,只要输出没问题,则这个函数就可以直接跳过不看,如果函数长,那么我必须一行行的去看究竟是哪一个地方出现了问题。

不同的语言不同,函数长度控制限制不同,比如 python 往往就会短一些,java 就会长一些,由于 golang 经常还会写一些 if err != nil 会更加拖长一些。

PS:个人一般会尽量控制在 35 以内,但未严格执行 lint。

缺乏封装

控制函数长度之后很容易导致的一个问题就是缺乏封装。你肯定会奇怪了,我都把原来一个 200 行函数拆成 5 个函数了,为什么你还说缺乏封装呢?
案例:

1
2
3
4
5
6
7
8
function test() {
a = testA()
b = a.testB()
c = b.testC()
...
// 或者写为
c = a.testB().testC()
}

我也经常会写这样的代码,但是其实隐藏一些细节,才是封装的精髓。提供一个经常在重构使用的思路:

将一个函数分为三段:前置条件检查,基本逻辑处理,后置返回值处理

我往往将一个函数分好之后,就会发现,函数中的几个调用虽然来源不同,但是都是在做同一个事情,职责相同,隐藏其中的细节会对函数有更好的封装。

控制函数参数长度

之前在 java 的 阿里规范里面提到 函数的参数数量的控制,超过一定数量就需要封装成一个类,这个没有问题,很多人也都能做到。
但是,千万不要故意把所有的参数封装为一个对象,特别是业务属性本来就是不同的,有的时候封装成两个对象会更加复用或更加满足职责单一的要求。

控制嵌套

Cyclomatic complexity CC 复杂度用来描述代码编写的一些复杂度,经常使用 else 或者 for 的嵌套会导致复杂度异常的高。
其实控制嵌套的本质,我经常遵循的就是下面这句话:

让你的主逻辑保持一条直线,能不用 else 就不用,不超过 3 层嵌套

在 golang 中我会使用 gocyclo 工具类检测函数复杂度。

DRY !!!

Don’t Repeat Yourself
不要写重复代码,这个原则说说容易,其实做起来真的很难,因为我自己也经常陷入这个泥潭里面。
但是也不知道是共性还是人性,实现一个类似功能,想到的第一个反应绝对是拷贝 (CV 程序员无疑)。
所以我看完《程序员修炼之道》总结了以下方案供你参考:

  • 如果你还在 feat 分支开发,重复吧,没事的
  • 如果你发现一个代码重复三次,立刻抽离成函数,别犹豫
  • 在你提交 PR 或者进行 CR(code review) 的时候,重构重复的部分,别灰心
  • 使用 lint 工具来检查重复代码
  • 有时候,结构也是重复,仔细点

因为在实际情况我在实践的时候,往往刚刚抽离完成,就发现业务逻辑需要被更改,导致代码又拆开来的情况,或者需要提供额外的参数。

协同工作的时候一定要 Code Review ,因为大多数情况重复代码不一定是同一个人写的,特别是一些工具类。DRY 通常是重构中最容易发现的问题,也最容易被修改,也最容易被程序员犯错。

四个基本原则

少暴露细节

无论是函数还是类,能私有就私有,能不被外面看到就不被,很多时候,暴露的越多,调用的越多,就会有越多的错误。

单一职责

这也是和函数的命名有关,如果一个函数它只做一件事,那么就做好它命名的这件事,不要做多,职责单一方便阅读也方便排查问题,也不会产生过多的依赖。

动静分离

将代码中一定不会变动的部分和经常会被变动的部分进行分离,特别是一些类和变量的声明,可以将变化的部分抽离单独编写。

开闭原则

开闭原则,对扩展开放,对修改关闭。第一次我知道这个原则的时候很不理解。直到不断写代码的过程中,我渐渐的明白了:

  1. 修改关闭:既然你不让修改,那么你依赖的就是接口,而非实现,接口的参数方法名不变,你就不会修改,你就更不会犯错。
  2. 扩展开发:如果你想进行功能扩展,那么中间的实现可以被改变,通过不同的实现来扩展。

最后的警告

这是血与泪的教训~ 如果你当前的并不是在业务的开发过程中,而是在一个已经完整的上线或运行的业务上进行重构,请务必添加有必要的单元测试。重构最基本的要求就是保证已有的业务正常运行,而能保证这件事的绝不是程序员口中的“我这样改和原来一样”。

因为大多数重构都是没有 KPI 的,那谁也不想因为重构而背锅。

总结

重构的基本思路:

  1. 发现坏味道*第三章
  2. 抽离/合并/删除 代码
  3. 重新测试

参考

https://zh.m.wikipedia.org/zh-hans/SOLID_(%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E8%AE%BE%E8%AE%A1)

https://book.douban.com/subject/30468597/

https://book.douban.com/subject/34986245/