........................

java中的异常、断言、日志(二)

上一篇 / 下一篇  2016-05-24 14:13:38 / 个人分类:接口测试

3.断言的使用 
  assertion(断言)是Java1.4引入的一个新特性,该特性的引入的目的是为了辅助开发人员调试和测试,是一种比较常用的调试、测试方案。assertion在软件开发过程中是一种比较常用的调试方法;不仅仅如此,使用assertion可以在开发过程中证明程序的正确性,只是这种用法会对系统的整体设计存在很大的挑战,而且目前很少投入到实用里面,所以一般情况下使用assertion的目的是为了调试和测试。 
  i.assertion概念以及基本用法 
  在代码实现的时候,需要使用关键字assert,而assertion本身在程序里面就是一条语句,它的作用是对boolean表达式进行检查,正确保证这个boolean表达式在程序运行到此刻的时候为true;一旦这个boolean表达式为false的话,就说明该程序已经处于了不正确的执行状态了,系统在断言开启的情况下会根据相关情况给出警告或者退出。 
  当在程序开发过程中,一般情况下使用assertion来保证整个应用程序里面最基本的、关键的正确性,而在操作过程中一般是开发和测试的时候开启该功能,一旦等软件开发完成过后,为了提高程序性能,发布的时候就将断言关闭。 
  1)语法: 
  Java里面使用assert关键字来支持assertion,其本身包括了两种表达方式: 
  [1]assert 表达式1; 
  [2]assert 表达式1:表达式2; 
  以上两种语法里面,表达式1表示一个boolean表达式,而表达式2一般是一个基本类型或者对象,这里需要说明的是在开发过程一般表达式2写的都是字符串以提供该断言失败的信息,但是真正在使用的时候应该理解的是表达式2也可以是某个对象或者基本类型,这里通过一个简单的例子来初次接触断言: 
/** 
*断言使用的概念说明代码 
**/ 
public class AssertionDriver { 
    public static void main(String args[]){ 
        Employee employee = new Employee(); 
        employee.setName("Lang Yu"); 
        employee.setEmail("silentbalanceyh@126.com"); 
        businessProcess(employee); 
    } 
    
    public static void businessProcess(Employee employee){ 
        try{ 
            assert employee.getName() != null && 
                employee.getEmail() != null && 
                employee.getPassword() != null: 
                    employee; 
        }catch(AssertionError error){ 
            System.out.println(error); 
        } 
    } 



class Employee{ 
    private String name; 
    private String email; 
    private String password; 
    public String getName() { 
        return name; 
    } 
    public void setName(String name) { 
        this.name = name; 
    } 
    public String getEmail() { 
        return email; 
    } 
    public void setEmail(String email) { 
        this.email = email; 
    } 
    public String getPassword() { 
        return password; 
    } 
    public void setPassword(String password) { 
        this.password = password; 
    } 
    @Override 
    public String toString(){ 
        return "\nName:" + name + "\n" + "Email:" + email + "\n" + "Password:" + password; 
    } 

