编写代码时,我们总是会做出一些假设,断言就是用于在代码中捕捉这些假设,可以将断言看作是​​异常处理​​​的一种高级形式。断言表示为一些​​布尔表达式​​,程序员相信在程序中的某个特定点该表达式值为真。可以在任何时候启用和禁用断言验证,因此可以在测试时启用断言,而在部署时禁用断言。同样,程序投入运行后,最终用户在遇到问题时可以重新起用断言。

Assert - 断言

使用断言可以创建更稳定,品质更好且不易于出错的代码。当需要在一个值为FALSE时中断当前操作的话,可以使用断言。​​单元测试​​必须使用断言(Junit/JunitX)。

除了类型检查和​​单元测试​​外,断言还提供了一种确定各种特性是否在程序中得到维护的极好的方法。

断言特性

前置条件断言:代码执行之前必须具备的特性

后置条件断言:代码执行之后必须具备的特性

前后不变断言:代码执行前后不能变化的特性

使用方式

断言只有在Debug模式下才有效,它可以有两种形式

1.assert Expression1

2.assert Expression1:Expression2

其中Expression1应该总是一个布尔值,Expression2是断言失败时输出的失败消息的字符串。如果Expression1为假,则抛出一个 AssertionError,这是一个错误,而不是一个异常,也就是说是一个不可控制异常(unchecked Exception),AssertionError由于是错误,所以可以不捕获,但不推荐这样做,因为那样会使你的系统进入不稳定状态。

启用断言

断言在默认情况下是关闭的,要在编译时启用断言,需要使用source1.4标记既javac source1.4 Test.java ,在运行时启用断言需要使用 -ea参数。要在系统类中启用和禁用断言可以使用 -​​esa​​和 -dsa参数。

例如:

​​public​​​​class​​​​AssertExampleOne{ ​​
​​ public​​​​AssertExampleOne(){} ​​
​​ public​​​​static​​​​void​​​​main(String args[]){ ​​
​​ int​​​​x=10; ​​
​​ System.out.println("Testing Assertion that x==100"); ​​
​​ assert​​​​x==100:"Out assertion failed!"; ​​
​​ System.out.println("Test passed!"); ​​
​​ } ​​
​​}​​

如果编译时未加 -source1.4,则编译通不过

在执行时未加 -ea 时输出为

Testing Assertion that x==100

Test passed

jre忽略了断言的就代码,而使用了该参数就会输出为

Testing Assertion that x==100

Exception in thread "main" java.lang.AssertionError: Out assertion failed!

at AssertExampleOne.main(AssertExampleOne.java:6)

断言的副作用

由于程序员的问题,断言的使用可能会带来副作用,例如:

boolean isEnable=false;

//...

assert isEnable=true;

这个断言的副作用是因为它修改了程序中​​变量​​的值并且未抛出错误,这样的错误如果不细心的检查是很难发现的。但是同时我们可以根据以上的副作用得到一个有用的特性,根据它来测试断言是否打开。

​?​

public​​​​class​​​​AssertExampleTwo{ ​​
​​ public​​​​static​​​​void​​​​main(String args[]){ ​​
​​ boolean​​​​isEnable=false; ​​
​​ //... ​​
​​ assert​​​​isEnable=true; ​​
​​ if(isEnable==false){ ​​
​​ throw​​​​new​​​​RuntimeException("Assertion shoule be enable!"); ​​
​​ } ​​
​​ } ​​
​​}​​

使用断言

1.可以在预计正常情况下程序不会到达的地方放置断言:assert false

2.断言可以用于检查传递给私有方法的参数。(对于公有方法,因为是提供给外部的接口,所以必须在方法中有相应的参数检验才能保证代码的健壮性)

3.使用断言测试方法执行的前置条件和后置条件

4.使用断言检查类的不变状态,确保任何情况下,某个​​变量​​的状态必须满足。(如age属性应大于0小于某个合适值)

不用断言

断言语句不是永远会执行,可以屏蔽也可以启用

因此:

1.不要使用断言作为公共方法的参数检查,公共方法的参数永远都要执行

2.断言语句不可以有任何边界效应,不要使用断言语句去修改​​变量​​和改变方法的返回值.

C里的宏

宏名: assert

功能: 测试一个条件并可能使程序终止

用法: void assert(int test);

程序例:

#include <assert.h> ​​
​​#include <stdio.h> ​​
​​#include <stdlib.h> ​​
​​struct​​​​ITEM { ​​
​​ int​​​​key; ​​
​​ int​​​​value; ​​
​​}; ​​

