《架构整洁之道》用最少的人力成本满足构建和维护该系统的需求

本文主要是介绍《架构整洁之道》用最少的人力成本满足构建和维护该系统的需求,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!


目标

用最少的人力成本满足构建和维护该系统的需求

衡量指标

  • 版本迭代——工程师团队规模

  • 版本迭代——代码总行数

  • 版本迭代——代码变更行数

软件系统的价值

行为价值

  • 按需求文档编写代码

  • 可用性

    • 功能性bug

    • 性能

    • 稳定性

  • 紧急,但是并不总是重要,在紧急重要矩阵中占据A、C位置

架构价值

  • Soft: 当需求变更时,所需的软件变更必须简单方便

  • 变更实施的难度应该和变更的范畴(scope)成等比,而与变更的具体形状(shape)无关

  • 不紧急,占据B、D,D的工作就是过度设计,过度设计会提升开发和维护成本

编程范式

目的:设置限制,告诉我们不可以做什么

现有范式

  • 结构化编程

    • 目的:对控制权的直接转移进行了限制和规范

    • 内容:可以用顺序接口、分支结构、循环结构这三种结构构造出任何结构。限制goto的使用

    • 意义:用代码把一些已证明的结构串联起来,就可以推导出整个程序的正确性。实际上没有办法证明每个程序段是正确的,只能证伪,如果所有的基本单原都无法证伪,那么整个就是无法证伪的,那目前就是正确的。

    • 延伸:物理学与数学的区别,物理学的基本公式都是没有办法证明的,只能证伪,所以物理是实验科学,没有一个公式是完全靠得住的,只是目前靠得住。数据的基本公式都是可以证明的。

  • 面向对象编程

    • 封装:只暴露部分函数,数据则完全不暴露

    • 继承

    • 多态:其实只是函数指针的一种应用,通过接口和实现,抽象类和继承,替代了函数指针的使用

    • 目的:对程序控制权的间接转移进行了限制和规范

    • 定义

    • 意义:函数指针,是跨越组件边界的方法,是组件独立部署的基础,依赖反转的基础。依赖反转指的是让依赖与控制流向相反。

  • 函数式编程

    • 现有的应用是什么?区块链吗?

    • 目的:对赋值进行了限制和规范

    • 趋势:如果有足够大的存储量和计算量,应用程序可以用事件溯源的方式,用完全不可变的函数式编程,只通过事务记录,从头计算状态

    • 意义:所有的竞争问题、死锁问题、并发问题都是由可变变量导致的。

    • 应用:通过将状态修改的部分和不需要修改的部分分隔成单独的组件,提高系统的稳定性和效率

设计原则:SOLID

意义

  • 如何将数据和函数组织成类

  • 如何将类链接起来成为组件和程序

内容

  • OCP:开闭原则

    • 目标:让系统易于扩展,同时限制每次修改所影响的范围

    • 实现:划分组件,并将组件间依赖关系按层次结构进行组织

    • 本原则是我们进行架构设计的主导原则

  • SRP:单一职责原则

    • 同样的一块逻辑,如果服务于两个价值主体:因为一个价值主体而修改,那么第二个价值主体期望的功能将被影响。比如CTO和COO都要员工的工时,分别用于计算薪资和汇报,两者的计算方式可能目前是相同的,一方有了更改,另一方就bug了

    • 如果一块代码,归属于两个团队共同维护:就会有代码合并问题

    • 任何一个软件模块,都应该有且只有一个被修改的原因

    • ”被修改的原因“指系统的用户或所有者

    • 目标:指导类、组件拆分【拆分】

    • 定义

    • 痛点

  • LSP:里氏替换原则

    • 不是实现了同一个接口,它们的行为就一致并可以互相替换,长方形正方形是典型的案例

    • 如果两个组件,替换之后需要分别做特别的设置,那就说明抽象的还不足够,会引入许多if-else,可以同配置清单等方式消除

    • 目标:指导接口与实现方式【边界处理】

    • 内容

  • ISP:接口隔离原则

    • 不依赖任何不需要的组件、类、方法

    • 如果不同的用户分别使用一个大接口的几个不同的方法,那么应该把这个大接口拆分为针对这些用户的小接口

    • 目标:指导接口的定义【边界处理】

    • 内容

  • DIP:依赖反转原则

    • 目标:指导依赖方向【依赖】

    • 内容:组建间跨越边界的源码依赖的方向永远与控制流的方向相反

组件

定义:是软件的部署单元,是整个软件系统可以独立完成部署的最小实体

