读架构整洁之道的一些感悟

做产品开发时,我们经常跌落在一种无法打破的轮回中。

我们经常说,产品上线最重要,可以未来再重构代码, 但是结果大家都知道,产品上线以后重构工作就再没人提起了。首先,线上跑的好好的,动出问题怎么办?其次,后面有无数的新需求不断涌来,工程师应对这些新功能已经捉襟见肘,还要花很大一部分精力对现有系统的修修补补。所以, 重构的时候永远不会再有了。我们拆了东墙补西墙,循环往复,劳心劳神,互相扯皮,提桶跑路。

是什么原因导致这样的?

1. 程序员的实际工作

大部分程序员认为将需求文档转为实际的代码就是他们的全部工作,上面的领导者也只是关注你的需求是否得到正常实现,没有bug,而不会关注你的代码写的如此优雅。我亲身经历一个需求,当我说要额外花1周时间重构一下代码,新增一些自动化测试用例,被TL拒绝了,他认为BVT跑过了已经具备了上线条件就不要折腾了,提前上线吧,此后我再也没有写过自动化测试用例,手验几个就转测了。

我们所有人在不停的往前冲,即使是屎山堆屎,即使是自己实现的代码好比硬要把方螺丝拧到圆螺丝孔里面,究其原因无非是业务部门原本就是没有能力评估系统架构的重要程度的。

展开之前,我们先看看我们编程有哪些范式,也就是我们一直以来遵循的一种公认的编程模型或模式,然后从这种模型中探讨如何设计出优雅的组件,以及如何优雅地将多个组件聚合成一个成熟的架构。

2. 编程的三种模式

从编程发明开始至今,写代码的编程范式只有三种,从1958年提出的函数式编程,到1966年提出的面向对象编程,再到1968年提出的结构化编程。尽管我们的工具变了,硬件变了,但是软件编程的核心思想没有变,也就是约束我们的范式没有变,即计算机程序无一例外的由顺序结构,分支结构,循环机构和间接转移这几种行为组合而形成的,无可增加,无一例外。

2.1 结构化编程

结构化编程主要的价值是将一段程序递归降解为一系列可证明的小函数,然后再编写相关的测试来证明这些函数是否是错误的,如果这些测试用例无法证伪,那么我们就可以认为这些函数是足够正确的,进而推导出整个程序是正确的,这个解决方案是大名鼎鼎的结构程序设计之父Dijkstra提出来的,熟悉算法的朋友们应该对这个名字不陌生的,最短路径也是这位大神提出的。

2.2 面向对象编程

面向对象编程的主要价值是以多态为手段来对源代码中的依赖关系进行控制,这种能力能让我们可以构建出某种插件式架构,让高层策略组件和底层实现组件分离,底层组件可以被编译成插件,实现独立于高级组件的开发和部署,这就是我们常说的面向接口编程,即依赖倒转模式。

举个栗子,对于某种系统行为决定了控制流的软件架构,main函数调用函数A,函数A的具体实现又调用了B,这种情况下每个函数方必须要引用被调用方所在的模块,但有了多态就不一样了,我们抽象出一个中间的接口层<I>,函数A去直接依赖这个接口<I>,最底层的函数B去实现这个接口<I>,这样A和B就都可以单独部署,互不影响:

2.3 函数式编程

这个函数式编程不是我们熟知的那种将小功能封装成一个函数,而是一种编程理念,即变量被赋值后是不可变的。比如说for循环语句的循环变量i,它就是可变的,而函数式编程中,函数的值取决于函数的参数的值,不依赖于其他状态,比如abs(x)函数计算x的绝对值,只要x不变,无论何时调用、调用次数,最终的值都是一样。

其实,变量的可变性会导致很多问题,比如说多线程、多处理器下的死锁问题,资源竞争问题等等,如果没有可变变量的话这些都不可能发生了,不需要考虑加锁,编程更加简单,程序跑的飞起。

