01-软件工程基础

什么是软件工程?

  1. 应用系统的、规范的、可量化的方法来开发、运行和维护软件,即将工程应用到软件。
  2. 对1)中各种方法的研究。

02-软件工程的发展

五十年代到00年代的特点

  1. 1950s:科学计算;以机器为中心进行编程;像生产硬件一样生产软件。
  2. 1960s:业务应用(批量数据处理和事物计算);软件不同于硬件;用软件工艺的方式生产软件。
  3. 1970s:结构化方法;瀑布模型;强调规则和纪律。它们奠定了软件工程的基础,是后续年代软件工程发展的支撑。
  4. 1980s:追求生产力最大化;现代结构化方法/面向对象编程广泛应用;重视过程的作用。
  5. 1990s:企业为中心的大规模软件系统开发;追求快速开发、可变更性和用户价值;web应用出现
  6. 2000s:大规模web应用;大量面向大众的web产品;追求快速开发、可变更性、用户价值和创新。

05-项目启动

如何管理团队?

① 实验中采取了哪些方法?有哪些经验?

  • 建立团队章程:建立明确的团队章程,统一团队成员的目标、绩效和方法,指导团队管理工作的进行。常见内容有:
    • 团队目标。
    • 团队共同追求。
    • 团队结构和角色分工。
    • 团队的任务、活动与绩效。
    • 团队规则与约束。
    • 经验:有必要指定一定的章程,约束团队成员之间的行为,比如开会请假必须得到其他三人的同意,又如一旦某项决策做出,不同意者不能再后续阶段违反等。
  • 持续成功(最有效手段):设置小里程碑,每隔一段时间让团队体验成功。每次作业的检查结果一定程度上肯定了每个小阶段的工作。
  • 和谐沟通:建立开放的环境,鼓励交流,要有制度保障定期沟通,会议和书面交流均可。保证每个人的工作进展和感知情况及时传达给整个团队。比如沟通需求变更、接口调整、关键代码修改等。
  • 避免团队杀手:需要对别人的工作全心全意的信任,尽管评审是必要的。产品质量的降低会使得凝聚力下降。经验是需要回避以下团队杀手:
    • 防范式管理
    • 官僚主义
    • 地理分散
    • 时间分割
    • 产品质量的降低
    • 虚假的最后期限
    • 小圈子控制

② 团队结构有哪几种?

  • 主程序员团队:一名技术能力出色的成员做为主程序员,负责领导团队完成任务。工作效率高,保证一致性。缺点是项目复杂,主程序员能力不足那么主程序员成为瓶颈。适合于把握性大,时间要求紧的情况。

  • 民主团队:没有集中的瓶颈,成员发挥能动性,沟通成本高,工作效率降低,要防止陷入混乱。适合于敏捷过程或较有挑战性的项目。

  • 开放团队:为了创新而存在的。管理者黑箱管理,不知道团队成员的工作进展,只需要知道工作仍在进行中。

质量保障有哪些措施?

在软件开发过程中,执行质量保障计划,每达到一个里程碑就及时根据保障计划进行质量验证,质量验证的方法主要有评审、测试和质量度量三种。

常见的质量保障安排如下:

  1. 需求开发:需求评审、需求度量;
  2. 体系结构:体系结构评审、集成测试(持续集成);
  3. 详细设计:详细设计评审、设计度量、集成测试(持续集成);
  4. 构造阶段:代码评审、代码度量、测试(测试驱动和持续集成);
  5. 测试阶段:测试、测试度量。

常见的质量验证方法如下:

1) 评审:由作者之外的其他人来检查产品问题,使用评审检查表,发现问题,返工并跟踪。

2) 测试:项目中包括集成测试,单元测试,系统测试。

3) 质量度量:用数字量化的方式描述软件产品。项目中进行了需求度量,代码度量和测试度量。

  • 如果结合实验说活动说评审、测试、质量度量应该就行。

配置管理有哪些活动?

  1. 标识配置项:

    1. 确定哪些配置项需要被保存和管理。
    2. 为配置项确定标识,设置唯一的ID。
    3. 详细说明配置项特征,包括生产者、基线建立时间、使用者等。
  2. 版本管理:

    1. 为每一个刚纳入配置管理的配置项赋予一个初始的版本号,并在发生变更是更新版本号。
    2. 旧记录会被保留。
  3. 变更控制:

    1. 以可控、一致的方式进行变更处理,包括对变化的评估、协调、批准、否决、实现、验证。
    2. 通过变更处理,项目负责人可以在面对变化时做出周全决策。
  4. 配置审计:

    1. 确定一个项目满足需求的功能和物理特征的程度,确保软件开发工作按照需求规格和设计特征进行,验证配置项的完整性、正确性、一致性和可跟踪性。比如定期更新文档。
  5. 状态报告:

    1. 标识、收集和维持演化中的配置状态信息。一般由工具自动完成。
  6. 软件发布管理:

    1. 将配置项发布到开发活动之外。比如发布给客户。

用人话说1就是把需求、设计、代码等配置项标ID,然后2给初始版本号,3发生变更时进行控制(如pr然后merge),开发时也要4定期检查这些配置有没有问题,并5把每一阶段信息记入状态报告;最后6把配置项给开发活动之外如客户。

06-需求基础

什么是需求?

  1. 用户为了解决问题或达到某些目标所需要的条件或能力;
  2. 系统或系统部件为了满足合同、标准、规范或其它正式文档所规定的要求而需要具备的条件或能力;
  3. 对1或2中的一个条件或一种能力的一种文档化表述。

区分需求的三个层次

image-20220609220024368

  1. 业务需求:

    1. 是系统建立的战略出发点,表现为高层次的目标,它描述了组织为什么要开发系统。

    2. 定义系统特性SF(System Feature)

    3. 一般较为抽象,主观的目标

      示例:在系统使用3个月后,销售额度应该提高20%(期望,没有从软件角度进行描述,业务需求)

  2. 用户需求:

    1. 是执行实际工作的用户对系统所能完成的具体任务的期望,描述了系统能够帮助用户做些什么。

    2. 通常要补充问题域知识。

    3. 表达一般为“系统应该允许”,“系统能够”,“软件必须帮助用户”…

      示例:系统应该帮助收银员完成销售处理

  3. 系统级需求

    1. 需求分析模型:用户对系统行为的期望,每个系统级需求反映了一次外界与系统的交互行为,或者系统的一个实现细节
    2. 通常为某个用户需求的交互细节,因为反映一次交互行为或实现细节
    3. Eg.在接到客户经理的请求后,系统应该为客户经理提供所有会员的个⼈信息。

需求分类

image-20220609221358544

  1. 需求
    1. 项目需求(人的数量、计划成本、时间,对项目的期望)
      • R5:项目的成本要控制在60万元人民币以下。
      • R6:项目要在6个月内完成。
    2. 过程需求(人的分工、合作、方法、工具,是对项目开发过程的期望)
      • R7:在开发中,开发者要提交软件需求规格说明文档、设计描述文档和测试报告。
      • R8:项目要使用持续集成方法进行开发
    3. 系统需求
      1. 软件需求
      2. 硬件需求
        • R9:系统要购买专用服务器,其规格不低于…
      3. 其他需求(人力资源、软硬件与人力协同)
        • R10:系统投入使用时,需要对用户进行1个星期的集中培训。
  2. 不切实际的需求
    • R11:系统要分析会员的购买记录,预测该会员将来⼀周和⼀个月内、会购买的商品;(技术上不可行)
    • R12:系统要能够对每月的出入库以及销售行为进行标准的财务分析;(在有限的资源条件下不可行,因为财务分析很复杂)
    • R13:在使用系统时,收银员必须要在2个小时内完成一个销售处理的所有操作。(超出了软件所影响的问题域范围)

