写点什么

《编程的原则》读书笔记 (三): 软件架构的基本技法和非功能需求

作者:Chares
  • 2022-11-20
    北京
  • 本文字数:10460 字

    阅读完需:约 34 分钟

《编程的原则》读书笔记(三):软件架构的基本技法和非功能需求

✏️写作:个人博客InfoQ掘金知乎CSDN

📧公众号:进击的Matrix

🚫特别声明:原创不易,未经授权不得转载或抄袭,如需转载可联系小编授权。

前言


软件架构基本技法是优质代码的基本原理,共有以下 10 种:


  • 抽象

  • 封装

  • 信息隐藏

  • 打包

  • 关注点分离

  • 充足性、完整性、原始性

  • 策略和实现的分离

  • 接口与实现的分离

  • 单一引用点

  • 分治

为什么:优质代码都有“型”

优质代码都是有“型”的,这些“型”源于软件开发历史中无数程序员积累而来的实践经验。程序员认识到,对于一个问题,特定的解决方案要优于其他方案,于是这些解决方案被重复使用,这些方案就是基本技法。

怎么做:掌握“型”

我们要把基本技法应用到代码之中。基本技法不是从某种软件开发技术中总结出来的。这些技法是更为本质的东西,它们适用于一切开发方法以及编程语言。

软件架构基本技法

抽象

软件架构基本技法之①:抽象 Abstraction


抽象,是指在概念上明确划清界限,这种明确划清界限的方式可以将一个模块与其他模块区分开来。抽象由:


  • 舍象

  • 一般化


这两个观点组合而成


舍象:指的是舍去对象的几个性质,只关注其特定的性质。



一般化:指的是从具体对象中抽出共同的性质,将其固定位更加通用的概念。


为什么:抽象是对抗复杂的手段

抽象是人们在处理复杂事物时使用的基本手段。舍象可以除去对象的树叶,让它的本质暴露出来。复杂度下降之后,我们便可以更多的精力去对付真正的问题。抽象化的概念简明扼要,便于使用,应用范围极广。一般化通过抽取共同特征将多个对象聚成一组,将他们视为相同的事物,如此一来,从一个事物中学到的东西就可以应用到其他事物中,也就是所谓的闻一知十。

怎么做:使用“舍象”和“一般化”

要对事物进行抽象化处理,抽象化是程序员实现优秀设计的基本技术。在处理复杂事物时要进行舍象。舍弃多余的东西,抓住本质,需要注意的是,事物的本质固然重要,但在处理问题时,我们更应该关注当前问题的本质。在处理多个不同的事物时要采用一般化的手段,抽取对象共同的性质,将这些共同点组合成通用的概念。一般化是一种用于在多个事物之间总结原则的技术,是一种模式识别。抽象的反面是具体。具体的事物便于理解,但一个一个分析具体级别不同的事物会消耗无尽的时间,况且这种具体事物的分析结果并不能应用到其他事物中。只有经过抽象化处理,大量的对象才能套用统一的公式,因此在思考效率上,抽象远远胜于具象。

封装

软件架构基本技法之②:封装 Encapsulation


封装是给数据和逻辑分组,将相互关联的数据和逻辑分到一组,定义为一个模块。用模块这层外衣将关联性较强的数据群和逻辑群包裹起来的做法称为封装。各个模块会作为 完全不相关的东西分开进行处理。

为什么:不混淆抽象概念

通过分组,相互关联的元素被集中到一个模块中,共同担负起一个抽象概念,优点有:


  • 模块中不存在不相关的元素,代码可读性提高

  • 修改带来的影响被限制在模块内

  • 影响程序明确,代码更易于修改

  • 各个模块都是独立的零件,便于重复使用

  • 数据和逻辑分割成了小单位的模块,便于我们处理复杂问题

怎么做:将相关元素封装在一起

将相互关联的数据和逻辑分到一组,整理成一个模块。


封装时仅添加相关元素,决不能让无关的元素混入模块中。有了关联性很强的数据组成的数据结构,以及关联性很强的逻辑组成的函数群之后,我们便能获得简洁且高质量的模块了。

信息隐藏

软件架构基本技法之③:信息隐藏 Information Hiding


