aseert

原文:

http://sunxiunan.com/?p=1638

 

 

有下面2种方法:

方法一:Section *pImageSection = new Section(pImage);

assert(pImageSection);

方法二:略

ps:现在项目组代码用第一方法,并且也不写日志,每一次客户端down了,定位问题都要很久,让人很崩溃。并且到处都是assert。

我个人认为,用assert的地方,是比较严重的错误,甚至不能够让程序再运行下去。

如果到处用asset也太残忍了,有的时候应该温柔的跳过,然后写日志,返回。告诉我哪里运行失败了。

—————————————————-

下面是我对这个问题的回答:

首先需要指出一个非常重要、讨论前必须明确的问题,assert一般不应该直接用,而应该包装一下,比如微软就有ASSERT宏,可以跟踪line file这些有用信息,另外可以通过条件编译取消ASSERT。每次定位很久,只能说这种编程common sense还没有建立起来。

原文虽然说得很简单,其实涉及了几个方面,一个是版本管理发布,一个是错误处理机制,一个是如何写日志,我们一个个讨论。

首先是什么情况下用ASSERT,什么情况下应该处理wrong case?

一个成熟的代码里面,大概有百分之三十到七十的代码都是为了查错,fault tolerance。比如你写一个API或者对外使用的函数,别人要调用,你要确保传入的指针不为空,传入的参数合法,这时候都不应该,注意是不应该使用ASSERT,因为这些情况out of your control。

什么情况可以使用ASSERT,应该是那些绝少发生的例子,而且你可以控制的那些代码段落,比如Object* p = new Object,返回空指针,这种情况下多半是内存申请出错,即使你容错了,后面的逻辑也不会正常,这时候就应该停止运行报错了。还有就是你的内部函数(注意是你程序的内部哦),因为内部函数的代码责任其实是你自己的,你传错参数只能说水平太差怨不得别人。也许有人会说,同组里别的程序员万一也调用了这个函数怎么办?这种情况当然是会有的,其实也没法避免,但可以用ASSERT以及Code review这些手段来规范,同一个程序组里面的问题属于内部矛盾好解决多了。

所以,大的原则可以是这样:内部函数尽量简化容错机制,稍微苛刻一些,而对外接口则尽量容忍,出错处理也要温柔。

第二个问题,ASSERT该怎么写?

我已经提到,不要直接使用assert,最好包装一下,加入#ifdef _DEBUG这种条件编译选项,保证Release版本不弹出ASSERT。这里其实也涉及到版本管理问题,一个商业产品发布一般包含两个版本(Release版以及Debug checked版本),即使是Windows操作系统也是如此。在Release版本要Disasble assert,这时候如果出错可以用后面的Crash Dump机制捕捉跟踪,而且要说明的是Release版本一样要附带PDB文件(或者Linux同类文件),保证Crash Dump可以定位。Debug版本也要发给用户,用户可以通过它返回更详细信息。每个Build出来的Release、Debug都应该妥善备份,将来客户提交Bug,对应版本以及PDB,就可以准确定位,不会出现很难差错的现象。

另外也可以加入日志log选项。在这里跑一下题,日志记录也是个大问题,要考虑多线程以及性能问题,其实不是加入日志系统就万事大吉了,说句难听的,对于那些2B Number One的人来讲,任何一个好东西都可能被他们用到烂。一般ASSERT跑出来,对于客户来说就等同于Crash了,所以可以参考一下Google的Crash Report开源项目,比如Windows下可使用Crash Dump机制来捕捉崩溃时调用栈,这样即使Fail Soon Fail Fast也不会很难看。这些手段都是common sense的东西,一个正常的软件开发公司应该积累下这些代码财富才对。

 

原文:

http://topic.csdn.net/u/20100111/14/fff0c4e8-0d24-44b5-8591-055fb2580206.html