软件需求分类

  1. 功能需求:

    1. 和系统主要工作相关的需求,即在不考虑物理约束的情况下,用户希望系统所能够执行的活动,这些活动可以帮助用户完成任务。功能需求主要表现为系统和环境之间的行为交互

    2. 应当按三个抽象层次展开,即SF(业务需求)、UR(用户需求)和SR(系统需求)都可以是功能需求。

      示例:在接到客户经理的请求后,系统应该为客户经理提供所有会员的个人信息。(请求-提供是交互过程)

      在存储设备发生故障时,系统要在0.5秒内向用户发出警报。(向用户表现交互过程)

      用户输入的补货数中含有非数字字符时,系统必须提示输入错误。

  2. 数据需求(属于功能需求的一种,DR):

    image-20220618164016375

    image-20220618164006744

    1. 功能需求的补充:如果在功能需求部分明确定义了相关的数据结构,那么就不需要再定义数据需求。如果功能需求需要数据支持并且没有定义数据结构,则需要定义专门的数据需求。

    2. 数据需求是需要在数据库、文件或者其他介质中存储的数据描述,通常包括下列内容:

      1. 各个功能使用的数据信息

      2. 使用频率;

      3. 可访问性要求;

      4. 数据实体及其关系

      5. 完整性约束;

      6. 数据保持要求。

        示例:系统需要存储的数据实体及其关系为图6-14的内容。(数据实体及其关系)

        系统需要存储1年内的销售记录和退货记录。(数据保持)

        商品的标识由0-24位字母、数字混合组成的字符串。

        用户输入的收入金额中不能含有非数字字符。

  3. 性能需求:系统整体或系统组成部分应该拥有的性能特征,例如CPU使用率、内存使用率等。

    1. 速度:系统完成任务的时间
      • 所有用户查询必须在10s内完成
    2. 容量:系统能存储的数据量
      • 系统因该能够存储至少100万个销售信息
    3. 吞吐量:系统在连续的时间内完成的事务数量
      • 解释器每分钟应该能够至少解析5000条没有错误的语句
    4. 负载:系统可以承载的并发工作量
      • 系统应该允许50个营业服务器同时从集中服务器上进行数据的上传或下载
    5. 实时性:严格的实时要求
      • 系统监测到异常时,监视器必须在0.5s内发出警报,和故障警报不一样,故障不是系统的正常功能
  4. 质量属性(QA):系统为了满足规定的及隐含的所有要求而需要具备的要素称为质量

    1. 可靠性:在规格时间间隔内和规定条件下,系统或部件执行所要求能力的能力。
      • 在进行数据的下载和上传中,如果网络故障,系统不能出现故障。故障不是正常功能!
    2. 可用性:软件系统在投入使用时可操作和可访问的程度或能实现其指定系统功能的概率。
      • 系统的可用性要达到98%
    3. 安全性:软件组织对其程序和数据进行未授权访问的能力,未授权的访问可能是有意,也可能是无意的。
      • VIP顾客只能查看自己的个人信息和购买记录
    4. 可维护性:软件系统或部件能修改以排除故障、改进性能或其他属性或适应变更了的环境的容易程度,包括可修改性(Modifiability)和可扩展性(Extensibility)。
      • 如果系统要增加新的特价类型,要能够在2个人月内完成。
    5. 可移植性:系统或部件能从⼀种硬件或软件环境转换至另外一种环境的特性。
      • 集中服务器要能够在1人月内从Window 7操作系统更换到Solaris 10操作系统。
    6. 易用性:与用户使用软件所花费的努力及其对使用的评价相关的特性。
      • 使用系统1个月的收银员进行销售处理的效率要达到10件商品/分钟。
      • 经过10天培训的收银员就能够熟练使用系统。
    7. 往往会有形容词和副词
  5. 对外接口:系统和环境中其他系统之间需要建立的接口,包括硬件接口、软件接口、数据库接口等等。

    1. 接口的用途
      • 注册使用Google Maps API
      • image-20220618163921880
    2. 接口的输入输出
    3. 数据格式
      • 使用扫描仪扫描文件,传递回的数据为pdf格式文件。
    4. 命令格式
    5. 异常处理要求
  6. 约束:进行系统构造时需要遵守的约束,例如编程语言、硬件设施

    1. 系统开发及运行的环境(包括目标机器、操作系统、网络环境、编程语言、数据库管理系统等)
      • 系统使用Java语言进行开发
      • 系统必须能够与Oracle数据库交互
    2. 问题域内的相关标准(包括法律法规、行业协定、企业规章等)
      • 已过保质期的食品不能销售
    3. 商业规则:(用户在任务执行中的一些潜在规则也会限制开发人员设计和构建系统的选择范围)
      • 顾客可以使用美元付款

    注:需求的灵活性,可以有中高低标准

07-需求分析方法

image-20220610100816421

建立用例图

用例描述是交互流程:用户怎么样,系统怎么样

  1. 用例的定义:
    • 在系统(或者子系统或者类)和外部对象的交互当中所执行的行为序列的描述,包括各种不同的序列和错误的序列,它们能够联合提供⼀种有价值的服务
  2. 图例:
    1. 用例:椭圆
    2. 参与者:小人,是系统或其他系统对要开发的系统所扮演的角色。
    3. 关系:简单的就是一条直线,包括相关、泛化关系、包含关系和继承关系。(角色之间也有关系,但一般不画)
    4. 系统边界:是一个框

image-20220610101818822

  1. 用例图的建立步骤
    1. 进行目标分析与确定解决方向:需要实现什么,一般是SF
    2. 寻找参与者:一般为角色名
    3. 寻找用例:每个用户的任务(目标)都是⼀个独立用例
      • image-20220610102423248image-20220610102458212
    4. 细化用例:判断标准是用例描述了为应对一个业务事件,由一个用户发起,并在一个连续时间段内完成,可以增加业务价值的任务。
      • image-20220610102648858image-20220610205521850
  2. 注意:
    1. 不要将用例细化为没有独立业务价值的单个操作:例如,不要将用户管理细化为增加、修改和删除三个更小的用例,因为它们要联合起来才能体现出业务价值。
    2. 不要将同⼀个业务目标细化为不同用例:例如特价策略制定和赠送策略制定
    3. 不要将没有业务价值(而是技术实现需要)的内容作为用例:
      1. 常见的错误有登录(应该描述为安全性质量需求)、”数据验证/输入/输出数据检查”(应该描述为数据需求或者业务规则)、”连接数据库”(属性软件内部实现而不是需求)、网络传输等。
    4. 不要将单个步骤细化为用例
    5. 不要将片面的一个方面细化为用例
    6. image-20220610103128601
    7. image-20220610103251499

例题:

image-20220610205400261image-20220610205429912

  • 注意把外面的框框画上
  • 借书、还书、查询书籍可以适当写出来,没必要合成一个书籍管理
  • 借书人这里可以合成一个,用注册或登记个人信息来区分
  • 右图把管理员和管理者分为两个我不是很同意

建立分析类图(概念类图)

  1. 基本元素:
    1. 对象
      • 标识符:对象自治、对象请求写作
      • 状态:存储数据,如密码、名称
      • 行为:利用数据做什么
    2. 类:对象集合的抽象
    3. 链接(link)(dependency)
      • 对象之间的互相协作的关系
      • 描述了对象之间的物理或业务联系
    4. 关联
      • 对象之间链接的抽象
      • 聚合与组合,聚合集合可以为空
    5. 继承:泛化关系


  1. 建立步骤

    1. 对每个用例文本描述,尤其是场景描述,建立局部的概念类图

      • 根据用例的文本描述,识别候选类(名词分析法)

      • 筛选候选类,确定概念类(状态和行为)

  • 状态和行为:概念类
    + 状态:其他概念类的行为,即属性
    + 行为:需求是否遗漏,否则转交给其他类
    + 无状态无行为:完全剔除

  • 识别关联

    • 发现协作,补充问题域内的关系
      • 聚合:空心菱形, 菱形位于聚合方,部分与整体的关系
    • 组合:实心菱形, 菱形位于组合方,整体对部分有完全的管理职责
      • 继承:空心三角, 三角位于基类
    • 协作关系写在直线中间
      • *代表数字N,..用来连接最小最大值,如0..1
    • image-20220610111216782
  • 识别重要属性(注意概念类图只有属性,详细类图才有方法)

    • 把重要属性加上

      1. 将所有用例产生的局部概念类图进行合并,建立软件系统的整体概念类图
  1. 先画关联关系,再添加类的属性 注意用:参与、发起、更新、使用、决定等词语,可以写被..由..

image-20220610205920541
  • 写出安全系统、账户系统是比较好的,把用户和银行账户分开也比较有道理
  • 像存取款、修改账户信息这种明确写出的,就写出来吧,不用合成ATM操作

建立顺序图

  • 系统顺序图是将系统整个看做一个黑箱的对象而描述的简单顺序图的形式,强调外部参与者和系统的交互行为,重点展示系统级事件。
  • 关于对象名冒号的意思:image-20220612112039430
  1. 组件:图名称+对象(方框或小人)+生命线(虚线)+执行态(实体小矩形)信息、创建、返回
  2. 消息类型:同步消息等返回,异步不等。一个同步消息对应一个返回消息。
  3. 组合段: loop opt alt [表示条件]
    • alt:多条路径,条件为真时执行。注意,每一种可选分支之间要用虚线分割,而且在表示执行态的圆柱上面要写监护条件,放在[]里面。
    • opt:任选,仅当条件为真时执行。把条件写[]里面。
    • loop:循环,条件为真时可多次执行。在旁边使用[]书写循环条件。
  4. 注意可以自己指向自己,比如更新操作。