只展示必要的信息,对使用模块的用户隐藏模块实现相关的信息。模块内有哪些数据,函数是使用何种逻辑实现功能的,这些信息全部对外隐藏。用户只能通过最低限度公开的函数来操作模块。

为什么:整理关系以达到简洁

将用户不必知晓的内部详细信息隐藏起来,可以减少接口的代码量,让信息交互变得更加简洁,降低代码的复杂程序。


从用户的角度来看,由于排除了多余信息的干扰,模块的使用方法变得简单,模块也变得更加好用了。


公开的部分越少,模块内部的修改就越不容易影响到外部。这样可以将修改代码的范围控制到最小。

怎么做:隐藏内部信息

仅公开模块简单的功能,内部状态和内部功能全部隐藏。禁止外部访问模块内部的数据以及仅供模块内部使用的函数。


实现信息隐藏需要使用封装的手段,分组可以整理“关系网”,降低隐藏信息的难度。

扩展:封装与信息隐藏的区别

封装与信息隐藏是两个不同的概念:


  • 封装


将相关元素集中起来模块化。将关系紧密的数据与函数整合在一起


  • 信息隐藏


隐藏模块的内部状态和内部函数。阻止外部对内部的直接访问

关联信息:Parnas 原则

Parnas 原则是面向对象编程中使用的原则,以下两个条件定义了该原则:


  • 对于模块的使用者,仅提供使用该模块所必需得所有信息,其余信息一概不予提供

  • 对于模块的开发者,仅提供实现该模块所必需的所有信息,其余信息一概不予提供


模块之间的关系越简单越好。这样我们就能获得两个优势:


  • 即使不知道模块的内部信息,也可以使用模块(重复利用)

  • 可以轻松替换模块的内部实现,而不必担心对使用者造成影响(维护)

打包

软件架构基本技法之④:打包 Packaging


打包是给模块分组,将模块按照某种有意义的单位整理并分组,其实就是将整个软件按照某种有意义的单位进行分割。这种分割后的产物称为"包"。

为什么:降低模块群的复杂度

将代码中的相关元素封装成模块,可以起到整理代码,降级复杂度的作用。


当软件规模大到一定程序之后,模块的数量也会变得非常多,这同样会增加复杂度。此时就需要对模块群进行分组,也就是打包。打包有以下优点:


  • 整个软件被分割成包,复杂度下降

  • 包内没有不相关的模块,便于管理

  • 基本可以把修改带来的影响限制在包内,代码的修改因此变得更加方便

  • 依赖关系得到整理,方便代码以包为单位重复使用

怎么做:自下而上地设计包

将相互关联的模块集中起来打包。


我们要等模块积攒到一定数量之后再自下而上地对包进行设计,这项工作不可能一开始就通过自上而下的方式来完成。


随着开发的进行,模块将越来越多,此时我们就可以开始自下而上地设计包了。包的设计并不是一锤子买卖,包还要随着编程的推进不断成长和进化。

关注点分离

软件架构基本技法之⑤:关注点分离 Separation of Concerns


根据关注点分离代码,关注点指软件的功能或目的。把关注点"分离",就是将各个关注点有关的代码集中起来做成独立的模块,与其他代码分离。分离后的模块要尽量减少公开的功能数量,与其他模块的关联也要维持在最低限度。


在设计技法中,有很多模式用于实现关注点分离。其中最具代表性的模式是“模型-视图-控制器”(Model-View-Controller, MVC)。在编程领域,关注点分离的代表技术是面向切面编程(Aspect-Oriented Programming,AOP)。

为什么:修改以关注点为单位

代码的修改通常以关注点为单位。因此,将代码按照关注点进行分离有以下好处:


  • 各个关注点相互独立,从而缩小了代码的修改范围,使代码更易于修改。

  • 修改带来的影响限制在了关注点之内,因此代码的质量能够保持稳定。

  • 因为代码的编写是以关注点为单位进行的,所以能够实现并行开发。

怎么做:以关注点为单位模块化

以关注点为单位创建模块,把不同的功能、不相关的功能分开。例如:“模型-视图-控制器”模式下,业务逻辑、用户显示和输入处理相互分离。

关联信息:面向切面编程

