HIT-SC-Chapter Twelve

目录

HIT-SC-Chapter Twelve

Construction for Robustness and Correctness
面向正确性与健壮性的软件构造

进入软件构造最关键的质量特性——健壮性和正确性。

1 What are Robustness and Correctness?

Robustness 健壮性

  • 健壮性:系统在不正常输入或不正常外部环境下仍能够表现正常的程度
  • Robust programming 面向健壮性的编程
    • 处理未期望的行为和错误终止
    • 即使终止执行,也要准确/无歧义的向用户展示全面的错误信息
    • 错误信息有助于进行debug

Robustness principle ( Postel’s Law)

  • Paranoia (偏执狂)
    • 总是假定用户恶意、假定自己的代码可能失败
  • Stupidity 愚蠢
    • 程序员认为用户会尝试不正确的、虚假的和错误的输入。
    • 把用户想象成白痴,可能输入任何东西
    • 因此,程序员返回给用户一个明确的、直观的错误消息,不需要查找错误代码。
    • 返回给用户的错误提示信息要详细、准确、无歧义
  • Robustness principle (Postel’s Law): 对别人宽容点,对自己狠一点
    • 你所做的事情上要保守;接受别人的东西要大方。
    • 发出的东西要保守,接受的东西要自由
    • 对自己的代码要保守,对用户的行为要开放

Principles of robust programming

健壮编程原理

  • 封闭实现细节,限定用户的恶意行为
    • 用户不应该获得对库、数据结构或数据结构指针的访问权。
    • 这些信息应该对用户隐藏,这样用户就不会不小心修改它们并在代码中引入bug。
    • 当这些接口被正确构建后,用户使用它们时不会发现漏洞来修改接口。
    • 因此用户只关注他或她自己的代码。
  • 不可能发生——代码被修改,可能会引入一种“不可能”情况发生的可能性。
    • 考虑极端情况,没有“不可能”
    • 因此,不可能的情况被认为是极不可能的。
    • 开发人员考虑如何处理极不可能发生的情况,并相应地实现处理。

Correctness 正确性

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

Robustness vs. correctness: at opposite ends of the scale.

  • 正确性:永不给用户错误的结果

    • 意味着从不返回不准确的结果;没有结果总比不准确的结果好。
  • 健壮性:尽可能保持软件运行而不是总是退出

    • 意思是总是尝试做一些事情来让软件保持运行,即使有时会导致不准确的结果。
  • 正确性倾向于直接报错(error),健壮性则倾向于容错(fault-tolerance)

  • image-20220610203214594

    • 健壮性:让用户变得更容易:出错也可以容忍,程序内部已有容错机制
    • 正确性:让开发者变得更容易:用户输入错误,直接结束。(不满足precondition的调用)
  • 在内部,寻找正确性;外部,寻求鲁棒性。

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

Reliability = Robustness + Correctness

  • 系统在规定的条件下无论何时需要都能执行其所需功能的能力,即故障间隔时间较长

改进健壮性和正确性的步骤

  • 步骤0:使用断言、防御性编程、代码审查、形式验证等以健壮性和正确性为目标进行代码编程
  • 步骤1:观察故障症状(内存转储、堆栈跟踪、执行日志、测试)
  • 步骤2:识别潜在故障(bug定位、调试)
  • 步骤3:修复错误(代码修订)

image-20220610203932344


2 How to measure robustness and correctness?

Mean time between failures (MTBF) 外部观察角度

  • Mean time between failures (MTBF,平均失效间隔时间)
    • 系统在运行过程中发生固有故障的时间间隔。
    • MTBF是指系统发生故障之间的算术平均时间。
  • MTBF的定义取决于什么是系统故障的定义。

Mean time between failures (MTBF)

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

Residual defect rates 内部观察角度(间接)

  • Residual defect rates 残余缺陷率
    • refers to “bugs left over after the software has shipped” per KLOC: 每千行代码中遗留的bug的数量
  • This can be discouraging for large systems.

