当前位置: 首页 > news >正文

细说KISS、YAGNI原则

如何理解“KISS原则”?

KISS原则的英文描述有好几个版本,比如下面这几个。

● Keep It Simple and Stupid.

● Keep It Short and Simple.

● Keep It Simple and Straightforward.

不过,仔细看你就会发现,它们要表达的意思其实差不多,翻译成中文就是:尽量保持简单。

KISS原则算是一个万金油类型的设计原则,可以应用在很多场景中。它不仅经常用来指导软件开发,还经常用来指导更加广泛的系统设计、产品设计等,比如,冰箱、建筑、iPhone手机的设计等等。不过,咱们的专栏是讲代码设计的,所以,接下来,我还是重点讲解如何在编码开发中应用这条原则。

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

不过,这条原则只是告诉我们,要保持代码“Simple and Stupid”,但并没有讲到,什么样的代码才是“Simple and Stupid”的,更没有给出特别明确的方法论,来指导如何开发出“Simple and Stupid”的代码。所以,看着非常简单,但不能落地,这就有点像我们常说的“心灵鸡汤”。哦,咱们这里应该叫“技术鸡汤”。

所以,接下来,为了能让这条原则切实地落地,能够指导实际的项目开发,我就针对刚刚的这些问题来进一步讲讲我的理解。

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

我们先一起看一个例子。下面这三段代码可以实现同样一个功能:检查输入的字符串ipAddress是否是合法的IP地址。

一个合法的IP地址由四个数字组成,并且通过“.”来进行分割。每组数字的取值范围是0~255。第一组数字比较特殊,不允许为0。对比这三段代码,你觉得哪一段代码最符合KISS原则呢?如果让你来实现这个功能,你会选择用哪种实现方法呢?

// 第一种实现方式: 使用正则表达式 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; }

第一种实现方式利用的是正则表达式,只用三行代码就把这个问题搞定了。它的代码行数最少,那是不是就最符合KISS原则呢?答案是否定的。虽然代码行数最少,看似最简单,实际上却很复杂。这正是因为它使用了正则表达式。

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

讲完了第一种实现方式,再来看下其他两种实现方式。

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

不过,你可能会说,第三种实现方式虽然实现起来稍微有点复杂,但性能要比第二种实现方式高一些啊。从性能的角度来说,选择第三种实现方式是不是更好些呢?

在回答这个问题之前,我先解释一下,为什么说第三种实现方式性能会更高一些。一般来说,工具类的功能都比较通用和全面,所以,在代码实现上,需要考虑和处理更多的细节,执行效率就会有所影响。而第三种实现方式,完全是自己操作底层字符,只针对IP地址这一种格式的数据输入来做处理,没有太多多余的函数调用和其他不必要的处理逻辑,所以,在执行效率上,这种类似定制化的处理代码方式肯定比通用的工具类要高些。

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

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

刚刚我们提到,并不是代码行数越少就越“简单”,还要考虑逻辑复杂度、实现难度、代码的可读性等。那如果一段代码的逻辑复杂、实现难度大、可读性也不太好,是不是就一定违背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原则。为什么这么说呢?

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

不过,平时的项目开发中涉及的字符串匹配问题,大部分都是针对比较小的文本。在这种情况下,直接调用编程语言提供的现成的字符串匹配函数就足够了。如果非得用KMP算法、BM算法来实现字符串匹配,那就真的违背KISS原则了。也就是说,同样的代码,在某个业务场景下满足KISS原则,换一个应用场景可能就不满足了。

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

实际上,前面已经讲到了一些方法。这里稍微总结一下。

● 不要使用同事可能不懂的技术来实现代码。比如前面例子中的正则表达式,还有一些编程语言中过于高级的语法等。

● 不要重复造轮子,要善于使用已经有的工具类库。经验证明,自己去实现这些类库,出bug的概率会更高,维护的成本也比较高。

● 不要过度优化。不要过度使用一些奇技淫巧(比如,位运算代替算术运算、复杂的条件语句代替if-else、使用一些过于底层的函数等)来优化代码,牺牲代码的可读性。

实际上,代码是否足够简单是一个挺主观的评判。同样的代码,有的人觉得简单,有的人觉得不够简单。而往往自己编写的代码,自己都会觉得够简单。所以,评判代码是否简单,还有一个很有效的间接方法,那就是code review。如果在code review的时候,同事对你的代码有很多疑问,那就说明你的代码有可能不够“简单”,需要优化啦。

