第六章 debug重看
第六章,主要讲述健壮性和正确性,通过lab3和Lab4,我对于健壮性与正确性有了更加深刻的认识,但是与此同时也产生了一些困扰,从某种层面上来讲,健壮性和正确性其实是比较相似的,这往往会带来困扰。那么有必要在这里重新再次阐述:
健壮性:系统在不正常输入或不正常外部环境下仍能够表现正常的程度
一个具有良好的健壮性的程序应该具有如下特点:处理未期望的行为和错误终止;即使终止执行,也要准确/无歧义的向用户展示全面的错误信息;错误信息有助于进行debug
健壮性原则:于律己,宽以待人。意思就是说永远要假设用户存在恶意行为。封闭实现细节,限定用户的恶意行为;考虑极端情况,没有“不可能”。 你要设想用户各种可能的,甚至是恶心人的输入。一个健壮性优越的程序,能够及时的判断出用户输入的错误,达到fail fast(这里以后再讲),总而言之,Nothing impossible
正确性:永不给用户错误的结果; 让开发者变得更容易:用户输入错误,直接结束(如果不满足前置条件)。
那么我们已经很容易就能看出两者的差别了:健壮性可能就像你妈妈一样,呵护着你,照顾着你,犯了什么错几乎都能原谅你。 正确性就像你女朋友一样,你干点规格外的事情(即非前置条件的事情)都能把你骂的狗血淋头,甚至分手(直接结束)。 那么你认为的可靠(relibility)的天使是谁呢?其实就是健壮性+正确性构成了可靠性。
那么在说完正确性和健壮性过后,如何评价这两个性质呢?利用以下两个性质:
- 外部观察角度:
- Mean time between failures (MTBF,平均失效间隔时间):描述了可修复系统的两次故障之间的预期时间,而平均故障时间(MTTF)表示不可修复系统的预期故障时间。
- 内部观察角度:
- 残余缺陷率:每千行代码中遗留的bug的数量
6-2 处理异常
在处理异常之前首先要明白异常是什么。异常的分类用下面这张图就能很清楚的表达出来

可以看到里面有Error 与Exception,这里说一点我以前Lab2和Lab3一个特别不好的习惯。 我在Lab2碰到如果有用户非法输入,经常就会直接调用下面这个语句
throw new Error("xxxx")
当时还没有经过第六章的学习,对于Java也是几乎完全懵逼的状态,在学习了第六章过后,我才发现这是一个多么愚蠢的行为。为什么说恶心呢?且听下面一说:
内部错误(error):包括内存用完了、爆栈了、JVM错误等。程序员通常无能为力,一旦发生,想办法让程序优雅的结束
异常:自己程序导致的问题,可以捕获、处理
异常:程序执行中的非正常事件,导致程序无法再按预想的流程执行
Exception将错误信息传递给上层调用者,并报告“案发现场”的信息
是return之外的第二种退出途径
若找不到异常处理程序,整个系统完全退出
所以我当初犯的错误是非常愚笨的,error一定是要指程序员已经无能为力了的错误,但显然以lab2的体量,至少在我的能力范围内,还无法发现这样的错误。
那么我应该用的就是异常了,其实在Lab4刚开始的时候,我觉得异常是一个很烦人的事情,因为我会去想,我Lab3都已经考虑了许多用户的输入了,咋还要整这些玩意呢。后来Lab4进行到最后,自己进行检测的时候才发现,异常所能处理的情况要多得多,而且异常最大的好处就是能够直观的看到信息,这也是log的信息,你试想,你要是不采用异常,而实用其他方法,要打印一个错误信息(就我现在的能力而言),也就System.out.println,而且还容易无法定位到底发生在哪里。Exception则把这些问题都解决了。而且到了快结束的时候,会发现,这玩意也不是很麻烦,try catch,连构造语句都是可以复用的。因此在未来的编程,要多使用异常处理机制(当然希望自己的程序还是能少触发一点异常)
说回正题:
运行时异常是程序源代码中引入故障所造成的,如果在代码中提前进行验证,这些故障就可以避免;非运行时异常是程序员无法完全控制的外在问题所导致的,即使在代码中提前加以验证,也无法完全避免失效发生(例如验证了文件是否存在,但是在文件处理过程中文件可能突然被删除导致爆炸)
错误和运行时异常并不能被编译器检测出来,这个时候需要重新编写代码来移除这些问题,不能try-catch啥的,类似动态检查;而其他异常可以被捕获处理,编译器也会帮助检查是否抛出了异常或进行了处理否则无法通过编译,类似静态检查。
相对而言,我们程序员是要尽可能解决运行时异常的。经典错误就是数组越界问题,数组越界问题经典运行时异常,这个我们能解决吗?太能了,你设置一个前置条件不就行了吗?但我之前这样做了吗,很少。(当然Lab4肯定做了的哈),所以我们要时刻留意这个问题,尽早发现尽早解决。这些问题都是我们能力范围内的。那么遇到无法处理的咋办呢?
尽量使用非检查类型异常来处理编程错误,因为不用在代码中显式处理。即使是必须抛出检查类型异常的地方,如果对处理无能为力也可以在catch中抛出RuntimeException将其转化为非检查类型异常错误可预料,但无法预防,但可以有手段从中恢复,此时就用检查类型异常;如果做不到这一点,就用非检查类型
在刚刚接触异常的时候(不是指lab4时,而是指刚刚开始学java时),初学者往往会喜欢这样一种处理机制:throws。好不好?不好!throws表示可能抛出该异常,方法添加throws便可以处理这些异常了,但是处理方式是非常不妥的,如果没有其他方法捕获这些异常,那么会十分粗暴的打印在控制台上,观感可想而知,而且还会可能引发其他各种错误。

在Lab4中,我们尝试了自己构造exception,为什么要自己构造exception?
如果JDK提供的异常类无法充分描述程序发生的错误,可以自己创建异常类 习惯上同时给出一个默认构造函数和一个包含详细消息的构造函数
在lab4中,还尝试了一种断言技术assert的使用范围很广,主要使用在内部不变量、RI、控制流不变量(swtich-case的default)、方法的前置条件、后置条件断言主要用于开发阶段,避免引入和帮助发现bug。实际运行阶段,不再使用断言,避免降低性能
这里有一点要注意,功能性代码不要利用断言机制 断言更倾向于保证正确性,异常更倾向于保证健壮性
浙公网安备 33010602011771号