3 Error and Exception in Java

  • 所有Exception对象的基类都是java.lang.Throwable,以及它的两个子类java.lang.Exception和java.lang.Error。
  • image-20220610205921849

Error and Exception

  • Error类描述了Java运行时系统中很少发生的内部系统错误和资源耗尽情况(例如,VirtualMachineError, LinkageError)。
    • 你不应该抛出这种类型的对象。
    • 如果发生这样的内部错误,除了通知用户并尝试优雅地终止程序外,您几乎无能为力。
    • 内部错误 error:程序员通常无能为力,一旦发生,想办法让程序优雅的结束
  • Exception类描述了由程序引起的错误(例如FileNotFoundException, IOException)。
    • 这些错误可以被你的程序捕获和处理(例如,执行一个替代操作或通过关闭所有文件、网络和数据库连接优雅地退出)。
    • 异常 exception:你自己程序导致的问题,可以捕获、可以处理

Sorts of errors

  • User input errors 用户输入错误

    • 除了不可避免的拼写错误之外,一些用户喜欢开辟自己的道路,而不是遵循方向。
    • 例如,用户请求连接一个语法错误的URL,网络层会抱怨。
  • Device errors 设备错误

    • 硬件并不总是按照您希望的那样工作。
  • Physical limitations 物理限制

  • image-20220610211219382

  • 在大多数时候,程序员不需要实例化Error

Some typical Errors

  • VirtualMachineError
  • 抛出,以指示Java虚拟机损坏或耗尽了继续运行所需的资源。

4 Exception Handling

  • 既然Error我们无能为力,那就转向关注我们能处理的Exception

Exceptions

  • 异常是指在程序执行过程中发生的中断程序正常运行的异常事件
  • 异常是代码将错误或异常事件传递给调用它的代码的一种特定方法
  • 如果不能以正常方式完成任务,Java允许每个方法都有另一个退出路径。
    • return之外的第二种退出途径
    • 该方法抛出一个封装错误信息的对象。
    • 该方法立即退出并且不返回任何值。
    • 此外,在调用方法的代码处不会继续执行
    • 相反,异常处理机制开始搜索能够处理这种特定错误条件的异常处理程序。
      • 若找不到异常处理程序,整个系统完全退出

Benefits of exceptions

  • Code that does not use Exceptions

  • image-20220610213913721

    image-20220610213926620


(2) Classification of exceptions

Exceptions are derived from Throwable

  • 在Java编程语言中,异常对象始终是派生自Throwable的类的实例。

  • image-20220610214229307

    image-20220610214255305


Runtime Exception and Other Exceptions

  • 在进行Java编程时,要关注Exception层次结构。
  • Exception层次结构也分为两个分支:
    • Exceptions that derive from RuntimeException 运行时异常
    • Those that do not. 其他异常
  • General rule:
    • A RuntimeException happens because you made a programming error.
      • 运行时异常:由程序员在代码里处理不当造成
    • Any other exception occurs because a bad thing, such as an I/O error, happened to your otherwise good program.
      • 其他异常:由外部原因造成

  • 继承自RuntimeException的异常包括以下问题:
    • A bad cast
    • An out-of-bounds array access
    • A null pointer access
    • ……
  • 不从RuntimeException继承的异常包括
    • Trying to read past the end of a file
    • Trying to open a file that doesn’t exist
    • Trying to find a Class object for a string that does not denote an existing class

RuntimeException

  • If it is a RuntimeException, it was your fault
    • 运行时异常,是程序 源代码中引入的故障所造成的
    • You could have avoided ArrayIndexOutOfBoundsException by testing the array index against the array bounds.
    • 如果您在使用变量之前检查它是否为空,就不会发生NullPointerException。
    • 如果在代码中提前进行验证,这些故障就可以避免
  • 实际上,在您检查文件是否存在之后,该文件可能会立即被删除。
    • 因此,“存在”的概念取决于环境,而不仅仅取决于代码。
    • 非运行时异常,是程序员无法完全控制的外在问题所导致的
    • 即使在代码中提前加以验证(文件是否存在),也无法完全避免失效发生。