image-20220610212232081

  1. 步骤:
    1. 确定上下文环境
    2. 根据用例描述找到交互对象
    3. 按照用例描述中的流程顺序,逐步添加消息

建立状态图

  1. 图例:
    1. 状态:一组可观察的情况,描述了一个系统在给定时间的行为
    2. 状态转换:从一个状态到另一个状态的转换
    3. 事件:使系统表现出某种可预测的行为形式的事件
    4. 行为:由于过渡而发生的过程

  1. 步骤:

    1. 确定上下文环境,明确状态主体和状态主题对应的上下文环境
    2. 识别状态:状态的主体表现出的一些稳定状态
    3. 建立状态转换:建立状态之间的转换。
    4. 补充详细信息,完善状态图

    image-20220610213805662

  • 注意有起点有终点
  • image-20220610213932477

08-需求文档化与验证

为什么要建立需求规格说明?

  1. 方便交流:软件开发过程中,子任务与人员之间存在错综复杂的关系,存在大量的沟通和交流,所以要编写软件开发中要编写不同类型的文档,每种文档都是针对项目中需要广泛交流的内容。因为软件需求需要进行广泛交流,所以要把需求文档化。
  2. 需求规格说明是在软件产品的角度以系统级需求列表的方式描述软件系统解决方案,书写需求规格说明,可以建立管理控制的基线,方便任务分配,制定工作计划,进行跟踪和度量。

用例文档和需求规格说明文档是最为常见的两种需求文档

用例文档从用户的角度以用例文本为主描述软件系统与外界的交互

软件需求规格说明文档则从软件产品的角度以系统级需求列表的方式描述软件系统解决方案

对给定的需求示例,判定并修正其错误

  1. 技术文档写作要点(简洁,精确,易读,易修改);
    1. 简洁:动词名词+辅助词,不要使用复杂长句、形容词和副词。
    2. 精确:不能产生起义或无法理解。
    3. 易读(查询):
      1. 有效使用引言、目录、索引等能够增强文档易读性的方法
      2. 使用系统化的方式组织内容信息,提供文档内容的可读性。
    4. 易修改:使用相同的语句格式组织相关联或相似的信息;使用列表组织独立、并列的信息;使用编号表达繁杂信息之间的关系。引用而不是重复
  2. 需求书写要点(使用用户术语,可验证,可行性);通常用可行性和可验证性来判断需求书写是否正确
  3. 需求规格说明文档书写要点(充分利用标准的文档模板,保持所以内容位置得当;保持文档内的需求集具有完备性和一致性;为需求划分优先级)

1
2
3
4
5
1. "After the payment process is complete, the relevant information should be appended to a log file."模糊的,不知道relevant information指代的是什么
2. "The system should be constructed so that it will be easy to add new functionality in the future."不可验证的,因为easy无法衡量
3. "The price of a gasoline purchase is computed as the price per gallon for the type of gas purchased, multiplied by the number of gallons purchased (use two decimal points for representing fractions of gallons)." 合格的,因为很完整
4. "The system should be available 24 hours a day, 7 days a week. 不现实的,不能保证永远工作
5.用户查询的界面应该友好。不对,友好不能验证,应当改成“用户完成任何一个查询任务时的鼠标点击数都不能超过5次”

对给定的需求示例,设计功能测试用例

  1. 以需求为线索,开发测试用例套件,确定输入/输出,开发测试用例。

  1. 制定测试用例
    • 方法用白盒或黑盒

09-软件设计基础

什么是软件设计?

  1. 软件设计是指关于软件对象的设计,是一种设计活动。软件设计既指软件对象实现的规格说明,又指这个规格说明产生的过程。
  2. 软件设计活动以需求开发的制品(需求规格说明和分析模型)为基础,构建软件设计方案描述和原型,为后期的构造活动提供规划或蓝图。
  3. 软件设计具有工程性、艺术性、演化性、决策性、约束性、概念完整性等性质。

软件设计的核心思想是什么?

image-20220610235059926

  1. 分解:横向上将系统分割为几个相对简单的子系统与子系统之间的关系
  2. 抽象:纵向上聚焦各子系统的接口(区别于实现,各子系统之间交互的契约),可以分离接口和实现,使得人们更好地关注软件系统本质,降低复杂度。

软件工程设计有哪三个层次?各层的主要思想是什么?

image-20220611000014292

  1. 高层设计:基于反映软件高层抽象的构件设计,描述系统的高层结构、关注点和设计决策。
    1. 部件承载了系统主要的计算与状态
    2. 连接件承载部件之间的交互
    3. 部件与连接件都是抽象的类型定义(就像类定义),它们的实例(就像类的对象实例)组织构成软件系统的整体结构,配置将它们的实例连接起来
  2. 中层设计:更加关注组成构件的模块的设计、导入/导出、过程之间调用关系或者类之间的协作
    1. 模块划分隐藏⼀些程序片段(数据结构+算法)的细节,暴露接口于外界
  3. 低层设计:深入模块和类的内部,关注具体的数据结构、算法、类型、语句和控制结构等。
    1. 将基本的语言单位(类型与语句),组织起来,建立高质量的数据结构+算法
    2. 屏蔽程序中复杂数据结构与算法的实现细节!

软件设计既可以分为体系结构设计(高)和详细设计(中低),也可以分为高中低三层。

10-软件体系结构基础

体系结构概念

image-20220611230756231

  1. 软件体系结构是由部件,连接件,配置组成的。

    1. 部件是软件体系结构的基本组成单位之⼀,承载系统的主要功能,包括处理与数据;

      • image-20220611230825849

      • 原始部件

      • 复合部件

    2. 连接件是软件体系结构的另一个基本组成单位,定义了部件间的交互,是连接的抽象表示;

      • image-20220611230947775
      • 原始连接件
      • 复合连接件:是由更细粒度的部件和连接件组成。
    3. 配置是对“形式”的发展,定义了“部件”以及“连接件”之间的关联方式,将它们组织成系统的总体结构。

      • image-20220611230924459

体系结构风格的优缺点(画图)

  1. 主程序子程序风格

    1. 优点:
      1. 流程清晰,易于理解
      2. 强控制性
    2. 缺点:
      1. 程序调用是一种强耦合的连接方式,非常依赖接口
      2. 程序调用的连接方式限制了部件之间的数据交互,可能会导致不必要的公共耦合。
    3. 适用:可以将系统功能依层次分解为多个顺序执行步骤的系统
  2. 面向对象风格

    1. 优点:
      1. 内部实现的可修改性(隐藏内部实现)
      2. 易开发、易理解、易复用的结构组织(契合模块化思想)
    2. 缺点:
      1. 接口的耦合性(由于方法调用机制,接口的耦合性无法消除)
      2. 标识的耦合性(一个对象要和其他对象交互,必须知道标识符)
      3. 副作用(难以理解、高耦合性以及数据的不一致视图)
        • 更难实现程序的“正确性”image-20220611231711934
    3. 适用:适用于那些能够基于数据信息分解和组织的软件系统。
  3. 分层风格

    1. 优点:
      1. 设计机制清晰,易于理解(抽象层次分离,隔离复杂度)
      2. 支持并行开发(层次之间遵守程序稳定的接口)
      3. 更好的可复用性与内部可修改性(接口的稳定性,不同层次的部件能够互相替代)
    2. 缺点:
      1. 交互协议难以修改(可能需要改变所有的层次,接口具有强耦合性)
      2. 性能损失(禁止跨层调用)
      3. 难以确定层次数量和粒度
    3. 应用:适用于主要功能是在不同抽象层次上进行任务分解的复杂处理,能建立稳定的不同抽象层次之间的稳定交互协议,没有很高的实时性能要求的系统
  4. MVC风格:

    1. 优点:
      1. 易开发性:抽象了业务逻辑,表现和控制机制清晰,易于开发
      2. 视图和控制的可修改性
      3. 适宜于网络系统开发的特征(MVC 不仅允许视图和控制的可修改性,而且其对业务逻辑、表现和控制的分离使得⼀个模型可以同时建立并保持多个视图,这非常适用于网络系统开发)
    2. 缺点:
      1. 复杂性:MVC将用户的任务分解成了表现、控制和模型三个部分,这会增加系统的复杂性,不利于理解任务实现。
      2. 模型修改困难,视图和控制都要依赖于模型

