软件工程之架构设计

 从公众号转载,关注微信公众号掌握更多技术动态

---------------------------------------------------------------

一、架构设计的目的

1.什么是复杂的软件项目

复杂的软件项目通常有两个特点:

  • 需求不确定

  • 技术复杂

技术的复杂性主要体现在四个方面:

  • 需求让技术变复杂:软件要能不断响应新的需求

  • 人员让技术变复杂:团队成员水平不一,擅长的技术方向也不一样

  • 技术本身复杂:技术本身的使用门槛较高

  • 保证软件稳定运行是复杂的:运行时的不确定性

2.软件复杂性

(1)复杂性的表现形式

①症状1-变更放大

变更放大(Change amplification)指得是看似简单的变更需要在许多不同地方进行代码修改。比较典型的代表是Ctrl-CV式代码开发,领域模型缺少内聚与收拢,当需要对某段业务进行调整时,需要改动多个模块以适应业务的发展。

②症状2-认知负荷

认知负荷(Cognitive load)是指开发人员需要多少知识才能完成一项任务。使用功能性框架时,我们希望它操作简单,部署复杂系统时,我们希望它架构清晰,其实都是降低一项任务所需的成本。盲目的追求高端技术,设计复杂系统,增加学习与理解成本都属于本末倒置的一种。

③症状3-未知的未知

未知的未知(Unknown unknowns)是指必须修改哪些代码才能完成任务,或者说开发人员必须获得哪些信息才能成功地执行任务。这一项也是John Ousterhout教授认为复杂性中最糟糕的一个表现形式。

当你维护一个有20年历史的项目时,这种问题的出来相对而言就没那么意外。由于代码的混乱与文档的缺失,导致你无法掌控一个500万行代码的应用,并且代码本身也没有明显表现出它们应该要阐述的内容。这时“未知的未知”出现了,你不知道改动的这行代码是否能让程序正常运转,也不知道这行代码的改动是否又会引发新的问题。这时候我们发现,那些“上帝类”真的就只有上帝能拯救了。

(2) 永远追求最优雅

    ​业务简单的系统不应用DDD架构,弱交互场景也无需进行前后端分离。不要盲从一些教条的观念,选择适合自己的,控制在可控制范围内,既不过度也不缺失。毕竟没有绝对的优雅,甚至没有绝对的正确。

3.架构设计如何解决“复杂”

    ​因为技术的复杂性,会导致软件开发变得很复杂,开发成本高。而架构设计恰恰可以在这些方面很好地解决技术复杂的问题。

主要从四个方面来:

  • 架构设计可以降低满足需求和需求变化的开发成本:通过对系统抽象和分解,把复杂系统拆分成若干简单的;对需求的变化,已经有一些成熟的架构实践。

  • 架构可以帮助组织人员一起高效协作:通过抽象再拆分,可以把复杂的系统拆分成开发人员可以各自独立完成的模块。

  • 架构设计可以帮助组织好各种技术:如分层架构

  • 架构设计可以保障服务稳定运行:如分布式架构、异地多活等

  • 业务与技术的隔离。以业务为核心,分离业务复杂度和技术复杂度。

  • 内部系统与外部依赖的隔离

  • 系统中常变部分与不常变部分的隔离

  • 隔离复杂性(把复杂性的部分隔离在一个模块,尽量不与其他模块互动)

4.什么是架构设计

    ​架构设计的方法都是基于工程领域分而治之的策略,本质上就是将系统分拆,将人员分拆。但是光拆还不够,拆完了还能拼回来,所以咬清楚架构设计的“道”。

    ​架构设计的道,就是组织人员和技术把系统和团队拆分,并安排好切分后的排列关系,让拆分后的部分能通过约定好的协议互相通信,共同实现最终的结果。

5.如何做好架构设计

    ​业界已经有了很多成熟的架构设计模式,不需要闭门造车,可以在理解清楚业务需求后,找到相近的架构设计,然后基于成熟的架构设计方案,进行改造,变成适合自己业务需求的架构。可以按以下步骤进行。

先模型,再接口,最后 是实现

(1)分析需求

需要对产品需求进一步进行抽象。一个常用的分析方法就是分析用例,也就是了解主要用户校色和其使用的场景。

(2)选择相似的成熟的架构设计方案

在了解清楚需求后,就可以从业界成熟的架构设计模式中选取一个或几个。具体选择 哪些架构设计模式,需要根据平时的学习积累来做判断。

在选好架构方案后,还需要考虑选择什么语言和开发框架。这部分选择需要根据团队情况和项目情况来综合评定。