(3) Checked and unchecked exceptions

  • 这是从异常处理机制的角度所做的分类
    • 异常被谁check?——编译器、程序员
  • image-20220610215647080

How is an exception handled?

  • Checked Exceptions
    • 您必须捕捉并处理异常,或者通过声明您的方法抛出异常来告诉编译器您无法处理它
    • 然后,使用您的方法的代码将不得不处理该异常(如果无法处理该异常,可以选择声明抛出该异常)。
    • 编译器可帮助检查你的程序是否已抛出或处理了可能的异常
  • Unchecked Exceptions
    • 编译器不会检查错误error和运行时异常Runtime Exceptions
    • 错误表示在应用程序之外发生的情况,例如系统崩溃。
    • 运行时异常通常是由于应用程序逻辑中的错误而发生的。
    • 在这种情况下,您什么也做不了,只能重新编写程序代码。
      • 编译器不会检查这些。
    • 这些运行时异常将在开发和测试阶段发现。然后我们必须重构代码以删除这些错误。

Unchecked exceptions

  • Programming error, other unrecoverable failure (Error + RuntimeException)

    • 程序编译不需要任何操作,但未捕获的异常将导致程序失败
    • 不需要在编译的时候用try…catch等机制处理
  • image-20220610220636651

  • 可以不处理,编译没问题,但执行时出现就导致程序失败,代表程序中的潜在bug

    • 类似于编程语言中的dynamic type checking
  • Common Unchecked Exception Classes

    • ArrayIndexOutOfBoundsException: JVM
      • 在代码使用数组索引时抛出的异常,该索引超出了数组的边界。
    • NullPointerException:
      • 当代码试图在需要对象引用的地方使用空引用时,由JVM抛出。
    • NumberFormatException:
      • 当试图将字符串转换为数字类型,但字符串没有适当的格式时,以编程方式抛出(例如,由Integer.parseInt())。
    • ClassCastException:
      • 当尝试强制转换对象引用失败时,由JVM抛出。

Checked exceptions

  • 每个调用者都应该知道并处理的错误
  • 必须捕获或传播,否则程序将无法编译(编译器检查您是否为所有检查的异常提供了异常处理程序)
    • 必须捕获并指定错误处理器handler,否则编译无法通过
    • 类似于编程语言中的static type checking

image-20220610221044332

image-20220610221651606

image-20220610221735180


Checked Exception Handling Operations

  • Five keywords are used in exception handling:

    • try
    • catch
    • finally
    • throws
    • throw
  • Java’s exception handling consists of three operations:

    • Declaring exceptions (throws) 声明“本方法可能会发生XX异常”

    • 注意差异,第三人称

    • Throwing an exception (throw) 抛出XX异常

    • Catching an exception (try, catch, finally) 捕获并处理XX异常

  • Unchecked异常也可以使用throws声明或try/catch进行捕获,但大多数时候是不需要的,也不应该这么做——掩耳盗铃,对发现的编程错误充耳不闻