既然有了这些范式,那么如何构建出一个优雅的组件呢?发布一个组件应该遵循哪些原则呢?下面将会介绍Bob大叔(架构整洁之道就是Bob大叔写的)提出的SOLID原则。

3. 构建出优雅的组件

3.1 单一组件构建原则

什么是一个优雅的组件,大家应该都有自己的一套准则,但不外乎要满足下面几点:

  • 使软件可容忍被改动
  • 使软件更容易被理解
  • 构建可在多个软件系统中复用的组件

SOLID原则其实是5个设计准则的首字母组合而成,分别是SRP单一职责,OCP开闭原则,LSP里氏替换,ISP接口隔离原则,DIP依赖反转,我来一一介绍一哈。

1.SRP单一职责:每个模块都应该只做一件事,确保每个函数只完成一个功能,这样就一个好处,就是避免了不同人为了不同的目的修改了 一份源代码,这很容易造成新问题,比如说一个组件会被两个业务团队修改,导致业务逻辑错误和代码合并的问题。

2.ISP接口隔离原则:在一般情况下,任何层次的软件设计如果依赖于不需要的东西,都会是有害的,从源代码层次来说,这样的依赖关系会导致不必要的重新编译和重新部署,对更高层次的软件架构设计来说问题是类似的。

3.LSP里氏替换:使用子类和父类时,两者完全可以互相替换。也就是说,任何基类可以出现的地方,子类一定可以出现,我们不会感知这个是子类还是父类。有一个臭名昭著的例子,就是正方形类派生于长方形类,并且新增了一个设置边长的函数:

这个反例就说明了Square类与Rectangle无法替换,因为Rectangle类的高宽可以分别修改, Square则必须一同修改,由于 User始终认为自己在操作Rectangle会带来一些混淆,唯一办法就是在User 类中增加用于区分Rectangle和Square的检测逻辑,那这就会引入硬编码,更加灾难。

4.DIP依赖反转:高层次的代码应该多引用低层次的抽象接口,而不是具体实现,依赖反转有一个非常好的应用就是抽象工厂模式:本来是application使用service,那么依赖关系是application->service,现在变成了application -> 抽象service工厂<- 具体的service工厂 -> 具体的service,控制流发生了反转。

5.OCP开闭原则:增加新功能不应该改老功能的逻辑,一般可以用两个步骤进行实施,我们可以先将不同需求的代码分组(SRP) ,然后再来调整这些分组之间的依赖关系(DIP)。

3.2 组件发布原则

1.REP复用发布等同原则:软件复用的最小粒度应等同于其发布的最小粒度,也就是说如果想要复用某个软件组件的话,一般就必须要求组件的开发由某种发布流程来驱动,并且有明确的发布版本号和发布文档。这样一来,软件工程师才能在收到相关组件新版本发布的通知之后,依据该发布所变更的内容来决定是继续使用旧版本还是做些相应的升级。

2.CCP共同闭包原则:提示我们要将所有可能会被一起修改的类集中在一处,也就是说,如果两个类紧密相关,不管是源代码层面还是抽象理念层面,永远都会一起被修改,那么它们就应该被归属为同一个组件,通过遵守这个原则,我们就可以有效地降低因软件发布、验证及部署所带来的工作压力。

3.CRP共同复用原则:当我们决定要依赖某个组件,最好是实际需要依赖该组件中的每个类。换句话说,我们希望组件中的所有类是不能拆分的,即不应该出现别人只需要依赖它的某几个类而不需要其他类的情况。否则,后续就会浪费不少时间与精力来做不必要的组件部署。

总结一哈,我们发布的组件,一定是最小粒度,每一个可能被修改的类都应该放在一块,每一个容易修改的类也要放在一块,这样会降低发布和验证的压力,并且用户可以根据发布的版本号和文档选择是否需要升级。

我们现在有了各个组件,那么如何将多个组件如何构成系统?

4.系统构建原则

4.1 ADP无依赖环原则

组件之间依赖关系没有环形,如下组件依赖关系就是有环的,这会非常难受:

