设计模式简记-设计原则之KISS原则

3.6 KISS原则

3.6.1 如何理解KISS原则?

  • KISS 原则的英文描述有好几个版本:

    • Keep It Simple and Stupid.
    • Keep It Short and Simple.
    • Keep It Simple and Straightforward.

    意思其实差不多,翻译成中文就是:尽量保持简单。

3.6.2 代码行数越少就越“简单”吗?

  • 例子:三段代码可以实现同样一个功能:检查输入的字符串 ipAddress 是否是合法的 IP 地址。

    // 第一种实现方式: 使用正则表达式
    public boolean isValidIpAddressV1(String ipAddress) {
      if (StringUtils.isBlank(ipAddress)) return false;
      String regex = "^(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|[1-9])\\."
              + "(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|\\d)\\."
              + "(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|\\d)\\."
              + "(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|\\d)$";
      return ipAddress.matches(regex);
    }
    
    // 第二种实现方式: 使用现成的工具类
    public boolean isValidIpAddressV2(String ipAddress) {
      if (StringUtils.isBlank(ipAddress)) return false;
      String[] ipUnits = StringUtils.split(ipAddress, '.');
      if (ipUnits.length != 4) {
        return false;
      }
      for (int i = 0; i < 4; ++i) {
        int ipUnitIntValue;
        try {
          ipUnitIntValue = Integer.parseInt(ipUnits[i]);
        } catch (NumberFormatException e) {
          return false;
        }
        if (ipUnitIntValue < 0 || ipUnitIntValue > 255) {
          return false;
        }
        if (i == 0 && ipUnitIntValue == 0) {
          return false;
        }
      }
      return true;
    }
    
    // 第三种实现方式: 不使用任何工具类
    public boolean isValidIpAddressV3(String ipAddress) {
      char[] ipChars = ipAddress.toCharArray();
      int length = ipChars.length;
      int ipUnitIntValue = -1;
      boolean isFirstUnit = true;
      int unitsCount = 0;
      for (int i = 0; i < length; ++i) {
        char c = ipChars[i];
        if (c == '.') {
          if (ipUnitIntValue < 0 || ipUnitIntValue > 255) return false;
          if (isFirstUnit && ipUnitIntValue == 0) return false;
          if (isFirstUnit) isFirstUnit = false;
          ipUnitIntValue = -1;
          unitsCount++;
          continue;
        }
        if (c < '0' || c > '9') {
          return false;
        }
        if (ipUnitIntValue == -1) ipUnitIntValue = 0;
        ipUnitIntValue = ipUnitIntValue * 10 + (c - '0');
      }
      if (ipUnitIntValue < 0 || ipUnitIntValue > 255) return false;
      if (unitsCount != 3) return false;
      return true;
    }
    
    • 第一种实现方式利用的是正则表达式。虽然代码行数最少,看似最简单,实际上却很复杂。这正是因为它使用了正则表达式:
      • 一方面正则表达式本身是比较复杂的,写出完全没有 bug 的正则表达本身就比较有挑战;
      • 另一方面,并不是每个程序员都精通正则表达式。对于不怎么懂正则表达式的同事来说,看懂并且维护这段正则表达式是比较困难的。
      • 这种实现方式会导致代码的可读性和可维护性变差,所以,从 KISS 原则的设计初衷上来讲,这种实现方式并不符合 KISS 原则。
    • 第二种实现方式使用了 StringUtils 类、Integer 类提供的一些现成的工具函数,来处理 IP 地址字符串:
    • 第三种实现方式,不使用任何工具函数,而是通过逐一处理 IP 地址中的字符,来判断是否合法。
    • 从代码行数上来说,这两种方式差不多。但是,第三种要比第二种更加有难度,更容易写出 bug。从可读性上来说,第二种实现方式的代码逻辑更清晰、更好理解。所以,在这两种实现方式中,第二种实现方式更加“简单”,更加符合 KISS 原则。
    • 第三方实现方式性能可能更好,但属于过度优化,除非这个功能成为性能瓶颈,否则这样的优化投入产出比不高。

3.6.3 代码逻辑复杂就违背 KISS 原则吗?

// KMP algorithm: a, b分别是主串和模式串;n, m分别是主串和模式串的长度。
public static int kmp(char[] a, int n, char[] b, int m) {
  int[] next = getNexts(b, m);
  int j = 0;
  for (int i = 0; i < n; ++i) {
    while (j > 0 && a[i] != b[j]) { // 一直找到a[i]和b[j]
      j = next[j - 1] + 1;
    }
    if (a[i] == b[j]) {
      ++j;
    }
    if (j == m) { // 找到匹配模式串的了
      return i - m + 1;
    }
  }
  return -1;
}

// b表示模式串,m表示模式串的长度
private static int[] getNexts(char[] b, int m) {
  int[] next = new int[m];
  next[0] = -1;
  int k = -1;
  for (int i = 1; i < m; ++i) {
    while (k != -1 && b[k + 1] != b[i]) {
      k = next[k];
    }
    if (b[k + 1] == b[i]) {
      ++k;
    }
    next[i] = k;
  }
  return next;
}

以上为字符串匹配算法KMP算法实现代码,当字符串匹配是某个产品的核心功能,或这部分功能为性能瓶颈的时候,就应该选择高性能的复杂的算法。

  • 本身就复杂的问题,用复杂的方法解决,并不违背 KISS 原则。

3.6.4 如何写出满足 KISS 原则的代码?

  • 不要使用同事可能不懂的技术来实现代码。比如前面例子中的正则表达式,还有一些编程语言中过于高级的语法等。
  • 不要重复造轮子,要善于使用已经有的工具类库。经验证明,自己去实现这些类库,出 bug 的概率会更高,维护的成本也比较高。不要过度优化。
  • 不要过度使用一些奇技淫巧(比如,位运算代替算术运算、复杂的条件语句代替 if-else、使用一些过于底层的函数等)来优化代码,牺牲代码的可读性。

3.6.5 YAGNI 跟 KISS 说的是一回事吗?

  • YAGNI 原则的英文全称是:You Ain’t Gonna Need It。直译就是:你不会需要它。
  • 核心思想就是:不要做过度设计
  • 比如,系统暂时只用 Redis 存储配置信息,以后可能会用到 ZooKeeper。根据 YAGNI 原则,在未用到 ZooKeeper 之前,没必要提前编写这部分代码。这并不是说就不需要考虑代码的扩展性。还是要预留好扩展点,等到需要的时候,再去实现 ZooKeeper 存储配置信息这部分代码。
  • KISS 原则讲的是“如何做”的问题(尽量保持简单),而 YAGNI 原则说的是“要不要做”的问题(当前不需要的就不要做)。
posted @ 2020-04-23 18:54  杨海星  阅读(725)  评论(0编辑  收藏  举报