拆分三原则

  • REP:复用、发布等同原则

    • 内容:软件复用的最小粒度应等同于其发布的最小粒度

  • CCP:共同闭包原则

    • 对大部分应用程序而言,可维护性的重要性远远大于可复用性

    • 因为一个原因需要做修改,这个修改最好在同一个组件中,如果分散在多个组件中,那么开发、提交、部署的成本都会上升

    • 内容:将为了相同目的而同时修改的类放在同一个组件中,是SRP原则在组件层面的描述

    • 执行

  • CRP:共同复用原则

    • 内容:不要强迫一个组件依赖它不需要的东西,是ISP原则在组件层面的描述

  • 张力图

    • 架构设计中有许多矛盾,研发性和复用性的矛盾、而研发性本身又有粘性(CCP)和排斥性的矛盾(CRP)

    • 架构师做的往往是在这个张力图中找到一个最符合现在需要的点,而这个平衡也是不断变化的,根据项目的规模、迭代的节奏等

依赖三原则

  • 无依赖环原则

    • 互相依赖的组件,实际上组成了一个大组件,这三个组件要一起发布、一起做单元测试

    • 通过依赖反转原则可以解依赖环

  • 稳定依赖原则

    • 可以通过抽接口,共同依赖接口的方式,修正违反稳定依赖的地方

    • 不稳定性(I) = 出向依赖数量 / (入向依赖数量 + 出向依赖数量)

    • 依赖必须指向更稳定的方向,接口是最稳定的

    • 组件的稳定性,指的是组件的变更困难度,影响因素有很多,比如代码的体量大小、复杂度、清晰度 等,但最最重要的一个因素就是依赖的数量——让组件难于修改的一个最直接的办法就是让很多其他组件依赖于它!

    • 组件的稳定性和它变更的频繁度没有直接的关联。或者说,稳定性可以分为价值(需求)的稳定性,和组件自身的稳定性

    • 内容

    • 定量指标

    • 方法

  • 稳定抽象原则

    • 抽象程度(A) = 组件中抽象类和接口的数量 / 组件中类的数量

    • 将不稳定性和抽象程度分别作为横轴和纵轴,画一个二维的图,(0,1)-(1,0)连线就是主序列线。靠近(0,0)的区域是痛苦区,改动成本很大,但是又很具体。靠近(1,1)的是无用区,非常抽象,但是没有别的组件依赖它,改动成本很小,通常是废弃的。

    • 离主序列先的距离 D = | A + I - 1 |, 可以定量化的衡量一个组件的健康程度。在 D 满足期望的条件下,约靠近(0,1)和(1,0)越好

    • 一个组件的抽象化程度应该与其稳定性保持一致

    • 为了防止高阶架构设计和高阶策略难以修改,通常抽象出稳定的接口或抽象类。越稳定的库就应该越抽象,这样它的稳定性就不会影响它的扩展性

    • 内容

    • 定量描述

软件架构

目的

  • 终极目的:最大化程序员的生产力,最小化系统的总运营成本

  • 细化目的:支撑软件系统的全生命周期,让系统便于理解、易于修改、方便维护、轻松部署

方针

  • 尽可能长时间地保留尽可能多的可选项

    • 具体选用哪个存储方式,或那个数据库

    • 使用哪种web服务

    • 使用哪种框架

    • 擅长于文件的快速查找和整体读取

    • 擅长于内容的查询

    • 数据库

    • 文件

    • 如果硬盘被淘汰时,用什么存储系统差别不大

    • 产品发展,框架不再满足需求

    • 框架本身朝着我们不需要的方向演进

    • 未来我们可能希望迁到一个新的更好的框架上

    • 框架的使用文档是开发者角度写的,他自然吹嘘自己能力,希望你完全耦合他们的框架

    • 风险

    • 选项指的是无关紧要的细节设计

    • 选项例子

  • 边界约完善,开发和部署成本越高。所以不完全边界能解决的,不要用完全边界,低层次解耦能解决的,不要用高层次解耦

