6.3 断言与防御式编程

1 复习:设计ADT
第一道防线:使bug不可能
▪ 对付bug的最好办法是通过设计使它们不可能。
–静态检查:通过在编译时捕获错误来消除许多错误。
–动态检查:Java通过动态捕获数组溢出错误,使其不可能发生。如果试图使用数组或列表边界之外的索引,Java会自动生成错误。
–未选中的异常/运行时错误-不可变:不可变类型是一种值一旦创建就永远无法更改的类型。
–不可变值:按final,可分配一次,但从不重新分配。
–不可变引用:按final,这使引用不可访问,但引用指向的对象可能是可变的或不可变的。
▪ 如果我们不能阻止bug,我们可以尝试将它们本地化到程序的一小部分,这样我们就不必费劲地查找bug的原因。–当本地化为单个方法或小模块时,只需研究程序文本就可以发现错误。–快速故障:观察到问题越早(越接近其原因),就越容易修复
断言:当不满足前提条件时,此代码通过抛出断言错误异常来终止程序。调用方的错误的影响被阻止传播。 ▪ 检查前提条件是防御性编程的一个例子——真正的程序很少没有bug。–防御性编程提供了一种方法,即使您不知道错误在哪里,也可以减轻它们的影响。

2 断言
什么是断言? ▪ 断言是在开发过程中使用的代码,它允许程序在运行时检查自身,即测试您的假设输出您的程序逻辑(如前置条件、后置条件和不变量)。–当断言为真时,意味着一切都按预期运行。–如果为false,则表示在代码中检测到意外错误。▪ 每个断言都包含一个布尔表达式,您相信当程序执行时该表达式将为真。–如果不正确,JVM将抛出断言错误。–此错误表示您的假设无效,需要修复。–断言确认了您对程序行为的假设,增加了您对程序没有错误的信心。 ▪ 断言比使用if-else语句要好得多,因为它是关于您的假设的适当文档,并且在生产环境中不承担性能责任。▪ 断言通常有两个参数——一个描述假设为真的布尔表达式——如果不是,则显示一条消息。▪ Java语言有一个关键字assert,它有两种形式:–assert condition;–assert condition:message;–两个语句都计算条件,如果布尔表达式的计算结果为false,则抛出AssertionError。–在第二条语句中,表达式被传递给AssertionError对象的构造函数,并转换为消息字符串。当断言失败时,描述将打印在错误消息中,因此可以使用它向程序员提供有关失败原因的其他详细信息。

要断言x是非负的,可以简单地使用语句assert x>=0;
▪ 或者将x的实际值传递给AssertionError对象,以便稍后显示: 断言x>=0:“x是”+x;
▪ 如果x=-1,则此断言失败,并显示错误消息

为什么要使用断言?
▪ 记录和测试程序员的假设,例如不变量;
▪ 验证程序员的理解▪ 快速发现bug
▪ 增加程序无错误的信心
▪ 断言将黑盒测试转换为白盒测试
经验表明,在编程时编写断言是检测和纠正错误的最快和最有效的方法之一。
一个额外的好处,断言用于记录程序的内部工作,增强可维护性。

(2) What to Assert and What not to

▪ 断言可用于验证:–内部不变量断言某个值在某个约束内,例如Assert x>0。–Rep不变量:断言对象的状态在约束内。在执行方法之前或之后,类的每个实例必须是什么样的?类不变量通常通过私有布尔方法验证,例如checkRep()。
–控制流不变量:断言无法到达某个位置。例如,switch case语句的default子句。
–方法的前提条件:调用方法时必须满足什么条件?通常用方法的参数或对象的状态来表示。
–方法的后置条件:方法成功完成后,什么必须为true?

▪ 前提条件:方法参数要求
▪ Post条件:方法返回值要求——这种断言有时称为自检。

▪ 控制流:覆盖所有-如果条件语句或开关不覆盖所有可能的情况,则使用断言来阻止非法情况是一种良好的做法。但是不要在这里使用assert语句,因为它可以被关闭。相反,在非法情况下抛出一个异常,这样检查就会一直发生

▪ 通常,您不希望用户在生产代码中看到断言消息;断言主要用于开发和维护期间。
▪ 断言通常在开发时编译为代码,并在生产时编译为代码。
▪ 在生产过程中,它们是从代码中编译出来的,这样断言就不会降低系统性能。
–在你写代码的时候,而不是事后。当你在写代码时,你要记住不变量。
–如果您推迟编写断言,则不太可能这样做,而且可能会遗漏一些重要的不变量。

