Chapter7-面向健壮性(鲁棒性)的软件构造方法

目录

Chapter 7: Software Construction for Robustness
本质主要讲面向健壮性的软件构造方法
考点:
健壮性和正确性
Throwable
Runtime异常、其他异常
Checked异常、Unchecked异常
Checked异常的处理机制(声明、抛出、捕获、处理、清理现场、释放资源)
自定义异常类
断言的作用、应用场合
调试的基本过程和方法
黑盒测试用例的设计(等价类划分、边界值分析)
以注释的形式撰写测试策略
JUnit测试用例写法
测试覆盖度

7.0、Objectives of this chapter

Coding(正确性,断言、异常、错误处理、防御式编程) -> Testing(健壮性,测试策略,集合测试,黑盒、白盒测试) -> Debugging(修复缺陷)
这里写图片描述

7.1、Robustness & Correctness
7.1.1 Robustness & Correctness(健壮性和正确性)
TypesRobustness(健壮性)Correctness(正确性)
Concepts不正常情况下(相对于spec)时,正常表现(返回正确的结果)的能力;两种选择①”优雅”的退出(给出错误信息)②容错性,对错误进行修复,转化为正常符合spec要求,永远不给出错误的结果(遵循fail fast,给出错误提示结束),最重要的质量指标!
principles①对自己的代码要保守,对用户的行为要开放②封闭实现细节,限定用户的恶意行为③考虑极端情况,没有”不可能”(spec、前文学过)
others(对自己的代码要保守,对用户的行为要开放)对外部接口开放;倾向于容错(fault-tolerance,);让用户变得更容易:出错也可 以容忍,程序内部已有容错机制对内部逻辑保守;直接报错(error);让开发者变得更容易:用户输 入错误,直接结束。
MeasuresReliability = Robustness + Correctness;①外部观察角度:Mean time between failures(MTBF,平均失效间隔时间)②内部观察角度(间接):Residual defect rates(残余缺陷率)
7.1.2 error -> fault/defect/bug -> failure

程序员犯错导致软件存在缺陷,导致软件运行时失效

e
errorfault(defect、bug)failure
在开发软件时做出的错误的决定(eg.write “+” to “-“)可能导致软件远离正确的状态,潜在的bug、缺陷运行时的外在表现,失效,没有给出正确结果
①并非所有的软件缺陷都是由代码错误引起的②并非所有缺陷都必然会导致失败③当环境改变时,缺陷可能会变成失败 ④单个缺陷可能导致各种故障症状
7.1.3 提高健壮性和正确性的方法

Step0:使用断言**assertions,防御性编程defensive programming,代码审查code review,形式化验证**formal validation等来编写具有健壮性和正确性目标的代码
Step1:观察故障症状(内存转储**Memory dump,堆栈跟踪stack traces,执行日志execution logs,测试**testing)
Step2:识别潜在故障(错误本地化**bug localization,调试**debug)
Step3:修复错误(**代码修订版**code revision)

7.2、Error and Exception Handling(错误和异常处理)

本节介绍错误处理和异常处理的点行技术
Throwable
Runtime异常、其他异常
Checked异常、Unchecked异常
Checked异常的处理机制(声明、抛出、捕获、处理、清理现场、释放资源)
自定义异常类

7.2.1、Error & Exception Types in Java

基类为java.lang.Throwable,有两个子类为java.lang.Exceptionjava.lang.Error
这里写图片描述

类型ErrorException
概念内部错误,程序员通常无能为力,一旦发生,想办法让程序优雅的结束异常:程序执行中的非正常事件,程序无法再按预想的流程执行;自己程序导致的问题,可以捕获、处理
分类①用户输入错误 ②设备错误 ③物理限制 ④代码错误 Classification1:①Runtime Exception(运行时异常,程序员处理不当造成的bug,eg.ArrayIndexOutOfBoundException,NullPointerException) ②Other Exceptions(其他异常,由无法控制的外部原因造成,eg.IOException)Classification2(异常处理机制角度):①Checked exceptions,从Exception派生出子类型,必须捕获异常并指定错误处理器handler,否则编译无法通过(类似静态检查);②Unchecked exceptions(Error派生+RuntimeException派生),从RuntimeException派生子类型,可以不处理,编译没问题,执行时出现就导致程序失败(类似动态检查);
处理技术〇通知用户,保存现场,回到某个安全状态,退出程序①Return a neural value(返回中间值,Robustness)②Log a warning message to a file(日志记录,Robustness)③Display an error message(提示错误信息Correctness)④Handle the error locally(局部解决错误,Correctness)⑤shutdown(停止,Correctness)(需要统一决策: 以一致的方式处理整个系统中的所有错误)①throws(Declaring Checked Exception):在方法声明后面抛出异常,交给上层调用函数处理(必须在spec中明确写清方法会抛出的所有checked exception,便于调用方法的client处理;不要抛出错误和unchecked异常;协变,子类型重写方法时,不能抛出更广泛的异常,应不抛或更具体) ②throw(代码中可能发生错误抛出异常throw new xxException(),之后使用throws抛给client 或者 try-catch捕获) ③try,catch,finally:try,可能出现异常的代码段;catch,捕获try中出现异常并处理;finally,清理现场,必执行;Hint:每个模块是分离的,内部声明的是局部变量,使用try(读取文件、控制台),不用close,会在执行完try后自动关闭)