在公司一个同事问我:"什么时候应该用assert?比如某个函数的输入参数为一个指针,要进行有效计算,该指针必须非空,我弹出一个警告对话框来提示对方不就行了吗?干吗要用assert?我想问的是什么时候用对话框提示用户,什么时候用assert?"

       这是一个值得思考的问题。首先明确assert(这里我说的是C++环境下)是需要在调试状态下才能生效的,在发布版是失效的。调试生效的事实告诉我们的是assert的一个重要作用是帮助开发者调试程序定位错误的。作为开发者一个普遍事实是解决bug并不难,定位bug往往是最难的。这也是我倡导多使用assert的原因所在。再深想一层,我们作一个假想:假如assert在发布版上也能用,那么什么时候用assert,什么时候用提示对话框,原则是什么?我认为这里有一个用户层次的问题。程序其实一般有两类用户:未发布前的开发者和发布后的用户。作为开发者,我们假定他是懂断言的,但对于用户,我们却不能假定他懂断言。如果这个接口是面对开发者的,那么就应该用断言来限制输入参数;如果是面向用户的,那么就应用提示对话框,比如弹出一个对话框选择让用户选择一个输入文件,但用户还没选择文件就按"确定"按钮,这应该用对话框就是提示,而不应该断言(这里如果弹出断言对话框,他还不知道是怎么回事呢)。

     suanyuan大侠在我的博客评论:
assert 其實是除錯的工具,在 debug mode 之下,我們會希望,在特定條件的錯誤之下,debugger 馬上帶我們道出錯的地方。 assert 最常犯的錯誤,因為在 debug mode 會被 debugger 給攔截。而 release mode 下 assert 的敘述會被略去。所以在 release mode 下,加上 assert 條件成立的錯誤處理的 error handler 就很重要,要優雅的讓程式在發生錯誤的條件下退場。 我常被 assert 之後,完全當成是正常情況處理的 code 氣死,因為那會讓遊戲出現一些匪夷所思的靈異現象,除錯時。才發現 assert 以後根本沒處理。 assert 另外常犯的毛病是,有人的程式,從頭 assert 到尾,在他的電腦沒事,道別人的電腦就狂 assert。assert 到想要拿球棒打他。尤其在系統整合的時候。 許多情況下,非必要的 assert 可以用 OutputDebugString() 或是自己寫到 debug log。 不明白 assert 到底是幹麼,而拼命用的程式,講個兩次不聽,第三次我就開除了。

     我针对他的评论给他回复:
多谢老哥前来捧场!你说的我有点不明白,盼指教!你说assert 其實是除錯的工具,这一点我是同意的。你说的在 release mode 下,加上 assert 條件成立的錯誤處理的 error handler 就很重要,要優雅的讓程式在發生錯誤的條件下退場。我觉得在release条件下并不需要对assert错误成立的处理。比如下例,照你的意思:
// 对文件进行处理,输入条件为文件非空
void ProcessFile(char *pszFileName)
{
assert(NULL!=pszFileName);
// 下面是release mode的优雅处理
if(NULL==pszFileName)
return;
}

但我个人认为: 
if(NULL==pszFileName)
return;
这两行代码是多余的。因为当调试程序时假如外部调用传入pszFileName的值为NULL,出现assert提示框,这其实是提醒外部调用者:老兄,这个函数你用错了,你传了一个空指针进来,因此你要修改你的外部调用代码,也就是说这里发生错误的责任在外部调用,而不是内部。其实微软的MFC也是采用这种想法,因此很多程序在debug模式下出错,在release模式运行却没有错误提示。这很可能就是release模式忽略了assert宏,也没让程序退出。但知道了这个assert机制,就有必要警醒自己:必须对debug模式下出现的错误给与足够的重视,否则你的程序就存在隐患。也不知我对你的评论有没有理解错,盼继续交流。


     大家说说,在添加assert语句进行参数有效性判断之后有没有必要再对非法参数进行error handling呢?我感觉没必要吧。

原文:

http://www.189works.com/article-44134-1.html

 

写这篇博客源于在阅读lighttpd源代码是遇到的一个关于assert应用的疑问。

在阅读lighttpd源代码时,发现比比皆是的对malloc的调用结果进行assert检查,比如:Buffer.c:

buffer* buffer_init(void) {
    buffer *b;

    b = malloc(sizeof(*b));
    assert(b);

    b->ptr = NULL;
    b->size = 0;
    b->used = 0;

    return b;
}

这里的assert(b)似乎有问题,实际release版本在运行中难道不会发生malloc返回NULL的情况吗?之后在阅读《Writing Solid Code 》一书时找到了答案。