▪ 运行时断言不是免费的。它们会使代码混乱,因此必须谨慎使用。
–避免琐碎的断言,就像避免无信息的评论一样。
–此断言在代码中找不到错误。–它会在编译器或Java虚拟机中发现错误,这些组件是您应该信任的,直到您有充分的理由怀疑它们。 –如果断言从其本地上下文中很明显,请将其忽略。
避免将可执行代码放入断言中
▪ 由于断言可能被禁用,程序的正确性永远不应取决于是否执行断言表达式。
▪ 特别是,断言的表达式不应该有副作用。–例如,如果要断言从列表中删除的元素实际上是在列表中找到的,请不要这样编写:
▪ 如果断言被禁用,则跳过整个表达式,并且x永远不会从列表中删除。改为这样写:
在这里插入图片描述
不要使用断言来测试程序外部的条件。
–例如文件的存在、网络的可用性,或用户输入的正确性。
–断言测试程序的内部状态,以确保它在其规范的范围内。
–当断言失败时,它表示程序在某种意义上已经脱离了轨道,进入了无法正常运行的状态。因此,断言失败指示错误。
–外部故障不是bug,并且没有任何可以提前对程序进行的更改来阻止它们的发生。
-应使用异常处理外部故障。避免琐碎的断言,就像避免无信息的评论一样

在不同阶段打开/关闭断言 ▪ 许多断言机制的设计使得断言只在测试和调试期间执行,并在程序发布给用户时关闭。–断言是一个很好的工具,可以保护代码不受bug的影响,但是Java默认情况下会关闭它们!▪ 这种方法的优点是可以编写非常昂贵的断言,否则会严重降低程序的性能。–例如,使用二进制搜索搜索数组的过程要求对数组进行排序。–断言此要求需要扫描整个数组,但是,将应该在对数时间内运行的操作转换为需要线性时间的操作。–您应该愿意在测试期间支付这一成本,因为这会使调试更加容易,但在程序发布给用户之后就不会了。
▪ 启用断言–使用-enablessertions或-ea选项运行程序:

▪ Java assert语句是与JUnit方法assertTrue()、assertEquals()等不同的机制。▪ 它们都断言关于代码的谓词,但设计用于不同的上下文。▪ assert语句应该在实现代码中使用,用于实现内部的防御检查。▪ JUnit测试中应该使用JUnit assertXXX()方法来检查测试的结果。▪ assert语句不会在没有-ea的情况下运行,但是JUnit assertXXX()方法始终运行。

(3) 断言使用指南
▪ 断言通常包括程序的正确性问题。–如果断言是由于异常情况触发的,则纠正操作不仅仅是为了优雅地处理错误,纠正操作是更改程序的源代码、重新编译并发布软件的新版本。
▪ 例外情况通常包括程序的可靠性问题。–如果错误处理代码用于处理异常情况,则错误处理将使程序能够对错误作出正确响应。
▪ 断言在大型复杂程序和高可靠性程序中特别有用。–它们使程序员能够更快地清除不匹配的接口假设、修改代码时出现的错误,等等。

断言与异常
▪ 对于您希望发生的情况,请使用错误处理代码(异常)–错误处理代码检查异常情况,这些异常情况可能不经常发生,但编写代码的程序员已经预料到,并且需要由生产代码处理。
▪ 在不应该发生的情况下使用断言——断言检查代码中的错误。
▪ 另一个观点是:不要在公共方法中使用断言来检查参数。▪ 原因:–参数检查通常是方法的已发布规范(或协定)的一部分,无论断言是启用还是禁用,都必须遵守这些规范。–使用断言进行参数检查的另一个问题是,错误的参数应导致适当的运行时异常(例如IllegalArgumentException、IndexOutfBoundsException或NullPointerException)。断言失败不会引发适当的异常。

如果参数来 自于外部(不受自己控制),使用异常处理
如果来自于自己 所写的其他代码,可以使用断 言来帮助发现错误(例如postcondition就需要)
按照惯例 ,public方法上的前置条件是通过抛出特定的、指定的异常的显式检查 来进行的
可以使用断言来测试非公共方法的前置条件,当 认为无论client对该类做什么,这些前置条件都正确时(正确性不由 client决定)
在public和nonpublic方法中,都可以用断言来 处理后置条件
断言和异常处理都可以处理同样的错误
–例如,在Microsoft Word的源代码中,会断言应该始终为true的条件,但如果断言失败,这些错误也会由错误处理代码处理。–对于像Word这样的大型、复杂、长寿命的应用程序,断言很有价值,因为它们有助于尽可能多地清除开发时的错误。▪ 但是这个应用程序是如此的复杂(百万行代码),并且经历了如此多代的修改,以至于在软件发布之前假设每一个可能的错误都会被检测和纠正是不现实的,因此错误也必须在系统的生产版本中处理。

3 防御式编程

什么是防御性编程?
▪ 防御性编程是一种防御性设计形式,旨在确保一个软件在不可预见的情况下继续运行。–防御编程实践通常用于需要高可用性、安全性或安全性的地方。
▪ 这个想法可以被看作是减少或消除墨菲定律生效的可能性。 ▪ “你的代码应该尽早失败!”

4防御性编程技巧
1) 保护程序不受无效输入的影响
▪ “垃圾输入,垃圾输出”—这个表达式本质上是软件开发的cavet emptor版本:让用户当心。货物出门概不退换
▪ 对于生产软件来说,垃圾输入、垃圾输出还不够好。
▪ 一个好的程序永远不会丢弃垃圾,不管它需要什么。
–“垃圾输入,无输出”
–“垃圾输入,错误消息输出”
–“不允许垃圾输入”
▪ “垃圾输入,垃圾输出”是一个草率、不安全的程序的标志。