(3)自顶向下层层细化

从整体到局部,不要过早陷入技术细节中。

①部署架构

②分层和分模块

③模块之间的交互关系

    ​比较常见的系统之间的交互方式有两种,一种是同步接口调用,另一种是利用消息中间件异 步调用。第一种方式简单直接,第二种方式的解耦效果更好。

    ​比如,用户下订单成功之后,订单系统推送一条消息到消息中间件,营销系统订阅订单成功 消息,触发执行相应的积分兑换逻辑。这样订单系统就跟营销系统完全解耦,订单系统不需 要知道任何跟积分相关的逻辑,而营销系统也不需要直接跟订单系统交互。

    ​除此之外,上下层系统之间的调用倾向于通过同步接口,同层之间的调用倾向于异步消息调 用。比如,营销系统和积分系统是上下层关系,它们之间就比较推荐使用同步接口调用。

④API设计、数据库设计、模块设计(业务逻辑)

数据库和接口的设计非常重要,一旦设计好并投入使用之后,这两部分都不能轻易改动。改动数据库表结构,需要涉及数据的迁移和适配;改动接口,需要推动接口的使用者作相应的 代码修改。这两种情况,即便是微小的改动,执行起来都会非常麻烦。因此,在设计接 口和数据库的时候,一定要多花点心思和时间,切不可过于随意。相反,业务逻辑代码侧重 内部实现,不涉及被外部依赖的接口,也不包含持久化的数据,所以对改动的容忍性更大。

(4)验证和优化架构设计方案

二、设计文档

1.概要设计

    ​在概要设计阶段,一般以子系统为维度来阐述系统各个角色之间的关系。对于关键的子 系统,还会进一步分解它,甚至详细到把该子系统的所有模块的职责和接口都确定下 来。

    ​这个阶段的核心意图并不是确定系统完整的模块列表,焦点是整个系统如何被有 效地串联起来。如果某个子系统不做进一步的分解也不会在项目上有什么风险,那么并 不需要在这个阶段对其细化。

    ​为了降低风险,概要设计阶段也应该有代码产出。 这样做的好处是,一上来我们就关注了全局系统性风险的消除,并且给了每个子系统或模块 的负责人一个更具象且确定性的认知。 代码即文档。代码是理解一致性更强的文档。 经过系统的概要设计,整个系统的概貌就了然于胸了。

2.详细设计

    ​详细设计阶段,是需要各个子系统或 模块的负责人,对他负责的部分进行进一步的细化。详细设计关注的是子系统或模块的全貌。概要设计不一定会把子系统或模块的完整接口都列出来, 实际上它只关注最核心的部分。但是从详细设计角度来说,接口描述的完备性是必需的。 详细设计并不是只谈实现就完事,更不是一个架构图。它包括以下这些内容。

(1)现状与需求

    ​现在在哪里,遇到了什么问题,要做何改进。从逻辑自洽的角度,任何一篇文档,首先关注的都应该是要解决的问题与目标。 现状与需求的陈述,要简明扼要。 现状更多的是陈述与我们要做的改变相关的重要事实,侧 重点在于强调这些事实的存在性和重要性。 假设要对某个模块重构。那么,现状就是要谈清楚现在的业务架构是怎样的?它 到底有什么样的问题。

    ​需求陈述是对痛点和改进方向的一次共识确认。痛点只要够痛,大家都知道,所以同样不需 要长篇累牍。 每个子系统或模块,都有自己的角色分工与用户故事。不用重新做一遍需求分析,但对 需求分析的核心结论,在详细设计开始之前需要明确。 这很重要。它是我们详细设计所要满足的业务目标。

(2)需求满足方式

    ​要做成啥样?交付物的规格,或者说使用界面(接口)。规格,或者说使用界面,体现的是别人要怎么使用。 使用界面(接口)应该自然体现业务需求,就是强调程序是为用户需 求服务的。而架构设计,在需求分析与后续的概要设计、详细设计等过程之间也要有 自然的延续性。

    ​使用界面这一部分要详细写,它是团队共识确认的关键。 我们的交付物有哪些可执行文件,有哪些包(package)?如果可执行文件,那么它是一个 界面程序,还是服务?如果是服务,网络协议是什么样的?如果是包,它又包含哪些公开的 类或函数。

​    ​更需要强调的是,使用界面的稳定是至关重要的。对使用界面的不兼容调整,可能出现严重的后果。技术上,可能会导致客户异常,出现编译

失败需要重写代码,或者更严重的是,可能导致他们的系统崩溃。商业上,则可能导致大量 的客户流失。接口的变更需谨慎!