对assert的基本用法就不再累述了,下面总结一下assert的实际应用的Recommended practice吧:

1、要使用断言对函数参数进行确认

主要有以下情况:

  • 指针不是NULL的断言;
  • index值或size值不是负值或小于已知限值的断言;这一条也可以这么描述:要从程序中删去无定义的特性或者在程序中使用断言来检查出无定义特性的非法使用

2、每个断言必须在头文件中的函数功能描述的断言部分进行说明(不要浪费别人的时间 ─── 详细说明不清楚的断言 ),例如:

*  Asserts:
 *      'size' is no greater then LIMIT.
 *      'format' is not NULL.
 *      The function result is no greater than LIMIT.
 */

如果没有断言, 写 “Nothing”:

 *  Asserts:
 *      Nothing
 */
(以上的格式也许严格了一些,不过如果真的这么做,对代码的可阅读性会很有帮助)

3、断言和错误校验的区别

正确使用断言,必须要清楚程序错误(program errors)和运行时错误(run- time errors)之间的区别;

  1. 一个程序错误是一个bug,永远不应该发生。
  2. 一个运行时错误是在程序运行的任何时候都可能会发生.

断言并不是一种处理运行时错误的机制。例如在需要输入正数的时候,用户输入了一个负数,如果用断言来检测这种情况就不是好的设计。对于这种情况需要用合适的错误检查和恢复处理的代码来进行处理。

再回到lighttpd中对malloc函数的返回值进行assert断言,我觉得也属于这个问题,这应该是一个运行时错误,而不是程序错误;所以,我觉得《C和指针》一书中对malloc返回NULL处理是通过一个错误检查分配器来处理的。

4、断言和bug

断言大致分为前置条件(Preconditions)、后置条件(Postconditions)、不变性条件(Invariants)

如果前置条件不成立,发生Assertion violations,则调用该函数的代码存在bug,需要尽快找到并解决;

如果后置条件不成立,发生Assertion violations,则(函数的)实现代码存在bug,需要尽快找到并解决;

例如:

void doBlah(int x)

{

    assert(x!=0);

    ....

} 

这段代码说明这个函数的调用不可能传入参数0,如果发生这种情况,说明调用这个函数的代码存在bug;

以上是自己的一点理解,欢迎高手指正!!!

 

 

 

原文:

http://www.cnblogs.com/nsnow/archive/2010/05/19/1739021.html

 

最近才发现,原来assert这么好用啊。。。
再看看是怎么实现的,又找到了些有趣的东西。

用法:

先包含
#inlcude <assert.h>

在想用的地方给一句:
assert(expression)就可以了。
expression是任意有效的逻辑表达式。
比如:

FILE *fp = fopen("in.txt","r") ;
if ( ! fp ){
      exit(0) ;
}
assert(fp != NULL) ;

当expression不满足时,就会报出一个很丑陋的框框,
然后向控制台输出assert不满足的文件和行号。

具体到debug的时候,
可以撒网式地在各个地方放上认为应该为真的表达式的assert,
说不定哪个就爆了,于是趁机发现了问题。

原理:

只要有源码就没有秘密,
所以打开assert.h,看看里面是怎么写的。

主要的就这两句:
_CRTIMP void __cdecl _assert(void *, void *,  unsigned);
#define assert(exp) (void)( (exp) || (_assert(#exp, __FILE__, __LINE__),  0) )

第一句就干的就是输出一些信息,然后弹出个框框,
顺便结束程序这些勾当。
他被调用的时候,是类似于:
_assert("false" , "c:\\1.cpp" , 15)
这样。

第二句的构造可谓精简啊,小小一句话还包含了挺多以前没注意到的事情。

1.短路求值

这个是c的重要特性,在处理&&的时候前面为假则不用继续,
在处理|| 的时候,前面为真则不用继续。
形象地说把后面的表达式短路了。

2.单行宏

#exp 生成"exp"这样的字符串
#@a 生成'a'这样的字符
a##b 把a和b连接起来

第一个用法在这里见到了,第二个暂时还没见到用的实例。
第三个在a和b是宏的参数的时候有用。否则直接的ab会被当作一个东西。

3.特殊的预定义宏

__FILE__ 会被替换成所在的文件,字符串形式
__LINE__ 会被替换成行号,unsigned类型
__DATE__ 会被替换成日期
__TIME__ 会被替换成时间