面向切面编程是一种擅长分离“横切关注点”的技术。所谓横切关注点,就是指横穿于各个关注点之间的关注点。横切关注点会散布在各个功能之中,横穿所有功能的日志功能就是一个很好的例子,日志功能虽然由独立的模块提供,但日志的调用会直接写在各个功能之中。在面向切面编程中,这些横切关注点会根据某种结合规则自动添加到各关注点的相应位置,从而避免了代码直接调用。面向切面编程就是通过这种方式实现关注点分离的。

充足性、完整性、原始性

软件架构基本技法之⑥:充足性、完整性、原始性 Sufficiency,Completeness,Primitiveness


表达要充足,完备且精练。经过封装,我们让相互关联的元素集中到一个模块来承载一个抽象概念。该模块承载的抽象概念要具有充足性、完整性和原始性。


  • 充足性


充足性是指模块表达的抽象概念非常充足例如:模块要表达“收集”这一概念,但只提供了 remove,没有提供 add,这就不足以表达“收集”这一概念了。


  • 完整性


完整性是指模块表达的抽象概念具有的所有特征。涵盖所有特征、没有缺漏的模块方便任何人拿去使用。例如:当模块要表达“收集”这一概念时,如果模块没有提供用于获取 size,我们就不能说改抽象概念具有完整性。


  • 原始性


原始性是指模块表达的抽象概念非常精练例如:当模块要表达“收集”这一概念时,如果已经提供了表示添加 1 个物品的 add,则不需要再提供表示添加 10 个物品的 add10。从抽象的精练性这一角度来看,这么做是多余的。

为什么:准确表达抽象概念

模块表达的抽象概念必须能向使用者传递有用的信息,准确表达意图。

怎么做:完美表达模块的抽象概念

要明确模块要表达的抽象概念。信息过多或过少会使信息的传达变得不准确。因此模块提供的函数要满足充足性、完整性和原始性。当出现多余时,要么将其删除、要么将其移至其他模块。

策略和实现的分离

软件架构基本技法之⑦:策略和实现的分离 Separation of Policy and Implementation


策略和实现不同时存在。模块可以承载策略或实现。但是,一个模块不可以同时承载二者。


  • 策略模块

  • 策略模块依赖于软件的前提条件,这类模块通常用于给业务逻辑或其他模块选择参数。

  • 实现模块

  • 实现模块不依赖于软件的前提条件,这类模块通常是独立的逻辑部分。软件的前提条件会作为传递给模块的参数给出。

为什么:实现稳定但策略不稳定

实现模块不依赖于特定的软件,是一种纯粹的模块,因此可供其他软件重复使用。而策略是为软件量身打造的,当软件发生变动时,策略模块也要被迫发生改变。因此,如果将实现和策略混在一起,那么当策略发生变动时,实现也会受到牵连,影响模块的重复使用。

怎么做:将策略与实现存放在不同的模块中

设计时要有意识地区分依赖于软件前提条件的策略部分和不依赖于软件前提条件的实现部分,然后将二者分别限写入不同的模块。当遇到无法分离的情况时,至少要在模块内部明确区分策略部分和实现部分。

接口与实现的分离

软件架构基本技法之⑧:接口与实现的分离 Separation of Interface and Implementaion


模块由接口和实现组成,模块由接口和实现着两个相互分离的部分组成。


  • 接口部分

  • 接口部分用于定义模块具备的功能,决定模块的使用方法。该部分由用户可访问的函数签名组成。

  • 实现部分

  • 实现部分其实就是实现模块功能的代码部分,该部分包含模块内部使用的逻辑和数据,用户无法访问实现部分。

为什么:使用者只需要理解接口

接口与实现分离之后,模块的使用者(用户)就不必了解实现的详细内容。另外,接口与实现分离能保证“模块的使用方法”(=接口)和“功能的实现方法”(=实现)的独立性。如此一来,修改实现部分时就不必担心会对接口造成影响了。

怎么做:针对接口编程

模块的设计原理:“针对接口编程,而不是针对实现编程”的格言,因此,模块之间的调用要保证只使用接口来完成。


接口的实现要隐藏在接口背后,该部分不能被直接调用。

单一引用点

软件架构基本技法之⑨:单一引用点 Single Point of Reference