  上边代码段使用了第二种表达式的方式,但是这里复杂的地方在于表达式2不是一个字符串,而是定义的Employee类的一个对象的实例,也就是说这里表达式2是一个Object实例,然后编译该代码,运行的时候打开断言,就可以得到下边的输出: 
java.lang.AssertionError: 
Name:Lang Yu 
Email:silentbalanceyh@126.com 
Password:null 
  【*:当断言中表达式1返回false的时候,try块里面就抛出**sertionError类型的断言错误,然后在catch块里面会将该错误打印出来,这种错误的格式为:java.lang.AssertionError:object.toString(),因为这里重写了Employee的toString方法,根据输出结果可以知道,返回false的boolean表达式为子表达式:(employee.getPassword() != null)】 
  通常,在对某个对象执行关键操作时会需要对它创建断言。这有助于增强代码的健壮性,比如如果在程序中出现了某种错误,可以更方便地调试程序。这样做要比程序在某处执行失败造成不良后果来发现错误要好得多。当知道程序失败是由于它违反了假设而引起的时候,跟踪失败的原因要简单得多。 
  2)语义: 
  在运行的时候,如果关闭**sertion功能,这些语句将不会起任何作用,JVM默认assertion的功能是关闭的,如果要上边这段代码输出该结果还需要一定的操作。如果assertion功能被打开,那么JVM会先计算表达式1的值,如果它为false,该语句会抛出一个AssertionError异常。若assertion语句包括了表达式2参数,程序将计算表达式2的结果,然后将这个结果作为AssertionError的构造函数的参数,用来创建AssertionError对象,并抛出该对象,若表达式1值为true,表达式2将不被计算。 
  这里简单看看AssertionError的API文档说明构造函数的定义: 
AssertionError() 
AssertionError(boolean detailMessage) 
AssertionError(char detailMessage) 
AssertionError(double detailMessage) 
AssertionError(float detailMessage) 
AssertionError(int detailMessage) 
AssertionError(long detailMessage) 
AssertionError(Object detailMessage) 
  在讲的断言里面,当表达式1为false的时候,就需要构造AssertionError对象,构造的时候,传入的就是表达式2,也就是说在使用assertion的时候,AssertionError构造函数的实参就是真正在运行的表达式2,这样也可以理解表达式2为什么可以是基础类型,也可以是Object。 
  这里再提供几个简单的代码段,加深印象: 
assert 0 < value; 
assert 0 < value:"value = " + value; 
assert ref != null:"ref doesn't equal null"; 
assert isValid(); 
  【*:再提醒一点,既然表达式1是一个boolean表达式,那么可以是一个返回值为boolean的函数。】 
  3)编译和运行: 
  【编译】 
  由于assert是JDK 1.4才出来的关键字,使用老版本的JDK是无法编译带有assert的程序的,因此在使用javac命令编译该代码的时候,必须使用JDK 1.4或者更新的Java编译器,如果编译的时候因为无法识别assert关键字报错,那么需要加上编译参数-source 1.4这里的版本号至少是1.4或者以上的。直接使用javac命令编译的时候-source 1.4表示使用JDK 1.4版本的方式来编译源代码,版本太低带有assert关键字的代码就无法通过编译。关于javac和java命令的内容后边会有专程的章节介绍 
  【运行】 
  在运行带有assert语句的程序时,使用了新的ClassLoader的Class类,因此必须保证程序在JDK 1.4以及以上的版本或者JRE 1.4以及以上的版本环境里面运行。而在运行的时候,因为JVM默认是关闭**sertion功能的,所以要使用assertion功能的话必须显式使用加入参数来选择启用或者禁用断言。另外,断言的参数可以使得java应用程序可以开启一部分类或包的assertion功能,所以运行相对编译而言,比较复杂,这里有两类参数需要说明: 
  [1]参数-esa和-dsa: 
  该含义为开启(关闭)系统类的assertion功能。由于新版的Java的系统类中,也使用**sertion语句,如果用户需要观察它们本身的运行情况就需要打开assertion功能,可以使用参数-esa参数打开,使用-dsa参数关闭。 -esa和-dsa的全名为-enablesystemassertions和-disenablesystemassertions,全名和缩写名具有同样的效果。 
  [2]参数-ea和-da: 
  它们的含义为开启(关闭)用户类的assertion功能:通过使用该参数,用户可以打开某些类或者包的assertion功能,同样用户也可以关闭某些类和包的assertion功能。打开assertion功能的参数为-ea;如果不带任何参数,表示打开所有用户类;如果带有包名称或者类名称,就表示打开这些类或包的assertion功能。-ea和-da的全名为-enableassertions和-disenableassertions 
  这里提供一个表格来说明运行时断言参数的用法 