其实之前翻过的跟C有关的书应该都讲了这些的。
不过拿着一个列表,又不给出真正实用的例子,
当然不知道这些东西是怎么回事,
久了自然也就忘了。

4.逗号表达式

感觉实在是一个用的很少的事情,
毕竟有多句话的时候,完全可以用分号就行了。
虽然有好多地方在if之类的里面很压缩的用逗号表达式写好几句话,
其实都可以改得不用逗号表达式的。

其一是逗号表达式的优先级很低,所以后面那对括号实在是不可缺少。

其二是逗号表达式的值为最右边式子的值。
这个估计很多人都记过,但不见得有啥重大意义。
这里,倒确实是发挥了他的意义。
因为_asert这个函数是void型的,
如果不使用逗号表达式在右边补个0的话,
会报告:(VC6)
error C2297: '||' : illegal, right operand has type 'void'

改编:

知道是怎么回事,当然可以很容易做出自己想要的东西。
再说还有asert.h里面的参照呢。

比如,我嫌默认的_assert弹出的东西看着太压抑了。。。
就自己写个就行了。

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>

#define MAX_BUFFER 200
void _assert(char *msg , char *file , unsigned line){
      char buf[MAX_BUFFER] ;
      sprintf(buf , "assertion fail:\n%s\nin file:\n%s\non line:\n%d" ,  msg , file , line) ;
      ::MessageBox(NULL , buf , "assertion failure" , MB_OK) ;
      exit(0) ;
}

#define assert(exp) ((exp) || (_assert(#exp , __FILE__ , __LINE__) , 0) )

int main(){
      assert(1 == 1 && 3 == 4) ;
      return 0 ;
}

效果:


至于我的这个是不是更压抑。。那不属于这里讨论的问题了。

反正通过简单变更_assert函数,可以把相关情况输出到文件,
或者选择另外的方式表达出来,能想到的都可以。

至于assert这个宏,也有可以动手脚的地方。
自带的是assert一个为真的表达式。
有的时候就想assert一个为假的表达式,当他为真的时候发出警告。

比如:

FILE *fp = fopen("in.txt","r") ;
if ( ! fp ){
      exit(0) ;
}
warn(fp == NULL)

套用上面的写法,既然是为真发警告,那么用&&去换||就行了。

 

 

原文:

用if、Exception还是assert?
          ——看JavaEye论坛帖子:《面试题:用Exception异常还是if判断》的感想

今天在JavaEye上看到一个很火的帖子,题目是:面试题:用Exception异常还是if判断http://www.iteye.com/topic/745640。内容大概是:
    当参数不合法的时候,究竟是if else 判断后返回一个值还是直接来个Exception ?
    如果是 if else 的话,有什么好处 ?
    如果是 exception 的话,又有什么好处 ?
    或者是根据不同情况来定?

实际中应该使用if else或Exception的都有,因而下面的回复也是各抒己见,很多人都有一定的道理。在此先列出几个个人觉得比较靠谱的回复:

1)sam_chi:看情况吧,如果能在方法里面处理不影响方法功能的话使用if else处理,如果参数错误导致方法不能正常工作,那么就得抛异常了,Java提供了java.lang.IllegalArgumentException,可以直接new一个抛出去,这是一个RuntimeException,不需要try..catch。

2)mercyblitz:if-else 方式的好处在于更贴近与逻辑思维,性能优于Exception。相对于Exception,其缺点是,不适合OOP,语义不明显,不易于错误错误跟踪或错误提示较少,并且类型比较单一(比如利用C语言的原生类型)或者难以统一(比如C语言结构和宏定义)。
exception方法的好处在于是业务逻辑和异常处理分离(代码相对清晰),try中处理业务,catch中处理异常情况。在API设计中,可以设计Exception Handler来处理异常,使得层次分明。同时,更好的OOP的封装和多态性。缺点在于性能相对差。

3)fireaap:根据情况来选择使用if...else,还是exception。选择的依据就是,你的方法的职责。也可以说是你方法的契约。