Use checked or unchecked exceptions in your code?

  • 当要决定是采用checked exception还是unchecked exception的时候,问一个问题:“如果这种异常一旦抛出,client会做怎样的补救?”
  • image-20220610222023879
    • 如果客户端可以通过其他的方法恢复异常,那么采用checked exception;
    • 如果客户端对出现的这种异常无能为力,那么采用unchecked exception;
    • 异常出现的时候,要做一些试图恢复它的动作而不要仅仅的打印它的信息。
  • 尽量使用unchecked exception来处理编程错误:
    • 因为unchecked exception不用使客户端代码显式的处理它们,它们自己会在出现的地方挂起程序并打印出异常信息。
    • 充分利用Java API中提供的丰富unchecked exception,如NullPointerException , IllegalArgumentException和IllegalStateException等,使用这些标准的异常类而不需亲自创建新的异常类,使代码易于理解并避免过多消耗内存。
    • 如果client端对某种异常无能为力,可以把它转变为一个unchecked exception,程序被挂起并返回客户端异常信息
      • image-20220610222339780
  • 如果异常中没有对客户端代码有用的信息,尽量不要创建新的自定义异常
    • 不要创建没有意义的异常,client应该从checkedexception中获取更有价值的信息(案发现场具体是什么样子),利用异常返回的信息来明确操作失败的原因。
    • 如果client仅仅想看到异常信息,可以简单抛出一个unchecked exception: throw new RuntimeException("Username already taken")

  • Checked Exceptions should be used for expected, but unpreventable errors that are reasonable to recover from.
  • 错误可预料,但无法预防,但可以有手段从中恢复,此时使用checked exception。
    • 已检查异常应用于预期的、但不可预防的、可以合理恢复的错误。
    • Expected but unpreventable: 可预料但不可预防
    • 不可预防:脱离了你的程序的控制范围
  • 未检查异常应该用于其他所有情况
  • Reasonable to recover from: 可合理的恢复
    • 告诉调用方预测无法恢复的异常是没有意义的。
    • 如果用户试图读取不存在的文件,调用者可以提示用户输入新的文件名。
    • 另一方面,如果方法由于编程错误(无效的方法参数或错误的方法实现)而失败,那么应用程序在执行过程中无法修复这个问题。
    • 它所能做的最好的事情就是记录问题,然后等待开发人员在以后修复它。
  • 除非你抛出的异常符合上述所有条件,它应该使用Unchecked exception。

Exception design considerations

  • 对特殊结果(即预期情况)使用检查异常
    • Use unchecked exceptions to signal bugs (unexpected failures)
    • Use checked exceptions for special results (i.e., anticipated situations)
  • 你应该只使用未检查的异常来表示一个意外的失败(例如一个bug),或者如果你期望客户端通常会编写代码来确保异常不会发生,因为有一种方便和廉价的方法来避免异常;
  • 否则,您应该使用检查异常。

image-20220610223002654


(4) Declaring Checked Exceptions by throws

  • A Java method can throw an exception if it encounters a situation it cannot handle.
    • 方法不仅会告诉Java编译器它可以返回什么值,还会告诉编译器什么可能出错。
    • “异常”也是方法和client端之间spec的一部分,在post-condition中刻画
      • 例如,试图读取文件的代码知道该文件可能不存在或它可能是空的。因此,试图处理文件中的信息的代码需要通知编译器,它可以抛出某种类型的IOException
  • 方法可以抛出异常的地方是方法的头部;标题更改以反映该方法可能抛出的已检查异常。
    • image-20220610223443999

How to declare exceptions in a specification

  • Checked exceptions that signal a special result are always documented with a Javadoc @throws clause, specifying the conditions under which that special result occurs.
  • Java may also require the exception to be included in the method signature, using a @throws declaration.

image-20220610223643863

  • 程序员必须在方法的spec中明确写清本方法会抛出的所有checked exception,以便于调用该方法的client加以处理

  • image-20220610223727096

  • Unchecked exceptions

    • 用于指示意外失败的未检查异常(客户机或实现中的错误)不是方法的后置条件的一部分,因此它们不应该出现在@throws或throws中
    • For example, NullPointerException need never be mentioned in a spec.
    • image-20220610223911728
  • Declare more than one Checked Exceptions

    • 如果一个方法可能抛出一个以上被检查的异常类型,则必须在头文件中列出所有异常类。
  • image-20220610223946392


Declaring Checked Exceptions by throws

  • 当您编写自己的方法时,您不必通告您的方法实际上可能抛出的每一个可抛出对象。
    • 你所调用的其他函数抛出了一个checked exception——从其他函数传来的异常
    • 当前方法检测到错误并使用throws抛出了一个checkedexception——你自己造出的异常
    • 此时需要告知你的client需要处理这些异常
  • 如果没有handler来处理被抛出的checked exception,程序就终止执行 static checking

