软件构造 6-3 Assertions and Defensive Programming
6.3 断言与防御式编程
一. 断言 assert
首先要遵循:以尽量不要引入 bug
的原则(防御式编程)编程。
- 静态检查
- 动态检查
- 不可变类型
- 不可变值
- 不可变引用
若无法避免,缩小 bug
的范围:
- 限定在一个方法内部,不扩散
- 尽快失败,就容易发现、越早修复
断言就是一种 fail fast
,避免 bug
的扩散。
1. assertion
断言:在开发阶段的代码中嵌入,检验某些“假设”是否成立。若成立,表明程序运行正常,否则表明存在错误。
public class AssertionTest {public static void main(String[] args) {int number = -5; // assumed number is not negative// This assert also serve as documentationassert (number >= 0) : "number is negative: " + number;// do somethingSystem.out.println("The number is " + number);}
}
每个断言都包含一个您认为在程序执行时为真的布尔表达式。
- 若不为真将出现
AssertionError
- 出现
AssertionError
,意味着内部某些假设被违反了 - 增强程序员对代码质量的信心:对代码所做的假设都保持正确
assert
具有以下两种方式之一:
assert condition
assert condition : message
:message
可以描述错误原因、记录案发现场
有时候为了安全性,防止错误对后续程序造成影响,release
版本使用 assert
,但一般不使用。
2. assert
适用范围
assert
可以使用在各种不变量中:
Internal Invariants
内部不变量Rep Invariants
表示不变量:checkRep()
Control Flow Invariants
控制流不变量Pre conditions of methods
方法的前置条件Post conditions of methods
方法的后置条件,如:
public double sqrt(double x) {assert x >= 0;double r;... // compute result rassert Math.abs(r*r - x) < .0001;return r;
}
Control-flow
控制流,如:不使用不分流;判断是否执行到不该执行的代码之后
switch (vowel) {case 'a':case 'e':case 'i':case 'o':case 'u': return "A";default: assert false ;
}
void foo() {for (...) {if (...)return;}assert false;// Execution should never reach this point!
}
其实等价于使用语句如:
default: throw new AssertionError ("must be a vowel, but was: " + vowel);
assert
还可以判断:
- 指针是否为空
- 传入方法的数组或其他容器至少可以包含 X 个数据元素
- 一个表被初始化为实数值
- 当方法开始执行(或完成)时,容器是空的(或满的)
- 从一个高度优化的、复杂的方法得到的结果与从一个较慢但清晰编写的例程得到的结果相匹配
实际上可以对程序执行时的某些条件进行验证。
实际上, assert
- 断言主要用于开发阶段,避免引入和帮助发现
bug
- 实际运行阶段,不再使用断言,运行阶段对你编写的程序不可以使用
assert
以保证健壮性。但可以使用异常 - 避免降低性能
- 使用断言的主要目的是为了在开发阶段调试程序、尽快避免错误
因此,程序之外的事,不受你控制,不要乱断言,注意:
- 如:文件/网络/用户输入等不要检测。这是因为检测了也没办法
- 断言只是检查程序的内部状态是否符合规约
- 断言一旦
false
,程序就停止执行 - 你的代码无法保证不出现此类外部错误 ,外部错误要使用
Exception
机制去处理。而assert
一般检测程序内部的错误(开发阶段)。
正常情况下,Java
缺省关闭断言,要使用 assert
时要记得打开 (-ea
)。
默认情况下, assert
功能是关闭的,这是因为
- 断言不应该出现在工程的
release
版本。client
想要挽救bug
而不是中止程序。 - 断言非常影响运行时的性能
开启 assert
需要使用相应的命令或者打开相应的按钮
3. 断言使用的指导
assert
与 Exception
的区别
- 断言的目标是保证正确性,使用在开发阶段,针对程序员。使用断言处理“绝不应该发生”的情况
- 错误/异常处理的目标是保证健壮性,使用在发布阶段,针对用户端。使用异常来处理你“预料到可以发生”的不正常情况。
对于 pre-/post-condition
,是否使用 assert
有争议(前者通常用 Exception
, 后者通常用 assert
)。争议的理由有以下几点
- 不管是否
-ea
,spec
中的pre-/post-conditions
都能够被保证 - 即使
spec
被违反,也不应通过assert
直接fail
,而是应抛出具体的RunTimeException
。
我们使用如下原则:
如果参数来自于外部(不受自己控制,如 public
方法的参数),使用异常处理;如果来自于自己所写的其他代码,可以使用断言来帮助发现错误(例如 post condition
就需要;如 private
方法)。
/**
* Sets the refresh rate.
*
* @param rate refresh rate, in frames per second.
* @throws
IllegalArgumentException if rate <= 0 or
* rate > MAX_REFRESH_RATE.
*/
public void setRefreshRate(int rate) {// Enforce specified precondition in public methodif (rate <= 0 || rate > MAX_REFRESH_RATE)throw new IllegalArgumentException ("Illegal rate: " + rate);setRefreshInterval(1000/rate);
}
/**
* Sets the refresh interval (which must correspond to a legal rate).
*
* @param interval refresh interval in milliseconds.
*/
private void setRefreshInterval(int interval) {// Confirm adherence to precondition in nonpublic methodassert interval > 0 && interval <= 1000/MAX_REFRESH_RATE :... // Set the refresh interval
}
/**
* Returns a
BigInteger whose value is (this 1 mod m).
*
* @param m the modulus.
* @return this -1 mod m.
* @throws ArithmeticException m <= 0, or this BigInteger
* has no multiplicative inverse mod m (that is, this BigInteger
* is not relatively prime to m).
*/
public BigInteger modInverse(BigInteger m) {if (m.signum <= 0)throw new ArithmeticException ("Modulus not positive: " + m);... // Do the computationassert this.multiply.(result).mod(m).equals(ONE) : this;return result;
}
异常能做到断言能做的事。断言和异常处理都可以处理同样的错误。但由于断言可以容易地开启关闭,故断言的使用仍然广泛。
开发阶段用断言尽可能消除 bugs
在发行版本里用异常处理机制处理漏掉的错误。
二. 防御性编程及工具
1. 阻止程序的非法输入
正确性使得程序员只需对正确数据有正确反应,错误数据可以随意处理。但是从健壮性角度,仍然需对错误数据做出良好的反应,如无反应、提示错误信息或不允许输入错误数据。这时规约应该添加上对错误的处理方法。
对来自外部的数据源要仔细检查,例如:文件、网络数据、用户输入等。
2. 设置路障
不安全的方法(输入可能不合法的方法)通过检测手段检测数据输入是否有错误。路障可以使不正确的输入被过滤掉,使得不安全的方法变成安全的方法。
可以使用 public
和 private
方法判定,前者为不安全的方法,后者可能是安全的方法。
类的 public
方法接收到的外部数据都应被认为是 dirty
的,需要处理干净再传递到private
方法——隔离舱方法、操作间技术。
“隔离舱”外部的函数应使用异常处理,“隔离舱”内的函数应使用断言。
这种方法是代理模式(Proxy
设计模式)。代理类可以做数据校验。
三. SpotBugs
工具
SpotBugs
是 Java
静态代码分析工具。
软件构造 6-3 Assertions and Defensive Programming相关推荐
- 【软件构造】实验笔记(一)Lab1-Fundamental Java Programming and Testing
一.前言 <软件构造>课程是我校根据MIT.CMU等计算机领域名校的相关课程近年来开展的软件开发相关的课程.课程的实验和课件都很大程度上参考了上述学校. 本笔记对在课程实验练习进行中遇到的 ...
- 软件构造的视图与质量指标
软件构造的实质:Transformation between different views(不同视图之间的转换) Multi-dimensional software views 按阶段分:buil ...
- 软件构造 第一章第二节 软件开发的质量属性
软件构造 第一章第二节 软件开发的质量属性 1.软件系统质量指标 External quality factors affect users 外部质量因素影响用户 Internal quality ...
- 软件构造 Lab4 Lab5 实验日记
软件构造 Lab4 Lab5 实验日记 SC Lab4 实验日记(持更)自定义异常 防御策略 log debug week11-week12 Debugging Exception Handling ...
- Defensive Programming 防御式编程(Defensive Programming)
Defensive Programming 防御式编程(Defensive Programming)是提高软件质量技术的有益辅助手段 怎么理解呢?防御式编程思想的理解可以参考防御式驾驶: 在防御式驾驶 ...
- 防御性编程(Defensive Programming)
什么是防御性编程?(What is Defensive Programming?) garbage in ,garbage out (GIGO),作为一条计算机界的"俗语",一条相 ...
- 面向对象软件构造(第2版)-第7章 静态结构: 类 (下)
7.9 PUTTING EVERYTHING TOGETHER 7.9 组合一切 The previous discussions have introduced the basic mechanis ...
- 软件构造 第二章 第一节 软件生命周期和版本控制
软件构造第二章 第一节 软件生命周期和版本控制 基本内容 Software Development Lifecycle (SDLC) Traditional software process mode ...
- 软件构造-Reading 1:静态检查
大纲: 阅读1:静态检查 目标: 冰雹序列 计算冰雹序列 类型 静态类型 静态检查.动态检查.无检查 惊喜:原始类型并不是真正的数字! 阅读练习 数组和集合 迭代 方法 变化的值与重新分配变量 记录假 ...
最新文章
- 第四范式申请港交所上市:2021上半年营收7.88亿,研发费用占七成
- yii2 导入开源php项目_终于来了,yii2和phpstorm的完美整合,支持到你不要不要的。...
- 三代组装软件canu学习笔记
- 透彻理解高斯核函数背后的哲学思想与数学思想
- boost::logic::tribool相关用法的测试程序
- 【spring boot】Controller @RequestMapping 数据绑定:接收 Date 类型参数时遇错,将 String 类型的参数转换成 Date 类型
- 128位计算机 ps2,64位就最强?为啥没有128位电脑?
- 11年潜心研究产品 全屋智能品牌Aqara终于要开发布会了
- 计算机科学竞赛加拿大,滑铁卢大学计算机科学与数学竞赛最新考试时间!
- 天线越大越好吗_无线路由知识误区!解读天线数量与信号强弱的关系
- 浙江大学计算机科学与技术博士培养研究方向,浙江大学博士研究生培养方案
- 开箱体验: Web研发从石器时代过渡青铜时代复盘心得
- 上海市土木工程人才需求暴涨
- 自然语言处理——谷歌BERT模型深度解析
- 糖果(2019第十届蓝桥杯省赛C++A组I题) 解题报告(状压dp) Apare_xzc
- 小程序关注公众号official-account组件
- 再谈用VS+VC绿色版编译多版本
- wrong ELF class: ELFCLASS32
- 7-11 Say Hello to Integers
- IPRAN PTNRAN