没有页面写接口得变更

(3)程序 = 数据结构 + 算法

①数据结构

    ​数据结构从大的层面分,可分为基于内存的数据结构,和基于外存(比如 SSD 盘)的数据 结构。在服务端我们谈数据结构,谈的不是内存数据结构,往往谈的是数据库的表结构设计。

    ​不管我们用的是哪种数据库,出于惯例我们往往还是以 “定义表结 构” 一词来表达想干什么。其实定义表结构和定义内存数据结构本质是完全一致的。定义内存中的一个类 (或结构体),我们也关心字段名(成员变量名)和类型,也关心字段的含义,以及它是否 指向另一个类(或结构体)的某个字段(成员变量)。

②算法​

    ​在架构过程中,需求分析阶段,我们关注用户需求的精确表述,会引入 角色,也就是系统的各类参与方,以及角色间的交互方式,也就是用户故 事。 到了详细设计阶段,角色和用户故事就变成了子系统、模块、类或者函数的 使用界面(接口)。使用界面(接口)应该自然体现 业务需求,就是强调程序是为用户需求服务的。而我们的架构设计,在需求 分析与后续的概要设计、详细设计等过程之间也有自然的延续性。 所以算法,最直白的含义,指的是用户故事背后的实现机制。

那么,怎么描述一个用户故事对应的算法?

  • 基于 UML 时序图(Sequence Diagram)。

  • 基于伪代码(Pseudo Code)。在逻辑较为复杂时,伪代码往往有更好的呈

  • 现效果。

三、如何做好技术选型

1.项目决策

(1)问题定义

问题定义阶段需要明确两个问题:

  • 为什么需要技术选型

  • 技术选型目标是什么

只有明确了技术选型的目标,才有一个标准来评判该选择哪一个方案。

(2)调研

在明确技术选型的目标后,需要进行调研看有哪些技术选型可以满足目标,可以从这几个方面去分析:

  • 是否满足技术选型目标

  • 是否满足时间、范围和成本的约束

  • 是否可行

  • 有什么样的风险?是否可控

  • 优缺点是什么

(3)验证

可以通过一个快速原型项目,用候选技术方案快速做一个原型出来,做的过程中才知道,所做的技术选型是否真的满足技术选型的目标。

(4)决策

在调研和验证完成后,需要召集所有利益相关人一起,就选择的方案做一个调研结果评审的会议,做出最终的决策。

2.架构思维

    ​架构设计是要控制技术的复杂性。对于架构师来说,要控制技术复杂性,有几种有效的方式:抽象、分治、复用和迭代。架构师思维其实就是这几种思维的集合。

  • 抽象思维:对需求进行抽象建模后,可以帮助我们隐藏很多无关紧要的细节,在高层次的架构设计时,可以关注在几个主要的模型上,而不必关心模型内的细节实现。

  • 分治思维:架构设计的一个重点,就是要对复杂系统分而治之。

  • 复用思维:通过对相同内容的抽象,让其能复用于不同的场景,是一种非常简单的提升开发效率的方法。

  • 迭代思维:好的架构通常不是一步到位,而是先满足好当前业务需求,然后随着业务的变化而逐步演进。

3.架构选型这注意点

(1)产品选型要服从于项目整体目标

局部最优的选择拼装在一起未必是全局最优的方案。如果你的目标是要对整个应用系统做彻底重构,例如把单体架构改为微服务架构,那么要解决原来某些局部的问题,可能会有 更多选择。这时候要从整体上评估技术复杂度、工程实施等因素,而不是仅选择局部最合 理的方案。

(2)先进的产品可能会延长项目交付时间

最先进的产品不一定是完美的选择。尤其是有进度要求时,往往会选择更稳妥、快速的办 法。但是,这本质上是在短期利益和长期利益之间做权衡,没有绝对的对错,搞清楚你想要的是什么就行。

(3)当产品选型可能导致业务流程变更时,请慎重对待

对任何项目来说,协作范围的扩大一定会增加实施难度。当技术部门对业务流程变更没有决定权时,我认为这是多数情况,通过技术手段避免这种变更往往是更好的选择。

(4)评估技术潮流对选型影响

跟随潮流并不是人云亦云,你必须能够独立对技术发展趋势做出研判。太过小众的技术往 往不能与工程化要求兼容。但同时,保持对新技术的敏感度和掌控力,也是非常必要的。

四、技术债务

1.什么是技术债务

范围不减、成本不加,还想节约时间,就会影响到质量。技术债务就是软件项目中对架构质量和代码质量的透支。

