【哈工大软件构造】学习笔记10 第十章、第十一章、第十二章
目录
第十章 面向可维护性的构造技术
1 软件维护和演化
2 可维护性的度量
3 模块化设计和模块性准则
模块划分的五个准则
模块设计的五个原则
耦合度和聚合度
4 OO设计准则:SOLID
SRP
OCP
LSP
ISP
DIP
总结
5 语法驱动的构造
正则语法和正则表达式
第十一章 面向可复用性和可维护性的设计模式
1 创建型模式
工厂方法模式
2 结构型模式
(1)适配器模式
(2)装饰器模式
3 行为类模式
策略模式
模板模式
迭代器模式
Vistor Pattern
设计模式的共性和不同
第十二章 面向正确性与健壮性的软件构造
什么是健壮性和正确性
如何度量健壮性和正确性
Java中的错误和异常
异常处理
什么是异常
异常的分类
Checked和Unchecked异常
使用throws声明Checked异常
如何抛出异常
创建异常类
捕获异常
重抛和链接异常
finally 语句
断言
什么是断言
使用断言
断言和异常的区别
第十章 面向可维护性的构造技术
1 软件维护和演化
- 修复错误和改善性能。软件开发中最困难的工作之一,涉及其他所有环节。处理来自用户报告的故障。还需要测试所做的修改,回归测试,记录变化。
- 除了要修复问题,在修改中不能引入新的故障。
- 最大的问题:没有足够的文档记录和测试用例。
- 纠错性(25%)、适应性(21%)、完善性(50%)、预防性(4%)
- 软件演化是软件维护的重要部分:对软件进行持续的更新,软件的大部分成本来自于维护阶段。from 1 to n
- 软件维护从设计和开发阶段就开始了,在这个阶段就要考虑将来的可维护性。设计方案“east to change”。
- 方法:模块化、OO设计原则、OO设计模式、基于状态的构造技术、表驱动的构造技术、基于语法的构造技术。
2 可维护性的度量
- 设计结构是否足够简单
- 模块之间是否松散耦合
- 模块内部是否高度聚合
- 是否使用了非常深的继承树,是否使用了委托代替继承
- 代码的圈/环复杂度是否太高(独立路径的数量)
- 是否存在重复的代码
- 可维护指数MI,根据各指标使用公式算出来的
3 模块化设计和模块性准则
高内聚,低耦合,分离关注点,信息隐藏
模块划分的五个准则
- 可分解性:大模块分解为子模块
使模块之间的依赖关系显示化和最小化
- 可组合性:小模块组合在一起,是模块可在不同的环境下复用
- 可理解性
- 可持续性:发生变化时,受影响范围最小
- 出现异常之后的保护:出现异常后受影响范围最小
模块设计的五个原则
- 直接映射(模块的结构与现实世界中问题领域的结构保持一致)
- 尽可能少的接口
- 尽可能小的接口(模块通讯,尽可能的交换少的信息)
- 显式接口
- 信息隐藏
耦合度和聚合度
耦合度:模块间的接口数目和每个接口的复杂度
要做到到高内聚低耦合
4 OO设计准则:SOLID
(SRP) The Single Responsibility Principle 单一责任原则
(OCP) The Open-Closed Principle 开放-封闭原则
(LSP) The Liskov Substitution Principle Liskov替换原则
(ISP) The Interface Segregation Principle 接口聚合原则
(DIP) The Dependency Inversion Principle 依赖转置原则
SRP
ADT中不应该有多于一个原因让其发生变化,否则就拆分开
OCP
对拓展性的开放:模块的行为可拓展
对修改的封闭:模块自身的代码是不应被修改的
LSP
子类型必须能够替换其基类型
ISP
不能强迫客户端依赖于它们不需要的接口:只提供必需的接口
避免接口污染
避免胖接口
只提供每个客户端需要的接口
DIP
高层模块不应该依赖于低层模块,二者都应该依赖于抽象。抽象不应该是依赖于实现细节,实现细节应该依赖于抽象。使用接口和抽象类。
通过这种实现方式,无论具体的reader和writer怎么实现,如何改变,都不需要重写copy。
总结
5 语法驱动的构造
连接:x ::= y z
重复:x ::= y*
选择:x ::= y | z
x是y或者空:x ::= y?
正闭包:x ::= y+
字母类:x ::= [a-c] equals to x ::= 'a' | 'b' | 'c' equals to x ::= [abc]
x ::= [^a-c] equals to x ::= 'd' | 'e' | 'f' .....
正则语法和正则表达式
正则语法:简化之后,可以表达为一个产生式,而不包含任何非终止节点
正则表达式:去除引号和空格
. any single character
\d any digit, same as [0-9]
\s any whitespace character, including space, tab, newline
\w any word character, including underscore, same as[a-zA-Z_0-9]
\., \(, \), \*, \+, ... 需要转义字符
Grammar定义语法规则(BNF格式的文本),Parser generator根据语法规则产生一个parser,用户利用parser来解析文本,看其是否符合语法定义并对其做各种处理(例如转成parse tree)。
java.util.regex
第十一章 面向可复用性和可维护性的设计模式
除了类本身,设计模式更强调多个类/对象之间的关系和交互过程,比接口/类复用的力度更大
包括创建型模式,结构型模式和行为类模式
1 创建型模式
工厂方法模式
也叫作虚拟构造器。
当client不知道/不确定要创建哪个具体类的实例,或者不想在client代码中指明要具体创建的实例时,用工厂方法。
定义一个用于创建对象的接口,让该接口的子类型来决定实例化哪一个类,从而使一个类的实例化延迟到其子类。
静态工厂方法: 既可以在ADT内部实现,也可以构造单独的工厂类。
相比于通过构造器(new)构建对象:
- 静态工厂方法可具有指定的更有意义的名称
- 不必在每次调用的时候都创建新的工厂对象
- 可以返回原返回类型的任意子类型
缺点:每增加一种产品就要增加一个新的工厂子类
遵循了OCP原则。
2 结构型模式
(1)适配器模式
将某个类/接口转换为client期望的其他形式。解决类之间接口不兼容的问题。通过增加一个接口,将已存在的子类封装起来,client面向接口编程,从而隐藏了具体子类。也叫作wrapper。
加个适配器以便于复用。
可采用继承或者委托的方法。
(2)装饰器模式
对一种数据结构进行不同的扩展,可以使用继承让每个子类实现不同的特性。但如果需要特性的任意组合,继承就不再适用,如果使用继承就会导致组合爆炸,有大量的代码重复。
问题就在于如何为对象增加不同侧面的特性。可以对每一个特性创造子类,然后通过委派机制增加到对象上。以递归的方式实现。
就像套娃一样。
3 行为类模式
策略模式
有多种不同的算法来实现同一个任务,但需要client根据需要动态切换算法,而不是写死在代码里。
例如排序有冒泡排序,快排等等。
可以为不同的实现算法构造抽象接口,利用委托,运行时动态传入client倾向的算法类实例。
优点:容易拓展新的算法实现;把算法和客户上下文分隔开。
使用委派来实现
模板模式
做事情的步骤一样,但具体方法不同。
共性的步骤在抽象类内公共实现,差异化的步骤在各个子类中实现。模板方法定义了一个算法的步骤,并允许子类作为一个或多个步骤提供实现。
使用继承和重写实现模板模式,被广泛用在框架中,白盒框架。例子如下
public abstract class CarBuilder {protected abstract void BuildSkeleton();protected abstract void InstallEngine();protected abstract void InstallDoor();// Template Method that specifies the general logicpublic void BuildCar() { //通用逻辑BuildSkeleton();InstallEngine();InstallDoor();}
}public class PorcheBuilder extends CarBuilder {protected void BuildSkeleton() {System.out.println("Building Porche Skeleton");}protected void InstallEngine() {System.out.println("Installing Porche Engine");}protected void InstallDoor() {System.out.println("Installing Porche Door");}
}public class BeetleBuilder extends CarBuilder {protected void BuildSkeleton() {System.out.println("Building Beetle Skeleton");}protected void InstallEngine() {System.out.println("Installing Beetle Engine");}protected void InstallDoor() {System.out.println("Installing Beetle Door");}
}
迭代器模式
客户端希望对放入容器/集合类的一组ADT对象进行遍历访问,而无需关心容器的具体类型。也就是说,不管对象被放到哪里,都应该提供同样的遍历方式。
让自己的集合类实现Iterable接口,并实现自己的独特Iterator迭代器,允许客户端利用这个迭代器进行显示或隐式的迭代遍历。
Vistor Pattern
对特定类型object的特定操作(visit),在运行时将二者动态绑定到一起,该操作可以灵活更改,无需更改被visit的类。
本质上:将数据和作用于数据上的某种/些特定操作分离开来。
为ADT预留一个将来可扩展功能的“接入点”,外部实现的功能代码可以在不改变ADT本身的情况下在需要时通过委派介入ADT
迭代器:以遍历的形式访问集合数据而无暴露其内部表示,将“遍历”这项功能委托到外部的迭代器对象。
Visitor: 在特定的ADT上执行某种特定操作,但该操作不在ADT内部实现,而是委托到独立的visitor对象,客户端可灵活扩展/改变visitor的操作算法,而不影响ADT。
策略和游客都是通过委派建立两个对象的动态联系。
但是游客强调的是外部定义某种对ADT的操作,该操作于ADT自身关系不大(只是访问ADT),故ADT内部只需要开放accept(visitor)即可,client通过它设定visitor操作并在外部调用。
而Strategy则强调是对ADT内部某些要实现的功能的相应算法的灵活的替换。这些算法是ADT功能的重要组成部分,只不过是委托到外部strategy类而已。
区别:visitor是站在外部client的角度,灵活增加对ADT的各种不同操作(哪怕ADT没实现该操作),strategy则是站在内部ADT的角度,灵活变化对其内部功能的不同配置。
设计模式的共性和不同
第十二章 面向正确性与健壮性的软件构造
什么是健壮性和正确性
健壮性:系统在不正常输入或不正常外部环境下仍能够保持正常的程度。
面向健壮性的编程:处理未期望的行为和错误终止;即使终止执行,也要准确/无歧义的向用户展示全面的错误信息;错误信息有助于进行debug。
准则:程序员应总是假定用户恶意、假定代码失败。严于律己,宽以待人。
- 封闭实现细节,限定用户的恶意行为。
- 考虑极端情况
正确性:程序按照spec加以执行的能力,是最重要的质量指标。
正确性倾向于直接报错,健壮性则倾向于容错。
对比的例子如下:
健壮性:让用户变得更容易:出错也可以容忍,程序内部已有容错机制。
正确性:让开发者变得更容易:用户输入错误,直接结束。
对外的接口,倾向于健壮;对内的实现,倾向于正确。
可靠性=健壮性+正确性
如何度量健壮性和正确性
平均故障间隔时间(MTBF):指两次故障之间的平均工作时间。总的运行时间/总的故障次数。
MTTF(故障前平均时间)描述不可修复系统
残余缺陷率:每千行代码中遗留的bug的数量
Java中的错误和异常
不要抛出Error对象。
内部错误:程序员通常无能为力,一旦发生,想办法让程序优雅的结束。
异常:自己的程序导致的问题,可以捕获可以处理。
异常处理
什么是异常
程序执行中的非正常事件,导致程序无法再按预想的流程执行。
Exceptions将错误信息传递给上层调用者,并报告“案发现场”的信息。
return之外的第二种退出途径。若找不到异常处理程序,整个系统完全退出。
不使用处理机制的缺点:正常的逻辑代码与错误处理代码交织在一起。使用错误处理机制,把它们分隔开。
异常的分类
RuntimeException:运行时异常,由程序员在代码里处理不当造成,例如数组越界,只要修改代码就可。
其他异常:由外部原因造成。能想到可能会出现这种错误,但不可避免,要进行处理。
Checked和Unchecked异常
编译器可帮助检查程序是否已抛出或处理了可能的异常。(Checked Exceptions)
编译器不会检查Unchecked Exceptions。但在执行时出现就会导致程序失败,是程序中的潜在bug。类似动态类型检查。
Checked异常处理操作,关键词
- try
- catch
- finally
- throws(声明异常,本方法可能会发生XX异常)
- throw(抛出XX异常)
使用Unchecked还是Checked
当客户端可以通过其他的方法恢复异常,那么采用Checked
当客户端对出现的这种异常无能为力,那么采用Unchecked
尽量使用unchecked exception来处理编程错误。如果客户端对一个Checked异常无能为力,可以把它转换为Unchecked异常,客户端会被挂起,并打印异常信息。
try{
..some code that throws SQLException
}catch(SQLException ex){
throw new RuntimeException(ex);
}
总结
- Checked异常应该让客户端从中得到丰富的信息,以做出相应的处理
- 要想让代码更加易读,倾向于使用Unchecked异常来处理程序中的错误
错误可预料,但无法预防,但可以有手段从中恢复,此时使用Checked异常,否则使用Unchecked异常。
Use checked exceptions for special results (i.e., anticipated situations)
Use unchecked exceptions to signal bugs (unexpected failures)
使用throws声明Checked异常
“异常(Checked)”也是规约的一部分,在post-condition中刻画。
Unchecked异常不用声明。
应该声明的异常包括调用的其他函数传来的异常和自己造出的异常。如果没有handler处理抛出的Checked异常,程序就会中止执行。
LSP:子类型可以抛出父类型抛出的异常的子类型,也可以不抛出任何异常。但不能抛出比父类型更宽泛的异常。
如何抛出异常
抛出一个异常类的对象,可以添加参数把信息传递给client
throw new EOFEXception();String gripe = "Content-length: " + len + ", Received: " + n;
throw new EOFEXception(gripe);
创建异常类
public class FooException extends Exception {public FooException() { super(); }public FooException(String message) { super(message); }public FooException(String message, Throwable cause) { super(message, cause); }public FooException(Throwable cause) { super(cause); }
}
捕获异常
异常发生后,如果找不到处理器,就终止执行程序,在控制台打印出stack trace。
try {codemore codemore code
}
catch (ExceptionType e) {handler for this type
}
try中代码抛出异常,并被catch捕获之后,try中抛出异常代码之后的代码不会再执行,执行捕获异常的catch中代码。
也可以不在本方法内处理,而是传递给调用方,由client处理。
注意:如果父类型的方法没有抛出异常,那么子类型中的方法必须捕获所有的Checked异常。
捕获多种异常,把父类型写下面。
重抛和链接异常
在catch中抛出异常,目的是更改异常的类型,更方便client端获取错误信息并处理。
try {access the database
}
catch (SQLException e) {throw new ServletException("database error: " + e.getMessage());
}
但这么做最好保留“根原因”
try {access the database
}
catch (SQLException e) {Throwable se = new ServletException("database error");se.initCause(e);throw se;
}
Throwable e = se.getCause();
finally 语句
当异常抛出时,方法中正常执行的代码被终止,如果异常发生前曾申请过某些资源,那么异常发生后这些资源要被恰当的清理。可以使用finally。
finally部分的代码,无论是否捕获异常都会被执行。
- 未抛出异常:不执行catch,执行try之后执行finally。再执行模块以外的语句
- 捕获抛出的异常:不执行try中抛出异常之后的代码,执行catch,finally,再执行模块以外的语句
- 抛出异常但未被捕获:不执行try中抛出异常之后的代码,不执行catch,执行finally然后程序终止。
finally的优先级最高,以下代码返回false
static boolean decision() {try {return true;} finally {return false;}
}
断言
最好的防御就是不要引入bug。主要针对正确性,不符合,抛出error
避免扩散
什么是断言
在开发阶段的代码嵌入,检验某些“假设”是否成立。
可以添加信息
assert (number >= 0) : "number is negative: " + number;
断言是对代码中程序员所做假设的文档化,不会影响运行时性能(在实际使用时,断言会被disabled)。
使用断言
保证:内部不变量、表示不变量、控制流不变量、方法的前置条件和后置条件符合要求或保持不变。
因为在使用时,断言会被disabled。所以在断言中不要包含业务逻辑。
// don't do this:
assert list.remove(x);// do this:
boolean found = list.remove(x);
assert found;
程序之外的事情,不受控制的不要乱断言。断言是为了保证程序正确性。
使用异常处理文件/网络/用户输入等。
断言和异常的区别
断言保证正确性(开发阶段),处理绝不应该发生的错误
错误、异常处理保证健壮性(写在开发阶段,处理运行时的异常),处理预料到可以发生的不正常情况。
有些人认为不应该针对参数的合法性使用断言。(看参数的来源,内部就用断言,外部就抛出异常)
开发阶段尽可能用断言消除bugs,在发行版本里用异常处理机制处理漏掉的错误。
【哈工大软件构造】学习笔记10 第十章、第十一章、第十二章相关推荐
- 哈工大软件构造学习笔记1 Views and Quality Objectives of Software Construction
先要搞清楚软件构造的对象是什么,如何刻画,在关注如何构造. 1,Five key quality objectives of software construction 软件构造的五个关键质量目标 容 ...
- 哈工大2022春软件构造学习笔记1
课程概述 第一部分:软件构造基础 第二部分:ADT+OOP 第三部分:面向可复用性和可维护性的软件构造 第四部分:面向健壮性与正确性的软件构造 第一章 软件构造的多维度视图和质量目标 软件构造的多维度 ...
- 软件构造学习笔记(九)面向复用的软件构造技术
目录链接 Part I What is Software Reuse? Part II How to measure "reusability"? Part III Levels ...
- 2020春季学期哈工大软件构造学习心得一
前言: 今年是特殊的一年,由于新冠病毒的爆发,导致我们无法正常开学,所以网上开课如期进行,其中软件构造是这学期我们要面临的巨大挑战. 准备工作: 上学期末得知本门课程需要学习Java语言进行编程,所以 ...
- 软件构造学习笔记-第八周
本周重点是Liskov可替换原则.它要求父类和子类的行为一致性,子类要有更强的不变量.更弱的前置条件.更强的后置条件.在该原则的要求下,每个子类都可以对父类进行替换.这在开发过程中会带来极大的便利,在 ...
- 哈工大软件构造2022笔记(持续更新----1)
Class 1 第0节的主要内容是讲课程要求: 实验占35分,个人博客占5分,期末考试占60分(闭卷) 在cms中加入课程:链接在老师发的ppt里面有,这里就不展示了. 实验要求: 在Java+Ecl ...
- 2020春季学期哈工大软件构造学习心得二
前言 上一章主要讲了软件构造的结果形态以及如何是一个"好"的软件 这一章主要学习软件开发遵循着一个什么样的过程 - 软件生命周期与配置管理 From 0 to 1,from 1 t ...
- 软件构造学习笔记ATD
在面向对象的编程中,ADT的编写十分重要,与传统的c语言不同,面向对象的编程更加商业化一点,所以保密需要做好,有点商业机密的感觉.如何设计良好的抽象数据结构,通过封装来避免客户端获取数据的内部表示,避 ...
- 软件构造学习笔记-第九周、第十周
因为本周五开始五一假期,所以只有一节软件构造课.因为内容还属于创建模式.结构模式.行为模式.将该堂课的内容整合到本博客中.本周的重点是程序开发模式,在写代码之前首先充分考虑采用哪种模式更有利于开发.维 ...
最新文章
- 武汉python培训哪一家好一些-武汉哪个Python培训机构比较好?
- UWP 查找模板中的控件
- android动画封装,Android属性动画封装,快速构建动画
- 如何查看linux系统的体系结构
- 158.5. manifests
- leetcode 278. 第一个错误的版本(Java版)
- 快速排序算法的优化思路总结
- mysql无法识别双引号_sqlite3迁移mysql问题集合攻略
- mysql省市联动_sql全国 省市 联动级联
- selenium入门详细指南(附淘宝抢购案例)
- 调用谷歌Chrome浏览器打不开网页崩溃了
- 炫酷粒子表白,双十一脱单靠它了!
- 20190713 关于session串号问题的记录
- 文科生学计算机有前途吗,文科生学习计算机有难度吗?计算机的前景如何?
- Python的编译器
- python的third party llibs
- et格式如何转换Excel
- Eclipse 从SVN检出项目之《文件夹 “” 已不存在 》
- KesionCMS V9.03 Final SQL注射
- MPEG LA推出一站式Qi无线充电许可