模块的各元素仅被声明和定义一次。例如,变量,仅定义一次就是指初始化之后就不再对值进行更改。这样我们就不用再追踪变量值的变迁过程,代码可读性也随之提高。

为什么:使编程无副作用

所谓编程的副作用,是指某一功能使用模块状态产生变化,对此后得到的结果造成影响。单一引用点原则,可以使编程无副作用。从另一方面想,当某一变量被大范围使用且该变量的值被频繁更改时,我们将很难对代码进行追踪。变量值固定则可以省去这些麻烦,从而提高代码的可读性。

怎么做:单一赋值

单一赋值:是指仅对变量执行一次赋值操作。我们要将副作用视为“应该避免的东西”,把变量看做“不变的东西”,给变量赋值后就不再对其进行更改。大多数编程语言允许对变量重新赋值,但是我们要跳出语法的限制,通过使用常量,设置规则来禁止重新赋值等方式,编写没有副作用的代码。

扩展:通过控制变量提升代码质量

容易发生故障的都是那些非必要的可变变量,如果消除不必要的可变性,增加不可变变量,代码的质量就能得到提升。同时,对于剩下的(必要的)可变变量,要尽量减少访问它的逻辑数量,缩小其作用域,我们要养成写小函数的习惯,限制各个函数的职能。要保证各个函数只作用于传递给它们的参数。

关联信息:引用透明性

引用透明性函数拥有以下两种特性:


  • 调用结果只依赖于参数


这个特性说的是数学中的函数,给参数传递相同的值,总能得到相同的返回值,也就是说,返回值只依赖于参数的值,这种特性称为“单纯性”。


  • 调用不影响其他功能的运行


这个特性是说函数没有副作用,所谓副作用,是指“某一处理引起状态改变,对此后的处理结果产生影响”。具有引用透明性的函数独立于外部状态运行,这类函数会在自身内部完成所有处理。引用透明性对优化处理也有一定好处,比如可以用已知的处理结果代替函数调用等。

分治

软件架构基本技法之⑩:分治 Divide and Conquer


分治是指将难以直接解决的大问题分割成多个小问题,然后逐个解决。分割后的小问题要比最初的问题简单许多,比较容易解决,各个小问题被逐一解决之后,原来的大问题就不复存在了。

为什么:大问题无法控制

直接解决大问题,难度较大,花费的时间比较多,最后可能还解决不了,之所以会出现这个现象,是因为规模过大的问题,其复杂度也比较高。先将问题分割成容易控制的大小,再着手解决,这么做效率比较高。

怎么做:将问题分割后逐个击破

先对问题进行分割,然后再一个一个解决:


  • 在设计整个软件时,先将软件分割成多个可以独立设计的部分,然后对这些部分一一进行设计

  • 在设计模块时,按照职责对模块进行分割

  • 在设计算法时,可以像归并排序一样,先自下而上地对问题进行分割,再探讨能否解决问题

  • 在处理海量数据时,可以像 MapReduce 一样,先将计算分割成较小单位,然后探讨能否把这些计算交给分布式环境并行处理。

软件架构的非功能需求

要想让软件具有高质量,真正服务于用户,单纯满足功能需求是不够的,还要满足非功能需求。在软件架构设计中,非功能需求一样重要。非功能需求包含以下几种观点:


  • 易操性

  • 互操作性

  • 效率性

  • 可靠性

  • 可测试性

  • 可复用性

为什么:非功能需求在软件发布后具有很大的影响力

非功能需求对开发、运维以及计算机资源的高效运用有着很大的影响。另外,在发布后的运维阶段,比较大的问题多是由性能、系统宕机等非功能需求引起的。


然而与功能需求相比,这些重要的非功能需求往往被忽视、推后。其实、非功能需求应该在开发最初,也就是软件架构的时候就纳入考量。

怎么做:从非功能需求的观点进行设计

在软件架构的设计阶段就将非功能需求纳入考量,绝对不能拖到后面再做。具体做法如下:


  • 在需求定义方面,确认各个观点的被需求程度

  • 在开发方面,从软件架构的设计阶段开始就非功能需求纳入结构之中

  • 在测试方面,确认需求是否得到满足

