资源描述
,单击此处编辑母版标题样式,单击此处编辑母版文本样式,第二级,第三级,第四级,第五级,*,错误和异常处理,郭东伟,www.skywind.name,目录,软件的正确性与鲁棒性,错误和异常处理方法,结构化错误处理技术,不同语言的实现细节,异常使用反例,资源申请与释放,一、软件的正确性与鲁棒性,复杂的软件系统由多个层次构成,在运行时会涉及到多个不同的软硬件系统。,如一个普通的航空订票系统:它的最高端是由一些,GUI,组件所组成,用来在用户的屏幕上显示内容并与用户交互。这些高端组件与那些封装了数据库,API,的数据存取对象相互作用。再往底层一些,那些数据库,API,与数据库引擎相交互,然而数据库引擎自己又会调用系统服务来处理底层的硬件资源,比如物理内存,文件系统和网络系统和安全模型。,这期间无论哪一个环节出现问题,都可能导致软件变得不可靠。,为什么我们要小心翼翼?,输入参数问题,类型不对、非法指针、空值,数据不一致(不符合由文档或者逻辑得出的谓词),内部流程,数组越界、非法访问,数据转型(,cast,)错误,数据一致性被破坏,不可预料的内部错误,硬件失效、除数为零、浮点溢出,为什么我们要小心翼翼?,调用其他方法,无法确保被调用方法在此过程中是否会出问题,如何检查被调用方法的状态?,资源申请与释放,申请资源是否能够成功?,申请的资源在不用时能否保证被释放,即使是其他部分出问题的时候?,如何防止资源被重复释放?,外部资源可能处于不可用状态(临时的或永久的),如何检查及处理?,为什么我们要小心翼翼?,当出现上述问题时候,如何应对?,能否就地解决?,如何向上汇报?,是否有关注者?,如何使错误处理流程尽量不干扰正常程序流程?,当软件出现问题时,如何修复,并使之回到良好的运行状态上?,原因归类,错误,Error,语法、逻辑、算法错误,输入数据错误(容易发现的),应该预料到,一般能在调用的上一级解决,异常,Exception,难以预料原因,一般来说直接原因源自第三方,无法打开必须的文件,网络传输错误,通常无法在本地解决,以至于不能预计会在调用层次的哪一级(直到用户交互)解决,严重失效,Fault,不能预料出现,而且可能在任何地点出现,内存溢出,外部硬件错误,通常无法在程序内解决,错误和异常,错误,能够预料到,能够直接修复,结果,而不是过程,异常,有些不能预料,一般不能直接修复,强调过程,而不是结果,一定语境下可以混用,重新审视,DbC,调用者(应用程序等)和被调用者(库函数、组件库等)是平等的,都有权利和义务的说明。,检查前置条件是一种权利;而传统上模块中检查错误被认为是一种,义务。,If(pointer=NULL),assert(pointer!=NULL),当出现错误,也就是契约(特别是前置条件)遭到破坏时,首先检查调用者,重新审视,DbC,明确错误和异常的关系,前置条件遭到破坏,是调用者的错误,满足前置条件下,虽经努力(由于其他原因),无法正常得到结果,是一种异常,需要抛出。,没有其他原因,必须满足后置条件,否则是提供者的错误。,设计接口必须设计契约,int ReadFile(const char*fn),FILE*fp=fopen(fn);,assert(fp);,int ReadFile(FILE*fp),assert(fp);,测试与错误,软件测试目的是发现错误,而不是消灭错误。,软件测试只能表明错误的存在,而不能表明错误的不存在。,测试是为了发现程序中的错误而执行程序的过程;,好的测试方案是极可能发现迄今为止尚未发现的错误的测试方案;,成功的测试是发现了至今为止尚未发现的错误的测试。,没有发现错误的测试也是有价值的,完整的测试是评定软件质量的一种方法,二、异常处理方法,异常处理的内容,传统异常处理技术,返回错误代码,使程序处于错误状态,终止程序,调用预定的错误处理程序,结构化异常处理(,C+,和,Java,),throw,try catch,能够以类的方式定义异常的类别和层次,异常,处理的内容,一般情况下,很多运行期错误会在底层代码中被检测出来。,但是它们不能,-,或者说不应该,-,试图自己处理这些错误。解决这些严格的运行期错误的责任应该由高端组件来承担。,错误的发生点并不是错误的处理和解决点,为了解决一个错误,高端组件必须得到错误发生的通知。本质上,错误处理包括错误检测和通知高端组件。这些高端组件依次处理错误并且试图从错误中恢复。,传统处理错误技术,Return a value representing error,/,返回错误代码,Return a legal value and leave the program in an illegal state,/,返回合法值,但使程序处于错误状态,(,如设置一个全局错误变量,),Terminate the program/,终止程序,Call a function supplied to be called in case of error./,当错误发生时,调用预定的错误处理程序,返回表示错误的值,在小型程序上比较简单易用,错误码很难统一。因为一个库的实现者可能选择返回值,0,来代表一个错误,然而另一个实现者却选择,0,来代表成功并且用那些非,0,值代表出现错误。,微软从来就没有一个统一的错误码查询表。,对于每个调用都要检查返回值,并将错误代码向上回传,可使代码倍增,.,有时根本不能返回值。如构造函数。,使程序处于错误状态,C,的,头文件中定义了一种机制用来检查和给一个全局整型标记,errno,赋值。,在一个多线程环境中,被一个线程赋予了一个错误码的,errno,有可能不经意的被另一个线程所改写,而调用者还未对,errno,进行检查。,上面两种方法的共同点,二者都提供一种机制来报告错误,但是二者却都不能保证错误被处理。,例如,一个函数没有成功打开一个文件可以通过给,errno,赋予一个合适的值来表明错误的发生。然而,它不能阻止另一个函数试图写入和关闭那个文件。,更进一步,如果,errno,表明一个错误并且程序员检测到而且按照预期处理了它,那么,errno,还必须被显式的复位。否则会引起其他函数误以为错误还没有被处理,从而去校正那个问题,引起不可预知的结果。,终止程序,最为残酷的处理运行期错误的方法是简单的终止程序。,这种解决方案去除了上面两种方法的一些缺点,突然终止可能使一些资源不能得到正确的释放。如自由存储、文件句柄、,I/O,设备。特别在面向对象设计中。,有些关键程序不应该在任何运行期错误存在的情况下突然终止。如股票交易程序、,Web,服务器等。,终止程序在极限环境下或者在调试阶段是可以被接受的。(包括调试对话框),调用预定的错误处理程序,“,调用预定的错误处理程序”,如果缺少关于异常情况的信息,该错误处理程序仍然无能为力,也只能采用类似前三种的方法,.,On Error Goto XXX,On Error Resume Next,三、结构化错误处理技术,结构化异常处理是一种把控制权从异常发生的地点转移到一个匹配的,handler,的机制。它将发现错误和处理错误的部分相互分离,即将错误处理代码从”正常”代码中分离出来,使程序更易阅读,.,异常对象可以是内在类型变量或用户定义类型的对象,可提供更多信息,异常处理使系统从错误中恢复过来,提高了程序的容错能力,.,异常处理机制由四部分:,try,块,一个或多个和,try,块相关的处理器,handler,(,catch,块),,throw,语句,以及异常对象自己。,优点,异常处理流程与正常流程分离,不使用返回值或全局对象,强制处理异常,可以使用类来表示异常,支持类体系定义,异常处理机制,异常处理的默认相应方式是终止程序,而传统的程序可以”装糊涂”继续运行,.,异常处理可以处理同步异常,如数组范围检查和,I/O,出错,不能处理异步异常,.,单击鼠标,(,交互行为,),、网络消息达到、,I/O,完成等。,异常处理机制是基于堆栈回退的非局部控制机制,可以完成局部变量的销毁工作。,如果程序的某些部分出现无法处理的情况,可通知高层的环境去处理它以便从这个“异常”中恢复过来。异常多用于程序不同组件之间。,异常处理五阶段,发生,表征,捕捉分发,处理,善后,恢复(,resuming exception,),终止(,terminating exception,),异常处理五阶段,int main(void),try,if(some_error)/*Stage 1*/,thow E();/*Stage 2*/,catch(E&)/*Stage 3*/,/*Stage 4*/,/*Stage 5*/,return 0;,异常四要素,异常对象:可以是任何类型,一般采用用户定义对象类型表示异常。因为要使它们易于分辨,同时能够包含更多异常信息。,try,块放上要检查异常的代码,每个,catch,块指定捕获的异常和处理这些异常的异常处理器,throw,用来抛出异常对象或者说明抛出哪些异常对象。,示例:,异常对象,#include,int main(),char*buf;,try,buf=new char512;,if(buf=0),throw Memory allocation failure!;,catch(char*str),std:cout Exception raised:str n;,/.,return 0;,Stack Unwinding/,栈回退,当一个异常被抛出,运行时机制首先在当前的作用域寻找合适的,handler,。如果不存在这样一个,handler,,那么将会离开当前的作用域,进入更外围的一层继续寻找。这个过程不断的进行下去直到合适的,handler,被找到为止。此时堆栈已经被解开,并且所有的局部对象被销毁。,如果始终都没有找到合适的,handler,,那么程序将会终止。,捕获异常,在异常处理过程中,对于一个异常,首先在抛出异常的当前函数中查找,如果,throw,的类型没有匹配在,try,块中,当前函数带着一个异常退出。,下一步是检查调用者,如果在调用者中对当前函数的调用位于一个,try,块中,则可用与该,try,块关联的,catch,子句列表中的某一个来处理,并进入该子句进行异常处理。,在这一过程中,因发生异常而逐步退出复合语句和函数定义,被称为栈展开(,stack unwinding,)。随着栈展开,在退出的复合语句和函数定义中声明的局部变量的生命期也结束了。,异常的类型结构,一个异常就是某个表示异常类的对象。,异常类型可以是任何类型,但习惯上将它们组织成一定结构如层次结构。从而对异常可以分类。如某数学库的异常可能,在,C+,中,异常类型可以是基本类型或者预定义类(,std:exception,)的派生类或者自定义类型,在,Java,中,异常类型必须是预定义类型,(Exception),的派生类,四、不同语言的实现细节,C+,中的异常,构造函数与异常,析构函数与异常,异常声明和描述,没有类体系的异常类型,Java,中的异常,finally,语句,checked,和,unchecked,异常,C+,中的异常,构造函数与异常,析构函数与异常,异常声明和描述,没有类体系的异常类型,构造函数与异常,语法上讲,构造函数可以抛出异常,由于构造函数没有返回值,有人认为“构造函数抛出异常是表示构造不成功的方法”,构造函数抛出异常后,该对象没有构造成功,不执行此级别的析构函数,如果继承链上某父类之前已经构造成功,则执行这些祖先对象的析构函数,如果构造函数已经执行一半,之前申请的资源就不会被释放,析构函数与异常,对象销毁时,自动执行析构函数,无论是,delete,(堆对象),还是作用域结束,或发生异常(栈对象)。,注意,,C+,保证局部对象被适当的销毁仅当在抛出的异常被处理的情况下。一个未被,捕获,的异常是否引起局部对象的销毁由实现决定的。,可以用来进行资源的保护,Auto_ptr,的使用,析,构函数中不能抛出异常(非语法限制),如果此析构函数运行在错误处理过程中,则程序立即崩溃,好的构造和析构模式,永远不在构造函数和析构函数里面抛出异常,或者执行可以跑出异常的动作(除非加好,catch,)。,最简单的构造函数,复杂对象,实现一个,init,函数,手工执行。如果需要,再实现一个,destroy,函数,C+,中的异常描述,在函数声明中列出该函数(自身)可能抛出的异常,并保证该函数(自身)不会抛出任何其他类型的异常。,抛,出特定类型的异常,int divide(int,int)throw(Zerodivide,MemOverflow);,抛出任何可能异常(原始的,C,类型声明),int divide(int,int),永远不会抛出任何异常,bool equals(int,int)throw();,函数实现时,必须与声明时的异常描述一致,虚函数覆盖时,其异常描述必须小于原函数,额外地运行时检测,如果在运行时,函数抛出了一个没有被列在它的异常规范中的异常时,则系统调用标准库中定义的函数,unexpected(),。,unexpected(),缺省操作是调用,teminate(),。,异常类型和使用,在,C+,中,异常类型可以是任何类型,包括基本类型和对象类型,建议使用完整的异常类体系,throw,什么?,throw,匿名临时对象,throw StackOverFlow(“size=100”);,不是堆对象,也不是预先建立的对象,catch,什么?,catch(StackOverFlow&e),支持多态,避免复制、避免忘记析构,C+,异常讨论,很多开发者认为,,C+,的异常机制不好用,甚至是失败的,应该避免使用的。,性能的下降,不可预知性,不可控,虚假的安全感,Java,中的异常处理,finally,块,确保在可能异常的程序块后一定被执行,进行资源恢复等清理工作,finally,不是析构函数,try,catch(A a),catch(B b),finally,/,一定被执行,构造函数和异常,在构造函数中可以抛出异常,表示对象构造不成功,但如果在构造函数中申请外部资源,事情仍然很麻烦,Java,中的异常声明,Java,异常分为两大类,:checked,异常和,unChecked,异常。所有继承,java.lang.Exception,的异常都属于,checked,异常。所有继承,java.lang.RuntimeException,的异常都属于,unChecked,异常。,当一个方法去调用一个可能抛出,checked,异常的方法,必须通过,trycatch,块对异常进行捕获进行处理或者重新抛出。,unChecked,异常也称为运行时异常,通常,RuntimeException,都表示用户无法恢复的异常,如。虽然 用户也可以像处理,checked,异常一样捕获,unChecked,异常。但是如果调用者并没有去捕获,unChecked,异常时,编译器并不会强制你那么做。,关于,checked,异常的讨论,使用,checked,异常的初衷是使程序员关注异常,并进行及时地处理。,Java,推荐人们在应用代码中应该使用,checked,异常,使用,checked,异常,应意味着有许多的,trycatch,在你的代码中,导致代码冗长,以及一些其他问题。,目前很多大师和流行的开源,Java,软件都开始避免,checked,异常,Bruce Eckel,语:“当少量代码时,,checked,异常无疑是十分优雅的构思,并有助于避免了许多潜在的错误。但是经验表明,对大量代码来说结果正好相反”,使用,checked,异常的一些误区,checked,异常导致了太多的,trycatch,代码,有很多,checked,异常对开发人员来说是无法合理地进行处理的,比如,SQLException,。而开发人员却不得不去进行,trycatch,。而这时,通常是简单的把异常打印出来或者是干脆什么也不干。特别是对于新手来说,过多的,checked,异常让他 感到无所适从。,实际上这掩盖了错误,checked,异常导致了许多难以理解的代码产生,当开发人员必须去捕获一个自己无法正确处理的,checked,异常,通常的是重新封装成一个新的异常后再抛出。这样做并没有为程序带来任何好处。反而使代码难以理解。,需要处理非常多的,trycatch.,,真正有用的代码被包含在,try,之内。代码的可读性遭到破坏,使得理解这个方法变得困难起来。,checked,异常导致破坏接口方法,一个接口上的一个方法已被多个类使用,当为这个方法额外添加一个,checked,异常时,那么所有调用此方法的代码都需要修改。,使用,checked,异常的一些建议,当所有调用者必须处理这个异常,可以让调用者进行重试操作;或者该异常相当于该方法的第二个返回值。使用,checked,异常。,这个异常仅是少数比较高级的调用者才能处理,一般的调用者不能正确的处理。使用,unchecked,异常。有能力处理的调用者可以进行高级处理,一般调用者干脆就不处理。,这个异常是一个非常严重的错误,如数据库连接错误,文件无法打开等。或者这些异常是与外部环境相关的。不是重试可以解决的。使用,unchecked,异常。因为这种异常一旦出现,调用者根本无法处理。,如果不能确定时,使用,unchecked,异常。并详细描述可能会抛出的异常,以让调用者决定是否进行处理。,五、异常使用反例,(,anti-pattern,),1 OutputStreamWriter out=.,2 java.sql.Connection conn=.,3 try /,4,Statement stat=conn.createStatement();,5,ResultSet rs=stat.executeQuery(,6,select uid,name from user);,7,while(rs.next(),8,9,out.println(ID,:,+rs.getString(uid)/,10,,姓名:,+rs.getString(name);,11 ,12 conn.close();/,13 out.close();,14,15 catch(Exception ex)/,16,17,ex.printStackTrace();/,,,18,反例,I,:丢弃异常,丢弃异常(代码:,15,行,-18,行),这段代码捕获了异常却不作任何处理,可以算得上,软件质量的杀手,。如果你看到了这种丢弃(而不是抛出)异常的情况,可以百分之九十九地肯定代码存在问题(在极少数情况下,这段代码有存在的理由,但最好加上完整的注释,以免引起别人误解)。,异常(几乎)总是意味着某些事情不对劲了,或者说至少发生了某些不寻常的事情,我们不应该对程序发出的求救信号保持沉默和无动于衷。调用一下,printStackTrace,算不上“处理异常”。不错,调用,printStackTrace,对调试程序有帮助,但程序调试阶段结束之后,,printStackTrace,就不应再在异常处理模块中担负主要责任了。,丢弃异常的情形非常普遍。在,JDK,的内部类中都可以看到类似的使用方法。可见,丢弃异常这一坏习惯是如此常见,它甚至已经影响到了,Java,本身的设计。,反例,I,:丢弃异常,1,、处理异常。针对该异常采取一些行动,例如修正问题、提醒某个人或进行其他一些处理,要根据具体的情形确定应该采取的动作。再次说明,调用,printStackTrace,不是“处理异常”。,2,、重新抛出异常。处理异常的代码在分析异常之后,认为自己不能处理它,重新抛出异常也不失为一种选择。,3,、把该异常转换成另一种异常。大多数情况下,这是指把一个低级的异常转换成应用级的异常(其含义更容易被用户了解的异常)。,4,、不捕获自己不能处理的异常。,结论一:既然捕获了异常,就要对它进行适当的处理。不要捕获异常之后又把它丢弃,不予理睬。,反例,II,:不指定具体的异常(,15,行),你能够处理所有异常么?,catch,语句表示我们预期会出现某种异常,而且希望能够处理该异常。异常类型的作用就是告诉,Java,编译器我们想要处理的是哪一种异常。,catch(Exception ex),就相当于说我们想要处理几乎所有的异常。,我们真正想要捕获的异常是什么呢?,一个是,SQLException,,这是,JDBC,操作中常见的异常。,另一个可能的异常是,IOException,,因为它要操作,OutputStreamWriter,。,显然,在同一个,catch,块中处理这两种截然不同的异常是不合适的。如果用两个,catch,块分别捕获,SQLException,和,IOException,就要好多了。这就是说,,catch,语句应当尽量指定具体的异常类型,而不应该指定涵盖范围太广的,Exception,类。,另一方面,除了这两个特定的异常,还有其他许多异常也可能出现。例如,如果由于某种原因,,executeQuery,返回了,null,,该怎么办?答案是让它们继续抛出,即不必捕获也不必处理。实际上,我们不能也不应该去捕获可能出现的所有异常,程序的其他地方还有捕获异常的机会,?,直至最后由,JVM,处理。,结论二:在,catch,语句中尽可能指定具体的异常类型,必要时使用多个,catch,。不要试图处理所有可能出现的异常。,反例,III,:占用资源不释放,占用资源不释放(代码:,3,行,-14,行),异常改变了程序正常的执行流程。这个道理虽然简单,却常常被人们忽视。如果程序用到了文件、,Socket,、,JDBC,连接之类的资源,即使遇到了异常,也要正确释放占用的资源。为此,,Java,提供了一个简化这类操作的关键词,finally,。,finally,是样好东西:不管是否出现了异常,,Finally,保证在,try/catch/finally,块结束之前,执行清理任务的代码总是有机会执行。遗憾的是有些人却不习惯使用,finally,。,当然,编写,finally,块应当多加小心,特别是要注意在,finally,块之内抛出的异常,?,这是执行清理任务的最后机会,尽量不要再有难以处理的错误。,结论三:保证所有资源都被正确释放。充分运用,finally,关键词。,反例,IV,:不说明异常的详细信息,不说明异常的详细信息(代码:,3,行,-18,行),仔细观察这段代码:如果循环内部出现了异常,会发生什么事情?我们可以得到足够的信息判断循环内部出错的原因吗?不能。我们只能知道当前正在处理的类发生了某种错误,但却不能获得任何信息判断导致当前错误的原因。,printStackTrace,的堆栈跟踪功能显示出程序运行到当前类的执行流程,但只提供了一些最基本的信息,未能说明实际导致错误的原因,同时也不易解读。,因此,在出现异常时,最好能够提供一些文字信息,例如当前正在执行的类、方法和其他状态信息,包括以一种更适合阅读的方式整理和组织,printStackTrace,提供的信息。,结论四:在异常处理模块中提供适量的错误原因信息,组织错误信息使其易于理解和阅读。,反例,V,:过于庞大的,try,块,过于庞大的,try,块(代码:,3,行,-14,行),经常可以看到有人把大量的代码放入单个,try,块,实际上这不是好习惯。这种现象之所以常见,原因就在于有些人图省事,不愿花时间分析一大块代码中哪几行代码会抛出异常、异常的具体类型是什么。把大量的语句装入单个巨大的,try,块就象是出门旅游时把所有日常用品塞入一个大箱子,虽然东西是带上了,但要找出来可不容易。,一些新手常常把大量的代码放入单个,try,块,然后再在,catch,语句中声明,Exception,,而不是分离各个可能出现异常的段落并分别捕获其异常。这种做法为分析程序抛出异常的原因带来了困难,因为一大段代码中有太多的地方可能抛出,Exception,。,结论五:尽量减小,try,块的体积。,反例,VI,:输出数据不完整,输出数据不完整(代码:,7,行,-11,行),不完整的数据是,Java,程序的隐形杀手。仔细观察这段代码,考虑一下如果循环的中间抛出了异常,会发生什么事情。循环的执行当然是要被打断的,其次,,catch,块会执行。就这些,再也没有其他动作了。已经输出的数据怎么办?使用这些数据的人或设备将收到一份不完整的(因而也是错误的)数据,却得不到任何有关这份数据是否完整的提示。对于有些系统来说,数据不完整可能比系统停止运行带来更大的损失。,较为理想的处置办法是向输出设备写一些信息,声明数据的不完整性;另一种可能有效的办法是,先缓冲要输出的数据,准备好全部数据之后再一次性输出。,结论六:全面考虑可能出现的异常以及这些异常对执行流程的影响。,改写后的代码,OutputStreamWriter out=.,java.sql.Connection conn=.,try,Statement stat=conn.createStatement();,ResultSet rs=stat.executeQuery(,select uid,name from user);,while(rs.next(),out.println(ID,:,+rs.getString(uid)+,,姓名,:+rs.getString(name);,catch,(SQLException sqlex),out.println(,警告:数据不完整,);,throw new ApplicationException(,读取数据时出现,SQL,错误,sqlex);,catch,(IOException ioex),throw new ApplicationException(,写入数据时出现,IO,错误,ioex);,改写后的代码,finally,if(conn!=null),try,conn.close();,catch(SQLException sqlex2),System.err(this.getClass().getName()+.mymethod-,不能关闭数据库连接,:+sqlex2.toString();,if(out!=null),try,out.close();,catch(IOException ioex2),System.err(this.getClass().getName()+.mymethod-,不能关闭输出文件,+ioex2.toString();,六、资源申请与释放,一个函数申请了资源,-,如打开了一个文件、在自由存储空间分配了一些存储、设置了某种访问锁等,为保证系统将来的运行,一个关键问题是应该正确的释放这些资源,假如,fopen,调用后,,fclose,调用前,出现了异常,怎么办?,void use,_,file,(,const char,*,fn,),FILE,*,fp,=,fopen,(,fn,w,);,/,use f,fclose,(,fp,);,正确关闭文件的一个解决方法,void use_file(const char*fn),FILE*f=fopen(fn,r);,try,/use f,catch(.),fclose(f);,throw;,fclose(f);,总这样做,可能太罗嗦、不清晰、代价高,利用构造函数和析构函数,/,类,File_ptr,的构造和析构函数负责文件的,/,申请与释放,class File,_,ptr,FILE,*,p,;,public,:,File,_,ptr,(,const char,*,n,const char,*,a,),p,=,fopen,(,n,a,);,File,_,ptr,(,FILE,*,pp,),p,=,pp,;,File,_,ptr,(),fclose,(,p,);,operator FILE,*(),return p,;,;,使用,File_ptr,void use_ file(const char*fn),File_ptr f(fn,r);,/use f,无论是正常结束还是异常退出。都能保证会调用,File_ptr,对象,f,的,析构函数,“,向上回退穿过堆栈”去为某个异常查找处理器的过程,通常称为“堆栈回退”。在“堆栈回退”过程中,所有局部对象的,析构函数,会正常被调用,资源申请即初始化,利用局部对象管理资源的技术即所说的”资源申请即初始化”,.,它依赖于构造函数和析构函数的性质,.,只有在一个构造函数执行完之后,对象才建立起来,只有在此后,堆栈回退时,其析构函数才被调用,.,一个由子对象构成的对象的构造一直持续到它的所有子对象构造完,.,数组的构造一直持续到它的每个元素构造完,(,堆栈回退时,只有那些构造完成的元素的析构函数才被调用,).,关于异常处理机制的讨论,虚假的安全感(,by Tom Cargill,),While entirely in favor of robust error handling,I have serious doubts that exceptions will engender software that is any more robust than that achieved by other means.I am concerned that exceptions will lull programmers into a false sense of security,believing that their code is handling errors when in reality the exceptions are actually compounding errors and hindering the software.,关于异常处理机制的讨论,违反,OO,原则(,by Robert Miller and Anand Tripathi,),Abstraction:generalization of operations and composition construction may involve changing of abstraction levels and dealing with partial states.,Encapsulation:the exception context may leak information that allows implementation details or private data of the signaler to be revealed or accessed.,Modularity:design evolution(function and implementation)maybe inhibited by exception conformance.,Inheritance:the inheritance anomaly can occur when a language does not support exception handling augmentation in a modular way.,关于异常处理机制的讨论,I consider exceptions to be no better than“gotos”,(,by Joel Spolsky,),They are invisible in the source code.,They create too many possible exit points for a function.,by John Robbins,缺点,1,:不具有纯语言特征,缺点,2,:容易被滥用,关键问题,不可预知的出口,影响性能,,branch prediction,失败,破坏结构化和封装原则,性能上的副作用,难以在软件工程中实施,何时何处,可以,/,不可,抛出,/,捕捉,一些结论,是否使用结构化异常处理目前仍有争论,模块内和内部模块之间不要用抛出异常代替错误返回;,设置全局性的异常预处理和未处理异常补救措施;,定义完善的异常处理政策和机制,日志,完整的异常类体系或者异常代码表,资源而不是静态字符串,Thats end,Thank you,
展开阅读全文