Don’t throw Error and unchecked exceptions

  • 不需要通告内部Java错误-从Error继承的异常
    • 任何代码都可能抛出这些异常,它们完全超出您的控制范围。
    • 虚拟机或运行时库发生内部错误。
  • 不应该公布从RuntimeException继承的未检查异常。
    • 如果您非常关心数组索引错误,那么您应该花时间修复它们,而不是宣传它们可能发生的可能性。
    • 您犯了一个编程错误,例如[-1]= 0会引发未检查的异常rrayIndexOutOfBoundsException)
    • image-20220610224718082

Considering subtyping polymorphism

  • 如果子类型中override了父类型中的函数,那么子类型中方法抛出的异常不能比父类型抛出的异常类型更宽泛
    • 子类型方法可以抛出更具体的异常(父亲异常的子类),也可以不抛出任何异常
    • 如果父类型的方法未抛出异常,那么子类型的方法也不能抛出异常
  • 参见LSP原则目标是子类型多态:
    • 客户端可用统一的方式处理不同类型的对象,子类型可替代父类型

Liskov Substitution Principle (LSP)

  • LSP is a particular definition of a subtyping relation, called (strong) behavioral subtyping 强行为子类型化
  • 在编程语言中,LSP依赖于以下限制:
    • image-20220610225120381
  • image-20220610225139971

(5) How to Throw an Exception

  • image-20220610225257996

    image-20220610225408144

  • 利用Exception的第二个构造函数(可以带参数),将发生错误的现场信息充分的传递给client。

    • image-20220610234742288
  • 如果现有的异常类中有一个对你有效,抛出异常就很容易

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


(6) Creating Exception Classes

  • 您的代码可能会遇到任何标准异常类都无法充分描述的问题。
  • 只需从Exception或者从Exception的子类(如IOException)派生它
    • 习惯上,提供一个默认构造函数和一个包含详细消息的构造函数。
    • Throwable超类的toString方法返回一个包含详细消息的字符串,便于调试。

An example for checked exception

  • 要定义一个checked exception,你需要创建java.lang.Exception的一个子类(或子类的层次结构):

    • image-20220610235610190
  • 可能引发或传播此异常的方法必须声明:

    • image-20220610235646745
  • 调用此方法的代码必须处理或传播此异常(或两者兼有):

    • image-20220610235724726

An example for unchecked exception

  • 有时,在某些情况下,您不希望强制每个方法在其throws子句中声明异常实现。在这种情况下,您可以创建一个扩展java.lang.RuntimeException的未检查异常。

    • image-20220610235807880
  • 方法可以抛出或传播FooRuntimeException异常,而无需声明它。

    • image-20220610235833858
  • image-20220610235944915

    image-20220610235956036


(7) Catching Exceptions

  • 异常发生后,如果找不到处理器,就终止执行程序,在控制台打印出stack trace。

    • GUI程序捕获异常,打印堆栈跟踪消息,然后返回到用户界面处理循环。
  • To catch an exception, set up a try/catch block :

    • image-20220611000133837
  • 如果try块中的任何代码抛出catch子句中指定的类的异常,那么

    • 程序跳过try块中的其余代码
    • 程序执行catch子句中的处理程序代码。
  • 如果try块内的代码都不抛出异常,那么程序将跳过catch子句。

  • 如果方法中的任何代码抛出与catch子句中指定的异常类型不同的异常,则该方法立即退出

  • 希望它的一个调用者已经为该类型提供了一个catch子句。(否则会一直退出,直到结束或被捕获)

  • image-20220611000453337