扩展:非功能测试

功能测试着眼于“做什么”,而非功能测试着眼于“怎样运作”。二者的着眼点不同。非功能测试与功能测试同样重要。软件的目的不是实现功能,而是让用户达到自己的目的,其中非功能需求占了很大的比重。


要想满足非功能需求,首先要找出适合当前软件的非功能需求标准,然后给非功能测试设置一个合格线,毕竟没有目标就无法满足需求。

关联信息一:非功能安全性需求

在非功能需求中,“安全性”(security)非常重要。简单来说,信息的安全性就是指保护软件所涉及的信息资产不被非法访问、泄露和篡改。随着以使用网络为前提的软件越来越多,用户对安全性的要求也变得越来越高。


信息安全的定义是维持信息的机密性、完整性和可用性。人们将机密性(confifentiality)、完整性(integrity)和可用性(availability)称为“信息安全三要素”。取这三个单词的首字母,信息安全三要素又叫作“信息安全的 CIA”。


  • 机密性

  • 机密性指对未获授权的个人、实体或进程不公开信息或禁止其使用信息。也就是不让未获得许可的人或其他对象使用信息。如果第三者能轻松访问重要信息,就会有信息泄露的风险,因此,我们需要给重要的信息添加访问控制,也可以给信息加密,这样一来,即使信息泄露,其他人也无法阅读信息内容。

  • 完整性

  • 完整性指确保信息正确、完整。如果第三者擅自改写、删除重要的信息,这些信息就会失去使用价值,不仅如此,一旦使用者没有发现信息遭到篡改,问题的严重性就可能会进一步加大。因此,人们通常使用数字签名等技术来确保完整性。

  • 可用性

  • 可用性指当获得授权的实体发来请求时,允许实体访问和使用信息。也就是说,实体可以随时访问和使用信息。对组织而言,能够随时访问自己所持有的重要信息这一点非常重要。

易变性

软件架构的非功能需求①:易变性 Changeablility


易变性指软件能被轻松改良的能力,具体来讲,易变性要求软件能被轻易修改,轻易扩展,轻易重组、轻易移植到其他平台,此外,在保证质量的前提下迅速完成概览也是易变性的要求之一。

为什么:软件的寿命比预想的长

我们总希望软件在发布之后一直不用修改,但事实并非如此。软件在使用周期内会不断被修改和扩展。原需求会不断发生变更,新需求也会不断添加进来。


要想长期快速应对用户的各种琐碎要求,我们就需要设计出便于修改代码的软件架构。

怎么做:可维护性、可扩展性、重组和可移植性

软件架构的设计要从可维护性、可扩展性、重组和可移植性这四个方面进行考察。


  • 可维护性

  • 可维护性指解决问题的难易度,即修改故障代码的难易度。要提高可维护性,使用的软件架构就必须能将修改控制在局部范围内,把对其他模块的副作用降至最低。

  • 可扩展性

  • 可扩展性添加新功能、更新模块版本以及删除不需要的功能和模块等操作的难易度。提高可扩展性需要降低模块之间的结合度。可扩展性最求的是能在不给客户端造成影响的情况下完成模块交换的结构,以及能将新模块整合到现有软件架构中的结构。

  • 重组

  • 重组指重新组织模块间的关系。当对模块的位置进行调整时(比如将模块移至其他子系统),就需要进行重组。为方便重组,我们设计的软件架构要能在不影响模块实现的前提下灵活对模块进行配置。

  • 可移植性

  • 可移植性指将软件移植到各种硬件平台,用户接口,操作系统,编程语言和编译器等的难易度。要提高可移植性,就要在设计软件时考虑软件对硬件的依赖性。要想做出不依赖硬件的软件,就要将系统库和用户接口库等对平台特有功能进行操作的部分整合成一个独立的专用模块。

扩展一:灵活性的取舍

设计优质软件架构的关键点在于,要清楚软件的哪些部分需要具备较高的灵活性以应对修改,哪些部分不会被修改。


在此基础上,对需要具备较高灵活性的部分套用支持修改的设计模式,提高灵活性。不过,这种支持修改的设计有一点需要我们注意。在选择高灵活性的软件架构时,人们往往把灵活性设计得过高。这样一来,模块就会失去简洁性,导致软件的可扩展性下降。


