理解软件设计的基本原则

待兔 等级 324 0 0

任何软件唯一不变的真理是变化,毕竟软件是"软"的。软件研发需要快速响应市场、需求的变化。

为了快速响应,我们可以通过增加人手来达到部分目的,但软件开发属于知识密集型工作,当人数增加到一定数量后,不仅不能够提升研发效能。反而增加管理成本,沟通成本及由于人与人沟通、理解上产生的歧义而最终造成软件实现的混乱和复杂度。

所以软件本身需要能够轻易的扩展,适应各种需求变化,即代码也要拥抱变化。

但做到这一点是非常困难的,毕竟当前软件都要复杂的领域知识,业务场景,不是几个简单的CRUD就能编写完成的应用。

降低软件成本,提高研发效能

减少软件成本最简单的理解就是投人少、产出快(老板喜欢)。我们先来看下简化了的软件成本的组成:

 软件成本 = 研发阶段人力成本 * 开发时间 + 维护阶段人力成本 * 开发时间 

一个软件的生命周期所花费的成本不仅仅是开发上线就结束了(当然开发的软件只为外包销售一次,而不使用的除外),所以我们减少成本,缩短周期不仅仅要考虑研发要快,也要考虑后续的维护(理解和变更)也要快。

如同打台球,每一杆出球都要考虑走位,使后面的击球同样简单,而最终赢得胜利。

复用

为了达到投人少,产出快的目的,复用就成了软件行业追求的目标。

通过复用而减少人力资源投入,快速发布软件。复用在一定程度上也确实取得了很多的进展和效果。

但是很遗憾复用本身也存在理解的歧义。举几个曾经工作中遇到的真实的例子:

1.曾经见到过一个表字段存储了多种概念的值,甚至是不同类型的值,如”123” 和“abc”,调用端判断数字和字符串分别执行不同逻辑。

2.一个Product对象,存储了汽车信息,和奶粉信息,调用端判断是汽车还是奶粉

3.一个微服务支持多租户,多租户的行为完全不同,根据租户的type在整个微服务中进行if else判断。

上述不同抽象层次出现的问题,我问当时的开发,给出的理由都是复用,复用字段,复用对象,复用公共微服务。

这种”复用“大大增加了系统复杂度,增加了系统的维护成本。

另外一味的追求复用会增加软件研发阶段的成本、复杂度或过度设计,而有时简洁,直接,够用就可以大大降低研发成本。

软件设计原则

既然复用不是追求的目标,那如何保证软件的可扩展,快速响应变化呢?

Code Review标准中所述的那样,

软件设计的各个方面几乎从来都不是纯粹的风格问题或个人偏好。它们是建立在基本原则基础上的,应该以这些原则为依据,而不是简单地以个人观点为依据。

同样我们只要遵循软件开发的基本原则,就能大大降低整个软件生命周期,包括研发阶段及维护阶段,需要投入的人力和时间。

所以在这里介绍一部分相关的基本原则和个人对这部分原则的理解。

DRP-不要重复你自己(Don't Repeat Yourself)

对于代码来说,重复是万恶之源。当一段代码在代码基里有多份copy时,针对于这部分代码的逻辑变更,就会修改多次,即代码坏味道中的散弹式修改。

首先工作量对应增加重复次数的倍数,但更大的问题在于遗漏了修改而造成bug。

而重复又细分为完全重复和结构性重复。完全重复不需要解释,以下两个方法即存在结构性重复:

 public List<Apple> findApple(List<Apple> appleRepo,String color) {

        List<Apple> result = new ArrayList<>();

        for(Apple apple : appleRepo){

            if(apple.getColor().equals(color)){

                result.add(apple);

            }

        }

        return result;

    }

}

public List<Apple> findApple(List<Apple> appleRepo,int weight) {

        List<Apple> result = new ArrayList<>();

        for(Apple apple : appleRepo){

            if(apple.getWeight()>=weight){

                result.add(apple);

            }

        }

        return result;

    } 

copy paste是造成重复的主要原因之一,所以一定慎用或不用copy paste。

KISS原则(Keep it simple and stupid)&& YAGNI(You Aren’t Gonna Need It)

 The KISS principle states that most systems work best if they are kept simple rather than made complex; therefore simplicity should be a key goal in design and unnecessary complexity should be avoided 