当Database组件的程序员需要发布新版本时,他们需要与 Entities 组件进行集成,但现在由于出现了循环依赖, Database组件就必须也要与 Authorizer 组件兼容,而 Authorizer组件又依赖于Interactors组件,这样一来, Database 组件的发布就会变得非常困难。

这还只是问题的冰山 角,请想象一下我们在测试 Entities 组件时会发生什么?情况会让人触目惊心,我们会发现自己必须将Authrizer,Interactors集成到一起测试, 这些组件之间的耦合度非常令人不安。

4.2 稳定依赖原则

依赖关系必须要指向更稳定的方向,任何一个我们预期会经常变更的组件都不应该被一个难于修改的组件所依赖,否则这个 变的组件也将会变得非常难以被修改。如何量化一个组件的稳定性呢?可以采用入度出度的比值,I= Fan-out /(Fan-in + Fan out),其中Fan-in为被依赖个数,Fan-out为依赖其他组件的个数。

该指标的范围是 [0,1], I=0意味着组件是最稳定的,I=1意味着组件是最不稳定。

如果一个系统中的所有组件都处于最高稳定性状态,那么系统就一定无法再进行变更了,这显然不是我们想要的,事实上,我们设计组件架构图的目的就是要决定应该让哪些组件稳定,让哪些组件不稳定。

4.3 解耦+决策延迟

良好的软件架构不是一开始就确定,比如要采用的框架,数据库,工具库,我们尽量将这些决策延后进行,也就是说最开始我们只做组件+用例+部署源码的解耦,但不强制规定各个组件之间的交互方式,该系统就可以随时根据不断变化的运行需求来转换成各种运行时的线程、进程或服务模型,简言蔽之,随着系统在开发、部署、运行各方面所面临的问题持续增加,我们应该挑选一下可以将哪些可部署单元转化为服务,并且逐渐将系统向这个方向转变,而随着时间的流逝,系统的运维需求可能又会降低。之前需要进行服务层次解耦的系统可能现在只要进行部署层次或源码层次的解耦就够了。这也就是我们常说的,拖着拖着,事就不用干了(hhhh)

4.4 可测试架构

一个好的架构是可测试的,系统架构的所有设计都应该围绕用例来展开,我们在运行测试的时候不应该运行Web服务,也不应该需要连接数据库,测试的应该只是一个简单的业务实体对象,没有任何与框架、数据库相关的依赖关系,总而言之,应该通过用例对象来调度业务实体对象,确保所有的测试都不需要依赖框架。

谦卑对象模式的设计目的是帮助单元测试的编写者区分容易测试的行为与难以测试的行为,并将它们隔离,其设计思路非常简单,就是将这两类行为拆分成两组模块或类,其中一组模块被称为谦卑组,包含了系统中所有难以测试的行为,而这些行为已经被简化到不能再简化了,另一组模块则包含了所有不属于谦卑对象的行为,例如, GUI 通常是很难进行单元测试的,因为让计算机自行检视屏内容,并检查指定元素是否出现是非常难的事情(奇林平台可以做这个事)。 然而, GUI中的大部分行为实际上是很容易被测试的,这时候,我们可以利用谦卑对象模式将 GUI 的这两种行为拆分成展示器与视图两部分。

最后以Kent Beck关于软件构建的建议作为结尾,他描述了软件构建过程中的三个阶段:

  1. “先让代码工作起来”一一如果代码不能工作作,就不能产生价值
  2. “然后再试图将它变好”一一通过对代码进行重构, 我们自己和其他人更好地理解代码,并能按照需求不断地修改代码
  3. “最后再试着让它运行得更快”一一按照性能提升的“需求”来重构代码

Reference

https://book.douban.com/subject/30333919/

编程范式(一):结构化编程 - 掘金

沧沧凉凉:函数式编程-入门篇章

SenLin:里氏替换原则(LSP)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/436536.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

动态规划(算法竞赛、蓝桥杯)--树形DP树的中心

