• 设计模式之美——KISS、YAGNI原则


    在这里插入图片描述
    KISS 原则算是一个万金油类型的设计原则,可以应用在很多场景中。它不仅经常用来指导软件开发,还经常用来指导更加广泛的系统设计、产品设计等,比如,冰箱、建筑、iPhone 手机的设计等等。

    我们知道,代码的可读性和可维护性是衡量代码质量非常重要的两个标准。而 KISS 原则就是保持代码可读和可维护的重要手段。代码足够简单,也就意味着很容易读懂,bug 比较难隐藏。即便出现 bug,修复起来也比较简单。

    下面这三段代码可以实现同样一个功能:检查输入的字符串 ipAddress 是否是合法的 IP 地址。一个合法的 IP 地址由四个数字组成,并且通过“.”来进行分割。每组数字的取值范围是 0~255。第一组数字比较特殊,不允许为 0。

    
    // 第一种实现方式: 使用正则表达式
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62

    一方面,正则表达式本身是比较复杂的,写出完全没有 bug 的正则表达本身就比较有挑战;另一方面,并不是每个程序员都精通正则表达式。对于不怎么懂正则表达式的同事来说,看懂并且维护这段正则表达式是比较困难的。这种实现方式会导致代码的可读性和可维护性变差。

    第二种实现方式使用了 StringUtils 类、Integer 类提供的一些现成的工具函数,来处理 IP 地址字符串。第三种实现方式,不使用任何工具函数,而是通过逐一处理 IP 地址中的字符,来判断是否合法。从代码行数上来说,这两种方式差不多。但是,第三种要比第二种更加有难度,更容易写出 bug。从可读性上来说,第二种实现方式的代码逻辑更清晰、更好理解。所以,在这两种实现方式中,第二种实现方式更加“简单”,更加符合 KISS 原则。

    尽管第三种实现方式性能更高些,但是第三种实现方式实际上是一种过度优化。除非 isValidIpAddress() 函数是影响系统性能的瓶颈代码,否则,这样优化的投入产出比并不高,增加了代码实现的难度、牺牲了代码的可读性,性能上的提升却并不明显。

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

    这段KMP 字符串匹配算法代码完全符合我们刚提到的逻辑复杂、实现难度大、可读性差的特点,但它并不违反 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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    字符串匹配是某个产品的核心功能(比如 Vim、Word 等文本编辑器),又或者字符串匹配算法是系统性能瓶颈的时候。我们就应该选择尽可能高效的 KMP 算法。而 KMP 算法本身具有逻辑复杂、实现难度大、可读性差的特点。本身就复杂的问题,用复杂的方法解决,并不违背 KISS 原则。

    在这里插入图片描述

    YAGNI 原则

    YAGNI 原则的英文全称是:You Ain’t Gonna Need It。直译就是:你不会需要它。这条原则也算是万金油了。当用在软件开发中的时候,它的意思是:不要去设计当前用不到的功能;不要去编写当前用不到的代码。实际上,这条原则的核心思想就是:不要做过度设计。

    YAGNI 原则跟 KISS 原则并非一回事儿。KISS 原则讲的是“如何做”的问题(尽量保持简单),而 YAGNI 原则说的是“要不要做”的问题(当前不需要的就不要做)。


    今天的内容有一些哲学味道,让我想到奥卡姆剃刀原理:
    如无必要,勿增实体。

    用同事不懂的技术,增加了整个团队的理解成本;重复造轮子和过度优化,大多数情况下带来的便利小于增加的成本;
    不要写炫技的代码,就像杜峰(CBA广东队教练)说的:“如果没有目的,就不要运球。(因为运球就可能丢球)”,降低出错的概率是一个数学问题,它能够真实得提高软件质量。

  • 相关阅读:
    js 图片路径转换base64格式
    一个Python的轻量级搜索工具--Whose
    负数如何取模?深度理解取模:取余运算
    MySQL(7) Innodb 原理和日志
    深度学习实战:基于TensorFlow与OpenCV的手语识别系统
    一文读懂vue3和vue2的API风格对比
    脑机直播 x 赛博驱魔!瑞云专访揭秘胶囊计划国风科幻神作《终极体验》制作幕后...
    MinIO 图片转文件的分界线RELEASE.2022-05-26T05-48-41Z
    MySQL启动错误总结
    Plumed分子模拟后分析
  • 原文地址:https://blog.csdn.net/iblade/article/details/128013935