Always implement things when you actually need them, never when you just foresee that you need them.

Decide as late as possible: Delaying decisions as much as possible until they can be made based on facts and not on uncertain assumptions and predictions.


此处两个原则都是为了防止过度设计。过度设计本身比不设计还要糟糕。因为每增加一层抽象,代码复杂度就上升一个高度,后续的维护(理解,变更)都会相应复杂。这也是敏捷迭代、重构和演进式设计被人们推崇的原因之一。

最少知原则(Least Knowledge)
----------------------

只需要给定当前够用的知识即可。如果开车同时要了解发动机在多少度燃烧、车门的漆料的化学组成等等这些知识,那么汽车也就不会如此普及和提升我们的生活质量了。

很多时候违反最少知原则的原因是以防万一以后要用,就先都做了。

方法出参、入参极其臃肿,出入参存在冗长的火车残骸式调用,导致方法内部细节被强依赖,不敢轻易变更;

消息体信息过于全面,导致维护消息Producer知晓具体哪些信息是被使用了是件很困难的工作,同时造成Smart Consumer,即消费端强依赖消息体的逻辑,需要随消息体的变更而变更。

所以,够用就好,以后再说以后的(通过重构来满足后续的需求),也许就没有以后了。

COC-约定优于配置(Convention Over Configuration)
-----------------------------------------

约定好协议,大家按协议执行。优于具体执行阶段去查询相关条例。

红灯停,绿灯行,大家都遵守。如果上海黄灯停,蓝灯行;北京绿灯停,紫灯行,所有人出门都要带着各地不同的交通灯规则说明书。那大家就都不敢去外地开车了。

maven为什么能流行起来,其中主要原因就是maven约定java目录结构优于ant的自定义配置目录结构。

所以大家在增加配置项时,思考下可否通过约定而撤销该配置。

SOLID原则
-------

### SRP-单一职责(Single Responsibility Principle)

A class or entity should have one and only one reason to change.


一个类、模块、微服务应该只有一个职责,引起其变更的原因应该只有一个。

理解好单一职责,我们首先需要理解什么是职责?是不是只要一个类只有一个方法如CreateOrderAction,CheckOrderExistAction,一个模块只有一种类型,如Service类型,Dao类型,一个微服务只操作一张表,就满足单一职责了呢?

很多这样设计的作者之所以这样设计的依据就是号称满足单一职责。这是对单一职责的一种误解。如果运用到类设计上,会造成类爆炸;如果运用到微服务上,后果就是很多不具备内聚性的微服务产生,从而造成复杂度上升,维护数据一致性困难。

单一职责的关注点是高内聚,即引起变更的原因只有一个,一个订单的创建与校验一般是在一个变化维度,一个模块的Controller,Service,Repository,Domian Entity一般也会同时变更。而一个微服务也应该完成一个完整的原子性业务操作。

单一职责是**封装**的理论指导。从类的角度来看,数据与行为高内聚,即方法尽可能使用直接依赖的属性(包括属性和入参);从模块的角度来看,应用服务层、领域层、基础设施层(Repository,Component等)要高内聚在一个模块下(可以理解为同在一个java包中);从微服务的角度来看,服务应该高度自治,完成独立的业务操作。

### OCP-半开半闭(Open Closed Principle)

Software components should be open for extension, but closed for modification.


半开半闭对修改是关闭的,对扩展是开放的。所谓修改和扩展这里都是指新增特性,当原来的程序有bug时,你是无法不修改原来的代码的。

但在新增特性时,我们要设计成用新增代码来满足新功能,拒绝更改原有代码满足新功能。

想想这样做的好处吧。理论上讲只要代码没有变动,就不需要测试。所以如果你的设计满足OCP,你永远不用担心你的代码会对原系统造成什么破坏性影响。测试的也不需要大量的回归原有功能了。

### LSP-里氏替换(Liskov Substitution Principle)

Let q(x) be a property provable about objects of x of type T. Then q(y) should be provable for objects y of type S where S is a subtype of T.

`````` Functions that use pointers to base classes must be able to use objects of derived classes without knowing it.