1、B站视频链接&#xff1a;E34 树形DP 树的中心_哔哩哔哩_bilibili #include <bits/stdc.h> using namespace std; const int N20010; int n,a,b,c,ans2e9; struct edge{int v,w;}; vector<edge> e[N]; int d1[N],d2[N],path[N],up[N];//path记录d1 void dfs1(in…

【Vue】sessionStorage存取数据

一. 需求 1.1 模板 Vab Admin Pro 1.2 组件 ElementUI 1.3 阐述 列表页面搜索【关键词】点击【查询】后&#xff0c;点击【查看】按钮跳转到【详情页】&#xff0c;详情页【返回】【保留原搜索关键词】 原图 搜索查询【关键词】 详情 返回后【保留】【搜索查询关键词…

潜水耳机哪个牌子好?认准这几个游泳耳机品牌就对了!

在科技日益发达的今天&#xff0c;人们对于运动设备的需求也在不断提升。作为一项独特的水上运动&#xff0c;潜水爱好者们对耳机的要求也越来越高。一款优秀的潜水耳机不仅能够提供卓越的防水性能和舒适度&#xff0c;还必须具备出色的音质。那么&#xff0c;在众多品牌中&…

2024宠物行业未来发展趋势:京东宠物健康(宠物营养保健和医疗)市场品类数据分析报告

近段时间&#xff0c;广州某知名宠物医院的医疗事故正在被大众热议&#xff0c;也让越来越多从业者开始关心宠物医疗行业的未来形势。 在2022年下半年&#xff0c;京东平台专门设立了一个一级大类目&#xff1a;宠物健康&#xff08;将其从原本的宠物生活类目中独立出来&#…

【C++】c++入门之递归上 数值类

文章目录 前言一、 递归1.1 基本概念1.2 递归的过程1.3 使用场景 二、例题讲解问题一&#xff1a;1002 - 编程求解123...n问题二&#xff1a;1241 - 角谷猜想问题三&#xff1a;1108 - 正整数N转换成一个二进制数问题四&#xff1a;1088 - 求两个数M和N的最大公约数 三、练习问…

Chrome禁止自动升级

一、关闭计划任务 1、首先我们需要右键点击我的电脑&#xff0c;在打开的选项里选择管理。   2、在打开的对话框中选择任务计划程序。   3、在任务计划程序库中找到两个和chrome自动更新相关的任务计划GoogleUpdateTaskMachineCore与GoogleUpdateTaskMachineUA。     4…

onlyOffice-windows 安装说明(二)

onlyoffice windows 安装 onlyoffice 支持多个平台比如&#xff1a;Windows Server、Linux、Docker 以下内容是对官网安装说明做了简单翻译&#xff0c;仅供参考&#xff0c;原文链接地址参见文末。 社区版允许您在本地服务器上安装ONLYOFFICE文档&#xff0c;并将在线编辑器…

【李沐精读系列】BERT精读

论文&#xff1a;BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding 参考&#xff1a;BERT论文逐段精读、李沐精读系列、李宏毅版BERT讲解 一、介绍 BERT(Bidirectional EncoderRepresentation Transformer&#xff0c;双向Transformer编码器…

JAVA 用二分法查找数组中是否存在某个值

二分法查找的概念 二分查找也称折半查找&#xff08;Binary Search&#xff09;&#xff0c;它是一种效率较高的查找方法。首先&#xff0c;将表中间位置记录的关键字与查找关键字比较&#xff0c;如果两者相等&#xff0c;则查找成功&#xff1b;否则利用中间位置记录将表分成…

pinia报错does not provide an export named ‘hasInjectionContext

你们好&#xff0c;我是金金金。 场景 我这里是uniappvue3编写的一个小程序项目&#xff0c;在集成pinia过程当中遇到此问题&#xff0c;报错请求的模块 未提供 导出名hasInjectionContext&#xff08;位于 pinia.mjs:6:10&#xff09; 以下我项目当中vue和pinia的具体依赖版本…

selenium等待机制

