设计模式学习笔记 - 设计原则 - 6.KISS原则和YAGNI原则
前言
今天,将两个设计原则:KISS 原则和 YANGI 原则。其中,KISS 原则比较经典,耳熟能详,但 YANGI 你可能没怎么听过,不过它理解起来也不难。
理解这个两个原则的时候,经常会有一个共同的问题,那就是看一眼就觉得懂了,但深究的话,又有很多细节不是很清楚。
- 怎么理解 KISS 原则中的 “简单” ?
- 什么代码才算 “简单”?怎样的代码才算 “复杂”?
- 如何才能写出 “简单” 的代码?
- YANGI 原则和 KISS 预原则说的是一回事吗?
理解 KISS 原则
KISS 原则的意思是尽量保持简单。它的英文描述有好几个版本:
- keep it Simple and Stupid。
- keep it Short and Simple。
- keep it Simple and Straightforward。
我们知道,代码的可读性和可维护性是衡量代码质量非常重要的两个标准。而 KISS 原则就是保持代码可读性和可维护性的重要手段。代码足够简单,也意味着很容易读懂,bug 难以隐藏。即使出现 bug,修复起来也比较简单。
不过这条原则只是高速我们,要保持代码简单,但是并没有给出特别明确的方法论,来指导如何开发出 “简单” 的代码。所以看着简单,但是不能落地。
接下来,为了能让这条原则落地,来进一步进行讲解。
代码行数越少就越“简单”吗?
我们先来看一个例子。下面的三段代码实现同一个功能:检查输入字符串 ipAddress 是否是合法的 IP 地址。
合法的 IP 地址由四个数字组成,并通过 “
.
” 进行分割。每组数字的取值范围是 0~255。第一组数字比较特殊,不能为 0。
对比下面的代码,你觉得哪段代码最符合 KISS 原则呢?如果让你实现,你选用哪种实现方式?
// 第一种实现方式:使用正则表达式
public boolean isValidIpAddress(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 isValidIpAddress2(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 isValidIpAddress3(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 ch = ipChars[i];if (ch == '.') {if (ipUnitIntValue < 0 || ipUnitIntValue > 255) { return false; }if (isFirstUnit && ipUnitIntValue == 0) { return false; }if (isFirstUnit) { isFirstUnit = false; }ipUnitIntValue = -1;unitsCount++;continue;}if (ch < '0' || ch > '9') { return false; }if (ipUnitIntValue == -1) { ipUnitIntValue = 0; }ipUnitIntValue = ipUnitIntValue * 10 + ch - '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表示模式串,
public static int[] getNexts(char[] b, int m) {int[] next = new int[m];next[0] = -1;int k = -1;for (int i = 0; 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;
}
这段代码完全符合我们提到的逻辑复杂、实现难度大、可读性差的特点,但它不违反 KISS 原则。为什么这么说呢?
KMP 算法以快速高效著称。当我们需要处理长文本字符串匹配问题(几百 MB 大小文本内容的匹配),或者字符串是某个产品的核心功能,又或者字符串匹配算法是系统性能瓶颈的时候,我们就应该选择尽可能搞下的 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 包缺失而频繁地修改 Maven 或者 Gradle 配置文件,提前往项目里引入大量常用的 library 包。实际上,这样的做法是违背 YAGNI 原则的。
从刚刚的分析可以看出,YAGNI 原则和 KISS 原则并非一回事。KISS 原则讲的是“如何做”的问题(尽量保持简单),而 YAGNI 原则说的是 “要不要做的问题”(当前不需要的就不要做)。
总结
KISS 原则是保持代码可读性和可维护性的重要手段。KISS 原则中的 “简单” 并不是以代码行数来考量的。代码行数越少并不代表代码越简单,还需要考虑代码的复杂度、实现难度、代码的可读性等。而且,本身就复杂的问题,用复杂的方法解决,并不违背 KISS 原则。除此之外,同样的代码,在某个业务场景下满足 KISS 原则,换一个场景就可能不满足了。
对于 KISS 原则,还总结了下面几条原则:
- 不要使用同事可能不懂的技术来实现代码
- 不要重复造轮子,要善于使用已有的工具类库
- 不要过度优化
相关文章:

设计模式学习笔记 - 设计原则 - 6.KISS原则和YAGNI原则
前言 今天,将两个设计原则:KISS 原则和 YANGI 原则。其中,KISS 原则比较经典,耳熟能详,但 YANGI 你可能没怎么听过,不过它理解起来也不难。 理解这个两个原则的时候,经常会有一个共同的问题&a…...

【Vue3-vite】动态导入路由
route文件结构 router moduleindex.ts 路由定义 // 需要导入的路由如下: const routes [{path: /manage,name: manage,component: () > import(/views/home/index.vue),children: manageRoutes,}]index.ts实现从module中自动导入 // 动态导入 const routeFil…...

C++——string类
前言:哈喽小伙伴们,从这篇文章开始我们将进行若干个C中的重要的类容器的学习。本篇文章将讲解第一个类容器——string。 目录 一.什么是string类 二.string类常见接口 1.string类对象的常见构造 2.string类对象的容量操作 3. string类对象的访问及遍…...

进制转换md5绕过 [安洵杯 2019]easy_web1
打开题目 在查看url的时候得到了一串类似编码的东西,源码那里也是一堆base64,但是转换成图片就是网页上我们看见的那个表情包 ?imgTXpVek5UTTFNbVUzTURabE5qYz0&cmd 我们可以先试把前面的img那串解码了 解码的时候发现长度不够,那我们…...

.kat6.l6st6r勒索病毒的最新威胁:如何恢复您的数据?
导言: 在当今数字化时代,数据安全变得至关重要。然而,随着网络威胁不断增加,勒索病毒已成为企业和个人面临的严重威胁之一。其中,.kat6.l6st6r勒索病毒是最新的变种之一,它能够加密您的数据文件࿰…...

Day 6.有名信号量(信号灯)、网络的相关概念和发端
有名信号量 1.创建: semget int semget(key_t key, int nsems, int semflg); 功能:创建一组信号量 参数:key:IPC对像的名字 nsems:信号量的数量 semflg:IPC_CREAT 返回值:成功返回信号量ID…...

MySQL 常用优化方式
MySQL 常用优化方式 sql 书写顺序与执行顺序SQL设计优化使用索引避免索引失效分析慢查询合理使用子查询和临时表列相关使用 日常SQL优化场景limit语句隐式类型转换嵌套子查询混合排序查询重写 sql 书写顺序与执行顺序 (7) SELECT (8) DISTINCT <select_list> (1) FROM &…...

算法刷题day22:双指针
目录 引言概念一、牛的学术圈I二、最长连续不重复序列三、数组元素的目标和四、判断子序列五、日志统计六、统计子矩阵 引言 关于这个双指针算法,主要是用来处理枚举子区间的事,时间复杂度从 O ( N 2 ) O(N^2) O(N2) 降为 O ( N ) O(N) O(N) …...

山人求道篇:八、模型的偏差与交易认知
原文引用https://mp.weixin.qq.com/s/xvxatVseHK62U7aUXS1B4g “ CTA策略一波亏完全年,除了交易执行错误导致的以外,这类策略都是多因子策略,一般会用机器学习组合多因子得出一个信号来进行交易。规则型策略几乎不会出现一波做反亏完全年的情况。这是有以下几个原因的: 多…...

MySQL 元数据锁及问题排查(Metadata Locks MDL)
"元数据"是用来描述数据对象定义的,而元数据锁(Metadata Lock MDL)即是加在这些定义上。通常我们认为非锁定一致性读(简单select)是不加锁的,这个是基于表内数据层面,其依然会对表的元…...

JS中的函数
1、函数形参的默认值 JavaScript函数有一个特别的地方,无论在函数定义中声明了多少形参,都可以传入任意数量的参数,也可以在定义函数时添加针对参数数量的处理逻辑,当已定义的形参无对应的传入参数时,为其指定一个默认…...

微信小程序开发常用的布局
在微信小程序开发中,常用的布局主要包括以下几种: Flex 布局:Flex 布局是一种弹性盒子布局,通过设置容器的属性来实现灵活的布局方式。它可以在水平或垂直方向上对子元素进行对齐、排列和分布。Flex 布局非常适用于创建响应式布局…...

Effective C++ 学习笔记 条款10 令operator=返回一个reference to *this
关于赋值,有趣的是你可以把它们写成连锁形式: int x, y, z; x y z 15; // 赋值连锁形式同样有趣的是,赋值采用右结合律,所以上述连锁赋值被解析为: x (y (z 15));这里15先被赋值给z,然后其结果&…...

算法简单试题
一、选择题 01.一个算法应该是( B ). A.程序 B.问题求解步骤的描述 C.要满足五个基本特性 D.A和C 02.某算法的时间复杂度为O(n),则表示该…...

CSS 自测题 -- 用 flex 布局绘制骰子(一、二、三【含斜三点】、四、五、六点)
一点 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name"viewport" content"widthdevice-width, initial-scale1.0" /><title>css flex布局-画骰子</title><sty…...

蓝桥集训之牛的学术圈 I
蓝桥集训之牛的学术圈 I 核心思想:二分 确定指数x后 判断当前c[i]是否>x(满足条件) 并记录次数同时记录 1后满足条件的个数最后取bns和m的最小值 为满足条件的元素个数ansbns为当前指数x下 满足条件的元素个数 #include <iostream>#include <cstring…...

软件设计师软考题目解析21 --每日五题
想说的话:要准备软考了。0.0,其实我是不想考的,但是吧,由于本人已经学完所有知识了,只是被学校的课程给锁在那里了,不然早找工作去了。寻思着反正也无聊,就考个证玩玩。 本人github地址…...

python读写json文件详解
在Python中,可以使用json模块来读写JSON格式的文件。下面是一个详细的示例,演示了如何读写JSON文件: import json# 写入JSON文件 data {"name": "John","age": 30,"city": "New York" }…...

#include<ros/ros.h>头文件报错
快捷键 ctrl shift B 调用编译,选择:catkin_make:build)(要先在vscode上添加扩展:ros) 可以点击配置设置为默认,修改.vscode/tasks.json 文件 修改.vscode/tasks.json 文件,否则ros.h头文件会报错 内容修改为以下内…...

mybatis单表curd笔记(尚硅谷
Mybatis 11111ibatis和mybatis不同 查询文档mybatis的日志输出id赋值输入(向sql语句传入数据单个简单类型单个实体对象多个简单类型map类型 输出数据的指定单个简单类型单个实体类型输出map类型输出list输出类型主键回显(自增长类型主键回显(…...

在线重定义-操作步骤
第一步:验证表是否能被在线重定义 验证是否能按主键重定义(默认,最后一次参数可以不加) 1 2 3 4 begin --dbms_redefinition.can_redef_table(scott,tb_cablecheck_equipment_bak); dbms_redefinition.can_redef_table(scot…...

16:00面试,16:06就出来了,问的问题过于变态了。。。
从小厂出来,没想到在另一家公司又寄了。 到这家公司开始上班,加班是每天必不可少的,看在钱给的比较多的份上,就不太计较了。没想到2月一纸通知,所有人不准加班,加班费不仅没有了,薪资还要降40%…...

基于dashscope在线调用千问大模型
前言 dashscope是阿里云大模型服务平台——灵积提供的在线API组件。基于它,无需本地加载大模型,通过在线方式访问云端大模型来完成对话。 申请API key 老规矩:要想访问各家云端大模型,需要先申请API key。 对于阿里云&#x…...

【Python】可变数据类型 不可变数据类型 || hash
🚩 WRITE IN FRONT 🚩 🔎 介绍:"謓泽"正在路上朝着"攻城狮"方向"前进四" 🔎🏅 荣誉:2021|2022年度博客之星物联网与嵌入式开发TOP5|TOP4、2021|2222年获评…...

MySQL 篇-深入了解多表设计、多表查询
🔥博客主页: 【小扳_-CSDN博客】 ❤感谢大家点赞👍收藏⭐评论✍ 文章目录 1.0 多表设计概述 1.1 多表设计 - 一对多 1.2 多表设计 - 一对一 1.3 多表设计 - 多对多 2.0 多表查询概述 2.1 多表查询 - 内连接 2.2 多表查询 - 外连接 2.3 多表查…...

【Java】Spring的ReflectionUtils类常用方法学习笔记
目录 ReflectionUtils介绍 常用方法 访问字段 方法调用 处理回调 示例 脑容量不够了,以简单的小知识作为一天的结尾吧(悲 ReflectionUtils介绍 ReflectionUtils是Spring Framework中非常实用的一个工具类,为开发人员提供了简便的反射操作方法&am…...

内存函数详解
1. memcpy函数 void * memcpy ( void * destination, const void * source, size_t num ); 1.1 函数的功能,使用与注意事项 1. memcpy函数的作用是内存拷贝,即将source指向的空间中的num个字节拷贝到destination指向的空间中去,然后返回de…...

事务(transaction)
事务,什么是事务,事务就是由单独单元的一个或多个sql语句组成,在这个单元中,每个sql语句都是相互依赖的。而整个单独单元是作为一个不可分割的整体存在,类似于物理当中的原子(一种不可分割的最小单位&#…...

Linux之cd、pwd、mkdir 命令
cd命令,切换目录 1)当Linux终端(命令行)打开的时候,会默认以用户的HOME目录作为当前的工作目录。 2)我们可以通过cd命令,更改当前所在的工作目录。 3)cd命令来自英文:C…...

【python高级编程教程】笔记(python教程、python进阶)第三节:(1)多态与鸭子类型(Polymorphism and Duck Typing)
参考文章1:【比刷剧还爽】清华大佬耗时128小时讲完的Python高级教程!全套200集!学不会退出IT界! 参考文章2:清华教授大力打造的Python高级核心技术!整整100集,强烈建议学习(Python3…...