11-软件体系结构设计与构建(高)

体系结构设计的过程?

  1. 分析关键需求和项目约束:
    • 分析用例文档和需求规格说明书(包含需求规格和项目约束)。注意既要考虑功能性需求,又要考虑非功能性需求,甚至很大意义上体系结构设计是为了满足非功能性需求
  2. 选择体系结构风格:
    • 选择分层风格(信息系统、并行开发、非web应用),进行评审。
  3. 进行软件体系结构逻辑(抽象)设计:产生逻辑包图
  4. 依赖逻辑设计进行软件体系结构(实现)设计:产生物理包图
  5. 完善体系结构设计:关键类图,持久化数据格式的定义等(VO PO)
  6. 添加构件接口:包、重要文件的创建,定义接口
  7. 迭代过程3-6

包的原则

image-20220611233603049

  1. 重用发布等价原则(REP):重用的粒度就是发布的粒度

    1. 为重用器分组组件(类)
    2. 单个类通常是不可重用的:几个协作类组成一个包
    3. 包中的类应构成可重用和可释放的模块:模块提供一致的功能
    4. 减少重新使用者的工作
    5. 和相关联的类一起发布,而不是单独进行发布
  2. 共同封闭原则(CCP):包中所有类对于同一类性质的变化应该是共同封闭的,一个变化若对一个包产生影响,则对该包中的所有类产生影响,而对于其他包不造成任何影响。

    1. 最小化修改都程序员的影响
    2. 包尽可能大,和CRP互斥
    3. 方法
      1. 将具有相似闭包的类分组
      2. 面向可以预期的变更封闭包
      3. 将更改限制为几个软件包
      4. 降低包装释放频率
      5. 减少程序员的工作量
      6. 只对可预测的变更有作用,不可预测的变更会为系统带来极大的破坏能力,并且无法进行预测。
  3. 共同重用原理(CRP):一个包中的所有类应该是能够共同重用的。

    1. 根据常见重用对类进行分组:避免给用户不必要的依赖
    2. 遵循CRP通常会导致软件包拆分:获得更多,更小,更专注的包
    3. 减少重新使用者的工作
    4. 包尽可能小,和CCP互斥
  4. 无环依赖原则(ADP):在包的依赖关系图中不能存在环。必须是有向无环图

    1. 第一种单环,DIP依赖倒置即可解决
    2. 第二种互环,A依赖B,且B依赖A

  • Y依赖于BY,但没有依赖于B。B依赖于BY,所以Y没有依赖B。
  • 应该看代码上的依赖,不是运行上的依赖。
  1. 稳定依赖原则(SDP):朝着稳定(别人的修改不影响我)的方向进行依赖
    1. Ca:输入耦合度,包外部依赖该包内的类个数
    2. Ce:输出耦合度,包内部依赖于包外部的类的个数
    3. 不稳定性: I = Ce / (Ce + Ca}), I越小越稳定

  1. 稳定抽象原则(SAP):包的抽象程度应该和其稳定程度一致
    1. 稳定的包应该是抽象的包
    2. 不稳定的包应该是具体的包
    3. Na:包中抽象类个数
    4. Nc:包中所有类个数
    5. 抽象度A = Na / Nc
  2. 前三条描述的是依赖性,后三条描述的是耦合性
  3. 包设计过程:
    1. 开发包(构件)设计
    2. 运行时的进程
    3. 物理部署

体系结构构建之间接口的定义(*)

  1. 首先确定模块对外接口
  2. 然后确定接口的规范
  • 逻辑层接口:image-20220611235306233
  • 数据层接口(其实和逻辑层接口一样,都是语法、前置条件、后置条件)image-20220611235823134

体系结构开发集成测试用例

  1. Stub:接受被测试部件请求,按照设定规则向被测部件返回结果

    • 为了完成程序的编译和连接而使用的暂时代码,比如这个模块没写好,先写一点静态数据,就可以测别的模块
    • image-20220612000758196
  2. Driver:设置环境,输入测试用例的输入数据、得到被测试部件的输出结果,与预期结果比较、并给出测试用例执行结论

    • View的测试比较特殊,其他层都需要增加Driver进行测试
      可以基于Junit编写Driver;基于接口规格设计测试用例

      • 开发View层时:需要logic的stub
      • 开发logic层时:需要模仿view的driver,需要data的stub,需要模拟同层调用的driver和stub
      • 开发data层时:需要模拟logic的driver
    • image-20220612001208513

    • 驱动一个测试,就是驱动一个Service的行为

  1. 持续集成:逐步编写各个模块内部程序,替换相应的桩程序
    • 真实程序不仅实现业务逻辑,而且会使用其他模块的接口程序(真实程序序或者桩程序)
    • 开始:客户端: view driver, logic stub, data stub;服务器端: logic driver, data stub
    • 进展:客户端: 逐步替换driver,逐步替换logic stub,data stub;服务器端:逐步替换logic driver,逐步替换 data stub
    • 最后联调:使用真实的客户端和服务器

集成测试

集成的目的是为了让各个模块合成为一个系统来工作,从而验证整个系统的功能。

  1. 大爆炸式
  2. 增量式:
  • 自顶向下式

    image-20220612001540901

  • 自底向上式

    image-20220612001604948

  • 持续集成

    image-20220612001630158

12-人机交互(高)

可用性

可用性不仅关注人使用系统的过程,同时还关注系统对使用它的人所产生的作用。有多维度的定义:

  1. 易学性:新手用户容易学习,能够很快使用系统。
  2. 效率:数量用户使用系统完成任务的速度,熟练的用户可以高效使用它。
  3. 易记性:使用过软件系统的用户,能够有效记忆或快速重新学会使用该系统。(超市可以缓存之前的信息)
  4. 出错率:指用户在使用系统时,会犯多少错,错误有多严重,以及是否能从错误中很容易地恢复。
  5. 主观满意度:让用户有良好的体验

人机交互设计原则

  1. 简洁设计(7±2原则):文字不如图形,右边要更简洁
    • image-20220612092914478
  2. 一致性设计(实际模型和人的精神模型一致),如下是一个不一致的例子(cancel和ok的位置变化,很奇怪)
    • 精神模型:用户进行人机交互时头脑中的任务模型。

  1. 低出错率设计(包括不适当的菜单功能灰色屏蔽、检查输入,出现错误的时候帮助用户修正错误,而不是重新填写,每一个输入都尽快的显示问题)

    • 出错时尽快提示、引导,帮助人们避免出错
  2. 易记性设计

    1. 减少短期记忆的负担(Excel显示已输入过的信息)
    2. 使用逐层递进的方式展示信息(图片的缩略展现)
    3. 使用直观的快捷方式(快捷方式导航栏等)
    4. 设置有意义的默认值(如日期等)
  3. 可视化设计

    1. 按照任务模型设计界面隐喻,同时不要把软件系统的内部构造机制暴露给用户
      • 不要把表关键字ID显示在界面上,不要把每个对象方法都设置成一个按钮,不要暴露ZIPimage-20220612093551668
    2. 可视化设计还应该基于界面隐喻,尽可能地把功能和任务细节表现出来。
      • image-20220612094114610
  4. 导航:提供好的入口,符合人的精神模型

    • image-20220612094428435
  5. 反馈:提示交互行为的结果,比如用状态栏提示交互结果而不是弹出对话框。

  6. 差异性:给新手提供易学性高的图形页面,给专家提供效率高的命令行、快捷方式、热键等

例题1:image-20220612094725016

  • 违反了:一致性设计。不一致的交互机制会导致用户的精神模型的不一致,造成不必要的麻烦和负担。这里按钮的位置不一致。
  • 体现了:
    • 反馈:对用户行为进行了反馈,让用户意识到行为的结果。这里单击按钮改变了按钮边框。
    • 简洁设计:右图的隐喻设计代替了描述文字,更为简洁。
    • 可视化设计:基于界面隐喻,把功能和任务细节表现了出来。这里选择style就可以看到preview图。

例题2:

image-20220612095355978

  • 导航:帮助用户找到任务的入口。
  • 简洁设计:利用图片的形式简洁地表达了交互信息。
  • 易记性设计:使用了直观的快捷方式
  • 可视化设计:基于界面隐喻,把功能和任务细节表现了出来。
  • 反馈:使用状态栏,提示用户交互行为的结果。