try-catch-finally代码

    InputStream in = new FileInputStream(. . .);
    try {
        // 1 可以在try中抛出异常,达到更改异常类型的目标
        code that might throw exceptions /* 抛出 */
        // 2 若上面抛出异常后,该部分不执行
    }
    catch (IOException e) {  /* 捕获 */
        // 3 抛出该类型异常则执行
        show error message /* 处理 */
        // 4
    }
    Finally {  /* 清理现场、释放资源 */
        // 5 必定执行
        in.close();
    }
        // 6
/* try(open操作)不用close,自动关闭 */
try (Scanner in = new Scanner(new FileInputStream("/dict/words")),
"UTF-8") {
    while (in.hasNext())
        System.out.println(in.next());
}

考虑一个特殊情况,finally必定执行,即使try中返回了true,最终也是返回false;

class Indecisive {
    public static void main(String[] args) {
        System.out.println(decision()); /* false */
    }
    static boolean decision() {
        try {
            return true;
        } finally {
            return false;
        }
    }
}
7.2.2、自定义异常

如果JDK提供的exception类无法充分描述你的程序发生的错误,可以创建自己的异常类。

/* 可以从Exception类派生或其子类如IOException派生 */
class FileFormatException extends IOException {
    public FileFormatException() {}
    /* 重载构造函数 */
    public FileFormatException(String gripe){
        super(gripe);
    }
}
/**
* 关于Exception的常用方法
* e.getMessage() 得到异常信息
* e.getClass().getName() 得到包含包名.类名信息, 而e.getClass().getSimpleName()仅得到类名
*/
7.3、Assertions and Defensive Programming

断言的作用、应用场合
黑盒测试用例的设计(等价类划分、边界值分析)
以注释的形式撰写测试策略
JUnit测试用例写法
测试覆盖度

7.3.1、断言的作用、应用场合

Assertions & Exceptions

异常处理 -> Robustness,允许错误发生,需要进行处理
断言 -> Correctness,fail fast, 防止扩散

断言的两个使用方法

assert condition
assert condition : message
(断言失败也是一种Error,AssertionError,意味着内部某些假设被违反了)

断言的应用场景

