好的命名可以减少别的小伙伴的理解成本,从而提高效率
坏味道排查列表
- 过于宽泛的命名
- 用技术术语命名
解决方案
命名前思考如下原则,用更好的命名来让我们的代码变得更加优雅。
- 命名应该描述意图而非细节
- 面向接口编程,接口是稳定的,实现是易变的(不好理解的话参考下文的例子)
- 使用业务语言进行命名
举例说明
宽泛的命名
举个例子来说明一下:
public void processChapter(long chapterId) {
Chapter chapter = this.repository.findByChapterId(chapterId);
if (chapter == null) {
throw new IllegalArgumentException("Unknown chapter [" + chapterId + "]");
}
chapter.setTranslationState(TranslationState.TRANSLATING);
this.repository.save(chapter);
}
映入眼帘你看到这里这个方法的名字,🤔方法名的含义翻译过来叫做处理章节。这时候如果我问,上面这段代码做了一件什么事情。那么你一定很懵逼,然后你需要去集中精力看一下代码,最后才能得出结论就是这个方法先获取了一个文章然后设置了翻译状态,最后进行了保存。
那么是什么导致的我们需要阅读这个代码的细节才可以知道这个代码是做什么的呢???
其实问题就出现在了函数的名字上,这个函数命名的含义过于宽泛。处理章节,怎么处理?删除章节?还是新增章节?还是修改章节内容?这个命名没有错,但是不够精准会增加我们的理解成本。
命名过于宽泛,不能精准描述,这是很多代码在命名上存在的严重问题,也是代码难以理解的根源所在。
下面列出几个宽泛命名的代表😂
- data
- info
- flag
- process
- handle
- build
- maintain
- manage
- modify
- 。。。
然后回过头来看,我们这个方法名称应该叫什么合适呢?
这段代码再做的就是“将章节修改为翻译中”,那么我们重新命名为changeChapterToTranslating如何呢?
诚然这个名字已经比上一个名字要好的多了,但是它也算不上是一个好的名字,原因是它更多的描述的是代码的细节。我们在写方法的时候,之所以进行封装一个很重要的原因就是,我们不想知道这么多的细节,如果把细节平铺开来,那本质上和阅读源码也没什么区别了。
所以一个好的名字应该描述意图,而非细节。
这段代码,我们为什么要把翻译状态改成翻译中呢?我们考虑下这里的业务意图,这时候我们就需要结合我们的业务需求去分析了,具体到这里的业务,我们把翻译状态修改成翻译中,是因为我们在这里开启了一个翻译的过程。所以我们再起一个名字,这个方法命名为startTranslation
用技术术语命名
再来看一段代码
List<Book> bookList = service.getBooks();
一段我们最常写的代码了吧,普通的不能再普通了,那这段代码有什么问题呢?
我们在编程的时候有一个很重要的原则就是面向接口编程。为什么要面向接口编程呢?这是因为接口是稳定的,而实现是易变的。绝大多书人对这句话的理解其实是针对接口或者说是接口的方法的。其实我们在命名上同样也可以遵循这样的原则,怎么理解呢,就是serice.getBooks这个方法获取的一定是book的集合,这个就是这个接口的约束,而具体的实现则是可能发生变化的,举个例子如果我发现,我现在需要的是一个不重复的作品集合,也就是说,我需要把这个变量的类型从 List 改成 Set。变量类型你一定会改,但变量名你会改吗?这还真不一定,一旦出现遗忘,就会出现一个奇特的现象,一个叫 bookList 的变量,它的类型是一个 Set。这样,一个新的混淆就此产生了。
所以这时候我们就需要一个更面向意图的名字了,我们需要一个能表示一堆书的命名,所以我们可以命名为books
总结
其实无论是命名过于宽泛,还是使用了技术名词,归根结底体现的问题就是:对业务的理解不到位。
编写可维护的代码要使用业务语言,怎么才能知道自己的命名是否使用的是业务语言呢?一个简单的做法就是,把这个词说给产品经理,看他知不知道怎么回事(这里在上家公司的时候清晰感觉到了,尤其在使用DDD进行开发的时候提供相关领域对象的能力的时候,方法的命名可以跟产品确认)
从团队的角度看,每个人对业务的理解都不同,所以命名一定是千奇百怪的,这个时候我们可以考虑建立一个团队词汇表,让每个成员都可以有参考依据(类似于编码规范之类的,一个常见的问题就是查询一条数据用get还是query,其实无论是get还是query都可以,只要统一代码的风格即可)