精神模型、差异性

  • 精神模型
  1. 精神模型就是用户进行人机交互时头脑中的任务模型。依据精神模型可以进行隐喻(Metaphor)设计:

    1. 隐喻又被称为视觉隐喻,是视觉上的图像,但会被用户映射为业务事物。用户在识别图像时,会依据隐喻将控件功能与已知的熟悉事物联系起来,形成任务模型;
    2. 隐喻本质上是在用户已有知识的基础上建立一组新的知识,实现界面视觉提示和系统功能之间的知觉联系。
  • 差异性
    • 不同用户群体的任务模型有差异,好的人机交互应该为不同的用户群体提供差异化的交互机制。
      • 既为新手用户提供易学性高的人机交互机制(图形界面)
      • 又为专家用户提供效率高的人机交互机制(命令行、快捷方式、热键)
  1. 新手用户
    1. 是对业务不熟悉的⼈
    2. 例如新员工或者新接触系统的⼈。为新手用户设计系统时要关注易学性,进⾏业务导航,尽量避免出错。如果⼀个系统的⼤多数⽤户都是新手用户,整个系统的⼈机交互设计都要侧重易学性
  2. 专家用户
    1. 是能够熟练操作计算机完成业务任务的⼈,⼀般都是⻓时间使⽤软件系统并且计算机操作技能熟练的人。
    2. 为专家用户设计系统时,要关注效率。如果⼀个系统的大多数用户都是专家⽤户,整个系统的⼈机交互设计都要侧重效率。
  3. 熟练用户:是介于新手用户和专家用户之间的⼈。为熟练用户设计⼈机交互系统要在易学性和效率之间进行折中。

导航、反馈、协作式设计

  • 导航

image-20220612094428435

  1. 导航是为了给用户提供一个很好的完成任务的入口,好的导航保证这个入口很符合人的精神模型
  2. 全局导航控件包括窗口、菜单、列表、快捷方式、热键等。以功能分层和任务交互过程为依据。
  3. 局部导航包括可视化控件布局与组合、按钮设置、文本颜色或字体大小等。以任务细节为依据。
  • 反馈
  1. 反馈能够避免用户进行错误的操作,让用户明确自己的等待过程的长度。
  2. 目的是提示用户交互的结果,但不能打断用户工作的意识流。
    • 比如使用状态栏提示完成一项任务,不要用弹出对话框。
  3. 根据任务选择适当的响应时间:
    1. 打字、光标移动、⿏标定位:50~150毫秒
    2. 简单频繁的任务:1秒
    3. 普通的任务:2~4秒
    4. 复杂的任务:8~12秒
  • 协作式设计
  1. 人和计算机是人机交互的方法,其中人的因素是比较固定的,一定时期内不会发生大的变化,所以要让两者交互顺畅,就需要让计算机更多地适应人的因素,这也是人机交互设计以用户为中心的根本原因
  2. 这种调整计算机因素以更好地适应并帮助用户的设计方式被称为协作式设计。
  3. 包括简洁设计、一致性设计、低出错率设计、易记性设计。

13-详细设计(中低)

详细设计的出发点

  1. 软件详细设计是在软件体系结构设计之后进行,以需求开发的结果(需求规格说明和需求分析模型)和软件体系结构的结果(软件体系结构设计方案与原型)为出发点。

职责分配

面向对象设计的过程中,通过职责建立静态设计模型:面向对象分解中,系统是由很多对象组成的。对象各自完成相应的职责,从而协作完成⼀个大的职责。

类的职责主要有两部分构成:属性职责和方法职责。类与类之间也不是孤立存在的,它们之间存在一定的关系。关系表达了相应职责的划分和组合。它们的强弱顺序为:依赖<关联<聚合<组合<继承。

image-20220612103321241

协作

面向对象设计的过程中,根据协作建立动态模型:(1)从小到大,将对象的小职责聚合形成大职责;
(2)从大到小,将大职责分配给各个小对象。

通过这两种方法,共同完成对协作的抽象。

控制风格

  1. 集中式控制风格:做决策的往往只有一个对象,其他对象都与中心控制对象进行交互。
  2. 委托式控制风格:做出决策的对象不止一个,职责的分解决定了控制对象的层次。
  3. 分散式控制风格:无法找到明确的控制对象,每个对象只承担一个相对较小的职责,靠各个对象自治的方式实现大的职责。

给定分析类图、系统顺序图和设计因素描述

建立设计类图

设计类图是通过职责建立静态设计模型。(相比概念类图添加方法、接口、辅助类povo等)

步骤:

  • 抽象类的职责:把属性和行为写出来image-20220612110346576
  • 抽象类之间的关系:依赖关联聚合组合继承image-20220612110427704
    • 依赖:虚线 单向加箭头,双向不加箭头
    • 关联:实线
    • 聚合组成聚成:实线
  • 添加辅助类:接口类、记录类(数据类)、 启动类、控制器类、实现数据类型的类、容器类
  • image-20220612111240511

建立详细顺序图

详细顺序图是通过协作建立动态设计模型。(和顺序图基本没区别)

image-20220612111757404

设计类图就是满足三种控制风格的类图,如下图是集中式,中心是顾客image-20220612112842729

image-20220612112610584

协作的测试

Mock Object:类间协作的桩程序,类似于Stub,但是更加简单

image-20220612113413149

14-模块化与信息隐藏

耦合

  • 描述的是两个模块之间关系的复杂程度:包括内容耦合,公共耦合,重复耦合,控制耦合,印记耦合,数据耦合。其中控制耦合及以上可以接受,数据耦合最好

image-20220612114450446

  1. 内容耦合:A的GOTO跳转到B的内部;A改变B的内部私有数据;A改变B的代码。

    image-20220612115403756image-20220612115554301

  2. 公共耦合:使用了全局变量。共享资源改变,所有都会变。image-20220612115225679image-20220612115624583

  3. 重复耦合:一段业务逻辑在多个地方被调用,修改时会很麻烦。

    image-20220612115503192

  4. 控制耦合:AB类之间非独立,都知道另一个的内部结构和逻辑。image-20220612115152995image-20220612115825860

  5. 印记耦合:传了一整个数据结构,但只在该数据结构的个别组件上进行了操作。image-20220612114900436image-20220612114919680

    image-20220612115911877
  6. 数据耦合:模块间通过参数传递,只共享对方需要的数据。**image-20220612115946667**

内聚

  1. 内聚:表达的是一个模块内部的联系的紧密性:包括偶然内聚、逻辑内聚、时间内聚、过程内聚、通信内聚、功能内聚和信息内聚。其中偶然内聚和逻辑内聚不可以接受。

image-20220612120411654

  1. 偶然内聚:毫无关系,恰好堆砌在这个模块内。image-20220612120251879

  2. 逻辑内聚:模块执行一系列逻辑上相似但没有直接关联(因为都调用其他模块的方法),比如坐汽车/飞机。

    image-20220612120328438

  3. 时间内聚:一系列操作在同一时间段内发生。比如刷牙\洗脸\洗澡,顺序不一定但有时间相关性。初始化的时候一般是这个。image-20220612120625247

  4. 过程内聚:多个操作有顺序关系,比如洗车、打磨、上蜡。image-20220612120929941

  5. 通信内聚:操作针对同一数据,比如查询书名、价格、出版社和作者。

    改写的话:拆成两个image-20220612121058842

    image-20220612121140745

  6. 功能内聚:每个方法执行单一目的的操作,比如计算平方根,计算最短路径。image-20220612121200252

  7. 信息内聚:每个操作都有入口点和出口点,代码相对独立。主要用来实现抽象的数据类型。image-20220612121234149

信息隐藏

基本思想

信息隐藏利用了抽象的方法,抽象出每个类的关键细节,也就是模块的职责(什么是公开给其他人的,什么是隐藏在自己模块中的)。换句话说,抽象出来的就是接口,隐藏的就是实现,他们共同体现了模块的职责。

核心设计思路:每个模块都隐藏⼀个重要的设计决策——职责。职责体现为模块对外的⼀份契约,并且在这份契约之下隐藏的只有这个模块知道的决策或者说秘密,决策实现的细节仅模块自己知道。

两种常见的信息隐藏决策

  1. 根据需求分配的职责,因为实践表明,需求是经常变化的,频率和幅度都很大;
  2. 内部实现机制,常见的变化主题包括硬件依赖,输入输出形式,非标准语言特征和库,负责的设计和实现,复杂的数据结构,复杂的逻辑,全局变量。数据大小限制等。

说明例子信息隐藏程度好坏

image-20220612132630360

感觉是看职责分配的好不好,有没有合适的接口,有没有隐藏具体实现,发生变更时只孤立在模块内部,不影响其他的类。

信息隐藏就是隐藏认为会改变的角色,把设计指派给单独的模块,使得变化局限在内部,不会影响整体

看封装、职责、接口做的好不好