内容

  • 组件拆分

    • 源码层次

    • 部署层次

    • 服务层次

    • 从上到下,(开发、部署)成本依次升高,如果低层次的解耦已经满足需要,不要进行高层次的解耦

    • 做了接口、类依赖上的(不完全的)解耦,但是放在同一个组件中,通常放在不同的路径下

    • 仍然运行在同一个机器上,彼此通过函数调用通讯

    • 运行在不同的机器上,通过url、网络数据包等方式进行通讯

    • 服务不等同于模块,比如横跨型变更需要改动所有服务,但是可能并不会改动架构,例子见27章

    • 水平分层

    • 按用例垂直切分

    • 重复

    • UI界面

    • 应用独有的业务逻辑

    • 领域普适的业务逻辑

    • 存储

    • 一条策略距离系统的输入、输出越远,它的层次越高

    • 例子

    • 每个用例几乎涉及到所有的水平分层,如何做到新加用例,不影响旧的用例

    • 比如:订单、聊天

    • 如果两段代码,看起来重复,但是走的是不同的演进路径,就不是真正的重复

    • 切分

    • 解耦模式

    • 组件是一组描述如何将输入转化为输出的策略语句的集合,这些策略的变更原因、时间、层次相同

  • 组件排列(依赖)

    • 业务实体

    • 用例

    • 接口适配器

    • 框架与驱动程序

    • 测试层

    • 包含关键业务数据和业务逻辑

    • 与界面无关、与存储无关、与框架无关,只有业务逻辑,没有别的

    • 不要把业务实体直接当做输入数据对象或者输出数据对象,因为他们会以不同的原因和速率发生变更

    • 需要用户提供的输入数据(注意解耦输入方式,这里只关心数据)

    • 用户应该得到的输出数据(注意解耦输出方式,这里只关心数据)

    • 从输入数据到输出数据,应该采取的处理步骤

    • 特定场景下的业务逻辑

    • 三要素

    • 注意

    • 整个MVC

    • 对存储、设备、界面等的接口声明和使用

    • 因为与硬件太相关的部分,比如用户界面,是不可测的,所以这里的边界处理通常使用谦卑对象模式

    • 谦卑对象要有自知之明,简化到不能再简化,不应该包含对数据的任何处理。数据处理全部放到接口适配器(比如视图模型)中。

    • 测试也是一个组件

    • 测试关键之处是耦合,测试如何依赖所有其他组件的所有接口,那测试就是脆弱的,任何改动都引起n个case失效

    • 解法是给测试层单独写一套特有的API

    • 依赖关系与数据流控制流脱钩,与组件所在层次挂钩。所以组件的依赖是与组件的水平分层息息相关的

    • 分层

  • 组件通信

    • 省掉最后一步

    • 单向边界

    • 门户模式

    • 保留到源码层次的解耦

    • 声明好接口,做好分割后,仍然放在一个组件中。等到时机成熟时再拆出来独立编译部署

    • 正常的切割,应该使用两个接口,两个类各自使用对方的接口,而不是直接使用类,但是这样的开发成本很大,所以,只实现一个接口,高层用接口调用低层,而低层直接使用高层的类

    • 控制权的间接转移不用接口和实现去做,而是用门户类去做,接口都不用声明了。

    • 调用双方都声明接口

    • 专用的输入数据类型

    • 专用的返回数据类型

    • 接口调用

    • 服务调用

    • 方式

    • 完全边界

    • 不完全边界

软件系统的生命周期

  • 开发

    • 不同团队负责的组件不交叉

    • 不使用大量复杂的脚手架

  • 部署

    • 减少组件数量,内部组件外部组件结合的方式

    • 不依赖成堆的脚本和配置文件

  • 运行

    • 这方面的价值对架构的影响最小

    • 不同吞吐量、不同的响应时长要求,是架构设计要考虑的点。采用微服务?单线程?多线程?

    • 架构应起到揭示系统运行的作用:用例、功能、行为设置应该都是对开发者可见的一级实体,以类、函数或模块的形式占据明显位置,命名能清晰地描述对应的功能

  • 维护

    • 做改动时,可能衍生出新的问题

    • 对现有软件系统的挖掘,确定新功能或修复问题的最佳位置和方式

    • 探秘成本

    • 风险成本

封装方式

经典的封装方式

  • 按层封装

    • 无法展现具体的业务领域

    • 不能防止跨层调用。通常情况下,绕过业务逻辑是不合理的,尤其是要控制权限时

    • 问题

  • 按功能封装

  • 端口和适配器(书中强推的方式)

  • 按组件封装

组织形式与封装的区别

  • 如果没有封装和隐藏的功能,采用任何架构风格都没有区别

PD常常不能分清哪些行为价值是重要的,开发人员常常不能分清哪些结构设计时重要的,因此,区分A和C、B和D非常重要

这篇关于《架构整洁之道》用最少的人力成本满足构建和维护该系统的需求的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/705457

相关文章

Spring Cloud:构建分布式系统的利器

引言 在当今的云计算和微服务架构时代,构建高效、可靠的分布式系统成为软件开发的重要任务。Spring Cloud 提供了一套完整的解决方案,帮助开发者快速构建分布式系统中的一些常见模式(例如配置管理、服务发现、断路器等)。本文将探讨 Spring Cloud 的定义、核心组件、应用场景以及未来的发展趋势。 什么是 Spring Cloud Spring Cloud 是一个基于 Spring