▪ 检查来自外部源的所有数据的值
从文件、用户、网络或其他外部接口获取数据时,请检查数据是否在允许的范围内。
▪ 示例:–确保数值在公差范围内,并且字符串足够短,可以处理。–如果字符串的目的是表示一个受限制的值范围(例如金融交易ID或类似的内容),请确保该字符串对于其目的是有效的;否则拒绝该字符串。–如果您正在开发一个安全的应用程序,请特别注意可能会攻击系统的数据:尝试的缓冲区溢出、注入的SQL命令、注入的html或XML代码、整数溢出等等。

(2) 路障
▪ 路障是一种损伤控制策略。
–原因类似于在船体上有独立的隔间,在建筑中有防火墙。
–出于防御编程目的设置路障的一种方法是将某些接口指定为“安全”区域的边界。检查跨越安全区域边界的数据是否有效,并在数据无效时做出合理的响应。
定义软件中一些处理脏数据的部分和一些处理干净数据的部分可以有效地减轻大多数代码检查坏数据的责任。

▪ 类的公共方法假定数据是不安全的,它们负责检查数据并对其进行清理。–一旦数据被类的公共方法接受,类的私有方法就可以假设数据是安全的。
▪ 另一种方法是作为手术室技术。操操作间技术–数据在允许进入手术室之前进行消毒。手术室里的任何东西都被认为是安全的。–关键的设计决策是决定在手术室里放什么,什么东西放在外面,以及在哪里放门,哪些例行程序被认为在安全区域内,哪些在安全区域外,以及哪些对数据进行消毒。–最简单的方法通常是在外部数据到达时对其进行消毒,但数据通常需要在多个级别进行消毒,因此有时需要进行多个级别的消毒。

▪ 在输入时将输入数据转换为适当的类型–输入通常以字符串或数字的形式到达。–有时该值将映射到布尔类型,如“yes”或“no.”–有时该值将映射到枚举类型,如Color_Red、Color_Green和Color_Blue。–在程序中任何时间段携带可疑类型的数据都会增加复杂性,并增加有人通过输入“是”这样的颜色而导致程序崩溃的可能性–输入数据后,请尽快将其转换为正确的格式。

路障与断言的关系
▪ 使用路障可以区分断言和错误处理。–路障之外的例程应该使用错误处理,因为对数据进行任何假设都是不安全的。–路障内的例程应该使用断言,因为传递给它们的数据应该在经过路障之前进行消毒。如果路障内的某个例程检测到错误数据,则这是程序中的错误,而不是数据中的错误。
、▪ –路障的使用也说明了在架构级别决定如何处理错误的价值。–决定哪些代码在内部,哪些在路障之外是一个架构级的决定。

5防御性编程检查表
▪ 过多的防御性编程会产生问题,会为不可能发生的错误引入不必要的代码,增加软件的复杂性,从而浪费运行和维护成本。
–为防御性编程而安装的代码并不能免受缺陷的影响,而且您在防御性编程代码中发现缺陷的可能性与在任何其他代码中发现缺陷的可能性一样大。
–代码还可能捕获或阻止太多异常,从而可能导致未被注意到的错误结果。
▪ 考虑一下需要防御的地方,并相应地设置防御编程的优先级。

▪ 常规-例程是否保护自己不受错误输入数据的影响?
–您是否使用断言来记录假设,包括先决条件和后决条件?
–断言是否只用于记录不应该发生的情况?
–体系结构或高级设计是否指定了一组特定的错误处理技术?
–体系结构或高级设计是否指定错误处理应支持健壮性还是正确性?
–是否设置了路障以遏制错误的破坏性影响,并减少必须关注错误处理的代码量?
-代码中是否使用了调试辅助工具?
–是否已使用信息隐藏来包含更改的效果,以便它们不会影响已更改的例程或类之外的代码?
–是否安装了调试辅助工具,使其可以激活或停用而不必大惊小怪?
–防御性编程代码的数量是否合适,既不太多也不太少?
–您是否使用过攻击性编程技术,使开发过程中难以忽略的错误?
▪ 异常
-您的项目是否定义了异常处理的标准化方法?
–您是否考虑过使用异常的替代方案?
–是否在本地处理错误,而不是在可能的情况下引发非本地异常?
–代码是否避免在构造函数和析构函数中引发异常?
–对于抛出异常的例程,是否所有异常都处于适当的抽象级别?
–每个异常是否包含所有相关的异常背景信息?
–代码是否没有空的catch块?(或者如果一个空的catch块确实是合适的,那么它是否被记录在案?)
▪ 安全问题
-检查错误输入数据的代码是否检查尝试的缓冲区溢出、SQL注入、html注入、整数溢出和其他恶意输入?
–是否检查了所有错误返回码?
–是否捕获所有异常?
–错误消息是否避免提供有助于攻击者侵入系统的信息?

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值