selenium等待机制 影响元素加载的外部因素1.计算机的性能2.服务器的性能3.浏览器的性能4.网络因素 强制等待1.强制等待2.页面加载超时机制 隐性等待显性等待1.WebDriverWait类2.WebDriverWait类提供的方法untileuntile_not显性等待的语法格式 3.expected_conditions模块方法exp…

「Mybatis深入三」:高级查询-模糊查询

一、需求 根据username 模糊查询user 表 二、代码演示 1、方式1 数据库环境 CREATE DATABASE mybatis_db; USE mybatis_db; CREATE TABLE user (id INT(11) NOT NULL AUTO_INCREMENT,username VARCHAR(32) NOT NULL COMMENT 用户名称,birthday DATETIME DEFAULT NULL COMMEN…

Java开发从入门到精通(一):Java的基础语法进阶

Java大数据开发和安全开发 &#xff08;一&#xff09;Java注释符1.1 单行注释 //1.2 多行注释 /* */1.3 文档注释 /** */1.4 各种注释区别1.5 注释的特点1.5 注释的快捷键 &#xff08;二&#xff09;Java的字面量&#xff08;三&#xff09;Java的变量3.1 认识变量3.2 为什么…

【宏观经济】全国各地级市及上市公司“信息惠民国家试点”DID(2010-2024)

数据说明&#xff1a;信息惠民国家试点城市是&#xff0c;2014年6月23日&#xff0c;根据国家发改委网站发布的通知&#xff0c;国家发展改革委等12部门决定的&#xff0c;将全国80个城市列为信息惠民国家试点城市。推进信息惠民国家试点城市建设&#xff0c;有利于加快提升公共…

vue+Nodejs+Koa搭建前后端系统(九)-- 上传图片

web2.0的到来使网页世界正式进入了寒武纪&#xff0c;各式各样的多媒体资源屡见不鲜&#xff0c;上传资源变得刻不容缓&#xff01; 前言 本文是在该系列的基础上&#xff0c;针对前后端代码的修改。 准备 HTTP上传图片时Content-Type值常见的有2种&#xff1a;application…

Django模型层(附带test环境)

Django模型层(附带test环境) 目录 Django模型层(附带test环境)开启测试环境数据的增加数据的删除修改数据查询数据查询所有数据去重查询排序查询统计剔除指定数据多表查询校验数据是否存在字段的筛选查询 开启测试环境 首先在app下找到tests.py文件并进入 MyDJ.settings要换成…

【QA-SYSTEMS】CANTATA-解决Jenkins中build Cantata报错

【更多软件使用问题请点击亿道电子官方网站查询】 1、 文档目标 解决Jenkins中build Cantata测试项目报找不到license server的错误。 2、 问题场景 在Jenkins中build Cantata测试项目&#xff0c;报错“Failed to figure out the license server correctly”。 3、软硬件环…

虚拟化相关面试题集锦(0)—— 引言

经常关注博主的朋友应该能够发现&#xff0c;我近期开始在虚拟化尤其是QEMU/KVM上下功夫。这是由于我个人非常看好这个方向&#xff0c;把它当作今后的学习和工作的战略目标&#xff0c;同时也是个人非常喜欢和感兴趣的课题。 笔者看好虚拟化的原因是当前云计算已经如日中天&a…

短视频矩阵系统----矩阵系统源码搭建(技术门槛?)

短视频矩阵是什么意思&#xff1f;短视频矩阵的含义可以理解为全方位的短视频账号&#xff0c;通过不同的账号实现全方位的品牌展示。实际上是指一个短视频账号&#xff0c;通过不同的链接实现品牌展示&#xff0c;在不同的粉丝流量账号中互相转发同一个品牌&#xff0c;在主账…

05 | 深入浅出索引(下)

在上一篇文章中&#xff0c;我和你介绍了 InnoDB 索引的数据结构模型&#xff0c;今天我们再继续聊聊跟 MySQL 索引有关的概念。 在开始这篇文章之前&#xff0c;我们先来看一下这个问题&#xff1a; 在下面这个表 T 中&#xff0c;如果我执行 select * from T where k betwe…