Pass the exception on to the caller

  • Another choice to handle the exceptions: do nothing at all and simply pass the exception on to the caller.
    • 也可以不在本方法内处理,而是传递给调用方,由client处理(“推卸责任”)
    • Let the caller of the read method worry about it!
    • 可能是最好的选择(比起健壮性,更喜欢正确性)。
  • 如果我们采用这种方法,那么我们必须声明这个方法可能会抛出IOException
    • image-20220611000621611
  • 尽量在自己这里处理,实在不行就往上传——要承担责任
  • 但有些时候自己不知道如何处理,那么提醒上家,由client自己处理
  • 如果父类型中的方法没有抛出异常,那么子类型中的方法必须捕获所有的checked exception——为什么?
    • 子类型方法中不能抛出比父类型方法更多的异常

Get detailed information from an Exception

  • To find out more about the object, try e.getMessage() to get the detailed error message (if there is one)
  • 使用e.getClass(). getname()获取异常对象的实际类型。

Catching Multiple Exceptions

  • 您可以在一个try块中捕获多个异常类型,并以不同的方式处理每种类型

    image-20220611000921133


(8) Rethrowing and Chaining Exceptions

  • 本来catch语句下面是用来做exception handling的,但也可以在catch里抛出异常

    • 通常,当您想要更改异常类型时,您需要这样做
  • 这么做的目的是:更改exception的类型,更方便client端获取错误信息并处理

  • image-20220611001044290

    在这里,ServletException是用异常的消息文本构造的。

  • 但是,更好的做法是将原始异常设置为新异常的“原因”

  • image-20220611001131015

    • 当捕获异常时,可以检索原始异常
    • image-20220611001158866
  • 强烈推荐使用这种包装技术。它允许您在子系统中抛出高级异常,而不会丢失原始故障的细节。


(9) finally Clause

  • 当异常抛出时,方法中正常执行的代码被终止
    • 如果异常发生前曾申请过某些资源,那么异常发生后这些资源要被恰当的清理
  • 一种解决方案是捕获并重新抛出所有异常。
    • 但是这种解决方案很繁琐,因为您需要在两个地方清理资源分配—在正常代码和异常代码中

Try-Catch-Finally

  • 无论是否捕获异常,finally子句中的代码都会执行。

  • In the following example, the program will close the file under all circumstances:

    • image-20220611001407573

      Case 1: The code throws no exceptions.

      image-20220611001513215

      image-20220611001522602

      image-20220611001630766


Using finally without a Catch

  • The in.close() statement in the finally clause is executed whether or not an exception is encountered in the try block.
  • 如果遇到异常,它将被重新抛出,并且必须在另一个catch子句中捕获。

(10)栈跟踪元素分析

Method Call Stack

image-20220611001839299

image-20220611001906325


Call Stack Trace

image-20220611001924927

image-20220611001948955


Exception & Call Stack

  • 当Java方法内部发生异常时,该方法创建一个exception对象,并将该exception对象传递给JVM(即,该方法“抛出”一个exception)。

    • Exception对象包含异常的类型,以及异常发生时程序的状态
  • JVM负责查找异常处理程序来处理exception对象。

    • 它向后搜索调用堆栈,直到找到对应exception对象的特定类的异常处理程序(在Java术语中,称为“捕获”exception)。
    • 如果JVM在调用堆栈中的所有方法中都找不到匹配的异常处理程序,它就会终止程序。
  • image-20220611002154569

    image-20220611002215340

  • StackTraceElement类具有获取执行代码行的文件名和行号,以及类名和方法名的方法。

  • toString方法生成一个包含所有这些信息的格式化字符串。


5 Assertions

First Defense: Make Bugs Impossible

image-20220611002333868

  • Static checking
  • Dynamic checking:
  • Immutability
  • Immutable values
  • Immutable references

Second Defense: Localize Bugs

  • 如果无法避免,尝试着将bug限制在最小的范围内
    • 当本地化到单个方法或小模块时,只需研究程序文本就可以发现错误。
    • 尽快失败,就容易发现、越早修复
  • Pre-condition如果违反,该方法可以做任何事
    • 然而,由于错误的调用表明调用方存在错误,因此最有用的行为应该尽早指出错误。
  • 当先决条件不满足时,此代码通过抛出AssertionError异常终止程序。调用者的错误的影响被阻止传播。
    • Checking preconditions is an example of defensive programming检查前置条件是防御式编程的一种典型形式

