当前位置:网站首页>【HIT-SC-MEMO6】哈工大2022软件构造 复习笔记6
【HIT-SC-MEMO6】哈工大2022软件构造 复习笔记6
2022-08-04 05:32:00 【XMeow】
六、健壮性
6.1 健壮性和正确性的含义与区别
- 健壮性:系统在不正常输入或不正常外部环境下仍能够表现正常的程度
- 面向健壮性的编程
- 处理未期望的行为和错误终止
- 即使终止执行,也要准确/无歧义的向用户展示全面的错误信息
- 错误信息有助于进行debug
- Robustness principle (Postel’s Law):对自己的代码要保守,对用户的行为要开放
正确性:程序按照spec加以执行的能力,是最重要的质量指标!
- 区别
- 正确性:永不给用户错误的结果
- 健壮性:尽可能保持软件运行而不是总是退出
- 正确性倾向于直接报错(error),健壮性则倾向于容错(fault-tolerance)
6.2 错误与异常处理
Throwable
Java中的内部错误(Error) & 异常(Exception)
Error 内部错误 and Exception 异常
- 内部错误:程序员通常无能为力,一旦发生,想办法让程序优雅的结束
- 用户输入错误
- 设备错误
- 物理限制
- 异常:你自己程序导致的问题,可以捕获、可以处理
- 由于程序员对Error通常无法预料无法解决,因此重点关注可被解决的Exception
异常处理
Java中Exception可以被分为两个部分,蓝色的运行时异常和绿色的其他异常。
- 运行时异常:由程序员在代码里处理不当造成,在源代码中引入了故障,而如果在代码中提前进行验证,这些故障就可以避免。动态类型检查的时候会发现这种异常,而一旦出现,代码就必然有错误,可以通过调试解决。
- 其他异常:由外部原因造成,程序员无法完全控制的外在问题所导致的,即使在代码中提前加以验证,也无法完全避免失效发生。
Java’s exception handling consists of three operations:
- Declaring exceptions (
throws
)声明“本方法可能会发生XX异常”- Throwing an exception (
throw
)抛出XX异常- Catching an exception (
try
,catch
,finally
) 捕获并处理XX异常
Checked & Unchecked Exceptions区别
Checked exception | Unchecked exception | |
---|---|---|
Basic | 必须被显式地捕获或者传递 (try-catch-finally-throw ),否则编译器无法通过,在静态类型检查时就会报错 | 异常可以不必捕获或抛出,编译器不去检查,不会给出任何错误提示 |
Class of Exception | 继承自Exception 类(上图中的绿色部分) | 继承自RuntimeException 类(上图中的蓝色部分) |
Handling | 从异常发生的现场获取详细的信息,利用异常返回的信息来明确操作失败的原因,并加以合理的恢复处理 | 简单打印异常信息,无法再继续处理 |
Appearance | 代码看起来复杂,正常逻辑代码和异常处理代码混在一起 | 清晰,简单 |
选取checked exception还是unchecked exception可遵循下面的原则:
- checked exception:如果客户端可以通过其他的方法恢复异常,而对开发者来说错误可预料但不可预防,它的出现已经脱离了程序能够掌控的范围。
- unchecked exception:如果客户端对出现的这种异常无能为力,而对开发者来说错误可预料可预防,它可以通过调整程序来避免出现。
Checked异常的处理机制
自定义异常类
可以选择创建自定义异常类型:
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); }
}
声明异常&抛出异常
使用throw
关键字抛出异常,如:throw new EOFException()
;
String readData(Scanner in) throws EOFException // 声明:本函数可能发生该异常
{
. . .
while (. . .)
{
if (!in.hasNext()) // EOF encountered
{
if (n < len)
throw new EOFException(); // 异常在这里发生了
}
. . .
}
return s;
}
捕获异常 & 处理异常
可以使用try-catch
语法对抛出的异常进行处理,也可以用throws
语法将异常抛给上一级调用,然后在上一级中使用try-catch
处理。
public static void fun() throws IOException {
// 已声明可能抛出的异常
... } public static void main(String args[]) {
try{
fun();
} catch (IOExeption e) {
// 延迟到此处捕获
e.printStackTrace();
}
}
所以,try-chtch
所捕获到的异常可能有两个来源,一是自己内部的代码产生的,二是调用了其他的方法,并且该方法未处理抛给了本方法。
- 本来catch语句下面是用来做exception handling的,但也可以在catch里抛出异常
- 这么做的目的是:更改exception的类型,更方便client端获取错误信息并处理
try {
...
}
catch (AException e) {
// 捕获到A异常
// 抛出B异常,并带上异常消息
throw new BException( " xxx error:" + e. getMessage());
}
Finally
- 处理异常时释放资源:当异常抛出时,方法中正常执行的代码被终止,如果异常发生前曾申请过某些资源,那么异常发生后这些资源要被恰当的清理。
所以形成了try-catch-finally
结构。不管程序是否碰到异常,finally
都会被执行。
LSP&异常
- 如果子类型中override了父类型中的方法,那么子类型中方法抛出的异常不能比父类型抛出的异常类型更宽泛——异常不能逆变
- 子类型方法可以抛出更具体的异常,也可以不抛出任何异常——异常可以协变
- 如果父类型的方法未抛出异常,那么子类型的方法也不能抛出异常
目的还是为了能够让客户端能够用统一的方式处理不同类型的对象。
6.3 断言与防御式编程
防御式编程的基本思想
- 最好的防御就是不要引入bug
- 如果无法避免
- 尝试着将bug限制在最小的范围内
- 限定在一个方法内部,不扩散
- Fail fast:尽快失败,就容易发现、越早修复
断言Assertion
断言:
- 在开发阶段的代码中嵌入,检验某些“假设”是否成立。若成立,表明程序运行正常,否则表明存在错误。
- 断言即是对代码中程序员所做假设的文档化,也不会影响运行时性能(在实际使用时,
assertion
都会被disabled) - 语法:
assert condition : message
;- 所构造
message
在发生错误时显示给用户,便于快速发现错误所在
- 所构造
作用:
- 最高效、快速地找出/改正bug
- 提高可维护性
Assertion | Exception |
---|---|
提高“正确性” | 提高“健壮性” |
错误/异常处理是提高健壮性,处理外部行为;断言是提高正确性,处理内部行为 | 使用异常来处理你“预料到可以发生”的不正常情况;使用断言处理“绝不应该发生”的情况 |
内部行为 | 外部行为 |
处理“绝不应该发生”的情况 | 处理“可以预料到会发生”的情况 |
assert
使用场所:
- 内部不变量:判断某个局部变量应该满足的条件,
assert x > 0
- 表示不变量:
checkRep()
- 控制流不变量:例如,若不想让程序走向
switch-case
的某个分支,则可以用断言直接在分支上assert false
; - 方法的前置条件:判断传入参数是否满足前置条件
- 方法的后置条件:判断结果时候满足后置条件
6.4 代码调试
防御式编程 >> 测试 >> 调试
调试的基本过程和方法
调试 Debug:
- Debug的目的是寻求错误的根源并消除它
- Debug占用了大量的开发时间
- Debug是测试的后续步骤:test发现问题,debug消除问题
测试步骤
常用方法:假设-检验
Diagnose:
Instrumentation 测量
- 即最基本的打印内容,把要观察的对象全部打印到控制台(最后要把这些语句都删掉)。
可使用log功能统一管理。
- 即最基本的打印内容,把要观察的对象全部打印到控制台(最后要把这些语句都删掉)。
Divide and Conquer 分治
- 防狼围栏算法
Slicing 切片
- 寻找特定有关于“错误变量”的代码部分
Focus on difference 寻找差异
- 充分利用版本控制系统,找出在哪个commit之后出现了bug症状
- Delta Debugging基于差异的调试:两个测试用例,分别通过/未通过;通过查找二者所覆盖的代码之间的差异,快速定位出可能造成bug的代码行。
Symbolic Debugging 符号
- 符号化执行
- 不需输入特定的值,使用“符号值”(而非“实际值”)作为输入,解释器模拟程序执行,获得每个变量的“符号化表达式”,从而可判断是否执行正确。
Debugger 调试器
使用日志开展调试
日志管理工具:
- JDK logging
- Apache Log4j
java.util.logging
通过设定日志级别来确定要log哪些信息,也可以通过Handler将日志存储在不同的地方。
日志级别(从高到低):SEVERE
、WARNING
、INFO
、CONFIG
、FINE
、FINER
、FINEST
设定级别:logger.setLevel(Level.INFO)
;
Logger
- 可以设定全局的logger,但是会造成信息混乱的后果,于是需要定义自己的logger,然后在程序中使用它。
import java.util.logging.*;
private static final Logger myLogger = Logger.getLogger("com.mycompany.myapp");
//often using class name as logger name
//或者用这种方式
public class LogTest {
static String strClassName = LogTest.class.getName(); //get class name
static Logger myLogger = Logger.getLogger(strClassName);
// using class name as logger name
...
myLogger.info(“ XXXX ”);
}
Logging Handlers
- Handler是日志的输出位置,缺省输出到控制台。
- 日志处理器也需要设定日志级别。
- Handler类型:
StreamHandler
、ConsoleHandler
、FileHandler
、SocketHandler
、MemoryHandler
… - 设定处理器:
logger.addHandler(new FileHandler(“test.txt”)
- 日志的格式:
SimpleFormatter
、… - 设定格式:
fileHandler.setFormatter(new SimpleFormatter())
6.5 软件测试与测试优先的编程
- Unit单元测试:function、class
- Integration集成测试:classes、packages、components、subsystems
- System系统测试:system
- Regression回归测试:修改后再测试
- Acceptance验收测试
Static/Dynamic 测试
- Static:
- 不执行程序
- Reviews
- walkthroughs 预排/演练/走查
- inspections 视察
- Dynamic:
- 执行程序,有测试用例
- Debugger
使用JUnit进行单元测试(Unit Test)
单元测试:
- 针对软件最小单元
- 隔离模块
- 容易定位错误,容易调试
JUnit在测试方法前使用@Test
annotation来表明这是一个JUnit测试方法。如果要在测试开始之前做一些准备则在准备方法前添加@Before
annotation,如果要在测试结束后做一些收尾工作则在收尾方法前添加@After
annotation。
JUnit使用的是断言机制来完成测试,常用的有三种测试方法:assertEquals()
、assertTrue()
、assertFalse()
。
黑盒测试
- 白盒测试:对程序内部代码结构的测试
- 黑盒测试:对程序外部表现出来的行为的测试
黑盒测试:
- 检查代码功能
- 不关心内部细节
- 检查程序是否符合规约
- 用尽可能少的测试用例尽快运行、尽可能大发现程序错误
等价类划分和边界值分析
Equivalence Partitioning 等价类划分:
- 针对每个输入数据需要满足的约束条件,划分等价类,导出测试用例
- 每个等价类代表着对输入约束加以“满足/违反”的“有效/无效”数据集合
- 基于假设:相似的输入会展示相似的行为
- 因此,每个等价类选一个做代表即可,可以降低测试用例数量
Boundary Value Analysis 边界值分析
- 大量错误出现在输入域的边界而不是中央
- 对等价类划分的补充
覆盖划分的方法
- Full Cartesian product:笛卡尔积、全覆盖
- 测试完备、用例数量多、测试代价高
- Cover each Part:覆盖每个取值,最少1次即可
- 测试用例少、代价低、测试覆盖度不够高
例:大整数乘法
- 二维输入空间
- 两个数的正负性:++,±,-+,–
- 特殊值:0,1,-1
- 很大的数
例:Max()
测试覆盖度
Code Coverage代码覆盖度:已有的测试用例有多大程度覆盖了被测程序 。
- 代码覆盖度越低,测试越不充分;
- 代码覆盖度越高,测试代价越高。
测试覆盖种类:
- Function 函数覆盖
- Statement 语句覆盖
- Branch 分支覆盖
- if、while、switch-case、for
- Condition条件覆盖 ≈ 分支覆盖
- Path路径覆盖
- 分支的组合 = 路径
测试效果:路径覆盖 > 分支覆盖 > 语句覆盖
测试难度:路径覆盖 > 分支覆盖 > 语句覆盖
100%语句覆盖是common(正常)目标
100%分支覆盖是desirable(令人满意的),arduous(很难实现),有些行业有更高标准
100%路径覆盖是infeasible(不可实行的)
以注释的形式撰写测试策略
- 在程序中显式记录测试策略(根据什么来选择测试用例)
- 在代码评审的过程中,其他人可以理解你的测试,并评判测试是否足够充分
边栏推荐
- 通过socks5代理下载webrtc源码错误:curl: (7) Can't complete SOCKS5 connection xx.xx.xx.xx
- SFTP的用法
- arm-2-基础阶段
- CSDN spree -- college round table spree
- FAREWARE ADDRESS
- C语言对文件的操作(完整版)
- Question 1000: Input two integers a and b, calculate the sum of a+b, this question is multiple sets of test data
- C语言静态变量static的分析
- [English learning][sentence] good sentence
- 安装Apache服务时出现的几个问题, AH00369,AH00526,AH00072....
猜你喜欢
随机推荐
Object. RequireNonNull method
[日常办公][杂项][vscode]tab space
JDBC第一学之进行数据库连接时出现The server time zone.....解决办法
线程池原理
MVC custom configuration
【c语言】整数的二进制表现形式是什么?
虚幻引擎 5 完整指南[2022六月最新课程学习内容]
IEEE802.X protocol suite
实现高并发服务器(二)
[English learning][sentence] good sentence
淘宝分布式文件系统存储(二)
C语言静态变量static的分析
2020-03-27
最全的最详细的指针讲解(C语言)
C语言结构体(必须掌握版)
C语言数组的深度分析
分布式cache项目
淘宝分布式文件系统存储引擎(一)
How to get started with MOOSE platform - an example of how to run the official tutorial
Install Minikube Cluster in AWS-EC2