第十二章 面向正确性与健壮性的软件构造

第十二章 面向正确性与健壮性的软件构造

健壮性和正确性

健壮性

  • 系统在不正常输入或不正常外部环境下仍能够表现正常的程度

  • 面向健壮性的编程

    • 处理未期望的行为和错误终止
    • 即使终止执行,也要准确/无歧义的向用户展示全面的错误信息
    • 错误信息有助于进行debug
  • 对自己的代码要保守,对用户的行为要开放

  • 原则

    • 封闭实现细节 , 限定用户的恶意行为
    • 考虑极端情况 , 没有“不可能”

正确性

  • 程序按照 spec 加以执行的能力,是最重要的质量指标!

比较

  • 正确性:永不给用户错误的结果
  • 健壮性:尽可能保持软件运行而不是总是退出
  • 正确性倾向于直接报错(error),健壮性则倾向于容错(fault-tolerance)
  • 健壮性让用户变得更容易:出错也可
    以容忍,程序内部已有容错机制
  • 正确性:让开发者变得更容易:用户输入错误,直接结束。(不满足precondition的调用)

对外的接口,倾向于健壮;对内的实现,倾向于正确

  • 在内外部之间做好隔离,防止“错误”扩散

可靠性=健壮性+正确性
在这里插入图片描述

评测健壮性和正确性

外部观察角度

  • Mean time between failures (MTBF ,平均失效间隔时间 )

    • 平均故障间隔时间 (MTBF) 描述了可修复系统两次故障之间的预期时间,而平均故障时间 (mean time to failure MTTF) 表示不可修复系统的预期故障时间。

内部观察视角(间接)

  • 残余缺陷率

Java中的错误和异常

在这里插入图片描述

所有的异常对象都继承java.lang.Throwable

Error

  • 内部错误:程序员通常无能为力,一旦发生,想办法让程序优雅的结束

  • 种类

    • 用户输入错误

    • 设备错误

      • 网页不可用
      • 打印机关闭
    • 物理限制

      • 磁盘不足
      • 内存不足
  • 通常不需要实例化,也可实例化,捕获

Exception

  • 异常:你自己程序导致的问题,可以捕获、可以处理

  • return之外的第二种退出途径

  • 两个分支

    • 运行时异常

      • 由程序员在代码里处理不当造成
      • 类型转换,空指针,数组越界
    • 其他异常

      • 外部原因造成,程序员无法完全控制的外在问题所导致的,即使在代码中提前加以验证(文件是否存在),也无法完全避免失效发生
      • 尝试越过文件末尾读取;尝试打开不存在的文件;尝试为不表示现有类的字符串查找 Class 对象
  • checked和unchecked

    • checked

      • 必须捕获或者声明的异常

      • 编译器会检查

      • 从Exception派生出子类型

      • Java处理异常的三个操作

        • Declaring exceptions (throws) 声明“本方法可能会发生XX异常”
        • Throwing an exception (throw) 抛出XX异常
        • Catching an exception (try, catch, finally) 捕获并处理XX异常
      • 应该出现在规约的postcondition中,用@throws标记

    • unchecked

      • 不需要捕获和声明

      • 编译器不会检查错误和运行时异常

      • 类似动态类型检测

      • 举例

        • 数组越界
        • 空指针
        • NumberFormatException :Integer.parseInt(“abc”)
        • ClassCastException :类型强转错误
      • Unchecked 异常也可以使用 throws 声明或 try/catch进行捕获,但大多数时候是不需要的,也不应该这么做

      • 不该出现在规约的postcondition中

      • 即使通过throws声明,在调用者中也不会检查是否捕获或者声明这个异常

    • 如何选择

      • 如果客户端可以通过其他的方法恢复异常,那么采用checked exception;

        • client应该从checked exception中获取更有价值的信息(案发现场具体是什么样子),利用异常返回的信息来明确操作失败的原因
        • 如果client仅仅想看到异常信息,可以简单抛出一个unchecked exception:
      • 如果客户端对出现的这种异常无能为力,那么采用unchecked exception;

        • 尽量使用unchecked exception 来处理编程错误
        • 充分利用Java API中提供的丰富unchecked exception
        • 如果client端对某种异常无能为力,可以把它转变为一个 unchecked exception,程序被挂起并返回客户端异常信息
        • 要想让代码更加易读,倾向于用unchecked exception来处理程序中的错误
    • 比较

      • 在这里插入图片描述