参数 例子 说明 
-ea java -ea 打开所有用户类的assertion 
-da java -da 关闭所有用户类的assertion 
-ea:<classname> java -ea:AssertionDriver  开打AssertionDriver类的assertion 
-da:<classname> java -da:AssertionDriver 关闭AssertionDriver类的assertion 
-ea:<packagename> java -ea:packagename 打开packagename包的assertion 
-da:<packagename> java -da:packagename 关闭packagename包的assertion 
-ea:... java -ea:... 打开缺省包(无名包)的assertion 
-da:... java -da:... 关闭缺省包(无名包)的assertion 
-ea:<packagename>... java -ea:packagename... 打开packagename包以及其子包的assertion 
-da:<packagename>... java -da:packagename... 关闭packagename包以及其子包的assertion 
-esa java -esa 打开系统类的assertion 
-dsa java -dsa 关闭系统类的assertion 
综合使用 java -dsa:ClassOne:pkgOne 关闭ClassOne类和pkgOne包的assertion 

  在上边的表格里面,需要说明的是: 
  [1]...代表该包和该包对应的子包,如果系统有两个包分别为pkgOne和pkgOne.subpkg,则pkgOne...就代表这两个包 
  [2]可是使用编程的方式来禁用或者启用assertion,下边提供一段代码来说明该功能:编程方式的assertion 
/** 
*使用程序开启断言的代码段 
**/ 
class Loaded 

    public void go() 
    { 
        try 
        { 
            assert false:"Loaded.go()"; 
        } 
        catch(AssertionError error){ 
            System.out.println(error); 
        } 
    } 



public class LoaderAssertions 

    public static void main(String args[]) 
    { 
        ClassLoader.getSystemClassLoader().setDefaultAssertionStatus(true); 
        new Loaded().go(); 
    } 

  直接运行上边的代码会有下边的输出: 
java.lang.AssertionError: Loaded.go() 
  除了上边的代码使用的setDefaultAssertionStatus方法以外,ClassLoader的API里面还有以下几种方法: 
  setDefaultAssertionStatus:用于开启/关闭assertion功能 
  setPackageAssertionStatus:用于开启/关闭某些包的assertion功能 
  setClassAssertionStatus: 用于开启/关闭某些类的assertion功能 
  clearAssertionStatus:用于关闭assertion功能 
  ii.关于断言的设计和使用 
  1)assertion的设计: 
  assertion在开发中是必要的,如果没有统一的assertion机制,Java程序通常使用if-else或者switch-case语句来进行assertion的检查,而且检查的数据类型有可能不相同,但是一旦有**sertion机制过后,Java程序员就可以使用统一的方式来处理assertion的问题,而不是按照每个人不同的习惯以及方式来处理。而且还有一点,断言在未开启的情况下,发布代码的过程是不起任何作用的,只是在调试和测试阶段发生作用,一般情况在发布过程就关闭assertion功能,如果用户按照自己的方式处理进行相关检查,这些代码在发布过后不可能像assertion一样失去作用,就使得用户自己方式处理的assertion代码部分会影响程序本身的性能。Java提供的assertion统一机制,从语言层次上讲,使得assertion对系统性能的影响减小到最小。 
  Java通过增加一个关键字assert来实现assertion机制,而不是使用一个函数来支持,这说明java已经将assertion机制内置到语言内部称为了语言特性,本身在设计上java语言认为assertion机制是很重要的。 
  Java中的assertion的开启和C语言不太一样: 
  在C语言里面,assertion的开启是编译时决定的,可以在debug版本里面开启而直接在release版本中自动关闭;在Java里面,assertion的开启和关闭是在运行时决定的。两种方式各有优缺点。如果采取编译时决定,开发人员将处理两种类型的目标文件,就debug版本和release版本,这种方式加大了文档的管理难度,但是提高了代码的运行效率。而运行时的方式,所有的assertion都会放到目标代码里面,统一目标代码可以使用不同的方式运行,增加了一部分代码灵活性,但是牺牲了一部分性能。但是从语言发展角度,既然从纯面向过程的C的设计演变到C++的过程和对象共存的设计再到Java的纯面向对象的设计就知道,实际上Java里面这种性能的损失是可以忽略不计的,所以java这中使用运行时决定assertion的机制的方式是真正在开发过程中的首选。 
  另外,有一点我在异常章节里面已经提及过的,在上边的代码里面,AssertionError类作为了Error的子类,而不是RuntimeException。这一点一致都没有想过为什么,实际上所有Error的子类里面,只有这一个类可以使用catch块进行捕捉,那么按照对异常的理解,AssertionError本应该是属于一个RuntimeException而不应该是一个Error,那么回到上边关于Error和RuntimeException的定义: 
  Error通常代表一些错误,不可以恢复 
  RuntimeException强调了该错误是在运行时才发生的 
  那么AssertionError通常情况下是在调试以及测试过程打开了断言开关才会遇到的关键性错误,这里遇到的错误,往往是整个程序关键性的缺陷,这一点在后边使用assertion的时候会介绍assert关键字的使用场合。从这点来讲,与AssertionError有关的错误往往都是没有办法恢复的,而且assertion机制也不鼓励程序员通过编程或者其他手段来恢复折中类型的错误。因此,为了强调Assertion机制的概念以及定义,AssertionError作为了一个Error的子类进行设计,并且它的运行原理类似RuntimeException,在运行过程中是可以直接被catch块捕捉到的。 
  2)assertion和继承 
  那么在assertion机制里面,父类和子类在assertion的开启和关闭的时候会发生什么现象呢,这里提供一段代码: 
