目录

高级软件工程笔记

目录

简介:Advanced-software-engineering课程笔记。复习用。非原创。

第一章 工欲善其事必先利其器

1.1 vscode快捷键

  • 选中某个文件编辑器 Ctrl + 1,2,3
  • 打开文件夹 Ctrl + O
  • 新建文件 Ctrl + N
  • 关闭文件 Ctrl + W
  • 编辑和保存文件 Ctrl + S
  • 调出VS Code命令管理,Ctrl + shift + P
  • 调出切换VS Code的终端,Ctrl + `
  • 文件资源管理器,Ctrl + shift + E
  • 源代码管理,Ctrl + shift + G
  • 跨文件搜索,Ctrl + shift + F
  • 启动和调试,Ctrl + shift + D
  • 查看错误和警告,Ctrl + shift + M
  • 管理扩展插件,Ctrl + shift + E

第二章 工程化编程实战

2.1KISS(Keep It Simple & Stupid)原则

  • 一行代码只做一件事
  • 一个块代码只做一件事
  • 一个函数只做一件事
  • 一个软件模块只做一件事

2.2 接口的概念

接口就是互相联系的双方共同遵守的一种协议规范,在我们软件系统内部一般的接口方式是通过定义一组API函数来约定软件模块之间的沟通方式。换句话说,接口具体定义了软件模块对系统的其他部分提供了怎样的服务,以及系统的其他部分如何访问所提供的服务。

在面向过程的编程中,接口一般定义了数据结构及操作这些数据结构的函数;而在面向对象的编程中,接口是对象对外开放(public)的一组属性和方法的集合。函数或方法具体包括名称、参数和返回值。

2.3 接口规格包含五个基本要素

  • 接口的目的;

  • 接口使用前所需要满足的条件,一般称为前置条件或假定条件;

  • 使用接口的双方遵守的协议规范;

  • 接口使用之后的效果,一般称为后置条件;

  • 接口所隐含的质量属性。

2.4 Call-in方式的函数接口和Callback方式的函数接口

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
/*
 * Search a LinkTableNode from LinkTable
 * int Conditon(tLinkTableNode * pNode);
 */


tLinkTableNode * SearchLinkTableNode(tLinkTable *pLinkTable, 
int (*Conditon)(tLinkTableNode * pNode));

/*
给Linktable增加Callback方式的接口,需要两个函数接口,一个是call-in方式函数,如SearchLinkTableNode函数,其中有一个函数作为参数,这个作为参数的函数就是callback函数,如代码中Conditon函数。
*/

callback函数又称为回调函数:通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

2.5 微服务的概念

  • 由一系列独立的微服务共同组成软件系统的一种架构模式;

  • 每个微服务单独部署,跑在自己的进程中,也就是说每个微服务可以有一个自己独立的运行环境和软件堆栈;

  • 每个微服务为独立的业务功能开发,一般每个微服务应分解到最小可变产品(MVP),达到功能内聚的理想状态。微服务一般通过RESTful API接口方式进行封装;

  • 系统中的各微服务是分布式管理的,各微服务之间非常强调隔离性,互相之间无耦合或者极为松散的耦合,系统通过前端应用或API网关来聚合各微服务完成整体系统的业务功能。

微服务架构的基本概念可以简单概括为通过模块化的思想垂直划分业务功能,传统单体集中式架构和微服务架构如下图示意

img\微服务架构.png

2.6 简要介绍Restful API

RESTful API是目前最流行的一种互联网软件接口定义方式。它结构清晰、符合标准、易于理解、扩展方便,得到了越来越多网站的采用。

REST即Representational State Transfer的缩写,可以翻译为“表现层状态转化”。有表现层就有背后的信息实体,信息实体就是URI代表的资源,也可以是一种服务,状态转化就是通过HTTP协议里定义的四个表示操作方式的动词:GET、POST、PUT、DELETE,分别对应四种基本操作:

  • GET用来获取资源;

  • POST用来新建资源(也可以用于更新资源);

  • PUT用来更新资源;

  • DELETE用来删除资源。

2.7 微服务接口举例

(可以发现,定义接口都要按照接口的5大基本要素来定义)

  • 该微服务接口的目标是手写识别服务,通过微服务命名ocr-handwriting来表明接口的目的;

  • 该微服务接口的前置条件包括取得调用该微服务接口的授权auth_code,以及已经有一张手写图片handwriting.png

  • 调用该微服务接口的双方遵守的协议规范除HTTP协议外还包括PNG图片格式和识别结果JSON数据格式定义;

  • 调用该微服务接口的效果即后置条件为以JSON数据的方式得到了识别的结果;

  • 从以上示意代码中该微服务接口的质量属性没有具体指定,但因为底层使用了TCP协议,因此该接口隐含的响应时间质量属性应小于TCP连接超时定时器。

2.8+ 模块化的基本原理

模块化(Modularity)是在软件系统设计时保持系统内各部分相对独立,以便每一个部分可以被独 立地进行设计和开发。这个做法背后的基本原理是关注点的分离 (SoC, Separation of Concerns) 分解成易解决的小问题,降低思考负担。 每个模块只有一个功能,易于开发,并且bug会集中在少数几个模块内,容易定位软件缺陷,也更加容易维护。 软件设计中的模块化程度便成为了软件设计有多好的一个重要指标,一般我们使用耦合度 (Coupling)和内聚度(Cohesion)来衡量软件模块化的程度。

耦合度是指软件模块之间的依赖程度,一般可以分为紧密耦合、松散耦合和无耦合。一般在软件设计中我们追求松散耦合。

内聚度是指一个软件模块内部各种元素之间互相依赖的紧密程度。 理想的内聚是功能内聚,也就是一个软件模块只做一件事,只完成一个主要功能点或者一个软件特性。

2.8 耦合概念

对于软件模块之间的耦合度,前文中提到,耦合度是指软件模块之间的依赖程度,一般可以分为紧密耦合、松散耦合和无耦合。一般在软件设计中我们追求松散耦合。

2.9 耦合细致划分

耦合度可分为七级:

耦合方式 介绍
无耦合 非直接耦合。两模块间没有直接关系,之间的联系完全是通过主模块的控制和调用来实现的。
数据耦合 一个模块访问另一模块,彼此间通过简单数据参数来交换输入、输出信息。这里的简单数据参数不同于控制参数、公共数据结构或外部变量。
标记耦合 如一组模块通过参数表传递记录信息,就是标记耦合。这个记录是某一数据结构的子结构,不是简单变量。
控制耦合 一个模块通过传递开关、标志、名字等控制信息,明显的控制选择另一模块的功能。
外部耦合 一组模块都访问同一全局简单变量而不是同一全局数据结构,而且不是通过参数传递该全局变量的信息。
公共耦合 一组模块都访问同一个公共数据环境。该公共数据环境可以是全局数据结构、共享的通信区、内存的公共覆盖区等。
内容耦合 一个模块直接修改另一个模块的数据,或直接转入另一个模块。

2.10 通用接口定义方法

  • 参数化上下文。通过参数来传递上下文的信息,而不是隐含依赖上下文环境。
  • 移除前置条件。
  • 简化后置条件。

2.11 线程的基本概念

线程(thread)是操作系统能够进行运算调度的最小单位。它包含在进程之中,是进程中的实际运作单位。一个线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。一般默认一个进程中只包含一个线程。

2.12 可重入函数概念

可重入(reentrant)函数可以由多于一个任务并发使用,而不必担心数据错误。相反,不可重入(non-reentrant)函数不能由超过一个任务所共享,除非能确保函数的互斥(或者使用信号量,或者在代码的关键部分禁用中断)。可重入函数可以在任意时刻被中断,稍后再继续运行,不会丢失数据。可重入函数要么使用局部变量,要么在使用全局变量时保护自己的数据。

2.13 可重入函数的基本要求

  • 不为连续的调用持有静态数据;

  • 不返回指向静态数据的指针;

  • 所有数据都由函数的调用者提供;

  • 使用局部变量,或者通过制作全局数据的局部变量拷贝来保护全局数据;

  • 使用静态数据或全局变量时做周密的并行时序分析,通过临界区互斥避免临界区冲突;

  • 绝不调用任何不可重入函数。

2.14 什么是线程安全

如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。

线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行读写操作,一般都需要考虑线程同步,否则就可能影响线程安全。

2.15 可重入函数和线程安全之间关系

  • 可重入的函数不一定是线程安全的;可重入的函数在多个线程中并发使用时是线程安全的,但不同的可重入函数(共享全局变量及静态变量)在多个线程中并发使用时会有线程安全问题;

  • 不可重入的函数一定不是线程安全的。

2.16 看待软件质量的几个不同的角度

软件工程研究的主要目标就是寻找开发高质量软件的策略。那什么样的软件是高质量软件?我们一般从三个不同角度来看待软件质量:

  • 产品的角度,也就是软件产品本身内在的质量特点;

  • 用户的角度,也就是软件产品从外部来看是不是对用户有帮助,是不是有良好的用户体验;

  • 商业的角度,也就是商业环境下软件产品的商业价值,比如投资回报或开发软件产品的其他驱动因素。

这三个角度的软件质量有着内在的联系,比如具有商业价值的软件产品是以用户质量为前提的,具有良好用户质量的软件产品也往往有一些好的产品内在质量特点。

第三章 从需求分析到软件设计

3.1 需求

  • 需求就是对用户期望的软件行为的表述;

  • 获取需求就是需求分析师通过关注用户的期望和需要,从而获得用户期望的软件行为,然后对其进行表述的工作;

  • 需求分析是在获取需求的基础上进一步对软件涉及的对象或实体的状态、特征和行为进行准确描述或建模的工作。

3.2 需求类型

  • 功能需求:根据需要的活动描述需要的行为

  • 质量需求或非功能需求:描述软件必须具备的一些质量特征

  • 设计约束:设计决策,如平台或接口组件的选择

  • 流程约束:对可用于构建系统的技术或资源的限制

3.3 有哪些和需求相关的人员

  • 客户:为将要开发的软件付费

  • 顾客:软件开发完成后购买

  • 用户:使用系统

  • 领域专家:熟悉软件必须自动化的问题

  • 市场研究人员:进行调查,以确定未来的趋势和潜在的客户

  • 律师或审计员:熟悉政府、安全或法律要求

  • 软件工程师或其他技术专家

3.4 获取需求的方法

  1. 采访利益相关者
  2. 查看可用的文档
  3. 观察当前系统(如果存在)
  4. 与用户一起学徒,以更详细地了解用户的任务
  5. 以小组形式采访用户或利益相关者
  6. 使用特定领域的策略,例如联合应用程序设计
  7. 与当前和潜在用户进行头脑风暴

3.5 高质量需求的特点

  • 需求是可测试的
  • 是可解决冲突的,不同的利益相关者有不同的要求,解决潜在的冲突想法,同时需要考虑需求的优先级
  • 具备一些需求特征:正确的,无二义性的,完整的,可行的,无与主要目标不相关的需求,可测试的,可追溯的

3.6 需求分析的两类基本方法

原型化方法(Prototyping)和建模的方法(Modeling)是整理需求的两类基本方法。

  • 原型化方法可以很好地整理出用户接口方式(UI,User Interface),比如界面布局和交互操作过程。

  • 建模的方法可以快速给出有关事件发生顺序或活动同步约束的问题,能够在逻辑上形成模型来整顿繁杂的需求细节。

3.7 用例的三个抽象层次

  • 抽象用例(Abstract use case)。只要用一个干什么、做什么或完成什么业务任务的动名词短语,就可以非常精简地指明一个用例;

  • 高层用例(High level use case)。需要给用例的范围划定一个边界,也就是用例在什么时候什么地方开始,以及在什么时候什么地方结束;

  • 扩展用例(Expanded use case)。需要将参与者和待开发软件系统为了完成用例所规定的业务任务的交互过程一步一步详细地描述出来,一般我们使用一个两列的表格将参与者和待开发软件系统之间从用例开始到用例结束的所有交互步骤都列举出来。

3.8 用例建模的基本步骤

  1. 从需求表述中找出用例,往往是动名词短语表示的抽象用例;
  2. 描述用例开始和结束的状态,用TUCBW和TUCEW表示的高层用例;
  3. 对用例按照子系统或不同的方面进行分类,描述用例与用例、用例与参与者之间的上下文关系,并画出用例图;
  4. 进一步逐一分析用例与参与者的详细交互过程,完成一个两列的表格将参与者和待开发软件系统之间从用例开始到用例结束的所有交互步骤都列举出来扩展用例。

其中第一步到第三步是计划阶段,第四步是增量实现阶段。

3.9 用例满足的四个必要条件

  • 是不是一个业务过程?

  • 是不是由某个参与者触发开始?

  • 是不是显式地或隐式地终止于某个参与者?

  • 是不是为某个参与者完成了有用的业务工作?

3.10 业务领域建模的基本步骤

  1. 收集应用业务领域的信息。聚焦在功能需求层面,也考虑其他类型的需求和资料;

  2. 头脑风暴。列出重要的应用业务领域概念,给出这些概念的属性,以及这些概念之间的关系;

  3. 给这些应用业务领域概念分类。分别列出哪些是类、哪些属性和属性值、以及列出类之间的继承关系、聚合关系和关联关系。

  4. 将结果用 UML 类图画出来。

3.11 统一过程

  • 统一过程(UP,Unified Process)的核心要义是用例驱动(Use case driven)、以架构为中心(Architecture centric)、增量且迭代(Incremental and Iterative)的过程。

  • 用例驱动就是用例建模得到的用例作为驱动软件开发的目标;以架构为中心的架构是后续软件设计的结果,就是保持软件架构相对稳定,减小软件架构层面的重构造成的混乱;增量且迭代体现在下图中。

增量

3.12 敏捷统一过程的四个关键步骤

  • 第一,确定需求;

  • 第二,通过用例的方式来满足这些需求;

  • 第三,分配这些用例到各增量阶段;

  • 第四,具体完成各增量阶段所计划的任务。

显然,第一到第三步主要是计划阶段的工作,第四步是接下来要进一步详述的增量阶段的工作

3.13 敏捷统一过程的增量阶段分为5个步骤

  • 用例建模(Use case modeling);

  • 业务领域建模(Domain modeling);

  • 对象交互建模(Object Interaction modeling);

  • 形成设计类图(design class diagram);

  • 软件的编码实现和软件应用部署;

3.14 形成软件设计方法的基本方法

(软件产品庞大复杂,前面的形成的设计类图只是其中一个用例得到的设计结果,我们需要对每一个用例进行分析和设计,最终再将各用例得到的设计结果综合成一个软件产品的整体设计方案。其中涉及两个基本的方法)(以上部分不写)

分析(analysis)和综合(synthesis)

分析是分解大问题变成易于理解的小问题。比如用例建模是将错综复杂的需求分解成一个一个的用例。在分析的过程中除了“分而治之”的切分分解的方法外,抽象方法的运用是一个关键。

**综合是将一个个小问题的解决方案组合起来构建软件的整体解决方案。**我们对每一个用例的关键步骤进行对象交互建模逐步形成了用例对应的解决方案,如何将多个用例的小解决方案组合起来构建软件整体设计方案?这在软件设计中是一个非常有挑战性的问题,一般我们通过参考已有的软件设计模式提供一个思路从而综合出一个软件整体解决方案。

第四章 软件科学基础概论

4.1 软件的基本构成元素

  • 对象(Object)

  • 函数和变量/常量

  • 指令和操作数

  • 0和1

4.2 软件的基本结构

  • 顺序结构

  • 分支结构

  • 循环结构

  • 函数调用框架

  • 继承和对象组合

多态

允许将不同的子类类型的对象动态赋值给父类类型的变量,通过父类的变量调用方法在执行时实际执行的可能是不同的子类对象方法,因而表现出不同的执行效果。

4.3 回调函数(重点)

回调函数是一个面向过程的概念,是代码执行过程的一种特殊流程。回调函数就是一个通过函数指针调用的函数。把函数的指针(地址)作为参数传递给另一个函数,当这个指针调用其所指向的函数时,就称这是回调函数。回调函数不是该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

经典必备代码,手写

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
//回调函数
int SearchCondition(tLinkTableNode * pLinkTableNode, void * args)
{
    char * cmd = (char*) args;
    tDataNode * pNode = (tDataNode *)pLinkTableNode;
    if(strcmp(pNode->cmd, cmd) == 0)
    {
        return  SUCCESS;  
    }
    return FAILURE;	       
}


//执行回调函数
tLinkTableNode * SearchLinkTableNode(
    tLinkTable *pLinkTable, 
    int (*Conditon)(tLinkTableNode * pNode, void * args),  
    void * args)
{
    ...
    tLinkTableNode * pNode = pLinkTable->pHead;
    while(pNode != NULL)
    {    
        if(Conditon(pNode, args) == SUCCESS)
        {
            return pNode;				    
        }
        pNode = pNode->pNext;
    }
    return NULL;
}

int main()
{
    ...
    //传递回调函数
    SearchLinkTableNode(head, SearchCondition, (void*)cmd);
    
}

4.4 闭包

闭包是变量作用域的一种特殊情形,一般用在将函数作为返回值时,该函数执行所需的上下文环境也作为返回的函数对象的一部分,这样该函数对象就是一个闭包。

4.5 异步调用(重点)

  • Promise对象可以将异步调用以同步调用的流程表达出来,避免了通过嵌套回调函数实现异步调用

  • ES6原生提供了Promise对象。所谓Promise对象,就是代表了未来某个将要发生的事件,通常是一个异步操作。Promise对象提供了一整套完整的接口,使得可以更加容易地控制异步调用。

  • ES6的Promise对象是一个构造函数,用来生成Promise实例。下面是Promise对象的基本用法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
/*Promise对象实际上是对回调函数机制的封装,也就是通过then方法定义的函数与resolve/reject函数绑定,简化了回调函数传入的接口实现,在逻辑上也更加通顺,看起来像是个同步接口。*/

var promise = new Promise(function(resolve, reject) {
  if (/* 异步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});

promise.then(function(value) { // resolve(value)
  // success
}, function(value) { // reject(error)
  // failure
});

4.6 匿名函数

lamda函数是函数式编程中的高阶函数,在我们常见的命令式编程语言中常常以匿名函数的形式出现,比如无参数的代码块{ code },或者箭头函数 { x => code },如下使用Promise对象实现的计时器就用到了箭头函数

1
2
3
4
5
6
7
8
9
function timeout(ms) {
  return new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
}

timeout(100).then(() => {
  console.log('done');
});

4.7 什么是设计模式

设计模式的本质是面向对象设计原则的实际运用总结出的经验模型。对类的封装性、继承性和多态性以及类的关联关系和组合关系的充分理解的基础上才能准确理解设计模式

4.8 使用设计模式的优点

  • 可以提高程序员的思维能力、编程能力和设计能力。

  • 使程序设计更加标准化、代码编制更加工程化,使软件开发效率大大提高,从而缩短软件的开发周期。

  • 使设计的代码可重用性高、可读性强、可靠性高、灵活性好、可维护性强。

4.9 设计模式的四个组成部分

  • 该设计模式的名称;

  • 该设计模式的目的,即该设计模式要解决什么样的问题;

  • 该设计模式的解决方案;

  • 该设计模式的解决方案有哪些约束和限制条件。

4.10 设计模式的分类

根据设计模式可以完成的任务类型来划分的话,可以分为创建型模式、结构型模式和行为型模式 3 种类型的设计模式:

  • 创建型模式:用于描述“怎样创建对象”,它的主要特点是“将对象的创建与使用分离”。比如单例模式、原型模式、建造者模式等属于创建型模式。

  • 结构型模式:用于描述如何将类或对象按某种布局组成更大的结构,比如代理模式、适配器模式、桥接模式、装饰模式、外观模式、享元模式、组合模式等属于结构型模式

  • 行为型模式:用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。比如模板方法模式、策略模式、命令模式、职责链模式、观察者模式等属于行为型模式

4.11 常用设计模式(重点)

创建型模式:

  • 单例(Singleton)模式:某个类只能生成一个实例,该类提供了一个全局访问点供外部获取该实例,典型的应用如数据库实例。

  • 原型(Prototype)模式:将一个对象作为原型,通过对其进行复制而克隆出多个和原型类似的新实例,原型模式的应用场景非常多,几乎所有通过复制的方式创建新实例的场景都有原型模式。

  • 建造者(Builder)模式:将一个复杂对象分解成多个相对简单的部分,然后根据不同需要分别创建它们,最后构建成该复杂对象。主要应用于复杂对象中的各部分的建造顺序相对固定或者创建复杂对象的算法独立于各组成部分。

结构型模式:

  • 代理模式:为某对象提供一种代理以控制对该对象的访问。即客户端通过代理间接地访问该对象,从而限制、增强或修改该对象的一些特性。代理模式是不要和陌生人说话原则的体现,典型的应用如外部接口本地化将外部的输入和输出封装成本地接口,有效降低模块与外部的耦合度。

  • 适配器模式:将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。继承和对象组合都可以实现适配器模式,但由于组合关系或聚合关系比继承关系耦合度低,所以对象组合方式的适配器模式比较常用。

  • 装饰模式:在不改变现有对象结构的情况下,动态地给对象增加一些职责,即增加其额外的功能。装饰模式实质上是用对象组合的方式扩展功能,因为比继承的方式扩展功能耦合度低。装饰模式在 Java 语言中的最著名的应用莫过于 Java I/O 标准库的设计了。例如,InputStream 的子类 FilterInputStream,OutputStream 的子类 FilterOutputStream,Reader 的子类 BufferedReader 以及 FilterReader,还有 Writer 的子类 BufferedWriter、FilterWriter 以及 PrintWriter 等,它们都是抽象装饰类

  • 外观模式:为复杂的子系统提供一个一致的接口,使这些子系统更加容易被访问。

  • 享元模式:运用共享技术来有效地支持大量细粒度对象的复用。比如线程池、固定分配存储空间的消息队列等往往都是该模式的应用场景。

行为型模式:

  • 策略模式:定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的改变不会影响使用算法的客户。策略模式是多态和对象组合的综合应用。

  • 命令模式:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行储存、传递、调用、增加与管理。

  • 模板方法模式:定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。模版方法是继承和重载机制的应用,属于类模式。

  • 职责链模式:为了避免请求发送者与多个请求处理者耦合在一起,将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。通过这种方式将多个请求处理者串联为一个链表,去除请求发送者与它们之间的耦合

  • 中介者模式:定义一个中介对象来简化原有对象之间的交互关系,降低系统中对象间的耦合度,使原有对象之间不必相互了解。在现实生活中,常常会出现好多对象之间存在复杂的交互关系,这种交互关系常常是“网状结构”,它要求每个对象都必须知道它需要交互的对象。如果把这种“网状结构”改为“星形结构”的话,将大大降低它们之间的“耦合性”,这时只要找一个“中介者”就可以了。在软件的开发过程中,这样的例子也很多,例如,在 MVC 框架中,控制器(C)就是模型(M)和视图(V)的中介者,采用“中介者模式”大大降低了对象之间的耦合性,提高系统的灵活性。

  • 观察者模式:指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,把这种改变通知给其他多个对象,从而影响其他对象的行为,这样所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布-订阅模式。

4.12 6大设计模式原则

  • 开闭原则:软件应当对扩展开放,对修改关闭。

  • 里氏替换原则:子类可以扩展父类的功能,但不能改变父类原有的功能。

  • 依赖倒置原则:高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象。其核心思想是:要面向接口编程,不要面向实现编程。

  • 单一职责原则:规定一个类应该有且仅有一个引起它变化的原因,否则类应该被拆分。

  • 迪米特法则:又叫作最少知识原则。如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。其目的是降低类之间的耦合度,提高模块的相对独立性。

  • 合成复用原则:要求在软件复用时,要尽量先使用组合或者聚合关系来实现,其次才考虑使用继承关系来实现。如果要使用继承关系,则必须严格遵循里氏替换原则。

  • 接口隔离原则:一个类对另一个类的依赖应该建立在最小的接口上。

4.13 三层架构

层次化架构是利用面向接口编程的原则将层次化的结构型设计模式作为软件的主体结构。比如三层架构是层次化架构中比较典型的代表,如下图所示

三层架构

4.14 MVC架构

MVC即为Model-View-Controller(模型-视图-控制器)。MVC是一种设计模式,以MVC设计模式为主体结构实现的基础代码框架一般称为MVC框架,如果MVC设计模式决定了整个软件的架构,不管是直接实现了MVC模式还是以某一种MVC框架为基础,只要软件的整体结构主要表现为MVC模式,我们就称为该软件的架构为MVC架构。

MVC中M、V和C所代表的含义如下:

  • Model(模型)代表一个存取数据的对象及其数据模型。

  • View(视图)代表模型包含的数据的表达方式,一般表达为可视化的界面接口。

  • Controller(控制器)作用于模型和视图上,控制数据流向模型对象,并在数据变化时更新视图。控制器可以使视图与模型分离开解耦合。

mvc架构

4.15 MVVM架构

MVVM即 Model-View-ViewModel

image-20220107180653382

4.16 MVVM相对MVC的优点

  • MVVM模式和MVC模式一样,主要目的是分离视图(View)和模型(Model),有几大优点:

  • 低耦合。视图(View)可以独立于Model变化和修改,一个ViewModel可以绑定到不同的"View"上,当View变化的时候Model可以不变,当Model变化的时候View也可以不变。

  • 可重用性。你可以把一些视图逻辑放在一个ViewModel里面,让很多View重用这段视图逻辑。

  • 独立开发。开发人员可以专注于业务逻辑和数据的开发(ViewModel),设计人员可以专注于页面设计。

  • 可测试。界面素来是比较难于测试的,测试可以针对ViewModel来写。

4.17 软件架构风格与策略

  • 管道-过滤器模式架构

  • 客户-服务模式架构

  • P2P模式架构

  • 发布-订阅模式架构

  • CRUD模式架构

  • 层次化模式架构

4.18 软件架构的描述方法

  • 分解视图:用软件模块勾划出系统结构,往往会通过不同抽象层级的软件模块形成层次化的结构。

  • 依赖视图:展现了软件模块之间的依赖关系。

  • 泛化视图:展现了软件模块之间的一般化或具体化的关系,典型的例子就是面向对象分析和设计方法中类之间的继承关系。

  • 执行视图:展示了系统运行时的时序结构特点,比如流程图、时序图等。视图中的每一个执行实体称为组件。

  • 实现视图:描述软件架构与源文件之间的映射关系。

  • 部署视图:将执行实体和计算机资源建立映射关系。

  • 工作任务分配视图:将系统分解成可独立完成的工作任务,以便分配给各项目团队和成员。

4.19 CMM/CMMI

CMM/CMMI的全称为能力成熟度模型,是评估软件能力与成熟度的一套标准,它侧重于软件开发过程的管理及工程能力的提高与评估,是国际软件业的质量管理标准。

4.20 CMM/CMMI 5个级别

  • CMMI一级,初始级。在初始级水平上,软件组织对项目的目标与要做的努力很清晰,项目的目标可以实现。但是由于任务的完成带有很大的偶然性,软件组织无法保证在实施同类项目时仍然能够完成任务。项目实施能否成功主要取决于实施人员。

  • CMMI二级,管理级。在管理级水平上,所有第一级的要求都已经达到,另外,软件组织在项目实施上能够遵守既定的计划与流程,有资源准备,权责到人,对项目相关的实施人员进行了相应的培训,对整个流程进行监测与控制,并联合上级单位对项目与流程进行审查。二级水平的软件组织对项目有一系列管理程序,避免了软件组织完成任务的偶然性,保证了软件组织实施项目的成功率。

  • CMMl三级,已定义级。在已定义级水平上,所有第二级的要求都已经达到,另外,软件组织能够根据自身的特殊情况及自己的标准流程,将这套管理体系与流程予以制度化。这样,软件组织不仅能够在同类项目上成功,也可以在其他项目上成功。科学管理成为软件组织的一种文化,成为软件组织的财富。

  • CMMI四级,量化管理级。在量化管理级水平上,所有第三级的要求都已经达到,另外,软件组织的项目管理实现了数字化。通过数字化技术来实现流程的稳定性,实现管理的精度,降低项目实施在质量上的波动。

  • CMMI五级,持续优化级。在持续优化级水平上,所有第四级的要求都已经达到,另外,软件组织能够充分利用信息资料,对软件组织在项目实施的过程中可能出现的问题予以预防。能够主动地改善流程,运用新技术,实现流程的优化。

4.21 重要软件质量属性

  • 易于修改维护

  • 良好的性能表现

  • 安全性

  • 可靠性

  • 健壮性

  • 易用性

  • 商业目标

第五章 软件危机和软件过程

软件危机是指落后的软件生产方式无法满足迅速增长的计算机软件需求,从而导致软件开发与维护过程中出现一系列严重问题的现象。

5.1 没有银弹

1986年,Brooks发表了一篇著名的论文“没有银弹”。断言“在10年内无法找到解决软件危机的杀手锏(银弹)”。Brooks认为软件工程专家们所找到的各种方法都是舍本逐末,它们解决不了软件中的根本困难,即软件概念结构的复杂性,无法达成软件概念的完整性和一致性,自然无法从根本上解决软件危机带来的困境。

5.2 软件生命周期

一般来讲,我们将软件的生命周期划分为:分析、设计、实现、交付和维护这么五个阶段。

  • 分析阶段的任务是需求分析和定义,比如在敏捷统一过程中用例建模和业务领域建模就属于分析阶段。分析阶段一般会在深入理解业务的情况下,形成业务概念原型,业务概念原型是业务功能和业务数据模型的有机统一体,比如用例的集合和业务数据模型,每一个用例在逻辑上都可以通过操作业务数据模型完成关键的业务过程。

  • 设计阶段分为软件架构设计和软件详细设计,前者一般和分析阶段联系紧密,一般合称为“分析与设计”;后者一般和实现阶段联系紧密,一般合称为“设计与实现”。

  • 实现阶段分为编码和测试,其中测试又涉及到单元测试、集成测试、系统测试等。

  • 交付阶段主要是部署、交付测试和用户培训等。

  • 维护阶段一般是软件生命周期中持续时间最长的一个阶段,而且在维护阶段很可能会形成单独的项目,从而经历分析、设计、实现、交付几个阶段,最终又合并进维护阶段。

5.3 瀑布模型

瀑布模型是一个项目开发架构,开发过程是通过设计一系列阶段顺序展开的,从系统需求分析开始直到产品发布和维护,每个阶段都会产生循环反馈。

瀑布模型是将软件生存周期的各项活动规定为按固定顺序而连接的若干阶段工作,形如瀑布流水,最终得到软件产品。

瀑布模型核心思想是按工序将问题化简,将功能的实现与设计分开,便于分工协作,即采用结构化的分析与设计方法将逻辑实现与物理实现分开。将软件生命周期划分为需求分析、软件设计、程序编写、软件测试和运行维护等六个基本活动,并且规定了它们自上而下、相互衔接的固定次序,如同瀑布流水,逐级下落。

5.4 原型化的瀑布模型

原型就是根据需要完成的软件的一部分,完成哪一部分是根据开发原型的目标确定,比较常见的有用户接口原型和软件架构原型。

5.5 V模型

V模型也是在瀑布模型基础上发展出来的,我们发现单元测试、集成测试和系统测试是为了在不同层面验证设计,而交付测试则是确认需求是否得到满足。也就是瀑布模型中前后两端的过程活动具有内在的紧密联系,如果将模块化设计的思想拿到软件开发过程活动的组织中来,可以发现通过将瀑布模型前后两端的过程活动结合起来,可以提高过程活动的内聚度,从而改善软件开发效率。这就是V模型

V模型

(个人理解)通俗来说,就是边开发边测试

5.6 分阶段的增量和迭代开发过程

分阶段开发的交付策略分为两种,一是增量开发,二是迭代开发。

  • 增量开发就是从一个功能子系统开始交付,每次交付会增加一些功能,这样逐步扩展功能最终完成整个系统功能的开发。

  • 迭代开发是首先完成一个完整的系统或者完整系统的框架,然后每次交付会升级其中的某个功能子系统,这样反复迭代逐步细化最终完成系统开发。

5.7 螺旋模型

螺旋模型是一种演化软件开发过程模型。

  • 兼顾了快速原型的迭代的特征以及瀑布模型的系统化与严格监控。

  • 螺旋模型最大的特点在于引入了其他模型不具备的风险管理,使软件在无法排除重大风险时有机会停止,以减小损失。同时,在每个迭代阶段构建原型是螺旋模型用以减小风险的基本策略。

螺旋模型将每一次迭代过程分为四个主要阶段:

  1. 制定计划,确定软件目标,选定实施方案

  2. 评估方案,识别解决风险

  3. 实施软件开发和验证

  4. 客户评估,评价开发工作,提出修正建议,进入下一开发阶段

image-20220107190511632

5.8 团队的基本要素

  • 团队规模

  • 团队的凝聚力

  • 团队协作的基本条件

5.9 如何建设高效团队

  • 建设具有凝聚力的团队

  • 设定有挑战性的目标

  • 反馈

  • 共同遵守的工作流程和框架

5.10 敏捷宣言

  • 个体和互动 高于 流程和工具

  • 工作的软件 高于 详尽的文档

  • 客户合作 高于 合同谈判

  • 响应变化 高于 遵循计划

尽管右项有其价值,我们更重视左项的价值。

5.11 什么是DevOps

DevOps(Development和Operations的组合)是一组过程、方法与系统的统称,用于促进软件开发、技术运营和质量保障(QA)部门之间的沟通、协作与整合。它的出现是由于软件行业日益清晰地认识到:为了按时交付软件产品和服务,开发和运营工作必须紧密合作。

传统的软件组织将开发、IT运营和质量保障设为各自分离的部门。DevOps是一套针对这几个部门间沟通与协作问题的流程、方法和系统。

5.12 精益创业

精益创业是一种消除浪费、提高速度与提升效率的方法。旨在以客户为中心,尊重客户价值,防止服务不足与服务过度,杜绝无价值的经济活动,并致力于持续改进、追求卓越,不断优化投入产出效益。

基本方法:

  • 全过程——不放过任何一个环节。

  • 全员化——要树立全员成本控制的理念,而不是仅靠创业者自己。

  • 标准化——要力求做到量化,能够定量的要定量,不能定量的要定性,做到成本管理有依据可衡量。

  • 责任化——明确承担相应责任的对象,如果没有明确的责任人的话,成本目标和手段等均将形同虚设。