设计模式(07)——设计原则(2)

KISS原则

Keep It Simple and Stupid 这个原则听起来比较简单,重点是理解什么样的代码是简单的,代码行数少就是简单的代码吗???还是说当程序的逻辑十分复杂不容易理解时就是一个复杂的代码呢???

下面就让我们来看看怎么样判断简单。

代码行数

代码就不展示了,其实懂的都懂,代码行数是一定不能作为判断依据的,因为很多代码行数少的代码,其实是采用了一些高级语法特性的。如正则表达式、位运算符等。

如果盲目的采用这些特性,会影响代码的可读性,因为这种运算法为了减少代码行数,会采用很多方式来规定一些特性,这些特性有时会不好理解。例如用位运算符来代替算数运算,这样虽然会提高代码性能和减少行数,但也不是 KISS 原则下的指导产物,其违反了代码的可读性。

逻辑复杂

那代码的逻辑复杂可以作为判断依据吗?其实也是懂的都懂,代码也不展示了(因为太复杂了,所以不展示了-v-),为什么逻辑复杂不能作为判断依据呢???

因为有些代码他就是逻辑复杂的,如果不信的话,直接打开算法相关实现,找到一个看不懂的,那么其就是我们的目标了。

因此如果没办法,在特定场景下,只能采用该算法的实现,那么使用这种复杂逻辑就不是违反 KISS 原则了,因为没办法。

场景

综上,我们该如何写出符合 KISS 原则的代码呢,具体来说,还是需要 结合业务发展和技术团队能力。

在实际场景下,我们尽量做到以下几点就可以了。

  • 不使用同事可能不懂的语法和技术,例如 lambda、正则等(不过这些还好,例如 lambda 虽然有些时候部分地方还不让使用,但其实其早已不是一个新技术了,还不懂的话,这种情况可以考虑换个地方或者进行下部门培训了)
  • 不要重复造轮子,因为自己造的轮子在一定程度上肯定没有网上大家都用的好,比较容易出现问题,而这些问题可能在对应的轮子上已经被解决来(但自己学习的时候,该造还是要造)
  • 不要过度优化,炫技,例如为了提高性能和减少代码行数,使用大量的位运算符、复杂的条件表达式等,这些都是给机器看的,一般我们都不需要在这个性能上过分的优化,反而牺牲来代码的可读性

YAGNI原则

定义

不要去设计当前用不到的功能,不要去写当前用不到的代码。

该原则是作为一个指导思想来做的,其作用就是防止过度设计,但需要注意的是其是让你不做,但需要有可能要做的意识,提前留好拓展点,这样如果要做的时候,也可以快速跟上。
**

后续会有一篇文章,来专门讲解如何在软件设计中,防止过度设计,但对对应该优化,该留好优化点如何实现。

DRY原则

定义

英语解释为:Dont repeat yourself,可以理解为不要写重复的代码,要做好代码的可复用性。但该规则跟 KISS 原则一样,听起来可能比较简单,但是在实际使用中,却要注重的一个原则。

因为在该原则中,有一个很关键的点,什么样的代码是重复的代码,只是简单的代码一样就是违反该原则了吗?下面就让我们来根据以下几种场景来判断什么是重复的代码。

实现逻辑重复

 public boolean validUsername(String username) {
        if (StrUtil.isEmpty(username)) {
            return false;
        }
        if (username.contains("77777")) {
            return false;
        }
        return true;
    }

    public boolean validNickname(String nickname) {
        if (StrUtil.isEmpty(nickname)) {
            return false;
        }
        if (nickname.contains("77777")) {
            return false;
        }
        return true;
    }

我们来看一下,这一段代码是重复的吗?

明显能看出,两个方法的执行逻辑是一模一样的,那么我们可以说其是重复的,而将其合为一个方法吗?

答案是不行的,首先如果合成一个就违反了单一职责原则,一个方法做了多件事,此外,现在两个参数的校验逻辑是一样的,因此看着重复,后面如果 nickname 的校验逻辑改了呢?那就需要侵入式的修改代码,需要在所有使用该方法的代码中进行修改,依据 nickname 来特定的进行判断,从而违反了开闭原则。

因此,实现逻辑重复,不能构成代码重复的条件。

功能语义重复

    public boolean checkPasswordByRegex(String password) {
        String regex = "123456";
        boolean matches = Pattern.matches(regex, password);
        return matches;
    }

    public boolean checkPasswordByStr(String password) {
        boolean matches = StrUtil.equals(password, "123456");
        return matches;
    }

我们来看一下,上述两段代码是否构成重复?

答案是这两段代码是重复的代码,虽然这两段代码的实现逻辑不一致,代码编写也不一致,但其仍然违反了 DRY 原则,因为这两段代码所实现的点是一样的,下面我们来看一下这样重复的代码会带来什么隐患。

这样的设计可能会导致以下问题,

  1. 当一个不知情的人看到这两段代码完成的任务是一样的,但采用的解决方式却是不一样的,则会想其的设计深意,以及不知道该用哪一个?
  2. 当函数的实现逻辑要改的时候,可能会导致修改了其中一个,而忘记修改另一个,这样则会造成逻辑的不一致。

代码执行重复

    // 代码执行重复
    public boolean validContainsA(String temp) {
        if (StrUtil.isNotEmpty(temp) && temp.contains("A")) {
            return true;
        }
        return false;
    }

    public boolean validTemp(String temp) {
        if (StrUtil.isNotEmpty(temp) && temp.contains("A")) {
            return true;
        }
        return validContainsA(temp);
    }

上述代码因为举的例子比较简单,应该能很简单的看出有一段代码被执行了两次,那么这样的设计违反了 DRY 原则吗?

我们认为其也是违反的,这里放的只是一个简单的逻辑判断,所以看不出来什么,如果这里是放了一个耗时操作呢,例如读取数据库、文件等。那么进行重复的执行,则会影响整个程序的对外性能。

总结

因此,判断如何一个代码是否是重复的,需要结合具体场景来做判断,在这里,我们可以看到代码实现方式一样不能作为判断依据,因此其实现的语义是不同的,这就是功能语义的判断范畴,而对于重复执行,我们在代码里需要做到尽量避免,因为如果重复执行费时操作,则会导致代码整体对外的性能下降。

迪米特原则

定义

迪米特原则是用来指导设计高内聚、低耦合代码的原则,因此我们先看一下什么是高内聚、低耦合。

  • 高内聚:相近的功能放到一个类或模块中,不相近的功能不要放到一个类中,暗合了单一职责原则;
  • 低耦合:类和类之间的依赖关系清晰简洁,即使有依赖,也要做到其中一个修改,依赖或被依赖的少修改。

看到想要完成的目标,现在再来看一下什么是迪米特原则。

其概念是:每个类或模块只需要了解与他关系密切即紧密关联的类或模块。再换句话说:

  • 不该有依赖关系的不要有依赖;
  • 有依赖关系,只依赖必要的接口。