抛出异常的方法

  • 找到一个能表达错误的Exception类/或者构造一个新的Exception类
  • 构造Exception类的实例,将错误信息写入
  • 抛出它
  • 一旦抛出异常,方法不会再将控制权返回给调用它的client,因此也无需考虑返回错误代码

创建异常类

  • checked

    • 继承自Exception及其子类
  • unchecked

    • 继承自RuntimeException

处理异常

  • try catch

    • 当catch中没有的时候,会抛向上一级
  • 本来catch语句下面是用来做exception handling的,但也可以在catch里抛出异常

    • 这么做 的 目 的是:更 改 exception 的 类型 ,更 方便 client 端获 取 错误信息并处理
    • 在这里插入图片描述
      但这么做的时候最好保留“根原因”
    • 在这里插入图片描述
      当异常被捕获时,可以获取到原始异常,它允许在子系统中抛出高级异常而不会丢失原始故障的详细信息
  • finally 语句

    • 当代码抛出异常时,它会停止处理方法中剩余的代码并退出该方法,如果该方法获取了一些只有该方法知道的资源(文件、数据库连接等),并且必须清除该资源,否则会出现问题。

      • 两种方法

        • 一种解决方案是捕获并重新抛出所有异常。

          • 但是这个方案很繁琐,因为你需要在两个地方清理资源分配——在正常代码和异常代码中。
        • Java 有一个更好的解决方案:finally 语句。

          • 无论是否异常都会被执行
    • 可以使用没有catch的语句

      • 在这里插入图片描述
    • 在这里插入图片描述

栈轨迹

  • 打印的栈轨迹是从栈顶开始的
  • 和栈帧类似
  • 在这里插入图片描述
    获取栈轨迹
  • 分析栈轨迹

断言

防止bug

  • 第一道防线:让bug成为不可能

    • 静态检测
    • 动态检测
    • immutable类型
    • final修饰
  • 第二道防线:定位bug

    • 尽快失败,就容易发现、越早修复

      • 断言

断言

  • 检查前置条件是防御式编程的一种典型形式

  • 断言 即 是对代码中程序员 所做 假设的 文档化 ,也不 会影响运 行时性能( 在实 际 使用时, assertion 都会被 disabled)

  • 两种形式

    • assert condition;
    • assert condition : message;(message是字符串)
    • 在这里插入图片描述
  • 可以用来检测

    • Internal Invariants 内部不变量

    • Rep Invariants 表示不变量

    • Control-Flow Invariants 控制流不变量

      • 在不会到达的地方assert false
    • Pre-conditions of methods 方法的前置条件

    • Post-conditions of methods方法的后置条件

  • 断言主要用于开发阶段,避免引入和帮助发现 bug,实际运行阶段,不再使用断言,避免降低性能

  • 在这里插入图片描述
    程序的正确性不应取决于断言表达式是否被执行,因为断言可能会被禁用

  • 程序之外的事,不受你控制,不要乱断言

    • 文件/网络/用户输入等
    • 断言只是检查程序的内部状态是否符合规约
    • 外部使用异常
  • 断言非常影响运行时的性能,Java缺省关闭断言

断言和异常

  • 断言保证正确性

    • 断言在大型复杂程序和高可靠性程序中特别有用。它们使程序员能够更快地清除不匹配的接口假设、修改代码时出现的错误等。
    • 使用断言处理“ 绝不应该发生”的情况
  • 异常保证健壮性

    • 使用异常来处理你“预料到可以发生“的不正常情况

pre-/post-condition

  • 如果参数来自于外部(不受自己控制),使用异常处理(public函数)
  • 如果来自于自己所写的其他代码,可以使用断言来帮助发现错误( 例如 post-condition 就需要(private函数)

防御性编程

保护程序免受无效输入

  • 对来自外部的数据源要仔细检查,例如:文件、网络数据、用户输入等
  • 对每 个函 数的输入参 数 合 法性要 做仔 细 检查 ,并 决 定 如 何处理非法输入

Barricade 设置路障

  • 类 的 public 方 法接 收到 的外部数 据都应被认 为是 dirty 的, 需 要处理 干净 再传递 到private 方 法 —— 隔离舱
  • “隔 离舱 ”外部的 函 数 应 使用异常处理,“隔 离舱 ”内的 函 数 应 使用断言。

The SpotBugs tool

  • 静态分析查找Java代码中的bugs
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值