15-面向对象的模块化

模块化设计原则

image-20220613101202464

  1. 原则一:全局变量是被认为是有害的

    1. 公共耦合有风险,而且会增加潜在连接数量
  2. 原则二:如果没有特殊要求,让代码清晰一点:

    1. 让代码兼顾明确和可修改性
    2. 显式(代码清晰,不进行不必要的隐藏,但有时候可能与可修改性冲突)
  3. 原则三:避免重复

    1. 面向接口编程,而不是重复地写逻辑上一致的代码
    2. 避免重复耦合
  4. 原则四:面向接口编程

  5. 原则五:迪米特法则

    1. 你可以自己玩。(this)
    2. 你可以玩自己的玩具,但不能拆开它们(自己的成员变量)
    3. 你可以玩送给你的玩具。(方法)
    4. 你可以玩自己制作的玩具。(自己创建的对象)
    5. 强调的是不能出现a.b.Methods这类情况,不能人去动狗的腿,而是人命令狗a调b,狗动腿b自己调method
  6. 原则六:接口隔离原则(ISP)/也叫接口最小化原则

    1. 将大的接口拆解成几个小的接口

    2. 这样可以有效避免出现不必要的依赖,显著降低耦合。

      image-20220613104549001

  7. 原则七:里氏替换原则(LSP)

    1. 所有派生类都必须可以替代其基类
    2. 派生类的前置条件更弱,后置条件更强
    3. 解决方案:在父类中添加方法或者函数(如把子类的更强条件移到父类),或者拆分接口

    image-20220613104337715image-20220613104406479

image-20220613103016084

  • 应该分为Door和Alerm两个接口,第一个CommonDoor不应该实现alarm()
  • image-20220613103004719
  1. 原则八:组合代替继承

    1. MyStack继承Vector不合适,可以用组合Vector

    2. 适用于不符合LSP但想要进行代码复用,尤其是只用部分接口时用继承没必要

      image-20220613104016181image-20220613104031160

  2. 原则九:单一职责原则:一个类只能有一个改变的理由

    image-20220613103758080image-20220613103822609

16-面向对象的信息隐藏

信息隐藏的含义

模块需要隐藏的决策主要有“职责的实现”和“实现的变更”两类 。

面向对象中,需要做到:

1) 封装类的职责,隐藏职责的实现

2)预计将会发生的变更,抽象它的接口,隐藏内部实现机制 (为变更而设计?)

封装

  1. 封装将数据和行为同时包含在类中,分离对外接口与内部是实现。

封装实现的细节

  1. 封装数据和行为:Getter-Setter,不是单单将方法和类成员关联,还可以获得间接持有信息,进行约束检查或者数据转换
  2. 封装内部结构:不应该知道内部实现(数组/链表)
    • getPositions/getPositionsArray不好,用getPosition(int index)
  3. 封装其他对象的引用:不要返回内部的对象,新建一个返回,这样外部如何操作引用都不会影响到内部实现
  4. 封装类型信息:隐藏其具体子类型,只需要知道共性类别 LSP
  5. 封装潜在变更:把会变更的地方独立,把不会变更的地方抽象为稳定接口(如算平方根)

OCP开闭原则

  1. 开闭原则是指:在发生变更时,好的设计只需要添加新的代码而不需要修改原有的代码,就能够实现变更。

  2. 具体内容是:1)好的设计应该对“扩展”开放 2)好的设计应该对“修改”关闭

  3. 示例:可以用多态或DIP实现OCP,只要满足不修改原来的,还能加新的就行。

    • image-20220613114548109

    • image-20220613114224890

DIP依赖倒置原则

  1. 定义:

    • 抽象不应该依赖于细节,细节应该依赖于抽象。因为抽象是稳定的,细节是不稳定的

    • 高层模块不应该依赖于低层模块,而是双方都依赖于抽象。因为抽象是稳定的,而高层模块和低层模块都可能是不稳定的。

    image-20220613114209601

  2. 示例:这个是强耦合的,通过添加Mapper接口分离image-20220613114013868

image-20220613114043730

例题

image-20220613114656643

设计原则汇总(12)

  1. 原则一:全局变量是被认为是有害的
    1. 公共耦合是有风险,而且会增加潜在连接数量
  2. 原则二:如果没有特殊要求,让代码清晰一点:
    1. 让代码兼顾明确和可修改性
  3. 原则三:避免重复
    1. 面向接口编程,而不是重复地写逻辑上一致的代码
  4. 原则四:面向接口编程
  5. 原则五:迪米特法则
    1. 强调的是不能出现a.b.Methods这类情况,不能人去动狗的腿,而是人命令狗,狗动腿
  6. 原则六:接口隔离原则(ISP)/也叫接口最小化原则
    1. 将大的接口拆解成几个小的接口。
    2. 这样可以有效避免出现不必要的依赖。
  7. 原则七:里氏替换原则(LSP)
    1. 所有派生类都必须可以替代其基类
    2. 派生类的前置条件更弱,后置条件更强
    3. 解决方案:在父类中添加方法或者函数,或者拆分接口

  • 应该分为Door和Alerm两个接口,第一个CommonDoor不应该实现alerm
  1. 原则八:组合代替继承
    1. MyStack 组合 Vector
  2. 原则九:单一职责原则:一个类只能有一个改变的理由
  3. 原则十:最小化类和成员的可访问性(x是可见)
    • 是不是需要public
    • 类声明前没有public,则包内可见
    • 方法生命前没有public,则包内可见
    • public修饰是全局可见

  1. 原则十一:开放/封闭原则(OCP)
    1. 对扩展开放:模块的行为可以被扩展,比如新添加一个子类
    2. 对修改关闭:模块中的源代码不应该被修改
    3. RTTI(运行时类型信息违反了开闭原则LSP),就是如果都有的话则抽象成基类的方法。
  2. 原则十二:依赖倒置原则(DIP):高级模块不应依赖于低级模块:两者都应依赖抽象。

17-设计模式

可修改性、可扩展性、灵活性

  1. 如何实现?接口和实现的分离

    1)通过接口和实现该接口的类完成接口与实现的分离

    2)通过子类继承父类,将父类的接口和子类的实现相分离(override)

    1. 实现的可修改性
      1. 方法实现的修改和类的调用代码无关
      2. 例如:修改现有促销策略
    2. 实现的可扩展性(DIP & OCP)
      1. 创建新的子类,不影响原来的旧代码
      2. 例如:增加一条新的促销策略(策略模式)
    3. 实现的灵活性
      1. 调用的接口不变,但指向不同的对象时会动态绑定不同实现
      2. 例如:动态修改更改某商品对应促销策略

设计模式

  1. 策略模式:减少耦合、依赖倒置。
  2. 抽象工厂模式:职责抽象、接口重用。
  3. 单件模式:信息隐藏、职责抽象。
  4. 迭代器模式:减少耦合、依赖倒置。

策略模式

image-20220617203230753

1.

  1. 在策略模式中,一个类的行为或其算法都可以在运行时改变。属行为型模式
  2. 在策略模式中,我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的context对象。策略独享改变context对象的执行算法.

image-20220613171055398

image-20220613171230985

抽象工厂模式

下面两张图是普通工厂模式。

image-20220613171434196image-20220613171444403

下面是抽象工厂模式:

image-20220617203402737

定义了一个创建对象的接口,由子类决定要使实例化哪一个类。

工厂方法让类的实例化延迟到子类。

image-20220613171708479

迭代器模式

对迭代功能的创新,屏蔽是HashSet还是LinkedList等实现。

image-20220613172218280

image-20220613172453772

单件模式

image-20220613172043677

18-软件构造

构造包含的活动

构造包含的活动:详细设计;编程;测试;调试;代码评审;集成与构建;构造管理(构造计划、度量、配置管理)

软件构造实践方法

  1. 重构:修改软件系统的严谨方法,它在不改变代码外部表现的情况下改进其内部结构
  2. 测试驱动开发:要求程序员在编写⼀段代码之前,优先完成该段代码的测试代码。 测试代码之后,程序员再编写程序代码,并在编程中重复执行测试代码,以验证正确性。
  3. 结对编程:两个程序员挨着坐在一起,共同协作进行软件构造活动。

代码改进

  1. 简洁性/可维护性
  2. 使用数据结构消减复杂判定
  3. 控制结构
  4. 变量使用
  5. 语句处理
  6. How to write unmaintainable(难以维持的) code
  7. 防御与错误处理