通信系统网络架构_2.广域网网络架构

1.概述          通俗来讲,广域网是将分布于相比局域网络更广区域的计算机设备联接起来的网络。广域网由通信子网于资源子网组成。通信子网可以利用公用分组交换网、卫星通信网和无线分组交换网构建,将分布在不同地区的局域网或计算机系统互连起来,实现资源子网的共享。 2.网络组成          广域网属于多级网络,通常由骨干网、分布网、接入网组成。在网络规模较小时,可仅由骨干网和接入网组成

Python应用开发——30天学习Streamlit Python包进行APP的构建(9)

st.area_chart 显示区域图。 这是围绕 st.altair_chart 的语法糖。主要区别在于该命令使用数据自身的列和指数来计算图表的 Altair 规格。因此,在许多 "只需绘制此图 "的情况下,该命令更易于使用,但可定制性较差。 如果 st.area_chart 无法正确猜测数据规格,请尝试使用 st.altair_chart 指定所需的图表。 Function signa

Linux系统稳定性的奥秘:探究其背后的机制与哲学

在计算机操作系统的世界里,Linux以其卓越的稳定性和可靠性著称,成为服务器、嵌入式系统乃至个人电脑用户的首选。那么,是什么造就了Linux如此之高的稳定性呢?本文将深入解析Linux系统稳定性的几个关键因素,揭示其背后的技术哲学与实践。 1. 开源协作的力量Linux是一个开源项目,意味着任何人都可以查看、修改和贡献其源代码。这种开放性吸引了全球成千上万的开发者参与到内核的维护与优化中,形成了

人工和AI大语言模型成本对比 ai语音模型

这里既有AI,又有生活大道理,无数渺小的思考填满了一生。 上一专题搭建了一套GMM-HMM系统,来识别连续0123456789的英文语音。 但若不是仅针对数字,而是所有普通词汇,可能达到十几万个词,解码过程将非常复杂,识别结果组合太多,识别结果不会理想。因此只有声学模型是完全不够的,需要引入语言模型来约束识别结果。让“今天天气很好”的概率高于“今天天汽很好”的概率,得到声学模型概率高,又符合表达

PS系统教程25

介绍软件 BR(bridge) PS 配套软件,方便素材整理、管理素材 作用:起到桥梁作用 注意:PS和BR尽量保持版本一致 下载和安装可通过CSDN社区搜索,有免费安装指导。 安装之后,我们打开照片只需双击照片,就自动在Ps软件中打开。 前提:电脑上有PS软件 三种预览格式 全屏预览 评星级 直接按数字键就可以 方向键可以更换图片 esc退出 幻灯片放

风水研究会官网源码系统-可展示自己的领域内容-商品售卖等

一款用于展示风水行业,周易测算行业,玄学行业的系统,并支持售卖自己的商品。 整洁大气,非常漂亮,前端内容均可通过后台修改。 大致功能: 支持前端内容通过后端自定义支持开启关闭会员功能,会员等级设置支持对接官方支付支持添加商品类支持添加虚拟下载类支持自定义其他类型字段支持生成虚拟激活卡支持采集其他站点文章支持对接收益广告支持文章评论支持积分功能支持推广功能更多功能,搭建完成自行体验吧! 原文

响应式架构

介绍 响应式架构(Reactive Architecture)是一种面向服务和事件的系统设计方法,旨在提高系统的可扩展性、弹性和容错能力。它适用于构建分布式系统,特别是在云环境和微服务架构中。响应式架构的核心理念是通过事件驱动和数据流来实现各个组件之间的解耦,从而提高整个系统的响应能力和可靠性。 响应式架构的主要特点包括: 响应性:系统能够快速响应外部事件和内部变化,确保在各种负载和故障情

Django 路由系统详解

Django 路由系统详解 引言 Django 是一个高级 Python Web 框架,它鼓励快速开发和干净、实用的设计。在 Django 中,路由系统是其核心组件之一,负责将用户的请求映射到相应的视图函数或类。本文将深入探讨 Django 的路由系统,包括其工作原理、配置方式以及高级功能。 目录 路由基础URL 映射路由参数命名空间URL 反向解析路由分发include 路由路由修饰符自

基于Spring Boot构建淘客返利平台

基于Spring Boot构建淘客返利平台 大家好,我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编,也是冬天不穿秋裤,天冷也要风度的程序猿!今天我们将讨论如何基于Spring Boot构建一个淘客返利平台。 淘客返利平台通过整合各种电商平台的商品信息,提供给用户查询和返利功能,从而实现流量变现。以下是实现一个简单的淘客返利平台的步骤。 1. 项目初始化 首先,使用Spri