(1) What and Why Assertions?

  • 断言:
    • 在开发阶段的代码中嵌入,检验某些“假设”是否成立。若成立,表明程序运行正常,否则表明存在错误。
    • 断言是在开发过程中使用的代码,它允许程序在运行时检查自身,例如,测试关于程序逻辑的假设(如前置条件、后置条件和不变量)。
    • When an assertion is true, that means everything is operating as expected.
    • When it’s false, that means it has detected an unexpected error in the code.
    • image-20220611002709112
  • 每个断言包含一个您认为在程序执行时为真的布尔表达式。
    • If it is not true, the JVM will throw an AssertionError.
    • 出现AssertionError,意味着内部某些假设被违反了
    • 增强程序员对代码质量的信心:对代码所做的假设都保持正确
  • 断言即是对代码中程序员所做假设的文档化,也不会影响运行时性能(在实际使用时,assertion都会被disabled)

  • An assertion usually takes two arguments

    • 描述假定为真的假设的布尔表达式
    • 如果不是,则显示一条消息。
  • The Java language has a keyword assert with two forms:

    • assert condition;
    • assert condition : message;
  • 两个语句都对条件求值,如果布尔表达式求值为false,则抛出AssertionError。

    • 所构造的message在发生错误时显示给用户,便于快速发现错误所在
  • image-20220611003017635

    image-20220611003103443


(2) What to Assert and What not to?

  • Assertion can be used for verifying:
    • Internal Invariants 内部不变量:
      • Assert that a value is within a certain constraint, e.g., assert x > 0
    • Rep Invariants 表示不变量:
      • Assert that an object's state is within a constraint. What must be true about each instance of a class before or after the execution of a method? Class invariants are typically verified via private boolean method, e.g., checkRep().
    • Control-Flow Invariants 控制流不变量
      • 断言无法到达某个位置。例如,switch-case语句的default子句
    • Pre-conditions of methods 方法的前置条件:
    • Post-conditions of methods 方法的后置条件

What to Assert?

  • Pre-condition: method argument requirements

  • Post-condition: Method return value requirements

    • image-20220611003457609
    • 这种断言有时被称为自检。
  • Control-flow: covering all

    • 如果一个条件语句或开关没有覆盖所有可能的情况,使用断言来阻止非法情况是一个很好的实践。
    • image-20220611003536349
    • 但是不要在这里使用assert语句,因为它可以被关闭。相反,在不合法的情况下抛出异常,以便检查总是会发生:
      • default: throw new AssertionError("must be a vowel, but was: " vowel);
  • image-20220611003647778

    image-20220611003702029


When to use assertions?

  • 断言主要用于开发阶段,避免引入和帮助发现bug
    • 实际运行阶段不再使用断言
  • 使用断言的主要目的是 为了在开发阶段调试程序、尽快避免错误
    • 当你编写代码时,而不是事后。在编写代码时,您要记住不变量。
    • 如果您推迟编写断言,那么您就不太可能这样做,而且您可能会忽略一些重要的不变量。

Avoid putting executable code in assertions

  • 因为可以禁用断言,所以程序的正确性永远不应该取决于是否执行断言表达式
  • In particular, asserted expressions should not have side-effects .
    • image-20220611003928289
    • 如果禁用断言,则跳过整个表达式,x永远不会从列表中删除。

Don’t Assert External Conditions

  • 程序之外的事,不受你控制,不要乱断言
    • image-20220611004008115
    • 外部错误要使用Exception机制去处理

