你真的思考过自己写的代码为啥这么垃圾吗? 1 前言 2 命名不一 方案

1 前言

不一致的代码会给团队的新人造成理解压力,明明大家都是在一个团队做项目:

  • 做同种事情,却有不同种做法
  • 起到类似作用的事物,却有不同名字

大部分程序员对一致性的理解都表现在较宏观方面,比如,数据库访问是叫 DAO还是 Mapper、Repository?
在一个团队内,这应该有统一标准,但编码层面,要求往往就没那么细致。所以,我们经常看到代码出现各种不一致写法。

2 命名不一

看一段代码:

也就是目前的分发渠道,看着没啥问题。但我作为新人就疑惑:

  • WEBSITE 和 KINDLE_ONLY 分别表示什么?

WEBSITE 表示作品只会在我们自己的网站发布,KINDLE_ONLY 表示这部作品只会在 Kindle 的电子书商店里上架

  • 所以都表示只在一个渠道发布?

是啊!

  • 既然二者都有只在一个平台上架发布的含义,为什么不都叫 XXX 或 XXX_ONLY?

好像也是哦......

所以问题就是这里 WEBSITE 和 KINDLE_ONLY 名字不一致。

表示类似含义的代码应该名字一致。比如,很多团队里把业务写到服务层,各种服务的命名也都叫 XXXService。
一旦出现不一致名字,通常都表示不同含义。比如,对于那些非业务入口的业务组件,它们的名字就会不一样,会更符合其具体业务行为,像BookSender ,它表示将作品发送到翻译引擎。

一般枚举值表示的含义应该都有一致业务含义,一旦出现不同,就需要确定不同点到底在哪里,这也就是疑惑的原因。

显然,这段代码的作者给这两个枚举值命名时,只分别考虑了它应该起什么名字,却忽略了这个枚举值在整体所扮角色。

修正代码统一形式:

方案不一致

当一个系统向另外一个系统发送请求时,需要带一个时间戳过去,这里就是把这个时间戳按照一定格式转成了字符串类型,主要就是传输用,便于另外的系统进行识别,也方便在开发过程中进行调试。

这段代码本身的实现是没有问题的。它甚至考虑到了 SimpleDateFormat 这个类本身存在的多线程问题,所以,它每次去创建了一个新的 SimpleDateFormat 对象。

那我为什么还说它是有问题的呢?因为这种写法是 Java 8 之前的写法,而我们用的 Java 版本是 Java 8 之后的。

这个项目里,我们的要求是使用新的日期时间解决方案,而这里的 SimpleDateFormat 和 Date 是旧解决方案的一部分。所以,虽然这段代码本身的实现是没有问题的,然而,放在项目整体中,这却是一个坏味道,因为它没有和其它的部分保持一致。

后来使用新的解决方案:

之所以会这样,因为一个项目中,应对同一个问题出现了多个解决方案,如果没有统一约定,项目成员会根据自己写代码时的感觉随机选择方案,导致方案不一致。

为什么一个项目中会出现多个解决方案?

  • 时间

时间消逝,技术发展,人们会主动意识到原方案的问题,就会提出新方案,像这里 Java 日期时间的解决方案,就是 JDK 本身随时间演化造成的。有的项目时间比较长,也会出现类似问题。

  • 自身原因引入

比如,在代码中引入做同一件事情类似的程序库。比如判断字符串是否为空或空串,就有 Guava 和 Apache Commons Lang,都能做同样事情,所以,程序员也会根据自己的熟悉程度选择其中之一来用,造成代码不一致。

这两个程序库是很多程序库的基础,经常因为引入了其它程序库,相应的依赖就出现在我们的代码中。所以,我们必须约定,哪种做法是我们在项目中的标准做法,以防出现各自为战的现象。比如,在我的团队中,我们就选择 Guava 作为基础库,因为相对来说,它的风格更现代,所以,团队就约定类似的操作都以 Guava 为准。

代码不一致

在翻译引擎中创建作品的代码:

  • 首先,根据要处理的作品 ID,获取其中已审核通过的作品
  • 然后,发送一个 HTTP 请求在翻译引擎中创建出这个作品

有什么问题?
这段代码的不一致,这些代码不是一个层次的代码!

首先是获取审核通过的作品,这是一个业务动作,接下来的三行其实是在做一件事,也就是发送创建作品的请求,这三行代码:

  • 创建请求的参数
  • 根据参数创建请求
  • 最后把请求发送出去

三行代码合起来完成了一个发送创建作品请求这么一件事,而这件事才是一个完整的业务动作。

所以,这个函数里的代码并不在一个层次上,有的是业务动作,有的是业务动作的细节。理解到这,把这些业务细节的代码提取到一个函数:

结果上看,原来的函数(createBook)里都是业务动作,而提取出来的函数(createRemoteBook)则都是业务动作的细节,各自语句都在一个层次。

分清代码处于不同层次,基本功还是分离关注点!

一旦分解出不同关注点,还可进一步调整代码的结构。
像前面拆分出来的这个方法,我们已经知道它的作用是发出一个请求去创建作品,本质上并不属于这个业务类的一部分。
所以,还可通过引入一个新模型,将这个部分调整出去:

一说到分层,大多数人想到的只是模型的分层,很少有人会想到在函数的语句中也要分层。各种层次的代码混在一起,许多问题也就随之而来了,最典型莫过长函数。

我们在做的依然是模型分层,只不过,这次的出发点是函数的语句。“分离关注点,越小越好”的意义所在。观察代码的粒度足够小,很多问题自然就会暴露出来。

程序员开始写测试时,有一个典型的问题:如何测试一个私有方法。有人建议用一些特殊能力(比如反射)去测试。
但推荐不要测私有方法。
你之所以想测试私有方法,本质还是分离关注点没做好,把不同层次的代码混淆。前面这段代码,如果要测试前面那个 createRemoteBook 方法还是有一定难度的,但调整之后,引入了 TranslationEngine 这个类,这个方法就变成了一个公开方法,就可以按照一个公开方法去测试了,问题迎刃而解。

很多程序员纠结的技术问题,其实是一个软件设计问题,不要通过奇技淫巧去解决一个本来不应该被解决的问题。

总结

代码中的不一致常常是把不同层次的代码写在了一起,最典型的就是把业务层面的代码和实现细节的代码混在一起。解决这种问题的方式,就是通过提取方法,把不同层次的代码放到不同的函数里,而这一切的前提还是是分离关注点,这个代码问题的背后还是设计问题。

请务必保持代码一致性。