关于软件工程
Table of Contents
zhangt考试题 #
请画出传统软工生命周期模型与现代软工开发(如敏捷开发)模型,论述两者的特点和不同。论述自己的实践项目开发属于哪种生命周期模型?
请论述德米特法则,结合自己的项目举例说明如何应用?
请写出几种软件开发团队的组织结构,论述各自的特点。结合自己的小组论述理想的团队组织是怎样的?
算AOE
算故障播种、二人测试
结合自己的软工项目实践,举例从体系结构、设计原则等方面进行的提高软件质量所做的重构工作。对比修改前后两种设计方案的特点不同。
结合自己的软工实践,写出项目中应用的两种设计模式及其特点。
本学期每次实验任务的最后一条要求属于软工哪个领域?结合自己的软工,写出其实践情况、意义、难点?结合项目实践写出软件文档的作用。
- 记录项目及小组的最新进度及工作量。记录项目及小组每个人最新的工作的进度、里程碑、工作量的跟踪图或表。每周更新。每人向组长汇报自己工作。组长汇总进度里程牌,提交小组共同报告。
习题要点 #
Ch1
- 软件危机及其解决
- 这种情况可能表明存在危机,因为软件的不足导致了悲惨的生命损失。
- 用户界面错误
- 用户反馈
- 测试
- 另一个问题是系统边界问题。虽然飞行员使用的航空图不在计算机系统的边界之外,但开发人员需要了解它们以及其中包含的代码和缩写。
- 错误、故障和失效之间的区别,举出错误导致故障的例子、故障导致失效的例子
- 错误是存在于开发人员思维过程中的误解,开发人员敲出了逻辑错误的代码。例如错误地认为圆的直径是其半径的三倍。
- 故障是因为存在错误导致软件以不正常的方式运行而导致的。
- 失败是故障导致整个软件对外表现为出错。
- 技术质量和产品质量的关系
- 例如numpy很厉害,可以在python领域取代matlab
- 但是对于不会python的人来说这不是一个好的产品,因为无法上手
- 系统边界
- 在系统边界上利用合同约定彼此的法律责任。
Ch2
- 过程模型,每种模型如何应对需求的变化
- 瀑布:完全推倒重来(从分析需求开始)
- V模型:同瀑布
- 原型:修改原型供用户确认
- 操作规范:re-tansform规范
- Transfor mational:重做相关转换
- 增量或迭代开发:在下一个增量或下一个迭代中加入新需求
- 螺旋:再转一圈即可
- 适用于软件开发的制造特性,和制造业的不同点?
- 关注确保产品质量,仔细规划和监控流程。
- 然而,软件开发是创造性的,因为几乎每个问题都是新的,需要一个至少部分以前从未做过的解决方案。因此,必须利用想象力来想象解决方案如何以及是否有效。
- 哪些过程模型具有很好的灵活性?为什么
- 增量和迭代模型都为适应需求变化提供了很大的灵活性。然而他们可处理的需求改变的种类不同。
- 增量模型擅长处理添加或删除功能需求,如果删除一个函数,它甚至可能尚未实现,并且可以从计划中删除,以便将来增加。
- 迭代模型最好地处理修改功能需求。在迭代开发中,每个函数都在开始时实现,但通过连续迭代进行细化。细化的过程中可以动态地修改。
Ch3
- AOE活动图计算
- 注意,要首先计算点上的值,而不是边上的值,否则没法写early和earliest表。
- 顶点vi的最早时间earliest(i)是 从开始点v0到该点vi的最长路长度
- 顶点vi的最晚允许时间latest(i)是 项目总时间 - 从结束点vn到该点vi的最长路长度
- 活动
ek = <vi,vj>
的最早时间early(k)是 顶点vi的最早时间earliest(i) - 活动
ek = <vi,vj>
的最晚时间late(k)是 顶点vj的最晚允许时间latest(j) - 边权w(i,j)
-
COCOMO II模型计算
- E:工作量,单位PM:person乘month
- S:代码量,单位KLOC:kilo-lines-of-code,千行代码
- D:开发时间,单位月。
- N:所需人数 = E/D.
- 常用的利用S来估计E:$E=5.25S^{0.91}$。
- E的公式:$E = aS^b$, D的公式:$D = cE^d$. 其中abcd是经验常数,见上表。
- 代码量估计的动态变化:例如增加了10%,那$S' = \frac{S}{1-10%}$.
-
风险暴露
- 风险暴露 = 风险影响 乘 风险概率。
- 利用决策树分析来对风险暴露进行定量。决策树给出所有的决策方案及其各后果发生的概率。损益期望值(EMV)是决策树的一种计算值,是收益和成功概率的乘积。
- 例如如图的决策树,Game A的EMV是
10*0.25 - 1.5 * 0.5 -3* 0.25.
-
生产率通常根据单位时间的单位规模来测量。例如,一个组织机构可能每天生产300行代码或每月生产1200个应用点。用这种方法测量生产率合适吗?
- 它不令人满意,可他是唯一可用的方法。
- 每个人的产出差异大;代码重用量会影响生产力;尺度可能不准确,需要长期进行估计
Ch4
-
UML类图
- 注意:不管是菱形还是三角,其都是画在射出的一方。对于基数关系,射出一方对于摄入一方(例如用户和订单)的数量基数写在射入一方(例如订单方)。
- 关联:代表之间会有交互,但是并不从属或继承。直接用线表示。
- 组合:代表一方属于另一方,用空心菱形表示。特别地,如果同生死共命运,那就用实心菱形。
- 继承:代表一方是另一方的派生类,用空心三角表示。特别地,如果是实现了接口,那线是虚线。
-
UML用例图
-
UML状态图
-
数据流图
-
时态逻辑:在模型的变量中加入时间参数。注意是布尔变量。
例如:
- 开发人员与客户和用户一起合作,定义糕求并指定提议的系统将要做什么。如果出现问题,应该谁负责?
- 三方都要负责,但是开发人员属于专业人员,需要负更多责
- 判定表(决策表)
- 如果有两列指定相同的条件(相同的真值集)但不同的操作,则规范是矛盾的。
- 如果存在某些条件没有列(即没有指定操作),则规范是模棱两可或不完整的。
- 如果一个判定表有两个相同的列,则需求规格说明是元余的。如何知道规格说明是否存在矛盾?判定表的其他哪些特性提醒我们需求中的问题?
- 如果有两列指定相同的条件(相同的真值集)但不同的操作,则规范是矛盾的。如果存在某些条件没有列(即没有指定操作),则规范是模棱两可或不完整的。
- Parnas表
- 是数学函数和关系的表格表示形式。
- 下图是一个二维的表,其行、列表头给出条件,其单元格给出结果的输出方法。如果是X则不存在这种情况(情况无效)。
- 下面的求根公式是一维的。
Ch5
- 体系结构风格
- 管道过滤器
- CS
- P2P:既是客户机又是服务器
- 发布-订阅:一旦该事件发生了,另一构件则进行发布来通知订阅者。
- 信息库:一个中心数据库,多个存取控制器
- 分层
- 信息库、数据抽象、隐含调用(一种发布-订阅类型),以及管道和过滤器,这些风格的高层构件可能有高或低的内聚和耦合吗?
- 都可以做到。
- a。 一个白动银行业务系统,用户可以使用它米存款和取款。 b.图像处理软件,允许用户使用各种操作来修改图片(如旋转、着色、修剪)。
- A) 使用存储库软件架构。中央数据存储(银行服务器)包含有关银行所有帐户的信息。每台自动银行机器都是一个数据访问器,能够检索给定帐户的帐户信息,并将现金提取或存入帐户。C) 使用管道和过滤软件架构。每个不同的操作都指&不同的滤镜(旋转、着色、裁剪等),可以临时组合以根据需要处理图片。
- 割集树
- 最小割集:割集树的所有二层子树的叶结点(即直接短语)分别构成最小割集,或者也可以用布尔代数的方式,从叶结点开始写出其表达式,并化简成与或式,那么其中的每个与项的项目就分别构成最小割集。
- 构建割集树:从根到叶的方向,见到and就把它的孩子结点生成一个集合,作为孩子;见到or就把割集分别拆。
Ch6
- 装饰器模式
Ch8
- 画程序框图,设计各种覆盖用例
- 故障播种
发现的播种故障占播种总数的比例 = 非播种故障占非播种故障总数的比例。
- 需要确保播种的故障和原有故障(即未播种故障)是同一类型并且同样容易发现,但是这很难。
- 两小组测试
- 主要用于解决故障播种的缺点。假定在任何范围内发现错误的效率是相同的,例如对于小组1,可以假设小组1的效率E1=x/n = q/y,即小组1发现的错误的数量占总错误数的比例等于小组1和小组2发现错误数量占小组2发现错误数量的比例。同理算出E2,套公式n=q/E1E2即可得到n的估计。
- 假定有两个小组测试同一个程序。小组1发现了26个故障,小组2发现了30个故障,其中,小组2发现的故障中有18个与小组1发现的故障相同。请利用Fault seeding原理,估算程序中的故障总数。则x=26,y=30,E1=q/y = 18/30, E2=q/x = 18/26. 根据n = q/E1E2 即可估算n。
- 可信度
- 可信度就是软件无故障或故障数量为N的可能性。我们首先断言该软件有N个故障(若N=0则断言软件没有任何故障)。播种S个故障,然后尝试检测故障,在检测故障的过程中发现了n个故障。
- 检测完所有播种故障从而计算可信度:
- Richards公式:不检测完所有播种故障从而计算可信度(这需要假设各故障发生的概率相等):
特别地,对于断言故障数量N=0,求一定置信度下的(例如95%)所需播种数量,例如:
Richards公式:
Ch9
- 因果图
- 用于自动生成无冗余、条理清晰的测试用例,并且可以显著减少测试用例的数量,因为他们都是有必要进行的。
- 输入称为原因,输出称为结果
- 有时,原因之间会有相关性,例如互斥、掩盖等。此时需要额外结点+虚线的方式来标注该信息。例如求斜率那个题里面,C3和C4分别是B为0或不为0,此时就需要注明他们之间的互斥关系。
注意:判定表的列数是因果图中指向结果node的边组合的数量。例如三条边指向一个点,而且他们是与起来的,那就只占用一列。
白盒测试用例设计
- 首先画程序框图,给语句编号
- 然后列出判定(即分支菱形)、条件(即菱形里面布尔表达式的每个子表达式)、路径(注意编号)、语句(注意编号)
黑盒测试用例设计
- 有效等价类即符合输入数据范围或意义要求的输入,无效等价类就是无效输入
Ch1 #
软件危机 #
如何开发和维护软件,以满足不断增长,日趋复杂的需求。(有点像xxx主要矛盾??)
- 成本和进度的估算
- 用户满意度差
- 软件质量差
- 代码可维护性低
- 文档不全面
- 成本上升
- 软件开发生产率的提高赶不上硬件性能的提升和用户需求的增长
软件危机的产生原因
- 忽视前期调研
- 忽视用户与程序员间的沟通
- 不写文档
- 没有QA体系
如何解决软件危机
- 使用良好的开发技术与方法
- 使用好的开发工具以提升效率
- 人员管理合理
计科和软工的关系 #
ε
Error, Fault和Failure #
ε
高质量软件 #
软件产品的质量
- 用户与开发人员
软件开发与维护过程的的质量
- CMM 能力成熟度模型:较为完善的软件项目管理的方法
- ISO 9000质量标准体系:给出软件质量的衡量准则,但是没告诉怎么做
商业价值
CMM #
见上
系统的要素、定义、边界 #
ε
改变软件开发的关键因素 #
ε
增量开发 #
ε
开发过程及团队成员的角色 #
ε
Wasserman规范 #
ε
CASE #
ε
Ch2 #
过程 #
针对一个给定目标的一系列操作步骤称为过程。在软件开发中,过程指的是根据软件开发的相关限制例如成本限制,进行软件开发的一系列活动的集合。
活动:例如需求分析活动,体系结构设计活动
顺序:例如要先分析,再设计
过程的分类 #
按照活动的性质,可以分为技术活动、管理活动。前者就是程序员搞开发,后者是统筹等工作。
过程的描述 #
软件开发过程由输入、实施方案、输出来描述。
意义:更条理,便于安排任务
软件生命周期 #
计划时期
- 分析:问题定义(搞清用户需求)+可行性分析(方案是否可行、成本估算和效益估算)
开发时期
- 分析:需求分析(设计系统所需的功能、设计原型供用户确认)
- 设计:总体设计+详细设计(设计总体结构和各模块,并细化各模块的设计)
- 编码
- 测试:单元测试+集成测试+确认测试+系统测试
运行时期
- 运维:运行与维护(提升软件的寿命和运行效益,对潜在的问题加以修复,对用户新的需求加以满足)
软件开发的9个阶段 #
【ε】
软件过程模型 #
瀑布模型
- 各阶段线性组织,前一阶段完成,后一阶段才可以开始
- 各阶段都要有文档,便于质量保障
- 问题
- 软件的问题不能及时反映,可能等到某一阶段突然发现
- 必须在开始前就说明一切需求
原型模型
- 原型是指用开发周期较短的方式开发一个模拟的系统,例如figma
- 用比较简单的实现方式来在开发的初期阶段构造产品原型,更快地交给用户产品效果,并由用户评价,不断改进原型,这便于开发人员和用户的交流
- 问题
- 不能考虑到实现细节
- 对于复杂的大型系统,难以模拟
- 项目难以规划和管理
- 应用
- 用于需求难以准确定义的系统
- 用于用户和开发人员难以交流的开发过程
增量模型
- 软件逐步被开发,可以先开发基本功能,后期不断添加新功能
- 软件被拆分成多个模块,他们分别走各自的开发流程
- 增量模型是瀑布模型的线性特征与原型模型的迭代特征相结合的产物。每个新增量被线性地开发,但是整个软件的开发过程是迭代的。它和原型的区别是:每一步迭代都是真东西,而不是假的糊弄客户的。
- 优点
- 提升代码可维护性、稳定性
- 降低失败风险
- 缺点
- 难以确定增量粒度
- 每次增量都得保证之前已经开发的部分不被破坏
- 容易退化成“边做边改”,软件开发不再系统化
- 适用于
- 软件更新升级
- 已有产品原型
螺旋模型
- 支持用户需求的动态变化,并进行风险管理
- 是螺旋上升的过程,每一轮都包括需求定义、风险分析、工程实现、用户评价。每一轮都按顺序走这四步,每次都开发出一些新功能,从而进行循环,直到某一次用户评价表示ok了。
- 需求定义:根据上一周期的开发情况,确定本周期需要做出哪些改进或增量
- 风险分析:根据风险情况判断是否要继续开发
- 螺旋模型实际上也是不断迭代的过程,只是每次迭代具有了更加规范的逻辑,特别是加入了动态风险评估环节,能够实时适配用户的需求变动
- 优点
- 支持用户需求的动态变化,从而及时调整决策,降低开发风险
- 软件具有良好的可扩充性和可维护性,每次扩充或维护只需重复螺旋上升过程
- 缺点
- 周期过长,从而开发进度较慢
敏捷开发 #
当今,技术迭代过快,用户需求变化过快,市场竞争对上线速度的要求过高,导致传统的开发模型难以适应开发过程。
敏捷开发以需求进化
为核心,采用迭代、循序渐进
的方法进行开发。在这个过程中,软件被切分
成若干子项目
,每个子项目都分别经过测试
,具有可视、可集成、可运行使用
的特征。
优点
- 计划简单,无需长期计划,开发周期短
- 采用增量开发,能适应用户需求变化
缺点
- 注重人员的交流,忽略了文档,如果人员流动大,代码可维护性下降
- 对开发经验要求高,如果新手过多,老员工就比较累
适用于
- 高风险、需求变化快的项目
- 开发人员可以参与决策的项目(因为注重人员的交流)
四个原则
ε
典型方法
ε
-
Extreme Programming (XP) (极限编程)
- 四个特性ε,12个实践操作ε
-
Crystal (水晶法)
-
SCRUM(并列争球法)
-
Adaptive Software Development(ASD) (自适应软件开发)
静态模型与动态模型 #
ε
Ch3 #
跟踪项目进展 #
project schedule:ε
activity活动:在一段时间内发生的事件
milestone里程碑:活动的完成,称为一个里程碑
precursor前驱:在当前活动之前必须要完成的事件的集合
工期:活动完成所需时间
截止时间:活动必须完成的时间点
Work Breakdown Structure(WBS) #
逐层次对项目的活动进行分解。可以用树表示。例如包饺子这一个大活动分为准备馅、准备皮,而准备馅可以分为买、剁、调配,以此类推。
可以通过以下三种方法来构建WBS树。
- 类比法:参照以往类似的软件开发案例。如果有类似开发经验,可选该方法。
- 自顶向下法:从最大的任务开始,逐层细化下去。如果项目管理者心中有数,能很好进行拆分,可以这样。
- 自底向上法:从各需要完成的工作开始,逐层归约上来。如果对项目没有整体的把握,可以这样。
活动图 Activity Graph #
活动图是由活动(里程碑)构成的图。各活动之间可能存在先后顺序,需要正确进行编排。
确定活动顺序的法则
- 强制依赖关系:必然存在的固有的依赖关系,例如肯定要先开发再测试
- 自由依赖关系:由项目组人为定义的依赖关系,有话好好说,可以商量
- 外部依赖关系:和非项目活动之间的关系,例如客户的某些活动
四种顺序依赖关系
- SS:两个项目必须同时开始
- FS:第一个项目得结束,第二个才能开始
- SF:第一个项目得开始,第二个才能结束
- FF:两个项目必须同时结束
AOE网络 #
Activity on Edges。
主要用于估算整个项目所需的时间、哪些活动是能影响整体进度的(不能按期,就拖累总时间)。
- 活动在边上,比权重是活动的持续时间。
- 进行到顶点,意味着其入度代表的活动都已完成。此时出度代表的活动可以开始。
- 整个网络只能有一个开始点(源点)和一个结束点(汇点)。
例如如图网络,点4的意思是活动活动3、4已经结束,6、7此时可以开始。边5的意思是其代表的活动需要持续2天。
应当采用关键路径法来解决AOE网络问题。给出如下定义。
- 顶点vi的最早时间earliest(i)是 从开始点v0到该点vi的最长路长度
- 顶点vi的最晚允许时间latest(i)是 项目总时间 - 从结束点vn到该点vi的最长路长度
- 活动
ek = <vi,vj>
的最早时间early(k)是 顶点vi的最早时间earliest(i) - 活动
ek = <vi,vj>
的最晚时间late(k)是 顶点vj的最晚允许时间latest(j) - 边权w(i,j)
利用上述定义确定以下两个关键。
- 关键活动:最早时间和最晚时间相等的活动。这些活动若延期就会拖延总进度。应当在最早(=最晚)时间立即开始。
- 关键路径:所有的关键活动构成了关键路径。
计算关键活动的算法
- 从开始点v0开始,设置earliest(0) = 0,每个点的earliest是其最大earliest的前驱的earliest + 边权。按拓扑正序一直这么算,直到结束点vn。
- 从结束点vn开始,设置latest(n) = 项目预计时间,每个点的latest是其最小latest的后继的latest - 边权。按拓扑逆序一直这么算,直到开始点v0。
- 对每条边ek,利用其定义分别计算early(k),late(k),然后找出early(k) == late(k)的边就是我们要的。
CPM #
ε
甘特图 #
ε
可以帮助理解哪些活动可以同时进行
项目人员 #
工作风格:可由四象限图表示
- 外向 - 告诉想法
- 内向 - 倾听想法
- 感性 - 基于感受来进行决策
- 理性 - 基于事实来进行决策
chief programmer team主程序员组
- 每个程序员可以主要与chief programmer 来交流,而不是和其他组员之间进行交流(非扁平化管理了属于是)
- 这可以提升沟通效率,且对于多数程序员是新手的情况有帮助
egoless approach #
ε
工作量估算:COCOMO模型(构造性成本模型) #
用于代码量估计。其他的代码量估计方法还有代码行数法、功能点法、专家评估法。
ε
软件风险 #
- 可能导致对软件项目的事实造成影响和损失,甚至造成失败的事件。例如过于乐观的工作量估计、人员变动、技术难题无法解决、甲乙双方合作破裂等。
- 它是难以预测的,无法避免,但是可以加以控制与管理。
风险暴露、风险影响、风险概率 #
风险暴露 = 风险影响 乘 风险概率。
利用决策树分析来对风险暴露进行定量。决策树给出所有的决策方案及其各后果发生的概率。
损益期望值(EMV)是决策树的一种计算值,是收益和成功概率的乘积。
例如如图的决策树,Game A的EMV是10*0.25 - 1.5 * 0.5 -3* 0.25.
ε
降低风险的3种策略 #
- 避免风险:改变功能需求设计来从根本上规避
- 转移风险:把风险转嫁到其他系统上,或者干脆购买保险
- 假设风险会发生:接受”可能会出现风险“的事实,并尝试避免之
风险杠杆 risk leverage #
风险杠杆 = (降低前的EMV-降低后的EMV)/降低风险所需要的成本
降低风险是需要成本的。量化了”是否有必要去改变现状“。
Ch4 #
SRS #
一个规格说明可以是一份写好的文档、一套图形化 的模型、一个形式化的数学模型、一组使用场景、 一个原型或以上各项的任意组合。
软件需求规格(SRS,Software Requirement Specification)是需求分析任务的最终“产品”, 它是客户、管理者、分析工程师、测试工程师、维 护工程师交流的标准和依据。
软件需求规格描述了系统的数据、功能、行为、性能需求、设计约束、验收标准、以及其他与需求相 关的信息。
敏捷需求建模 #
ε
需求引发:风险承担者 #
客户不总是清楚自己想要什么。项目成功的前提是大家的意见都是一致的。
需求的风险承担者包括委托人、客户、用户、相关领域专家、市场调查员、律师或审计人员
需求源 #
ε
需求类型 #
功能需求
- 实现系统所预期的功能。
- 功能需求可以随着开发进展而不断被编写和细化。
- 应当是完整的(反映全部需求)、一致的(不能前后矛盾)、准确的(不存在模糊的描述或二义性)。
非功能需求
- 与系统的功能无关,而与系统的性质有关
- 例如响应时间、易使用性等
- 实际上是难以检验的
领域需求
- 系统应用在特定的领域中,该领域可能对该系统有其他特殊的需求
- 例如图书管理系统的打印模块,需要在打印后立即删除,否则可能涉及版权问题
需求定义与规格说明 #
ε
需求建模:E-R图 #
需求建模:UML图 #
UML类图中的关系
- 关联
- 聚合
- 组合
- 泛化
需求建模:自动机 #
Ch5 #
软件体系结构文档 SAD #
ε
-
系统综述
-
视图
-
软件单元
-
分析数据和结果
-
设计合理性
-
定义,术语表,缩写词
模块化的 #
如果每个活动都仅由其对应的软件单元实现,并且每个这样的软件单元的输入输出是被明确定义的,那么这个软件设计是模块化的。
所谓明确定义就是其接口能正确翻译其外特征。
体系结构风格 Architecture Styles #
反映了众多系统中共有的结构和语义特性。指导如何把各模块拼成一个完整的有效的系统。
体系结构风格能够指导如何进行粗粒度软件复用。
管道-过滤器风格(数据流风格)
- 如果该模块的功能是对输入的数据进行处理,然后输出出来,则适用于该风格。
- 管道-过滤器、批处理序列都属于这种风格。对于输入数据进行处理的模块就称为过滤器,而不同的这样的模块间的连接可以看做是管道。因此,我们把数据流在功能模块之间的流动形象地称为管道-过滤器模型。值得注意的是,不同的”过滤器“之间是相互独立、互不影响的。
- 优点
- 便于设计,可以认为整个管道-过滤器结构是各过滤器功能的合成
- 可扩展性、可扩展性好
- 支持并发
- 支持一些特定的分析方式,例如计算吞吐率
- 缺点
- 交互式处理能力差
- 难以实现:需要考虑同步、互斥等问题
- 举例
- 编译器体系结构
调用-返回风格
- 实际上就是实现分层,下层为上层提供服务。
- 好处
- 扩展性好
- 复用性好:上下层之间独立
- 能够逐级进行抽象
- 缺点
- 不适用于所有的系统;有的系统很难找到合适的分层抽象方法
- 举例
- OSI七层模型
C/S(客户端-服务器)风格
- 基于客户端和服务端资源不对等的问题、且为了实现资源共享
- 构成三要素:数据库服务器、客户应用程序、网络
- 好处
- 界面可以很丰富
- 安全
- 响应速度快
- 缺点
- 适用面窄
- 用户群固定
- 维护成本高
B/S(浏览器-服务器)风格
- 利用不断成熟的WWW浏览器技术,特别是浏览器嵌入的各种脚本语言(换言之,利用不断发展成熟的Web前端实现)
- 构成三要素:数据库服务器、Web服务器、浏览器
- 好处
- 便于维护和扩展
- 交互性好
- 缺点
- 速度慢,安全性差
- 可能需要经常刷新页面(Ajax等可以解决该问题)
满足质量属性的策略 #
体系结构风格只能保证实现粗粒度上的质量属性的满足。但是对于具体的应用场景,还是需要如下几个具体的策略来进一步满足质量属性。
- 可修改性
- 性能
- 安全性
- 可靠性
- 健壮性
- 易用性
- 商业目标
体系结构的评估与改进:故障树分析 #
故障树
- 故障树是一个逻辑门电路。它用与门、或门来表示对故障的演绎推理。
最小割集(MCS)
- 最小割集是能使故障树顶事件发生的最低限度。例如这棵树有叶结点X1…X6,但是实际上只需要X1、X2同时发生,树顶事件就会发生了。因为没有比”X1、X2同时发生“这个条件更宽松的情况了,所以称为”最低限度“。
- 如图,例如图中的树,可以用逻辑代数直接推导。最后变成与-或式。
T
=A1+A2
=X1 X2 A3+X4 A4
=X1 X2 (X1+X3)+ X4 (X5+X6)
=X1 X2 A1+X1 X2 A3+ X4 X5+X4 X6
=X1 X2+ X4 X5+X4 X6
- 观察最后的与-或式,可以看出{X1,X2},{X4,X5},{X4,X6}是最小割集。
化简故障树
- 把最后的与-或式画出逻辑门的形式,可以得到一棵化简了的等效故障树。
定量分析:利用最小割集求树顶事件的发生概率(又称不可靠度)
- 已知故障树的MCS是K1,K2,…,Kn,并且满足他们的概率分别是x1,x2,…,xn,那么不可靠度就是他们的故障的和。(也就是各最小割集的并集的概率。)
- 值得注意的是,这些MCS之间是需要容斥原理来求其”和事件“的概率的。
KWIC的不同方案对比 #
ε
Active Design Review vs. Passive Design Review #
ε
ROI #
ε
Playback Period #
ε
Product Line 产品线的理念 #
ε
Core Assert Base #
ε
Ch6 #
设计原则 #
模块module:定义输入、输出及其功能特性的程序单元
构件component:可复用的软件单元
设计原则指导如何把系统功能分解成模块。
- 模块化
- ε
- 接口
- ε
- 信息隐藏
- ε
- 增量式开发
- ε
- 抽象
- ε
- 通用性
- ε
模块化原则 #
- 把各不相关的部分进行分离,从而进行独立开发
- 好处
- 每个模块便于理解,从而易于开发
- 便于故障定位和系统修改
耦合度 #
耦合度:衡量模块之间的依赖性。耦合度越低越好。
- 所谓依赖,就是进行了模块间的调用、数据依赖和进行控制。
- 模块之间的调用时,如果接口过于复杂,那么任何一方的细微改变都会直接寄,因此接口的复杂度也会增加两者的耦合度。
- 根据依赖程度分为紧密耦合、松散耦合、无耦合。
- 根据耦合类型可以分为以下几种耦合
- 非直接耦合:一方完全不用依赖另一方而存在。
- 数据耦合:一方只是因为需要另一方的数据而已。这种耦合程度很轻。
- 标记耦合:一方只是因为需要另一方的比较高级的数据而已。所谓高级数据就是除了单个数据以外的那种数据,例如数组、字符串。
- 控制耦合:一方依赖于另一方的控制而得以正常运行。
- 公共耦合:两方需要对统一段数据进行访问或操作。例如OS中的”共享内存“。根据他们的行为,如果是一方只读,一方只写,那么是松散的公共耦合,否则是紧密的公共耦合。
- 内容耦合:如果两个模块已经不分你我了,那就是内容耦合。”内容“是指模块的内容。例如一方直接进入对方访问其内部数据,或双方共用代码等。软件设计时,应当避免出现内容耦合。
内聚度 #
内聚度:衡量模块内部的元素的彼此之间相关程度。内聚度越高越好。
根据元素间的联系程度,划分为几种内聚:
- 偶然内聚:元素之间完全不相关。这会导致这个模块的功能难以理解,没有任何逻辑。
- 逻辑内聚:各元素完成的功能在逻辑上属于相同或相似的
- 时间内聚:各元素完成了需要在同一时间段内完成的任务。缺点是如果出现了问题,难以判断是哪个元素的问题,因为这些元素的任务的执行是没有先后顺序之分的。
- 过程内聚:各元素是一组具有执行先后顺序的执行模块。但是其功能不一定相关,或者说后者不一定需要前者的结果。
- 通信内聚:各元素具有完全相同的输入或输出。因为其都与该输入或输出有关,所以认为内聚度较高。
- 顺序内聚:各元素是一组必须按先后顺序执行的执行模块。而且其都是为了完成同一个功能的不同步骤,而且后者需要前者的结果。
- 功能内聚:各元素为了完成同一个功能而共同协作,互相密不可分。这是最高层次的内聚,设计软件应该保证模块都是功能内聚的。
SP和OOP的对比 #
项目 | SP | OOP |
---|---|---|
设计思路 | 自顶向下、层次化 | 自底向上、对象化 |
程序单元 | 函数 | 对象 |
设计方法 | 程序 = 算法 + 数据机构 | 程序 = 数据 + 方法 |
优点 | 相互独立,代码共享 | 接近于客观世界 |
缺点 | 维护困难 | 客观世界无序性被带入程序中 |
多态 #
多态是指同一个方法在不同的类中有不同的实现。
面向对象七大设计原则 #
单一职责原则
- 一个类应该仅完成专一的一个功能。
- 功能越多,越不会被复用,并且内聚度也就越低。
- 用来指导实现高内聚、低耦合的类设计
- 例子:Flutter中的GridView只负责构成界面,而不能Listener的监听功能。
开闭原则
- 一个类应当与扩展开放,对修改关闭。也就是不需要修改其内部实现,即可实现功能扩展。
- 一个类应当足够抽象,才能具有可扩展的空间。因为可以通过对某些方法的新实现来实现功能。
- 例子:Flutter中的Form需要一个Button参数,但是这个Button可以传入不同类型的Button来丰富按钮的表现形式,而不需要修改Form的底层代码,例如
FlatButton
和MaterialButton
。这个Button参数的设计就符合了开闭原则。
里氏替换原则
- 子类的对象被引用的地方,把该子类对象换成其父类也是没问题的。反过来说,如果某些地方能使用父类对象,那么该处使用其子类对象也是没有问题的。
- 例子:Flutter中的Form需要一个Button参数,但是这个Button可以传入不同类型的Button来实现,例如
FlatButton
和MaterialButton
,且FlatButton
继承自MaterialButton
,那么这两个Button类就符合了里氏替换原则。
依赖倒置原则
- 任何时候都不能依赖于实现细节进行编程。在编程的时候,要面向抽象编程,从而实现普适性。
- 例子:Flutter中的Form需要一个Button参数,但是这个Button可以传入不同类型的Button来实现,而并不关心具体的Button是什么,例如
FlatButton
和MaterialButton
。这个Button参数的设计就符合了依赖倒置原则。
合成复用原则
- 尽量把对象进行组合,来实现新功能。而不是通过对类进行继承来实现。那样只会把类复杂化,破坏系统的封装性,而且局限于此应用场景,它带来的新功能很难在别的地方用到(复用性差)。
迪米特法则
- 一个对象应当仅与有必要与他发生相互作用的对象发生相互作用。这样一来,不同对象间的耦合度就会更低。不用担心”只改了一点东西,整个程序的行为都发生了不可预知的变化“。
- 迪米特法则狭义描述:”必要的相互作用“仅包括直接通信。因此如果不是直接通信,需要第三者进行转发该通信,这样任意两者都是发生直接通信了。这种考虑会显著降低耦合度,但是存在降低通信效率的问题。
- 迪米特法则狭义描述那就是”必要的相互作用“包括了一切形式的通信了。
- 例子:
加上固定负责不同Form的Controller,变成了:
继承与组合 #
- 继承又称白箱复用。
- 它把类复杂化,破坏系统的封装性。
- 它带来的新功能只能局限于此应用场景,几乎不会在别的地方用到(复用性差)。
- 它带来的新功能是静态的,Hard-Coded的。
- 组合又称黑箱复用。
- 它具有更强的灵活性。
- 可以使类之间的耦合度进一步降低。
- 新功能是动态的,甚至在运行时都可以通过这种方式来拼凑新功能。
过程中的UML #
ε
设计模式 #
设计模式是一套被广泛复用的代码设计方法总结。
目的是增强可复用性和可维护性。
设计c主要分为创建型、结构型、行为型的模式。创建型模式主要用于描述如何创建对象,结构型模式用于指导如何通过继承或组合的方式来实现新功能,行为型模式来指导如何实现对象之间的通信和控制。
主要的设计模式如下。
单例模式 #
保证一个类只有一个实例,并且为了访问这个实例,需要设置全局的访问点(保证容易被访问到)。
实现思想
- 防止该类的对象被创建:把构造函数私有化
- 让凡是能引用该类的地方都能引用到该唯一对象:设置
getInstance()
静态方法,调用时返回唯一对象。为了能被static方法调用,在该类内部创建一个static的该类对象且只能被通过getInstance函数返回,其是私有的。
懒汉式实现
class Singleton{
private Singleton(){} //私有化构造函数
private static Singleton instance = null; //确保可以被static的getInstance方法拿到
public static getInstance(){
if (instance == null) instance = new Singleton();
return instance;
}
}
- 懒汉式的思想是:单例类一开始不持有该单例对象,等着需要使用的时候再创建。如果不需要用到该单例,那么性能就会更好。但是第一次用到的时候需要等待对象创建出来。
- 懒汉式是线程不安全的,因为如果两个调用者同时要getInstance,那么该instance可能会创建两次。
饿汉式实现
class Singleton{
private Singleton(){} //私有化构造函数
private static Singleton instance = new Singleton(); //确保可以被static的getInstance方法拿到
public static getInstance(){
return instance;
}
}
- 饿汉式的思想是:管你用不用这个单例,我都一上来就创建他。如果该对象用不到,这可能会导致冗余。
- 他是线程安全的,因为相比于懒汉式,该单例对象已经在那里了,不存在运行时创建逻辑。
建造者模式 #
建造者模式可以为构建一个复杂的对象提供方便。
其由四部分构成。
Product类是被构建的对象:
-
其包含了Product本身,例如用一个
List<Part>
来存储各部件,每次添加部件就添加进这个List里面(我们假设这个类的各个部件用Part进行抽象)。 -
另外包含了对那个List进行操作的方法,例如
void add(Part part);
-
另外还包含其他的方法。也就是具体的功能。这些方法与构建就没有关系了。
Builder类(接口)给出了具体构建方法的抽象:
- 该类负责把product对象创建出来,并一直持有product对象。
- 该类还得有一个getProduct()函数,从而可以拿到创建完成的Product对象。
- 例如
void AddCPU()
为计算机产品类增加CPU;但是并没有给出具体实现,这里也就是空函数体,其没有指出应当添加何种CPU。
ConcreteBuilder类是Builder接口的实现:
- 例如这里对
void AddCPU()
进行实现,其函数体可以是调用了Product类的对象的add方法,其add一个5990X
上去。 - 值得注意的是,这里的
5990X
是在函数体里面调用product的对象的add,例如product.add(5990X)
来实现的,而不是给 AddCPU()传入5990X作为参数实现的。
Director类是指导如何通过调用Builder类的方法来构建Product对象。例如按照何种顺序进行调用。
- 传入一个Builder类的对象builder,然后例如要构建一个电脑,那就依次调用
builder.addMotherBoard()
,builder.addCPU()
,builder.addMemory()
等。 - 值得注意的是,这里虽然传入Builder类的对象,但是传入的肯定是ConcreteBuilder类对象。因为Builder类的这些
addXXX()
没有被实现。这里,对于同一个函数例如builder.addCPU()
,在不同的类中有不同的实现方式,体现了多态的思想。
优点
- Director给出了该产品的统一创建流程。而ConcreteBuilder对Builder有不同的实现,所以可以通过完全一样的创建流程来创建不同的对象。只需要通过ConcreteBuilder的不同实现来方便地构造不同对象。ConcreteBuilder之间是独立的。
- 可以更精细地控制创建流程。
缺点
- 如果要求构建的对象在构成成分上有非常大的差异,那么统一的Director就不再适用。因为很难使用完全相同的构建流程来构建他们。
- 如果需要非常多不同的这种对象,那么ConcreteBuilder就会过多,导致系统过于复杂。
观察者模式 #
它实现了一种特别的对象之间的依赖关系,这种关系是一对多的。当一个对象(被观察者)发生某些变化,自动通知其他的一些对象(观察者)。
同一个被观察者可以被多个不同的观察者同时观察,而这些观察者之间是独立的,可以动态增删。
观察者模式又称发布-订阅(Publish-Subscribe)模式、模型-视图(Model-View)模式、源-监听器(Source-Listener)模式、从属者(Dependents)模式。
被观察者维护一个观察者列表,如果发生了改变,就逐个通知观察者列表中的成员。通过动态操作观察者列表来增删观察者。
观察者只需要实现一个”信息接收“函数,用于监听被观察者给自己发送的信息,从而做出其他动作响应。
优点
- 值得注意的是,具体的观察者监听抽象的被观察者接口发来的消息,具体的被观察者给抽象的观察者接口发消息。换言之,被观察者只知道给接口发消息,并不知道是给哪种实现发的;观察者只知道监听观察者接口,不知道是观察者的哪种实现发的,他们是松耦合的。也就是说,如果要新增被观察者或观察者的类,只需要给出新的被观察者或观察者接口的实现,而不需要修改接口的定义,这就符合开闭原则。
缺点
- 如果被观察者过多,那逐个通知就比较屎山。
- 如果观察者和被观察者是成环依赖,会崩溃。
- 没有机制能够给出”被观察者具体是如何变化的“这一细节信息,只是能指导它发生了变化了而已。
中介者模式 #
考虑一个局域网,它采用星形连接,每个机器都链接到局域网的其他任意机器。这些机器之间时不时地要进行通信,拔掉任何一根网线虽然可能不会造成太大问题(因为何时发生通信是不可预知的),但是也会造成大问题(你不知道他俩是不是强耦合在一起的)。
这个局域网让人头大。如果不采用这种连接,而是中间加一个交换机,就可以解决这个问题。所有的机器都跟交换机通信即可,交换机负责传达信息和协调处理,使各终端之间在很大的程度上实现了解耦,并且防止出现通信线路冲突。
这里的交换机就是中介者类,它负责传达信息、协调各方。传达信息是指通过它来实现这种复杂的相互通信关系(取消对象间的直接通信),协调各方的意思是实现一些多边功能,例如房产中介商作为中介者,可以为租客和出租者进行配对。
和观察者模式类似,这里的中介者只负责对抽象的同事接口进行信息传达和协调,这里的同事也只根抽象的中介者接口进行通信。因此它可以支持在不同类别的对象之间进行通信解耦。
好处
- 解耦,简化对象之间的交互
- 提升了对象的可重用性,因为他们的耦合度降低了
缺点
- 可能会使交互变得复杂,因为一些细节被隐藏
模式与框架 #
ε
Specialization Index #
ε
Ch7 #
编程规范与标准 #
P267
编程指导原则 #
P269
通用性P273
复用P276
文档 #
P276
内部文档与外部文档
编程过程 #
XP P281
结对编程P281
融合、小组协调(未找到)
Ch8 #
故障的类型 #
P288
测试步骤 #
P292
黑盒测试 #
P293
白盒测试 #
P294
单元测试 #
P295
测试点与测试用例:P301
集成测试 #
6种策略 P305-310
故障播种 #
P317
两小组测试 #
P318 注意例子
可信度 confidence #
P318
Ch9 #
软件故障根源 #
P325
测试过程 #
P327
构建或集成计划 #
P328
Spin(旋转) #
P328
配置管理 #
P329
回归测试 #
P330
控制版本和发布的三种方式 #
P331
测试小组 #
P333
因果图 #
P337
alpha测试、beta测试和并行测试 #
P347
测试文档 #
P349