Turn on/off Assert in different phases

  • Java缺省关闭断言,要记得打开(-ea)
  • 断言非常影响运行时的性能
  • Enable assertions
    • Running the program with the -enableassertions or -ea option:
      • java -enableassertions MyApp
    • The option -ea... turns on assertions in all classes of the default package
      • java -ea:MyClass MyApp
  • Disable assertions
    • Running the program with the -disableassertions or -da option:
      • java -ea:... -da:MyClass MyApp
  • default, assertions are disabled.
  • Enable assertions in Eclipse:
    • 在首选项中,选择“Java→Installed jre”。
    • 点击“Java SE 8”,点击“编辑…”,
    • 在“默认VM参数”框中输入:-ea

(3) Guidelines for Using Assertions

Assertion vs. Exception?

  • Assertions generally cover correctness issues of program.
    • 如果针对异常条件触发了断言,纠正措施不仅仅是优雅地处理错误——
      • 纠正措施是更改程序的源代码,重新编译,并发布软件的新版本。
      • 使用断言处理“绝不应该发生”的情况
      • Assertions check for bugs in the code.
  • Exceptions generally cover robustness issues of program.
  • 如果错误处理代码用于处理异常情况,则错误处理将使程序能够优雅地对错误作出响应。
  • 使用异常来处理你“预料到可以发生”的不正常情况
  • 断言在大型、复杂的程序和高可靠性的程序中特别有用。
    • 它们使程序员能够更快地排除不匹配的界面假设、修改代码时出现的错误等等。

Should pre-/post-condition be asserted?

  • 如果参数来自于外部(不受自己控制),使用异常处理

  • 如果来自于自己所写的其他代码,可以使用断言来帮助发现错误(例如postcondition就需要)

    • 您可以使用断言来测试非公共方法的前提条件,因为无论客户端对该类做什么,您都相信该方法为真。
    • 可以在公共和非公共方法中使用断言测试后置条件
  • image-20220611005343910

    image-20220611005354082

    image-20220611005511154


Combine assert & exception handling for robustness

  • 开发阶段用断言尽可能消除bugs在发行版本里用异常处理机制处理漏掉的错误

6 Defensive Programming

  • 防御性编程是防御性设计的一种形式,旨在确保软件在不可预见的情况下继续发挥功能。

  • 这个想法可以被视为减少或消除墨菲定律的影响。


Techniques

  • Protecting programs from invalid inputs
  • Assertions
  • Exceptions
  • Specific error handling techniques
  • Barricade
  • Debugging aids

防御性编码的最佳形式是一开始就不插入错误。


(1) Protecting Programs From Invalid Inputs

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

(2) Barricade 设置路障

  • 路障是一种遏制损害的策略
    • 定义一些处理脏数据的软件部分和一些处理干净数据的软件部分是一种有效的方法,可以减轻检查坏数据的大部分代码的责任。
  • 类的public方法接收到的外部数据都应被认为是dirty的,需要处理干净再传递到
    private方法——隔离舱
    • 一旦数据被类的公共方法接受,类的私有方法就可以假定数据是安全的
  • 另一种方法是作为一种手术室技术
  • Relationship between Barricades and Assertions
    • 路障的使用使断言和错误处理之间的区别一目了然。
    • 障碍物之外的例程应该使用错误处理,因为对数据进行任何假设都是不安全的。
    • 路障内部的例程应该使用断言,因为传递给它们的数据在通过路障之前应该被消毒。如果路障内部的程序检测到错误数据,那是程序的错误,而不是数据的错误
  • “隔离舱”外部的函数应使用异常处理,“隔离舱”内的函数应使用断言。
  • 决定哪些代码在路障内部,哪些代码在路障外部是一个架构级的决定。

7 The SpotBugs tool

  • FindBugs是一个使用静态分析来查找Java代码中的bug的程序。
  • 潜在的错误被分为四类:
    • 最可怕的、
    • 可怕的、
    • 令人不安的
    • 和令人担忧的。
  • 这是对开发人员可能的影响或严重性的暗示。

  • SpotBugs是一个使用静态分析来查找Java代码中的bug的程序。
    • 它是FindBugs的精神继承者,在其社区的支持下继续发展。

SUM

image-20220611010443150

posted @   三金同志  阅读(30)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示