​​/* add item to list, make sure list is not null */​​
​​void​​​​additem(struct​​​​ITEM *itemptr) { ​​
​​ assert(itemptr != NULL); ​​
​​ /* add item to list */​​
​​} ​​

​​int​​​​main(void) ​​
​​{ ​​
​​ additem(NULL); ​​
​​ return​​​​0; ​​
​​}​​

assert() 宏用法

注意:assert是宏,而不是函数。在C的​​assert.h​​头文件中。

assert宏的原型定义在<assert.h>中,其作用是如果它的条件返回错误,则终止程序执行,原型定义:

​​#include <assert.h> ​​
​​void​​​​assert( int​​​​expression );​​

assert的作用是先计算表达式​​expression​​​,如果其值为假(即为0),那么它先向标准错误流​​stderr​​​打印一条出错信息,然后通过调用​​abort​​来终止程序运行;否则,assert()无任何作用。宏assert()一般用于确认程序的正常操作,其中表达式构造无错时才为真值。完成调试后,不必从源代码中删除assert()语句,因为宏NDEBUG有定义时,宏assert()的定义为空。[

请看下面的程序清单badptr.c:

#include <stdio.h> ​​
​​#include <assert.h> ​​
​​#include <stdlib.h> ​​
​​int​​​​main( void​​​​) ​​
​​{ ​​
​​ FILE​​​​*fp; ​​
​​ fp = fopen( "test.txt", "w"​​​​);//​​​​以可写的方式打开一个文件,如果不存在就创建一个同名文件 ​​
​​ assert( fp ); //​​​​所以这里不会出错 ​​
​​ fclose( fp ); ​​
​​ fp = fopen( "noexitfile.txt", "r"​​​​);//​​​​以只读的方式打开一个文件,如果不存在就打开文件失败 ​​
​​ assert( fp ); //​​​​所以这里出错 ​​
​​ fclose( fp ); //​​​​程序永远都执行不到这里来 ​​
​​ return​​​​0; ​​
​​}​​

[root@localhost error_process]# gcc badptr.c

[root@localhost error_process]# ./a.out

a.out: badptr.c:14: main: Assertion `fp' failed.

已放弃

使用assert的缺点是,频繁的调用会极大的影响程序的性能,增加额外的开销。

在调试结束后,可以通过在包含#include <assert.h>的语句之前插入 #define NDEBUG 来禁用assert调用,示例代码如下:

​​#include <stdio.h> ​​
​​#define NDEBUG ​​
​​#include <assert.h>​​

用法总结与注意事项:

1)在函数开始处检验传入参数的合法性

如:

​​int​​​​resetBufferSize(int​​​​nNewSize) ​​
​​{ ​​
​​ //​​​​功能:改变缓冲区大小, ​​
​​ //​​​​参数:nNewSize缓冲区新长度 ​​
​​ //​​​​返回值:缓冲区当前长度 ​​
​​ //​​​​说明:保持原信息内容不变 nNewSize<=0表示清除缓冲区 ​​
​​ assert(nNewSize >= 0); ​​
​​ assert(nNewSize <= MAX_BUFFER_SIZE); ​​
​​ ... ​​
​​}​​

2)每个assert只检验一个条件,因为同时检验多个条件时,如果断言失败,无法直观的判断是哪个条件失败

​​/***​​​​不好***/​​
​​assert(nOffset>=0 && nOffset+nSize<=m_nInfomationSize); ​​
​​/****​​​​好****/​​
​​assert(nOffset >= 0); ​​
​​assert(nOffset+nSize <= m_nInfomationSize);​​

3)不能使用改变环境的语句,因为assert只在DEBUG个生效,如果这么做,会使用程序在真正运行时遇到问题

错误: assert(i++ < 100)

这是因为如果出错,比如在执行之前i=100,那么这条语句就不会执行,那么i++这条命令就没有执行。

正确: assert(i < 100)

i++;

4)assert和后面的语句应空一行,以形成逻辑和视觉上的一致感

5)有的地方,assert不能代替条件过滤

注意:当对于浮点数:

#include<assert.h> ​​
​​float​​​​pi=3.14f; ​​
​​assert​​​​(pi==3.14f);​​

在switch语句中总是要有default子句来显示信息(Assert)。

int​​​​number = SomeMethod(); ​​
​​switch(number) ​​
​​{ ​​
​​ case​​​​1: ​​
​​ Trace.WriteLine("Case 1:"); ​​
​​ break; ​​
​​ case​​​​2: ​​
​​ Trace.WriteLine("Case 2:"); ​​
​​ break; ​​
​​ default​​​​: ​​
​​ Debug.Assert(false); ​​
​​ break; ​​
​​}​​