断言的应用场景内部不变量(Internal Invariants)判断值是否在某个范围内assert x >= 0
表示不变量(Rep Invariants)判断一个方法执行前后,成员变量是否仍然满足条件CheckRep()
控制流不变量(Control-Flow Invariants)用在switch-case,if-else中不应该执行到的地方if(condition){ return x} assert condition(应该在次之前返回);switch语句到的default情况使用断言
方法的前置条件(Pre-condition)执行一个方法之前必须满足的条件,比如参数是否满足spec;检查前置条件是防御式编程的一种典型形式对于是否使用断言,充满争议,public方法使用异常处理(client调用,要给出信息),private方法使用断言(内部调用,不允许出错)
方法的后置条件(Post-condition)执行一个方法后,是否达到效果,比如返回值是否正常assert post-condition
输入参数的值落在预期范围内(或输出参数的值);文件或流在方法开始执行时(或结束执行时)打开(或关闭);文件或流处于开始位置(当一个方法开始执行时(或者当它结束执行时);一个文件或数据流是开放的,只读,只写,或读写两者;只输入变量的值不会被一个方法;指针是非空的;传递给方法的数组或其他容器可以包含至少X个数据元素;表已被初始化为包含实际值;当方法开始执行时容器为空(或全部) (或完成时);高度优化的复杂方法的结果与较慢但明确写入的例程的结果相匹配

断言主要用于开发阶段,避免引入和帮助发现bug;
断言只是检查程序的内部状态是否符合规约;
Java缺省关闭断言,要记得打开(-ea);
实际运行阶段,不再使用断言,避免降低性能。

7.3.2、黑盒测试用例的设计(等价类划分、边界值分析)

要转变心态,用“让其出错”和“尽快出错”作为写高质量代码的日常法宝(Not only “make it fail”, but also “fail fast”.)。

Concepts
黑盒测试:对程序外部表现出来的行为的测试;
白盒测试:对程序内部代码结构的测试;
测试用例:输入+执行条件+期望结果

Process
①先写spec
②再写符合spec的测试用例
③再写符合spec的测试用例

JUnit测试
测试方法有 assertEquals, assertTrue, and assertFalse.
代码案例

import static org.junit.Assert.assertEquals;
import org.junit.Test;
public class CalculatorTest {
    @Test /* 标志 */
    public void evaluatesExpression() {
        Calculator calculator = new Calculator();
        int sum = calculator.evaluate("1+2+3");
        assertEquals(6, sum); /* 测试sum是否为6 */
    }
}
/* 测试方法assertXXX in JUnit */
    assertArrayEquals("failure - byte arrays not same", expected, actual);
    /* 常用,判断是否与期望相等 */
    assertEquals("failure - strings are not equal", "text", "text");
    /* 判断是否为false */
    assertFalse("failure - should be false", false);
    assertNotNull("should not be null", new Object());
    assertNotSame("should not be same Object", new Object(), new Object());
    assertNull("should be null", null);
    assertSame("should be same", aNumber, aNumber);
    assertTrue("failure - should be true", true);
    assertThat("albumen", both(containsString("a")).and(containsString("b")));
    assertThat(Arrays.asList("one", "two", "three"), hasItems("one", "three"));
    assertThat("good", allOf(equalTo("good"), startsWith("good")));

等价类划分
基于等价类划分的测试:将被测函数的输入域划分为等价类,从等价类中导出测试用例。每个等价类代表着对输入约束加以满足*/违反有效/无效*数据的集合。

划分技巧:
如果输入条件指定范围,则定义一个有效和两个无效等价类。
如果输入条件需要特定值,则定义一个有效和一个无效等价类。
如果输入条件指定集合的​​成员,则定义一个有效和一个无效等价类。
如果输入条件是布尔型,则定义一个有效和一个无效类。

举例:

分类进行组合
(1n是正奇数:
    正数:>0、<0
    奇数:奇数、偶数
    奇数隐含着整数:整数、非整数
(2)输入的学号no需满足的条件:
    长度为10位:10、>10、<10
11603开头:以此开头、以其他开头
    之后两位数应为03-0603040506、其他

边界值分析(Boundary Value Analysis,BVA)
大量的错误发生在输入域边界(特殊情况)而非中央,当然,不仅要考虑边界,还要考虑边界的两侧边界值分析方法是对等价类划分方法的补充
一般最常考虑的就是最大值,最小值,是否越界问题。
举例

5个测试用例:
    最小值;略高于最小值;
    正常值;
    略低于最大值;最大值;
n个变量,有4n+1个测试用例
在5个测试用例的基础上增如:
    略高于最大值;
    略低于最小值;

注意与笛卡尔积(全覆盖)区分,该方法为覆盖每个取值:最少1次即可

7.3.3、以注释的形式撰写测试策略、JUnit测试用例写法

测试策略(根据什么来选择测试用例)非常重要,需要在程序中显式记录下来;
目的:在代码评审过程中,其他人可以理解你的测试,并评判你的测试是否足够充分。
这里写图片描述
其中判断start时,使用等价类划分为0,1,1

7.3.4、测试覆盖度(Code coverage)

代码覆盖度:已有的测试用例有多大程度覆盖被测程序
几种常见的覆盖:
①函数覆盖
②语句覆盖
③分支覆盖(if or while or switch-case or for)
④条件覆盖(if/whild/for/switch-case)
⑤路径覆盖

测试效果:路径覆盖>分支覆盖>语句覆盖
测试难度:路径覆盖>分支覆盖>语句覆盖

实际当中,根据预先设定覆盖度标准逐步增加测试用例的数量,直到覆盖度达到标准(例如语句覆盖100%、路径覆盖90%)

Eclipse中可以使用EclEmma进行测试覆盖度

7.4、Debugging

“症状”和“原因”可能相隔很远,高耦合导致的结果
defensive programming、assert、exception**->testing->**debug
常见bugs
数学错误:例如除零,算术溢出,四舍五入造成的不准确;
逻辑错误:例如无限循环和无限递归;
语法错误:例如使用错误的操作符,例如执行赋值而不是相等;
资源错误 : 使用未初始化变量资源泄漏,其中有限的系统资源(如内存或文件句柄)在没有发布的情况下通过重复分配而耗尽;缓冲区溢出,程序尝试将数据存储在分配的存储结束之后;
团队工作错误: 注释过期或不正确 ;文档与实际产品之间的差异;

调试的基本过程和方法
重现->诊断->修复->反思
其中,诊断:(wolf fence,防狼围栏算法),假设检验,使用分治,分别得到各部分的运行状态(通过pringlnlogdump等)

调试技术
①通过暴力攻击进行调试:看内存导出文件,println,自动化调试工具;
②通过诱导进行调试
③通过扣除进行调试
④通过回溯进行调试
⑤通过测试进行调试

Debugging tools(调试工具)
 Syntax and Logic Checking (not covered in this course)
 Source-code comparator (See SCM in Chapter 2)
 Memory heap dump
 Print debugging / logging(设置不同级别)
 Stack trace
 Compiler Warning Messages(消除所有warning)
 Debugger
 Execution Profiler (See Chapter 8)
 Test Frameworks (See Section 7-5)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值