自检:
变量声明之后是否有立即赋值,集合声明之后是否有立即添加元素
存在的问题:
变量的声明和赋值分离带来的问题就是,把赋值的过程与业务处理混杂在一起。
编程规则:
变量要一次性完成初始化
应对策略:
- 1.在声明前面加上final,用不变性的限制约束代码。
- 2.用声明式的方式进行集合的初始化。
- 传统的集合初始化方式是命令式的,而我们要做的就是用声明式的方式进行集合的初始化,让初始化的过程一次性完成。再进一步,以声明式的标准来看代码,会帮助我们发现许多的。
1. 变量的初始化
EpubStatus status = null; CreateEpubResponse response = createEpub(request); if (response.getCode() == 201) { status = EpubStatus.CREATED; } else { status = EpubStatus.TO_CREATE; }
上面的代码,从语义上说,第一行的变量初始化其实是没有用的,这是一次假的初始化。这段代码里的变量赋值是在声明很久之后才完成的,也就是说,变量初始化没有一次性完成。
这种代码真正的问题就是不清晰,变量初始化与业务处理混在在一起。通常来说,这种代码后面紧接着就是一大堆更复杂的业务处理。很多代码难读,一个重要的原因就是把不同层面的代码混在了一起。
这种代码在实际的代码库中出现的频率非常高,只不过,它会以各种变形的方式呈现出来。有的变量甚至是在相隔很远的地方才做了真正的赋值,完成了初始化,这中间已经夹杂了很多的业务代码在其中,进一步增加了理解的复杂度。所以,我们编程时要有一个基本原则:变量一次性完成初始化。
final CreateEpubResponse response = createEpub(request); final EpubStatus status = toEpubStatus(response); private EpubStatus toEpubStatus(final CreateEpubResponse response) { if (response.getCode() == 201) { return EpubStatus.CREATED; } return EpubStatus.TO_CREATE; }
在这段改进的代码中,我们提取出了一个函数,将 response 转成对应的内部的 EPUB 状态。
还有一点不知道你注意到了没有,在新的变量声明中,我加上了 final,在 Java 的语义中,一个变量加上了 final,也就意味着这个变量不能再次赋值。尽可能编写不变的代码,尽可能使用不变的量。所以,在能够使用 final 的地方尽量使用 final,限制变量的赋值。
对于 Java 程序员来说,还有一个特殊的场景,就是异常处理的场景,强迫你把变量的声明与初始化分开,就像下面这段代码:
InputStream is = null; try { is = new FileInputStream(...); ... } catch (IOException e) { ... } finally { if (is != null) { is.close(); } }
如果采用 Java 7 之后的版本,采用 try-with-resource 的写法,代码就可以更简洁了:
try (InputStream is = new FileInputStream(...)) { ... }
2. 集合的初始化
List<Permission> permissions = new ArrayList<>(); permissions.add(Permission.BOOK_READ); permissions.add(Permission.BOOK_WRITE); check.grantTo(Role.AUTHOR, permissions);
这和我们前面所说的变量先声明后赋值,本质上是一回事,都是从一个变量的声明到初始化成一个可用的状态,中间隔了太远的距离。
我们可以使用 Guava(Google 提供的一个 Java 库)进行修改优化:
List<Permission> permissions = ImmutableList.of( Permission.BOOK_READ, Permission.BOOK_WRITE ); check.grantTo(Role.AUTHOR, permissions);
这段代码里的 List 用的是一个 ImmutableList,也就是一个不可变的 List,也就是说,这个 List 一旦创建好了,就是不能修改了,对应的实现就是各种添加、删除之类的方法全部都禁用了。
针对Map同样有类似的用法:
private static Map<Locale, String> CODE_MAPPING = ImmutableMap.of( LOCALE.ENGLISH, "EN", LOCALE.CHINESE, "CH" );
对比我们改造前后的代码,二者之间还有一个更关键的区别:前面的代码是命令式的代码,而后面的代码是声明式的代码。
命令式的代码,就是告诉你“怎么做”的代码,就像改造前的代码,声明一个集合,然后添加一个元素,再添加一个元素。而声明式的代码,是告诉你“做什么”的代码,改造后就是,我要一个包含了这两个元素的集合。