美文网首页
代码坏味道:变量的声明与赋值分离

代码坏味道:变量的声明与赋值分离

作者: GuangHui | 来源:发表于2022-09-26 09:18 被阅读0次

    坏味道自检:

    变量声明之后是否有立即赋值,集合声明之后是否有立即添加元素

    坏味道存在的问题:

    变量的声明和赋值分离带来的问题就是,把赋值的过程与业务处理混杂在一起。

    编程规则:

    变量要一次性完成初始化

    应对策略:

    • 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"
    );
    

    对比我们改造前后的代码,二者之间还有一个更关键的区别:前面的代码是命令式的代码,而后面的代码是声明式的代码。

    命令式的代码,就是告诉你“怎么做”的代码,就像改造前的代码,声明一个集合,然后添加一个元素,再添加一个元素。而声明式的代码,是告诉你“做什么”的代码,改造后就是,我要一个包含了这两个元素的集合。

    声明式的代码体现的意图,是更高层面的抽象,把意图和实现分开,从某种意义上来说,也是一种分离关注点。所以,用声明式的标准来看代码,是一个发现代码坏味道的重要参考。

    相关文章

      网友评论

          本文标题:代码坏味道:变量的声明与赋值分离

          本文链接:https://www.haomeiwen.com/subject/iimtartx.html