因此,灵活性不是越高越好。我们要考虑灵活性与简洁性之间的平衡。

扩展二:软件老化

和人一样,软件也会变老,软件老化是软件随着时间的流逝慢慢退化,软件老化的原因有以下几点:


  • 设计灵活性不足,代码的修改使软件架构遭到破坏

  • 设计本身没有问题,但负责修改的人没有理解设计,于是软件架构遭到破坏

  • 软件架构难以让人理解,最终被混乱的修改所破坏

  • 更新停滞,被时代抛弃,软件越来越陈旧


我们无法阻止软件老化,但是我们能够减缓软件的老化速度。通过了解软件的老化的原因,按照一定步骤抑制老化进程,及时弥补损伤。

互操作性

软件架构的非功能需求②:交互操作性 Interoperablility


互操作性指软件与其他软件交互的能力。互操作性要不同的软件之间使用同一交换方式来交换数据、读取相同格式的文件、使用相同的协议等,从而实现相互交互连接的状态

为什么:软件间协作

软件并不是独立存在的,它是系统的一部分,会频繁地与其他系统或环境发生作用。良好的连接性能扩大软件的用途,缩短开发周期,减少成本。

怎么做:选择标准规格

在设计软件架构时,要明确定义需要访问的外部功能以及数据结构,这么做能够提升软件的价值。在选择协议和数据格式时,要选择业界标准规格。从长远来看,选用标准规格能长期保持软件的价值。

效率性

软件架构的非功能需求③:效率性 Efficiency


效率性指软件在运行过程汇总使用资源发挥性能的能力。大致分为以下两种:


  • 时间效率性

  • 时间效率性从时间的角度来定义资源的使用效率。时间效率性可以通过一定时间内可以完成的处理数量(通量)、从用户执行输入操作到应答所花费的时间(响应时间)、从用户开始操作到输出所需信息所花费的时间(周转时间)等来衡量。

  • 资源效率性

  • 资源效率性从计算机资源的角度来定义资源的使用效率。资源效率性可以通过 CPU 占用时间、内存使用量、存储空间占用量和网联传输量等来衡量。

为什么:资源是有限的

资源是有限的,所以软件需要高效的使用资源,资源使用方法不当会使软件的运行变得缓慢,导致用户体验变差


不过,效率问题并不是使用成体系的算法就能够解决的,要想提高效率,就要在软件架构的设计阶段将职责分散到各个模块,并将各个模块适当关联起来。

怎么做:活用资源

要合理使用计算机的资源。所谓合理使用,当然不是说用得越少越好,而是指有效利用既有资源来最大限度地发挥软件性能

扩展:间接化与效率性的平衡

为了避免各个模块直接关联,我们有时会在模块之间导入“媒介模块”。这种方法称为间接化。间接化是一种基础且适用范围很广的模块设计方法。有这样一句格言:“计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决。”


间接化有助于维持低耦合性,保持较高的可维护性、可扩展性与可复用性。很多设计模式将间接化作为基本思路之一来使用。


不过,在采用间接化这种设计方法时,我们要掌握好它与效率性之间的平衡,之所以这么说,是因为间接化会让处理变得冗长,对效率造成影响。我们要对软件所需要的效率性有清楚的认识,掌握好效率性与可维护性,可扩展性和可复用性之间的平衡,在此基础上间接化这一设计方法。

可靠性

软件架构的非功能需求④:可靠性性 Reliability


可靠性指软件在异常情况下或在被非法、非常规使用时维持自身功能的能力。可靠性体系在容错和健壮性这两个方面:


  • 容错

  • 容错指软件发生故障时仍保持正常运行的能力。它保证软件能在异常情况下正常运行,并在内部完成故障的修复工作。修复完成后,软件需要继续或从头开始执行异常位置的操作,比如分布式系统在发生通信异常时会先暂时切断连接,等问题修复完成后再重新连接,恢复软件的运行。

  • 健壮性

  • 健壮性是保护软件不受非正常使用方式或非输入影响的能力,具备该能力后,不论什么样的使用方式,软件都能准确迁移至系统定义的状态。健壮性只保证软件能迁移至系统定义的状态,并不要求软件修复或重新执行引发异常的处理。