在做开发的时候,一定不要过度设计,不要觉得简单的东西就没有技术含量。实际上,越是能用简单的方法解决复杂的问题,越能体现一个人的能力。

YAGNI跟KISS说的是一回事吗?

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

比如,目前系统暂时只用Redis存储配置信息,以后可能会用到ZooKeeper。根据YAGNI原则,在未用到ZooKeeper之前,没必要提前编写这部分代码。当然,这并不是说就不需要考虑代码的扩展性。还是要预留好扩展点,等到需要的时候,再去实现ZooKeeper存储配置信息这部分代码。

再比如,不要在项目中提前引入不需要依赖的开发包。对于Java程序员来说,经常使用Maven或者Gradle来管理依赖的类库(library)。我发现,有些同事为了避免开发中library包缺失而频繁地修改Maven或者Gradle配置文件,提前往项目里引入大量常用的library包。实际上,这样的做法也是违背YAGNI原则的。

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

重点回顾

KISS原则是保持代码可读和可维护的重要手段。KISS原则中的“简单”并不是以代码行数来考量的。代码行数越少并不代表代码越简单,还要考虑逻辑复杂度、实现难度、代码的可读性等。而且,本身就复杂的问题,用复杂的方法解决,并不违背KISS原则。除此之外,同样的代码,在某个业务场景下满足KISS原则,换一个应用场景可能就不满足了。

对于如何写出满足KISS原则的代码,我还总结了下面几条指导原则:

● 不要使用同事可能不懂的技术来实现代码;

● 不要重复造轮子,要善于使用已经有的工具类库;

● 不要过度优化。

http://www.jsqmd.com/news/984896/

相关文章:

  • 制造业领域:2026年值得关注的手推式/驾驶式/全自动工业扫地机制造商 - 企业推荐官【官方】
  • 第76篇 | HarmonyOS 保险箱详情页:私密照片如何浏览、恢复和导出
  • 2026义乌UV双喷服务机构整理推荐 - 奔跑123
  • 通诚无忧-通辽信息港信息平台运营策略:打造用户喜爱的通辽市本地服务社区
  • Kotlin单表达式函数在安卓开发中的精简艺术
  • MySQL(三):库操作与表操作
  • 大理黄金回收2026全流程高价避坑攻略 - 润富黄金回收
  • 自流平材料在现代装修设计中的创新应用及魅力解析
  • Playwright视觉比较(图片比对测试)
  • 伺服电机仿真(7):非线性因素的建模
  • 手把手教你用MATLAB复现圆柱绕流POD分解:从Brunton的代码到自己的流场图
  • 大医精诚·孙思邈
  • /etc/passwd和/etc/shadow区别?用户信息与密码哈希分工详解
  • 2026年实测:各类大赛人气投票链接生成方法,3分钟搞定(免费+强防刷) - 微信投票小程序
  • Linux驱动程序机制
  • AgentWatch MCP 服务说明文档
  • 聚焦脑机接口领域基础研究:国家自然科学基金委与术理创新共同设立民营企业创新发展联合基金(术理创新)
  • 基于 LlamaIndex + DeepSeek + Streamlit 搭建智能问答系统
  • 阳极与阴极浇铸质量检测仪哪家靠谱?上规模生产企业青岛普锐思介绍 - 品牌推荐大师1
  • 高效核销网点系统开发全解析
  • 10kV配网故障识别:波形分析全攻略
  • UVM源码探秘:start_item的sequencer参数怎么用?解锁更灵活的sequence驱动方式
  • 2026最新渭南市黄金回收价格一览表 回收避坑攻略靠谱商家推荐 - 余生黄金回收
  • 镇江丹徒区金价高企,市民闲置黄金变现正当时 - 专业黄金回收
  • 2026年佛山铰链供应商深度横评:全屋定制五金一站式采购避坑指南 - 年度推荐企业名录
  • 人工智能专业术语详解(I)
  • 手上资金少怎么创业?2026零基础低投入创业实操指南
  • Linux基础知识(二)
  • 【国产电脑python编译器配置】麒麟V10系统anaconda配置pycharm
  • 不只是降阶:用POD方法给你的CFD流场做一次‘体检’与‘瘦身’