设计原则
这里主要说 SOLID 以外的原则,想了解 SOLID 原则可自行去网上查找。
组合/聚合复用原则
- 当要扩展类的功能时,优先考虑使用组合,而不是继承
- 该原则在 23 种经典设计模式中频繁使用
- 如:代理模式、装饰模式、适配器模式等
无环依赖原则
- 当 A 模块依赖于 B 模块,B 模块依赖于 C 模块,C 依赖于 A 模块,此时将出现循环依赖。
- 在设计中避免该问题,可通过引入“中介者模式”解决。
共同封装原则
- 应该将易变的类放在同一个包里,将变化隔离出来。
- 该原则是“开放-封闭原则”的诞生。
共同重用原则
- 如果重用了包中的一个类,那么也就相当于重用了包中的所有类,我们要尽可能减少包的大小。
好莱坞原则
- Don't call me, I'll call you.
- “控制反转”(或称为“依赖注入”)
- 不需要主动创建对象,而是由容器帮我们来创建并管理这些对象。
不要重复你自己
- 不要让重复的代码到处都是,要让它们足够的重用,所以要尽可能地封装。
保持它简单与傻瓜
- 保持系统界面简洁,功能实用,操作方便。
高内聚与低耦合
- 模块内部需要做到内聚度高,模块之间需要做到耦合度低。
关注点分离
- 将一个复杂的问题分离为多个简单的问题,然后逐个解决。
- 类似算法中的分而治之。
- 难点:如何进行分离。
你不需要它
- 不要一开始就把系统设计得非常复杂,不要陷入“过度设计”的深渊
- 让系统足够简单,而又不失扩展性。
软件设计分层
架构种类
系统级架构
- 前端与后端的关系考虑的主要因素是:前后端分离的架构设计。
- 前后端分离架构其实是如何实施技术决策,用户鉴权、API 接口管理和设计、API 文档管理、Mock 的使用、BBF(服务于前端的后端,nodejs),是否需要服务端渲染等。
设计后端:只需要规定与后台数据传递的机制。包括:API 设计原则,访问授权的一个开放标准(OAuth)跳转 token 的验证,数据传递 cookie 等。应用在整个系统内,如与后台服务如何通信,与第三方系统如何集成。
设计前端首要条件:了解前端系统与其他系统之间的关系。关系包括业务关系和协作机制。
应用级架构
- 应用级架构可以看作是系统级架构的细化。
- 单个应用与其他外部应用的关系,微服务架构下多个应用的协作,数据交换等。
- 脚手架 模式库 设计系统
模块级架构
- 这部分内容是我们开始业务编码之前进行设计,我们称为迭代。
代码级架构
- 规范与原则
- 实操:开发流程 代码质量以及改善 规范而非默契
注:在开发中,要注意可维护性。简单的代码可维护性高;越是写的抽象的代码越难维护。
微前端
旨在解决单体应用在一个相对长的时间跨度下,由于参与的人员、团队的增多、变迁,从一个普通应用演变成一个巨石应用(Frontend Monolith)后,随之而来的应用不可维护的问题。
- 在一个系统内微前端是应用间的架构方案。
- 在多个应用之间,微前端则是一种系统间的架构方案。
- 微前端是将多个前端应用以某种形式结合在一起进行应用。
单实例:即同一时刻,只有一个子应用被展示,子应用具备一个完整的应用生命周期。
多实例:通常基于 url 的变化来做子应用的切换。同一时刻可展示多个子应用。
通常使用 Web Components 方案来做子应用封装,子应用更像是一个业务组件而不是应用。
如何保证架构的质量
系统的健壮性和稳定性
系统的健壮性和稳定性是软件架构的健壮性和稳定性是该软件规划时所确定的目标。若软件的实现未达原定目标,则该软件的健壮性和稳定性不够或不好。
健壮性和稳定性是特定的软件自身的要求。
健壮性和稳定性是软件处理的一部分。
系统的健壮性
-
定义:计算机软件在输入错误、磁盘故障、网络过载或有意攻击情况下,能否不死机、不崩溃,就是该软件健壮性的具体表现。
-
解释:一个系统容错能力强,运行不易被干扰,安全性好
-
度量标准:
- 一个软件可以从错误的输入推断出正确合理的输入
- 一个软件可以正确的运行在不同环境下
- 一个软件能够检测自己内部的设计或者编码错误,并得到正确的结果
系统的稳定性
-
定义:当一个实际的系统处于一个平衡的状态时,如果受到外来作用的影响时,系统经过一个过渡过程仍然能够回到原来的平衡状态,我们称这个系统就是稳定的,否则称系统不稳定
-
解释:架构设计的基石。可以更好的实现自我修复。
架构质量的衡量
- 拓展性
- 可管理
- 维护性
- 高可用(故障修复,容灾,降级,熔断)
日常开发过程中的架构质量
- 理解难度
- 奔溃率和错误率的指标
- 接入依赖的成本
- 开发效率
- 错误上报和信息收集等功能
架构师分类
系统架构师
- 从系统的角度,负责整个系统的架构设计。
- 主要是基础服务和各系统间协调,着眼全局。
- 比如关注负载,可靠性,伸缩,扩展,整体项目切分,缓存应用等方面的基础架构设计。
应用架构师
- 从应用程序的维度,负责某个应用的技术架构,主要偏业务系统。
- 关注理解业务,梳理模型,设计模式,接口,数据交互等方面。
业务架构师
- 从业务流程的维度,关注某一个行业、业务的领域分析,获取领域模型,最终获得系统的模型。
- 也可以叫业务领域专家、行业专家、产品咨询师、资深顾问。
技术前期准备
技术选型
- 社区氛围、发展规模、未来发展趋势、与当前团队的契合度、执行成本、维护和迁移成本、执行效率等内容的调研和报告。
- 充分调研每一项技术可能带来的利和弊。
- 最大程度上预测架构设计中的缺陷,以防止问题的发生。
技术优化
- 在架构发展过程中,可能会存在一些有悖于当前架构设计的实现,造成了架构发展阻塞,所以需要进行架构优化,使架构设计的适应性更高。
架构优化
- 架构不是一蹴而就的,在业务发展过程中,架构也在不断演进。
- 对架构设计进行实时调优,使架构优化称为常态化。
- 通过不断的调整架构实现,改进初始架构设计中的不足,补足短板。
技术填补
问题
- 开发过程中因为时间紧迫导致的实现不合理。 比如求 100000 以内的所有质数。
开始的实现,耗时几秒,不合理:
function findPrime(number) {
const result = []
for (let i = 2; i <= number; i++) {
let m = 0;
for (let j = 2; j < i; j++) {
if (i % j === 0) {
m = 1;
break;
}
}
if (m === 0) {
result.push(i)
}
}
return result;
}
const num = 100000
console.time('start')
findPrime(num)
console.timeEnd('start')
可改为筛选,耗时几十毫秒,合理:
function findPrime(number) {
const result = new Array(number).fill(1)
const len = result.length
for (let i = 2; i < number; i++) {
let m = 2;
let middle = m * i
while(middle < len) {
result[middle] = 0
m++
middle = m * i
}
}
return result.map((item, index) => {
if (item && index > 1) {
return index
}
}).filter(item => item)
}
const num = 100000
console.time('start')
findPrime(num)
console.timeEnd('start')
- 暂时没有想到更好的实现方式而妥协的版本。比如写了很多 if else。
妥协的版本:
function judge(a) {
if (a > 100) {
return 100
}
if (a > 50) {
return 50
}
if (a > 20) {
return 20
}
if (a > 10) {
return 10
}
return 0
}
更好的实现方式:
// 责任链
function is100(a) {
if (a > 100) {
return 100
}
return is50(a)
}
function is50(a) {
if (a > 50) {
return 50
}
return is20(a)
}
function is20(a) {
if (a > 20) {
return 20
}
return is20(a)
}
function is10(a) {
if (a > 10) {
return 10
}
return 0
}
- 架构设计前期没有考虑到的细节。
- 不合理的交互设计,导致技术实现复杂。
- 旧功能文档缺失,无正确拓展、修改和兼容旧功能,导致上线后问题剧增。
后果
- 修复变重构。
- 小的技术债务不做偿还,最后会演变成一场大规模的重构工作,导致业务产出不高。
- 影响开发速度。
- 技术债务的存在导致整体开发需要兼容的点过多,影戏开发效率,极大影响上线速度,导致整体项目迭代缓慢,失去核心竞争力。
- 容易陷入维护旧功能->开发新功能->在新功能里兼容旧功能->维护旧功能->开发新功能......这样的恶性循环。
解决方案
- 优秀的架构设计师基础。
- 必须能够有效处理当前需求可预见的情况,对于未知的、可能出现的特殊情况,很小的改动就能解决问题。
- 根据当前的业务,进行合理的项目拆分,尽量的代码解耦合。
- 必须有日志模块,操作日志,错误日志,业务日志等等。
- 良好的技术培训和传帮带能力。
- 让每一位开发者可以从更深一层次理解自己所需要实现的功能。
- 从最开始的代码规范、到熟悉业务、最后再到编写文档。
- 充分的技术方案可避免一部分技术债务的产生。
- 技术方案是充分理解需求之后所能产出的对需求理想的实现方式,必要性不言而喻。
- 不同工程师之间可以相互 review。
- CodeReview 是非常重要的,同时也是对自身的一个提高。
- 提升对修复技术债务重要性的认知。
- 工程师如果能预见一个债务可能导致的问题,自然愿意花时间去处理。
- 善于发现和定期处理一些技术债务。
- 勇于发现系统中的技术债务,让自己为系统负责。
等产品上线后,开发就没有那么紧啦,这个时间大家可以找个时间处理技术债务,一边建立感情,一边品味一下原来的代码,这种感觉极其酸爽。
崩溃预防
预防架构崩溃
- 架构崩溃是严重的架构设计事故,也是我们需要预防的关键所在。
- 系统崩溃的产生
- 日志记录,如:操作日志,错误日志,业务日志等。
- 用户行为抓取->争取在最新时间获取到用户操作链条
- 解决存量问题->技术债务
- 遏制新增->减少新增问题的概率,如 CodeReview、单元测试等
崩溃预防
- 对脏数据进行兜底和检验
- 单元测试
- 崩溃报警
- 自动化测试
- 更广的灰度触达
- 性能优化体系
系统重构
架构不是永恒不变的。架构也是具有生命周期的。也会经历初生、发展、巅峰、衰弱、消亡的过程。
什么是重构?
- 对软件内部结构的一种调整。
- 目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本。
实现方式:
- 使用一系列重构手法,在不改变软件可观察行为的前提下,调整其结构。
重构理念:
- 运用大量微小且保持软件行为(功能)的步骤,一步步达成大规模的修改。
早期系统优势:
- 开发速度快
- 代码复杂度低
- 代码规范都保持完好
- 严重注释开发规范,不会允许危机架构设计的代码产生
- 以上因素导致添加功能难度低,成本低
晚期系统
- 具备所有早期系统的劣势(早期系统的优势都变成了劣势)
- 代码复杂度高
- 代码规范不完善
- 很多需求或功能出现逾越架构设计的情况
- 添加新功能兼顾较多,涉及较多模块,牵一发而动全身
- 当发现一个现有架构体系已经不能满足当前迭代速度的时候就需要进行重构工作。
微重构
- 对有“坏味道”的代码通过一些重构手段进行微重构。
重构流程
- 确定问题点,确定重构功能和范围
- 旧架构设计和逻辑梳理
- 稳定性保证
- 性能保证
- 需求过程中的冲突问题(重构过程中把新功能加上)