4)konser:sam_chi 说的很对,首先大家要明白什么是异常,异常就是程序不能正常执行,错误的调用api的方法,资源失败等等原因 。程序逻辑又是一回事。为什么要把异常和逻辑判断混合起来做对比?  补充一下如果,当参数不合法的时候抛出IllegalArgumentException .
如果是 if else 的话,有什么好处 ?
参数不合法程序都不能正常执行了那你说有啥好处?
如果是 exception 的话,又有什么好处 ?
面向对象的思维,抛出每个异常时可根据不同异常以不同方式进行处理 。缺点是创建消耗内存,效率低。

5)IcedCoffee:java规范的定义是说异常不要参与控制流程,你不能把异常作为一种正常的控制流程作为程序的一部分,这样是不对的.
面试官正真要考的就是这个..
没有哪个公司会用异常来处理参数验证..

6)liupopo:先要明确异常是做什么的,异常是程序执行时的一些非正常的情况,如果有不正确的数据、逻辑等会抛出异常。
if else是逻辑判断,控制程序流程。
断言是预先决断应该是什么样的值,不应该是什么类型等明确的条件
从定义上不太好区分这些怎么去用,但可以从使用场景去考虑,下面的可能不十分准确,但大家可以参考一下:
异常:是程序员写给其他程序员的,比方说我写了一个方法,如果其他人使用我这个方法的时候有可能不按我想的路子使用,可能会引起我的方法产生不正常的处理,这时候我要使用异常,而且可以决定产生异常了是由我这个方法本身进行处理(catch语句中处理)还是交给调用者进行处理(直接把异常抛出)
if else 就是我写的方法,要进行逻辑判断,就用它,没什么好说的吧。
断言可用处大了,是我在调用别人写的方法,或者我处理程序时对通过其他途径得到的数据(如调用别人方法的返回值(好象不建议用断言),通过参数传入的值)等进行假设它就是某值时使用的。是方便开发、调试提供的一个语法元素

7)JonyUabka:1对可预见的运行时异常当进行捕捉并处理,比如空指针等。通常,对空指针的判断不是使用捕捉NullPointException的方式,而是在调用该对象之前使用判断语句进行直接判断,如:
//若不对list是否为null进行检查,则在其为null时会抛出空指针异常

Java代码 复制代码 收藏代码
  1. if(null != list && 0 < list.size()){  
  2. for(int i = 0; i &lt; list.size(); i++){  
  3. }  
  4. }  
if(null != list && 0 < list.size()){ 
for(int i = 0; i &lt; list.size(); i++){ 
} 
} 

2对于经常发生的可预计事件不要采用异常
3不要使用异常实现控制结构。
4通常的思想是只对错误采用异常处理:逻辑和编程错误,设置错误,被破坏的数据,资源耗尽,等等。
对于java的编码规范,有具体描述。我想考官希望得到的是这个方向的答案。

8)maomaolingyu:
if else
优点:
A 逻辑自己控制,清晰
缺点:
A 当情况复杂时 需要过多的if else .导致逻辑难以理解
B 容易存在漏洞,导致错误或者不期望的结果
Exception
优点:
A 异常通过异常链传播且会被jvm自动捕捉,减少逻辑代码
B 异常很容易确定代码问题段,方便测试
缺点:
A 当异常发生但是没有正确捕获时,会异常抛出到用户页面.

引用了这么多,下面谈谈本人的一点看法,不对之处敬请指正。

首先,对于这个问题,不应该回答:用Exception异常或者if判断,而应该视情况决定使用Exception还是if。个人觉得面试官比较看重的是你对异常的理解,回答的关键应该是两者的优缺点与适用场合上,顺带可以总结一些实际中你是如何处理异常的。对于有人说使用断言(assert),还说是标准答案,本人实在不赞同。就像有人回答说:题目问的是:用Exception异常还是if判断,却来个用断言,太离题了吧。而且这么回答的话,下面的问题怎么答?难道直接说他们都不好,assert好?

本人觉得liupopo说的靠谱。下面结合一些代码来说明一些问题。

在此之前,就像不少人说的一样,先要明确什么是Exception。《The Java Language Specification 3.0》中有这么一句描述:When a program violates the semantic constraints of the Java programming language, the Java virtual machine signals this error to the program as an exception.大意是:当程序违反Java语言的语义约束时,Java虚拟机会将这个错误(异常)发送给程序。

下面进入正题:

1、看Java API如何处理的

本人查看了DateFormat、Integer等类的源码,分析如下:

1.1 public final String format(Date date)方法

该方法的参数date只有一种情况是不正确的:null。然而,阅读源码发现并没有判断该参数是否为null。那么当为null时(违反了Java语言的语义),异常NullPointerException自然而然是JVM自动抛出的,而不是API通过throw抛出的。

1.2 public static Integer valueOf(String s, int radix) throws NumberFormatException方法

该方法有两个参数,第一个String类型的参数有可能为null,而方法声明抛出NumberFormatException异常,因而有一段if判断:
        if (s == null) {
            throw new NumberFormatException("null");
        }
对于radix参数有不少限制,因而源码中进行了几个if判断:
        if (radix &lt; Character.MIN_RADIX) {
            throw new NumberFormatException("radix " + radix +
                            " less than Character.MIN_RADIX");
        }

        if (radix > Character.MAX_RADIX) {
            throw new NumberFormatException("radix " + radix +
                            " greater than Character.MAX_RADIX");
        }
后面还有几个其他的判断。

总结:在论坛中,有人提到在JDK源码中,经常可以看见类似
        if (s == null) {
            throw new NumberFormatException("null");
        }
这样的代码。借用liupopo的话,JDK中的方法是写给Java程序员调用的,而且Java有自己内部的一套异常机制。比如,对于format()方法抛出的NullPointerException异常,JDK不可能自己进行处理,出现该异常是:这个方法的时候没按它的路子使用,我们要使用该方法就应该确保传入的Date参数不是null,即需要进行if判断,对于valueOf方法一样如此,调用方法前就应该对是否为null值进行if判断(或确保非null)。从JDK的处理radix参数的方法我们可以看出,if经常与Exception结合使用,if用于处理逻辑,Exception用于告诉用户“有问题”。所以,如果你写的代码是给其他人使用的,你需要考虑是否抛出异常,这个时候对于参数的判断更多的应该使用JDK这种方式:对于非法参数,if判断后,包装为统一的Exception抛出,当然异常也有可能没必要抛出,而是自己处理(各种开源框架中都有这样情况)。

2、现在推荐的Java异常机制

Java API在设计的时候大量使用了checked exception,当时很推荐这种方式。然而,用过Spring的人都知道,Spring中大量使用了unchecked exception即RuntimeException。《thinking in java》的作者Bruce Echel说:“当少量代码时,checked异常无疑是十分优雅的构思,并有助于避免了许多潜在的错误。但是经验表明,对大量代码来说结果正好相反”。很多时候对于Java API抛出的异常,程序员都不知道怎么处理,比如SQLException,大家无非就是catch,然后e.printStackTrace(),或者什么也不做,这样导致程序中很多的try...catch...,既然如此,那么抛出checked exception就没有必要了,而且抛出uncheck exception不会污染接口。对于异常处理的问题很是有必要研究研究的。

既然推荐使用uncheck exception,结合Exception本身的特点,在写应用程序(不是提供给其他开发人员的API)时,参数的判断应该更多的使用if,毕竟参数错误的情况不会很多。

另外,有人建议使用Java自己的IllegalArgumentException异常进行判断,当然这有时候也是可行的。比如,有些时候参数没法活很难通过if来判断(因为我们不知道非法参数会是什么),这个时候可以使用该异常。

其实,很多时候,对参数合法性检查时,需要考虑方法的返回值。比如,接收一个Date类型参数,然后返回一个对应的String类型在页面上显示(该参数不是必须的,比如生日),这个时候,如果Date类型参数为null,我们不应该抛出NullPointerException,而应该用if进行如下判断:
        if(date==null) {
            return "";
        }
返回空字符串很合理,这样页面就会显示空白(相当于没填)。其他情况,如返回值为boolean类型,很可能参数非法时会返回false。视具体情况而定。

3、关于断言(assert)

论坛回复中有人力推assert来判断参数合法性。不知道是没有理解题目的意思还是对断言本身不够了解,亦或对断言情有独钟。对于该面试题,可以肯定的是,用断言(assert)绝对是错误的。断言只是程序员测试和调试的一个工具,发布后一般也不会开启的,而且,Java规范也建议这么做。而参数是否合法性却总是存在的,所以用断言是完全错误的。