为什么:软件对可靠性的需求各不相同

软件对可靠性的需求是不尽相同的。


比如大规模系统或关键的业务系统就不允许出现中断服务的情况,就算缩减功能,也要持续提供服务。


而供个人使用的软件就不需要如此强大的持续性了,相较于在危险状态下继续提供服务,这类软件大多选择保持数据重新启动。因此,在设计软件架构时,我们需要先明确软件对可靠性的需求。

怎么做:冗余化、故障弱化和故障安全

从容错的观点出发,我们可以让软件架构存在内部冗余(双重冗余等)。也可以采用故障弱化的设计,在软件发生故障时缩减软件提供的功能,只保留关键功能,保证处理继续进行。


从健壮性的观点出发,我们可以采用故障安全的设计,在发生故障时剥离发生故障的部分,也可以采用故障保护的设计,保证软件在用户进行了错误操作的情况下也能安全运行,避免故障发生。

可测试性

软件架构的非功能需求⑤:可测试性 Testability


可测试性指软件有效且高效地进行测试的能力。


  • 有效的进行测试

  • 指测试有深度且高质量,即通过测试可以全面监测软件的质量。

  • 高效地进行测试

  • 指测试所需成本和劳力较少,即能够花费较少的成本快速地检查软件的质量。

为什么:测试的质量即产品的质量

随着软件体积的增大和复杂度的加深,测试的难度会越来越大,所需成本也会越来越高,因此我们要求软件架构不仅要保证软件正常运行,还要有简化测试的效果。无论是开发阶段还是维护阶段,保证修改后软件的质量都是一个非常重大的课题,在软件的各项需求中,可测试性的重要程度相对较高。


简化软件的测试需要有软件架构的支持,使能够简化测试的软件架构不仅方便调试代码和调试模块的临时整合,还可以提高排查及修复故障的效率。

怎么做:在设计产品时兼顾测试

从软件架构的设计阶段开始,我们就要将测试方法纳入考虑范围。提高可测试性的关键在于消除模块之间的依赖关系,如果存在依赖关系,难以测试的部分就会拖整个软件的后腿。我们要尽量消除模块之间的依赖关系,保证测试能够以较小的单位进行。

可复用性

软件架构的非功能需求⑥:可复用性 Reusability


可复用性指软件的整体或其中一部分可以在其他软件的开发过程中重复使用的能力。可重复性表现在两个方面:


  • 重复使用现有代码的软件开发

  • 重复使用现有代码的软件开发指在开发过程中重复使用项目内既有模块、以往项目的模块和各种库等,将可重复使用的现有代码直接或变形后整合至正在开发的软件中。

  • 以重复使用为目的的软件开发

  • 以重复使用为目的的软件开发指在当前软件开发中创造出可供未来项目重复使用的模块,为其他软件提供可重复使用的模块是这类软件开发的目的。

为什么:能不做就不做,提高开发效率

为了提高软件开发的效率和软件质量,我们应该尽量避免从 0 开发,也就是说,从其他地方借用代码是最好的选择。重复使用现有代码可以让我们少编写一些代码,降低软件开发的成本,缩短周期,同时,使用已取得成绩的成熟模块能够提高软件的质量。

怎么做:“插件”软件架构

如果是重复使用现有代码的软件开发,我们就要设计出能作为现有结构或模块插件使用的软件架构。


如果是已以重复使用为目的的软件开发,我们就要设计出能将自给自足的部分从正在开发的软件中分离出来的软件架构,自给自足的部分要能在不进行任何修改的情况下直接被其他系统使用,这部分最好做成可独立构建的模块或包。




最后欢迎大家点赞、收藏、评论,转发!


欢迎大家关注我的微信公众号!微信搜索:进击的 Matrix

发布于: 刚刚阅读数: 5
用户头像

Chares

关注

Stay hungry, Stay foolish 2018-08-12 加入

公众号:【进击的Matrix】 知乎:【可乐】 掘金:【Chares】

评论

发布
暂无评论
《编程的原则》读书笔记(三):软件架构的基本技法和非功能需求_软件工程_Chares_InfoQ写作社区