白话解释,基类(父类、抽象类或接口)可以被子类替换而客户端无需更改,即子类与基类是"IS-A"的关系。狗是一种基类抽象,泰迪,哈士奇是满足IS-A的派生类,但是玩具狗就不是IS-A的派生,不应该使用**继承**。

同时,当做出一种抽象时,所有的实现类都可以替换基类引用而不会造成编译错误(行为是不一样的,此处只是为了验证LSP)。

LSP是**多态**的基础,而多态是面向对象的核心,通过多态我们可以灵活的扩展。

### ISP-接口隔离(Interface Segregation Principle)

Clients should not be forced to implement unnecessary methods which they will not use.


客户端不应该去实现他们不需要的接口,即同时满足最少知原则。

让你的客户使用简单,傻瓜式操作,同时你可以获得最大灵活的控制权。因为软件总是再变化,需求也总是再变化,当你需要用户依赖你很多不需要的接口,首先对方使用很不方便,偌大的接口,对方需要有使用,学习的成本。

其次,我们失去了灵活的控制权。一旦你将接口暴露出去,即使对方不需要,当你面临接口变更时,你无法确定对客户的影响,造成维护成本变高。

### DIP-依赖倒置(Dependency Inversion Principle)

Depend on abstractions, not on concretions

```

客户端依赖于抽象,实现端也依赖于抽象。通过抽象进行解耦,客户端与实现端都可以独立变化而不受影响。即所谓的面向接口编程。

总结

当然软件设计的原则与模式还有很多,这里只是介绍了几种个人认为面向对象编程比较重要的原则。由于个人能力有限,有可能存在一些错误的理解,欢迎大家留言更正。

收藏
评论区

相关推荐

python的requests模块的使用
前言: 在web后台开发过程中,会遇到需要向第三方发送http请求的场景,python中的requests库可以很好的满足这一要求,这里简要记录一下requests模块的使用! 说明: 这里主要记录一下requests模块的如下几点: 1.requests模块的安装 2.requests模块发送get请求 3.requests模块
HTTP 的本质?HTTP 和 RPC 的区别?
身为 Java Web 开发我发现很多人一些 Web 基础问题都答不上来。 上周我面试了一个三年经验的小伙子,一开始我问他 HTTP/1、HTTP/2相关的他到是能答点东西出来。 后来我问他:你怎么理解 HTTP 的,HTTP 的作用是什么? 他支支吾吾答不出来。 经过了一番引导交谈,他回答是用来客户端和服务端之间传输的。 我接着问那你知道什么是
一个免费的开源的html转markdown语法的工具
一个免费的开源的html转markdown语法的工具 大家好,我是待兔,今天为大家分享一个由 www.helloworld.net 网站开发并开源的一个非常好用的工具 html2md 现在好的技术文章确实多,每天各种技术群里,各种技术社区,有很多质量非常好的技术文章,于是我们就收藏了,可是问题来了,我们收藏到哪呢? 怎么收藏呢? 1. 微信群里发的文
helloworld.net 的总结以及2021年的期待
没有反思的人生不值得过!由时不时向外张望,彻底转向向内审视的一年。 2020年,公历闰年,共366天,52周零2天。二十一世纪二十年代的第1年。 大家好,我是待兔, helloworld.net也就是 helloworld开发者社区的创始人之一,由于前几天感冒了,你知道的,这个时间感冒是有点麻烦的,所以导致这篇文章来的稍晚了点,好饭不怕晚,晚点写有晚点写
JS - ES6 的 Module
一、介绍 模块,(Module),是能够单独命名并独立地完成一定功能的程序语句的集合(即程序代码和数据结构的集合体)。 两个基本的特征:外部特征和内部特征 外部特征是指模块跟外部环境联系的接口(即其他模块或程序调用该模块的方式,包括有输入输出参数、引用的全局变量)和模块的功能 内部特征是指模块的内部环境具有的特点(即该模
python中的split()函数的用法
函数:split() Python中有split()和os.path.split()两个函数,具体作用如下: split():拆分字符串。通过指定分隔符对字符串进行切片,并返回分割后的字符串列表(list) os.path.split():按照路径将文件名和路径分割开 一、函数说明 1、split()函数 语法:str.split(str
阿里二面,面试官居然把 TCP 三次握手问的这么细致
TCP 的三次握手和四次挥手,可以说是老生常谈的经典问题了,通常也作为各大公司常见的面试考题,具有一定的水平区分度。看似是简单的面试问题,如果你的回答不符合面试官期待的水准,有可能就直接凉凉了。本文会围绕,三次握手和四次挥手相关的一些列核心问题,分享如何更准确的回答和应对常见的面试问题,以后面对再刁钻的面试官,你都可以随意地跟他扯皮了。 面试TCP的意义
Java练习(三)——返回集合中的最大的和最小的元素
题目:在一个列表中存储以下元素:apple,grape,banana,pear,现要求将集合进行排序,返回集合中的最大的和最小的元素,并将排序后的结果打印在控制台上,要求的打印输出方法分别为默认toString输出、迭代器输出、for循环遍历输出和增强for循环输出。 package test;import java.util.;public class P
人工智能数学基础-线性代数1:向量的定义及向量加减法
一、向量 1.1、向量定义向量也称为欧几里得向量、几何向量、矢量,指具有大小(magnitude)和方向的量。它可以形象化地表示为带箭头的线段。箭头所指:代表向量的方向;线段长度:代表向量的大小。与向量对应的量叫做数量(物理学中称标量),数量(或标量)只有大小,没有方向。1. 在物理学和工程学中,几何向量更常被称为矢量。 2. 一般印刷用黑体的小写
人工智能数学基础-线性代数4:矩阵及矩阵运算
一、矩阵定义矩阵(Matrix)是一个按照长方阵列排列的复数或实数集合,定义如下: 由 m × n 个数aij排成的m行n列的数表称为m行n列的矩阵,简称m × n矩阵。记作: 这m×n 个数称为矩阵A的元素,简称为元,数aij位于矩阵A的第i行第j列,称为矩阵A的(i,j)元,以数 aij为(i,j)元的矩阵可记为(aij)或(aij)m × n,m×
凉凉!面试阿里我被Redis技术专题给搞的昏倒在地~
凉凉!面试阿里我被Redis技术专题给弄死了📚我本以为我可以像是别的博主一样去阿里面试随随便便,因为Redis,我直接被阿里大佬淦翻在地上好了不装了 没过没关系 我总结了一些这些最难的知识点!!!!然后自己总结归类再去百度查询了一些 最终得出这份Redis技术专题 题目开淦 Redis集群的主从复制模型是怎样的?为了是在部分节点失败或者大部分节点无法通信的情
python的学习难?你的方法不对罢了,看看我的。
1、选择Python版本对于使用python的人来说,python的版本就是我们的工作环境,因此,在学习之前需要考虑好学习哪个版本,python2和python3的版本不同,会存在一些差异,虽说不大,但直接学习python3 的话相对来说会好一点,而且跑一趟还能3相对来说对零基础的小白来说更加的友好,容易上手。2、学习Python基础知识Python 是一个
c++类和继承面试点25连问
本篇文章连问面试时经常会遇到的类和继承相关25个问题,看看你能回答出几道题呀。还是先看一下思维导图,如下: 1. c++的三大特性是什么c++的三大特性,说白了其实就是面向对象的三大特性,是指:封装、继承、多态,简单说明如下: 封装是一种技术,它使类的定义和实现分离,也就是隐藏了实现细节,只留下接口给他人调用,另外封装还有一层意义是它把某种事物具现出属性和方
浅析常用的Python Web的几大框架
在各种语言平台中,python涌现的web框架恐怕是最多的,是一个百花齐放的世界,各种microframework、framework不可胜数;猜想原因应该是在python中构造框架十分简单,使得轮子不断被发明。所 以在Python社区总有关于Python框架孰优孰劣的话题。下面就给大家介绍一下python的几大框架: Django Django 应该是最出
JAVA回调机制(CallBack)之小红是怎样买到房子的??
JAVA回调机制CallBack 序言最近学习java,接触到了回调机制(CallBack)。初识时感觉比较混乱,而且在网上搜索到的相关的讲解,要么一言带过,要么说的比较单纯的像是给CallBack做了一个定义。当然了,我在理解了回调之后,再去看网上的各种讲解,确实没什么问题。但是,对于初学的我来说,缺了一个循序渐进的过程。此处,将我对回调机制的个人理解,按