技术债务具有以下特点:

  • 有利息:后期对软件做修改的时候,需要额外的时间成本。

  • 不一定都是坏的:如快速原型模型,就是通过技术债务的方式快速开发快速验证。验证不可行,则无需偿还债务。

2.如何管理

技术债务有利息也有收益,如何管理才能保证软件项目中的收益大于支付的利息。

(1)识别债务

软件项目中有很多指标来发现存在的技术债务:

  • 开发速度降低

  • 单元测试覆盖率低

  • 代码规范检查的错误率高

  • Bug数量越来越多

(2)处理技术债务策略

在识别之后,解决技术债务有三种策略:

  • 重写:推翻重来,一次还清

  • 维持:修修补补,只还利息。适用于不需要增加新功能的系统

  • 重构:新旧交替,分期付款

(3)实施策略

  • ​重写-正式项目来立项

  • 重构-将任务拆分并进行跟踪

  • 维持-制定计划

(4)预防

最好的方法是预防技术债务的产生:

  • 预先投资:好的架构,高质量的代码是一种技术投资

  • 不走捷径:做好代码审查、保障单元测试代码覆盖率等

  • 及时还债:记下欠的债务,及时还债。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Ubuntu防止休眠和挂起(笔记)

目录 1 动机2 禁用休眠3 解除休眠 1 动机 我要将 饿啊人制作成 noah-mp 的区域运行强迫,但是跑的慢,一晚上两天。后来发现是因为电脑自动 supend 了。Ubuntu 在电源那里最多只能设置 2 小时的防止休眠,2小时候又自动休眠,严重影响…

Python中的global关键字

Python中的global关键字 Python是一种简单而强大的编程语言,提供了许多功能和语法来帮助开发人员编写高效的代码。其中一个常用的功能是使用global关键字来在函数内部访问和修改全局变量。 在本文中,我们将深入探讨Python中global关键字的用法&#xf…

服务器端口该怎么维护_Maizyun

服务器端口该怎么维护 在互联网环境中,服务器端口是网络通信的重要通道。每个端口对应着特定的服务或应用程序。然而,不正确的端口配置或缺乏适当的维护可能导致安全漏洞和性能问题。因此,维护服务器的端口安全和稳定性至关重要。本文将探讨…

导入JSON到xmind

写在前面 这只是一个思路&#xff0c;解决大量树状数据&#xff0c;创建xmind低效问题。 函数可以根据你的实际情况优化 1. 转换json格式 function formatToXimd(atd, str) {if (atd) {for (let index 0; index < atd.length; index) {console.log(str - atd[index].…

RK3288升级WebView版本,替换webview app

升级高版本的WebView Apk&#xff1b;客户端播放网页&#xff0c;显示不正常&#xff1b;安装高版本的谷歌浏览器手动打开网页&#xff0c;显示正常&#xff1b;Android5.1开始&#xff0c;WebView具体实现从框架层剥离出来&#xff0c;通过一个包名来控制加载真正的WebView实现…

论在武汉考一个安全员B证能有多难?谁曾想

论在武汉考一个安全员B证能有多难&#xff1f;谁曾想 在武汉考一个安全员B证的考试难度&#xff0c;硬考&#xff08;自己练习系统题库备考&#xff09;还是有一定难度的。毕竟考试范围比较宽泛&#xff0c;里边涉及日常工作内容、法规知识、建筑管理知识等等。即使你考过了二…

CVE-2023-3710:Honeywell Products远程命令执行漏洞复现 [附POC]

文章目录 Honeywell Products远程命令执行(CVE-2023-3710)漏洞复现 [附POC]0x01 前言0x02 漏洞描述0x03 影响版本0x04 漏洞环境0x05 漏洞复现1.访问漏洞环境2.构造POC3.复现0x06 修复建议Honeywell Products远程命令执行(CVE-2023-3710)漏洞复现 [附POC] 0x01 前言 免责声明:…

AOP记录操作日志