关于Java的assert,本人几乎没使用过,也很少见人使用。对程序进行测试或调试,更多的是使用JUnit或IDE的Debug功能,他们已经工作的很好了。在网上找到一篇很有意思的文章,他是痛批java的assert(Java陷阱之assert关键字),感兴趣的可以读读。个人不完全同意其观点,Java引入assert应该有其好处。

在这里把《The Java Language Specification 3.0》中对assert的描述做个总结。

3.1 assert的语法

assert语句有两种形式语法:
    assert 表达式1 ;
    assert 表达式1 : 表达式2 ;
其中,表达式1的值必须是boolean类型,否则会编译错误。这里boolean类型有两种形式:1)var1==var2;2)var=true。其中第二种形式var是boolean类型,一般应该使用第一种形式,而第二种形式往往是程序员失误造成的。表达式2的值可以是除了void之外的任何类型。

3.2 断言开启时机

默认情况下断言没有开启。可以选择在哪个类上启用断言(断言启用没有继承性)。在类字段初始化器和静态初始化器初始化之前,类的类装载器根据配置决定启用或禁用断言。一旦类初始化完成后,断言的状态(启用或禁用)没法再改变。

然而,在规范中提到了一种例外情况:类层次结构之间的循环情况(实际中几乎不会这么做)。例子程序如下:
        public class Foo {
            public static void main(String[] args) {
                Baz.testAsserts();
                // Will execute after Baz is initialized.
            }
        }

        class Bar {
            static {
                Baz.testAsserts();
                // Will execute before Baz is initialized!
            }
        }

        class Baz extends Bar {
            static void testAsserts() {
                boolean enabled = false;
                assert enabled = true;
                System.out.println("Asserts " + (enabled ? "enabled" : "disabled"));
            }
        }
调试运行会发现,在assert没有开启的情况下,第一次输出的结果却显示assert已开启,当然第二条语句显示没有开启(注意,如果assert没开启,assert enabled = true这条语句是没有效果的)。这是说明,当断言语句在类初始化之前执行时,结果就如同开启了assert。(Polaris在调试时,发现一个奇怪的现象:就是第一次执行testAsserts方法时,单步调试,assert enabled = true;语句似乎跳过去了,不过结果却显示该语句是执行了的。很奇怪。)

关于断言的开启方法,控制台开启,IDE开启可以上网搜索,在此不介绍。

3.3 使用断言的注意事项

Java规范中对于断言语句的执行有详细的说明,有兴趣的朋友可以参阅http://java.sun.com/docs/books/jls/third_edition/html/statements.html#14.10

关于assert抛出的AssertionError,规范上建议不要捕获。

由于断言有可能没有开启,因而程序千万不要假定断言中的表达式会被计算,即不能依赖断言的结算结果(表达式1的值),所以,表达式1可能会产生一定的副作用:

1)assert语句虽然可以有副作用,如上面提到的表达式1返回boolean类型的第二种形式,然而这通常是不合适的,因为这可能导致依赖此值的程序在assert启用或禁用时有不同结果;

2)不要使用断言作为公共方法的参数检查,公共方法的参数永远都要执行。这一点可以很好的解释为什么说使用断言来判断参数合法性是错误的,尽管断言有时候可以用于检查传递给私有方法的参数。

Java规范在讲解断言副作用时,还说到了erroneous arguments should result in an appropriate runtime exception (such as IllegalArgumentException, IndexOutOfBoundsException or NullPointerException),即错误的参数检查应该产生一个合适的运行时异常。

在网上看到有总结使用断言的情况:(列举出来供参考)

1)可以在预计正常情况下程序不会到达的地方放置断言 :assert false 
2)断言可以用于检查传递给私有方法的参数。(对于公有方法,因为是提供给外部的接口,所以必须在方法中有相应的参数检验才能保证代码的健壮性) 
3)使用断言测试方法执行的前置条件和后置条件
4)使用断言检查类的不变状态,确保任何情况下,某个变量的状态必须满足。(如age属性应大于0小于某个合适值)

3.4 Polaris的建议

要进行调试或测试,可以使用IDE或JUnit。

说了这么多希望对大家有个参考作用,同时不对之处敬请批评指正。

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值