复杂决策:使用有意义的名称封装复杂决策,例如对于决策“ If( (id>0) && (id<=MAX_ID))”,可以封装为“If ( isIdValid(id)** )”,方法isIdValid(id)的内容为 “return ((id>0) && (id<=MAX_ID) )”
数据使用
(1)不要将变量应用于
与命名不相符的目的。例如使用变量total表示销售的总价,而不是临时客串for循环的计数器。
(2)不要将单个变量用于
多个目的。在代码的前半部分使用total表示销售总价,在代码后半部分不再需要“销售总价”信息时再用total客串for循环的计数器也是不允许的。
(3)限制
全局变量的使用,如果不得不使用全局变量,就明确注释全局变量的声明和使用处。
(4)不要使用
突兀**的数字与字符,例如15(天)、“MALE”等,要将它们定义为常量或变量后使用。
明确依赖关系:类之间模糊的依赖关系会影响到代码的理解与修改,非常容易导致修改时产生未预期的连锁反应。对于这些模糊的依赖关系,需要进行明确的注释

23-代码设计

单元测试用例的设计

image-20220616093308651

测试一个程序单元时,需要构建桩程序和驱动程序,将其与其他程序单元隔离。

  • 为方法开发测试用例

  • 使用Mock Object测试类方法

    image-20220616100124006

  • 为类开发测试用例

    • 用状态图,不仅看一个方法,而看不同方法间相互影响的情况

契约式设计

  • 异常方式:throw PreException/PostException,可对前置后置条件进行检查

    image-20220616094712681

  • 断言方式:

    • Java提供的断言语句:assert Expression1(: Expression2);
    • 如果不写2,1不满足就抛AssertionError异常;否则就用2作为参数构造AssertionError

    image-20220616094857474

    image-20220616095124446

防御式编程

image-20220616095550527

表驱动*

image-20220616094413921 image-20220616094430688

image-20220616094457997

对于特别复杂的决策,可以将其包装为决策表,然后用使用表驱动编程的方式加以解决。可以增强代码复用。

圈复杂度*

就记:V(G)=E-N+2

image-20220616100620051

19-软件测试

黑盒测试

黑盒测试是将测试对象作为一个黑盒子,完全基于输入和输出数据来判断测试对象的正确性。

等价类划分

把所有可能的输入数据,即程序的输入域划分为若干部分(子集),从每个子集中选取少量具有代表性的数据作为测试用例。

  • 需要同时考虑有效等价类和无效等价类

image-20220616101242398

边界值分析

  1. 边界值分析是等价类划分方法的补充,实践证明,错误最容易发生在各等价类的边界,而不是等价类的内部。
  2. 对定价类划分的补充,错误容易发生在各个等价类的边界上,而不是等价类的内部,因此针对边界情况设计测试用例,可以发现更多的缺陷。

image-20220616101444861

决策表

决策表是为复杂逻辑判断设计测试用例的技术。决策表示由条件声明、行动声明、规则选项和行动选项等四个象限组成的表格。

  • 包装为决策表后,用表驱动测试

image-20220616101657157

状态转换

  • 状态转换是针对复杂测试对象的测试技术。该类复杂测试对象对输入数据的反映是多样的,还需要依赖自身的状态才能决定。
  • 状态转换包含有效转换和无效转换,只有在复杂情况和可靠性要求较高的情况下才会为无效转换设计测试用例。

根据状态图,看每个状态下可能的状态转换

白盒测试

image-20220616104520816

语句覆盖

每行至少执行一次

image-20220616105251944

条件覆盖

每种条件至少执行一次,尤其是if-else的部分

image-20220616105232482

路径覆盖

所有路径都至少执行一次,即所有可能的情况,最多。

image-20220616105217016

题型*

  • 单元测试用例:使用Mock Object测试类方法,见上文23-代码设计
    • 感觉Mock Object是Stub,JUnit是Driver?
    • Mock Object:要调但是还没有写好
  • 集成测试用例:使用driver+stub,见11-软件体系结构设计
    • 通常替换整个模块
  • 功能测试用例:根据需求,写出完整有价值的序列,感觉用黑白盒比较多
  • 测试的度量
    • 需求覆盖率=被测试的需求数量/需求总数
    • 模块覆盖率=被测试的模块数量/模块总数
    • 代码覆盖率=被测试的代码行/代码行数

20-软件交付

用户文档、系统文档

(1)用户文档:这里的文档支持是指为用户编写参考指南或者操作教程,常见的如用户使用手册、联机帮助文档等,统称为用户文档。

  • 文档内容的组织应当支持其使用模式,常见的是指导模式参考模式两种。指导模式根据用户的任务组织程序规程,相关的软件任务组织在相同的章节或主题。指导模式要先描述简单的、共性的任务,然后再以其为基础组织更加复杂的任务描述。
    参考模式按照方便随机访问独立信息单元的方式组织内容。例如,按字母顺序排列软件的命令或错误消息列表。如果文档需要同时包含两种模式,就需要将其清楚地区分成不同的章节或主题,或者在同一个章节或主题内区分为不同的格式。

(2)系统文档:更注重系统维护方面的内容,例如系统性能调整、访问权限控制、常见故障解决等等。因此,系统管理员文档需要详细介绍软硬件的配置方式、网络连接方式、安全验证与访问授权方法、备份与容灾方法、部件替换方法等等。

21-软件维护与演化

软件维护的重要性

(1)由于会出现新的需求,如不维护软件将减小甚至失去服务用户的作用。
(2)随着软件产品的生命周期越来越长,在软件生存期内外界环境发生变化的可能性越来越大,因此,软件经常需要修改以适应外界环境的改变
(3)软件产品或多或少会有缺陷,当缺陷暴露出来时,必须予以及时的解决

开发可维护软件的方法

  1. 考虑软件的可变性

    1. 分析需求易变性
    2. 为变更进行设计
  2. 降低维护困难而开发

    1. 编写详细的技术文档并保持及时更新
    2. 保证代码可读性
    3. 维护需求跟踪链
    4. 维护回归测试基线

演化式生命周期模型

image-20220616112642238

初步开发—演化—服务—逐步淘汰—停止

前三个阶段:开发团队在负责,会进行维护

后两个阶段:开发团队不维护

  • 优点:
    • 使用了迭代式开发、并行开发高效、渐进交互降低风险
  • 缺点:
    • 无法在早期确定范围

初步开发

初始开发阶段按照传统的软件开发方式完成第一个版本的软件产品开发。第一版的软件产品可以实现全部需求,也可以(通常是)只包含部分需求——对用户来说非常重要和紧急的最高优先级需求。

演化

可能会有预先安排的需求增量,也可能完全是对变更请求的处理,它们的共同点都是保持软件产品的持续增值,让软件产品能够满足用户越来越多的需要,实现更大的业务价值。
总的来说,该阶段可能的演化增量有:预先安排的需求增量;因为问题变化或者环境变化产生的变更请求;修正已有的缺陷;随着用户与开发者之间越来越相互熟悉对方领域而新增加的需求
演化阶段的软件产品要具备两个特征:(1) 软件产品具有较好的可演化性。一个软件产品在演化过程中复杂性会逐渐增高,可演化性会逐渐降低直至无法继续演化。演化阶段的软件产品虽然其可演化性低于初始开发阶段的软件产品,但是还没有到达无法演化的地步,还具有较好的可演化性。(2) 软件产品能够帮助用户实现较好的业务价值。只有这样,用户才会继续需要该产品,并持续提供资金支持。
如果在演化过程中,一个软件产品开始不满足第(2)条特征,那么该产品就会提前进入停止阶段。如果软件产品满足第(2)条的同时不满足第(1)条特征,那么该产品就会进入服务阶段。如果开发团队因为竞争产品的出现或者其他市场考虑,也可以让同时满足上面两条特征的软件产品提前进入服务阶段。

服务

服务阶段的软件产品不再持续的增加自己的价值,而只是周期性的修正已有的缺陷
服务阶段的产品还仍然被用户使用,因为它仍然能够给用户提供一定的业务价值,所以开发团队仍然需要修正已有缺陷或者进行一些低程度的需求增量,保证用户的正常使用。

逐步淘汰

在逐步淘汰阶段,开发者已经不再提供软件产品的任何服务,也即不再继续维护该软件。虽然在开发者看来软件的生命周期已经结束,但是用户可能会继续使用处于该阶段的软件产品,因为它们仍然能够帮助用户实现一定的业务价值。只是用户在使用软件时必须要容忍软件产品中的各种不便,包括仍然存在的缺陷和对新环境的不适应。对于该阶段的产品,开发者需要考虑该产品是否可以作为有用的遗留资源用于新软件的开发,用户需要考虑如何更换新的软件产品并转移已有的业务数据。