创建数据库表 -- 操作日志 create table operate_log (id int unsigned primary key auto_increment commentid,operate_user int unsigned comment 操作人员Id,operate_time datetime comment 操作时间,class_name varchar(100)comment 操作类,method_name varchar(100)comme…

C语言——I /深入理解指针(五)

一、sizeof 和 strlen 的对比 1、sizeof sizeof是操作符&#xff0c;计算变量所占内存内存空间⼤⼩的&#xff0c;单位是字节&#xff0c;如果操作数是类型的话&#xff0c;计算的是使⽤类型创建的变量所占内存空间的⼤⼩。 sizeof 只关注占⽤内存空间的⼤⼩&#xff0c;不在…

鸿蒙4.0开发笔记之ArkTS语法基础的UI描述、基础组件的使用与如何查看组件是否有参数(八)

文章目录 一、声明式UI描述1、无/有参数组件2、如何查看组件是否有参数 二、Image组件的使用三、组件的属性设置四、补充1、使用组件的成员函数配置组件的事件方法2、配置子组件3、多组件嵌套 一、声明式UI描述 在HarmonyOS的ArkTS语法中&#xff0c;万物皆组件。ArkTS以声明方…

【Verilog】 FPGA程序设计---Verilog基础知识

目录 Verilog 和 VHDL 区别 Verilog 和 C 的区别 Verilog 基础知识 1 Verilog 的逻辑值 2 Verilog 的标识符 3 Verilog 的数字进制格式 4 Verilog 的数据类型 1) 寄存器类型 2) 线网类型 3) 参数类型 5 Verilog 的运算符 1) 算术运算符 2) 关系运算…

数据结构之选择排序

目录 直接选择排序 选择排序的时间复杂度 堆排序 向上调整算法 向下调整算法 向上调整算法建立堆 向下调整算法建立堆 堆排序整体代码 堆排序的时间复杂度 直接选择排序 在之前讲插入排序时&#xff0c;我们讲了这样的一个应用场景&#xff0c;我们在斗地主摸牌时&…

Lean语言学习笔记

Lean是什么&#xff1f; Lean 是一门可作为交互式定理证明工具的函数式编程语言。 创建Lean项目 可以使用 lake 来创建一个新的 Lean 项目&#xff1a; mkdir lean-playground && cd lean-playground lake init foo之后会得到下面的目录结构&#xff1a; ├── F…

国产智能运维操作系统新选择-浪潮KeyarchOS

1.背景 在CentOS停更&#xff0c;国有企业纷纷摒弃原有的开发与运维工具&#xff0c;全面拥抱国产。我司也顺应号召&#xff0c;更换原有CentOS系统。 在新系统选型上&#xff0c;我司有以下要求&#xff1a; 国产、快速更新迭代、社区活跃&#xff1b;拥有一定知名度&#x…

Kotlin 中的 var 和 val:选择正确的变量声明

在 Kotlin 编程语言中&#xff0c;var 和 val 是两个基本的关键字&#xff0c;用于变量声明。 它们的正确使用对于编写可维护和高效的代码至关重要。 一、对比分析&#xff1a; var &#xff1a;用于声明可变变量。使用 var 声明的变量可以在初始化后被重新赋值。val &#…

【Collection - PriorityQueue源码解析】

本文主要对Collection - PriorityQueue进行源码解析。 Collection - PriorityQueue源码解析 概述方法剖析 add()和offer()element()和peek()remove()和poll()remove(Object o) 概述 前面以Java ArrayDeque为例讲解了Stack和Queue&#xff0c;其实还有一种特殊的队列叫做Priori…

保障海外业务发展,Coremail提供高效安全的海外通邮服务

11月22日&#xff0c;Coremail举办《全球通邮&#xff1a;如何保障安全、快捷的海外中继服务》直播分享会&#xff0c;直播会上Coremail安全团队和直播嘉宾复旦大学校园信息化办公室徐艺扬老师就海外中继服务进行了深度分享。 ​ 海外通邮困难重重 境外垃圾邮件数量居高不下…

echarts中option个参数的含义

var option {title: {text: ECharts 入门示例},tooltip: {},legend: {data: [数量]},xAxis: {data: [衬衫, 羊毛衫, 雪纺衫, 裤子, 高跟鞋, 袜子]},yAxis: {},series: [{name: 数量,type: bar,data: [5, 20, 36, 10, 10, 20]}] }; title&#xff1a;主要控制图表的标题 legen…

语义分割 LR-ASPP网络学习笔记 (附代码)

论文地址&#xff1a;https://arxiv.org/abs/1905.02244 代码地址&#xff1a;https://github.com/WZMIAOMIAO/deep-learning-for-image-processing/tree/master/pytorch_segmentation/lraspp 1.是什么&#xff1f; LR-ASPP是一个轻量级语义分割网络&#xff0c;它是在Mobil…

使用Ansible Expect模块实现自动化交互式任务

Ansible是一种功能强大的自动化工具&#xff0c;可用于自动化配置管理、部署和任务执行。其中的Expect模块是Ansible的一个重要组件&#xff0c;它允许我们自动化处理需要与交互式命令行进行交互的任务。本文将介绍如何使用Ansible的Expect模块&#xff0c;并提供一些示例来说明…