/** 
*讨论assertion和继承的关系 
**/ 
class SuperClass{ 
    public void superMethod() 

    { 

        assert false:"Assert:This is Super Class"; 

        System.out.println("Super Method"); 

    } 




public class SubClass extends SuperClass{ 
    public void subMethod() 

    { 

        assert false:"Assert:This is Sub Class"; 

        System.out.println("Sub Method"); 

    } 

    public static void main(String[] args) 

    { 

        try 

        { 

            SubClass assertClass = new SubClass(); 

            assertClass.superMethod(); 

            assertClass.subMethod(); 

        } 

        catch(AssertionError error) 

        { 

            System.out.println(error); 

        } 

    } 


  这里使用几种情况来运行:【*:确保编译的时候使用的是1.4和1.4以上的JDK版本】 
  [1]直接运行的输出:java SubClass(这种方式和全关闭断言的方式一样的输出:java -da SubClass) 
Super Method 
Sub Method 
  [2]全开断言的输出:java -ea SubClass 
java.lang.AssertionError: Assert:This is Super Class 
  [3]仅仅打开父类断言输出:java -ea:SuperClass SubClass 
java.lang.AssertionError: Assert:This is Super Class 
  [4]仅仅打开子类断言的输出:java -ea:SubClass SubClass 
Super Method 
java.lang.AssertionError: Assert:This is Sub Class 
  仔细分析上边的输入输出可以知道:当父类的assertion只有在父类的assert开启的时候会起作用,如果仅仅打开子类的assert,父类的assert不会运行,比如上边[4]的输出,SuperClass的assert没有起任何作用,由此可以认为,assert语句不具有继承功能。 
  【*:其实仔细想想,这一点从语言本身设计上是合理的,assert机制本身的存在是辅助开发、调试、测试,因为有这样的一个assert存在,使得程序的数据流能够在合法的范围内在程序里面运行,而assert机制本身是类似踩点方式,如果觉得什么地方出现了程序的关键性问题,这里置放一个assertion用来确保数据的准确性,使得调试的时候不用去考虑数据在这个地方是否合法,因为一旦不合法可以通过assert进行记录,以及很容易发现assert监控的数据出问题的地方,如果assert支持继承的话,在一个设计得比较好的继承树系统里面,本身初衷是为了查询某个点的assert标记进行调试,如果它支持了继承,那么assert会形成一个不必要的链式结构,这种方式反而不利于找到程序的关键部分,所以个人觉得从概念上讲,assert本身不支持继承的设计也是蛮合理的。】 
  3)assertion的使用: 
  assertion既然能够辅助调试、测试以及开发,那么在使用assertion的时候,问题会变得相对复杂,因为这里会涉及到不同的程序员在使用assertion的目标、程序的性质以及各自不同的代码风格问题。 
  一般来讲,assertion主要用来检查一些程序的关键性问题,并且对整个程序或者局部功能的完成起到很大的影响,这种错误不是数据的非法性,往往这些部分可能导致整个程序出现不可恢复的问题。这里提供几种简单的应用: 
  [1]检查控制流: 
  在这个例子中,不应该发生的控制流上边使用assert false 
public class AssertionUseOne 

    public static void main(String args[]) 
    { 
        int a = 1;// 1,2,3,4 
        switch(a) 

        { 

            case 1:...;break; 

            case 2:...;break; 

  &nb

TAG: java

 

评分:0

我来说两句

Open Toolbar