停止

一个软件正式退出使用状态之后就进行停止状态。开发者不再进行维护,用户也不再使用

逆向工程、再工程

  1. 逆向工程:分析目标系统,标识系统的部件及其交互关系,并且使用其它形式或者更高层的抽象创建系统表现的过程。逆向工程的基本原理是抽取软件系统的需求与设计而隐藏实现细节,然后在需求与设计的层次上描述软件系统,以建立对系统更加准确和清晰的理解。

image-202206161138081422. 再工程:检查和改造一个目标系统,用新的模式式及其实现复原该目标系统。目的是对遗留软件系统进行分析和重新开发,以便进一步利用新技术来改善系统或促进现存系统的再利用。
主要包括:改进人们对软件的理解;改进软件自身,通常是提高其可维护性、可复用性和可演化性。
常见具体活动有:重新文档化;重组系统的结构;将系统转换为更新的编程语言;修改数据的结构组织。

image-20220616113937360

22-软件开发过程模型

软件生命周期

image-20220616114910826

6个阶段,书本也正是按照这个编写的。

  • 软件开发生命周期模型

软件过程模型

构建-修复模型

  1. 最早也是最自然产生的软件开发模型,对软件开发活动没有任何规划和组织,是完全依靠开发人员个人能力进行软件开发的方式。
  2. 缺点:
    1. 在这种模型中,没有对开发工作进行规范和组织,所以随着软件系统的复杂度提升,开发活动会超出个人的直接控制能力,构建-修复模型就会导致开发活动无法有效进行而失败;
    2. 没有分析需求的真实性,给软件开发带来很大的风险;
    3. 没有考虑软件结构的质量,使得软件结构在不断的修改中变得质量越来越糟,直至无法修改;
    4. 没有考虑测试和程序的可维护性,也没有任何文档,软件的维护十分困难。
  3. 适用:软件规模很小的软件

image-20220616145317155

瀑布模型

  1. 瀑布模型是按照软件生命周期模型将软件开发活动组织为需求开发、软件设计、软件实现、软件测试、软件交付和软件维护等活动,并且规定了它们自上而下、相互邻接的次序。
  2. 优点:为软件开发活动定义了清晰的阶段划分(包括输入/输出、主要工作及其关注点),这让开发者能够以关注点分离的方式更好地进行那些复杂度超越个人能力的软件项目的开发活动。
  3. 缺点:
    1. 文档的过高的期望具有局限性
    2. 对开发活动的线性顺序假设具有局限性
    3. 客户、用户的参与具有局限性:成功的项目开发需要客户、用户从始至终的参与,而不仅仅是一个阶段。(只把需求限制为一个阶段)
    4. 里程碑粒度具有局限性:里程碑粒度过粗,基本丧失了”早发现缺陷早修复”这一思想(一个阶段后才验证)
  4. 适用:
    1. 适用于比较成熟,没有技术难点的软件

image-20220616145526117

增量迭代模型

  1. 增量迭代模型是在项目开始时,通过系统需求开发和核心体系结构设计活动完成项目对前景和范围的界定,然后再将后续开发活动组织为多个迭代、并行的瀑布式开发模型。需求驱动(开始时就知道需求)。
  2. 少量的不确定性和影响不大的需求变更通过迭代的方式加以解决
  3. 优点:
    1. 迭代式开发更加符合软件开发的实践情况,具有更好的适用性;
    2. 并行开发可以帮助缩短软件产品的开发时间;
    3. 渐进交付可以加强用户反馈,降低开发风险。
  4. 缺点:
    1. 由于各个构件是逐渐并入已有的软件体系结构中的,所以加入构件必须不破坏已构造好的系统部分,这需要软件具备开放式的体系结构
    2. 增量交付模型需要一个完备、清晰的项目前景和范围以进行并发开发规划,但是在一些不稳定的领域,不确定性太多或者需求变化非常频繁,很难在项目开始就确定前景和范围。(每个增量可以单独完成交付)
  5. 适用范围:适用于大规模软件系统的开发

image-20220616145819797

演化模型

  1. 演化模型将软件开发活动组织为多个迭代、并行的瀑布式开发活动。演化模型能够更好地应对需求变更,更适用于需求变更比较频繁或不确定性较多的领域
  2. 优点:
    1. 使用了迭代式开发,具有更好的适用性,尤其是其演化式迭代安排能够适用于那些需求变更比较频繁或不确定性较多的软件系统的开发;
    2. 并行开发可以帮助缩短软件产品的开发时间;
    3. 渐进交付可以加强用户反馈,降低开发风险。
  3. 缺点:
    1. 无法在项目早期阶段建立项目范围,所以项目的整体计划、进度调度、尤其是商务协商事宜无法准确把握;
    2. 后续迭代的开发活动是在前导迭代基础上进行修改和扩展的,这容易让后续迭代忽略设分析与设计工作,蜕变为构建-修复方式。
  4. 适用与不稳定领域的大规模软件系统开发

image-20220616150300620

比较

相同点:都是迭代、并行开发和渐进交付,都适合大规模开发

不同点:

  • 增量迭代模型需要在项目早期确定目标和范围,有较完整的系统需求开发和核心体系结构设计;
  • 而演化模型模糊了维护与新开发的界限,能够更好地应对需求变更,更适用于需求变更比较频繁或不确定性较多的领域。

原型模型

  1. 原型模型将需求开发活动展开为抛弃式原型开发活动和演化式原型开发活动。原型模型在整体安排迭代的情况下,强调”抛弃式原型“的演化模型。抛弃式原型解决对未来知识的局限性产生的不确定性,将未来置于现在进行推敲。
  2. 抛弃式原型(作出一些功能,但不一定有用)
    1. 它通过模拟”未来”的产品,将”未来”的知识置于”现在” 进行推敲,解决不确定性。
    2. 存在的原因是”不确定的”,这一类原型在后续的开发过程中会被抛弃
  3. 演化式原型(确定被使用的软件部分)
    1. 在迭代中构建,是系统的核心,并不断扩充,最终成为真正的软件产品。
    2. 它将作为真正产品的一部分,所以必须有很好的质量。在迭代式开发中,通常会在第一个迭代中构建一个核心的体系结构演化式原型,并且在后续迭代中不断扩充,成为真正的软件产品。
  4. 优点:
    1. 对原型方法的使用加强了与客户、用户的交流,可以让最终产品取得更好的满意度;
    2. 适用于非常新颖的领域,这些领域因为新颖所以有着大量的不确定性。
  5. 缺点
    1. 原型方法能够解决风险,但是自身也能带来新的风险,例如原型开发的成本较高,可能会耗尽项目的费用和时间;
    2. 实践中,很多项目负责人不舍得抛弃”抛弃式原型”,使得质量较差的代码进入了最终产品,导致了最终产品的低质量。
  6. 适用性:适用于具有大量不确定的新颖领域进行开发活动组织。

image-20220616151016940

螺旋模型

  1. 螺旋模型是风险驱动的,完全按照风险解决的方式组织软件开发活动。
    1. 确定目标、解决方案和约束
    2. 评估方案,发现风险
    3. 寻找风险解决方法
    4. 落实风险解决方案
    5. 计划下一个迭代
  2. 自内向外,螺旋模型有4次风险解决迭代,分别解决了几个高风险的阶段的问题
    1. 解决系统需求开发中的风险,尤其是产品概念设计风险,得到一个确定的产品前景和范围。
    2. 解决软件需求开发中的风险,得到清晰的软件需求
    3. 解决软件体系结构设计中的技术风险,构建高质量的核心体系结构原型。
    4. 解决详细设计和实现中的关键技术风险,建立一个可实现的高质量软件结构。
  3. 优点:可以降低风险,减少项目因风险造成的损失
  4. 缺点:
    1. 风险解决需要使用原型手段,也就会存在原型自身带来的风险,这一点与原型模型相同;
    2. 模型过于复杂,不利于管理者依据其组织软件开发活动
  5. 适用于高风险的大规模软件系统开发

image-20220616151628321

模型 适用
构建-修复 规模很小,质量、维护要求不高
瀑布 很少使用,需求、技术非常成熟可靠,复杂度适中,文档驱动
增量迭代 成熟稳定的领域(需求、设计明确),大规模,需求驱动
演化 不稳定领域(需求、设计开始时不知道),大规模
原型 不确定的新颖领域
螺旋 高风险,大规模,风险驱动

软件工程知识体系的知识域

软件需求,软件设计,软件构造,软件测试,软件维护,软件配置管理,软件工程管理,软件工程过程,软件工程工具和方法,软件质量