异常之死

来源:互联网 发布:剑灵龙男捏脸数据可爱 编辑:程序博客网 时间:2024/04/27 17:57

异常之死

早在1995年,C++里就出现了“显式异常指定”(explicit exception specification)。如果你还不知道那是什么,可以回想一下你是否曾经看到过类似如下的东西,尤其是在C++标准库里:

void foo() throw(X,Y)
{
...
}

就是那个throw,它说:“foo这个函数可能会并只会抛出异常X和Y”。

这个东西天生煞星像,打在娘胎里时人们就在争论该不该生下来,生下来后更是让很多人敬而远之。直到Java出生时和他指腹为婚,才算正式有了名分(Java里用throws加异常列表)。但是好景不长,几年后C#出生了,在Java大肆嘲笑C#是抄袭的时候,却发现C#没有显式异常指定。于是论战再起。

Java的哲学观秉承了C,她必须是原始基本的——所有能用已有语法元素和类库表达的东西都不添加新的语法元素(1.5版开始不再坚持);同时她又必须是严谨的——没有指针,没有内存疏漏。所以,对待异常,Java的哲学是“所有的异常都必须被处理”。Java要求一个函数必须将其不处理或抛出的异常使用显式异常指定。

C#是实用主义者,也是时尚前卫派。C#的哲学观点是“程序只需要处理它能处理的异常”。更准确的说,整个dotNET平台都如此看待异常,不支持显式异常指定。


按照显式异常指定的设计目的,异常必须是可控的,正确地处理了异常的程序才是健壮(Robust)的程序。而且,如果一个函数指明了它会抛出的异常,也会利于该函数的使用者认识到潜在的异常。这听起来很不错,那让我们来试试。

void foo() throws IOException, MyFooException {}

这可以了吗?等等,要是内存不够出了NotEnoughMemoryException怎么办?出了NullReferenceException怎么办?那些系统错误异常怎么办?都列出来?太黑了吧。还好,Java规定了一些系统异常是不用列出来的。

那么这回行了吧。等等,如果我需要修改foo()的代码,然后需要抛出一个新的异常ObjectNotInitializedException怎么办?如果在throws列表里加上,使用了这个类库的程序就无法编译运行了,因为他们没有catch这个新的异常。很不幸,因为你一开始没有设计好,你只能添加一个新的函数foo2()然后鼓励使用你类库的程序都移植到新的版本。

这回行了吗?我们写出一个有4、5个模块的普通规模的程序,最后我们的胶合层模块将把他们联系在一起。好了,问题又来了,现在我们胶合层模块里每个函数名后面都有一个很长的异常列表,几乎包括了它所用的每个函数会抛出的异常。这写起来很痛苦,看起来更痛苦,就像癌症晚期,肿瘤在扩散。一定是哪里出错了。于是我们喝杯茶想想,为什么要把异常都抛出来呢,应该尽量把异常在函数内部处理掉。于是我们仔细挑出那些非抛不可的异常,把其他的通通吃掉。

这回行了吗?我们调试程序,发现程序报告成功,但却没有做它该做的事!一定是哪里出错了!但是哪里?鬼知道。于是我们在每个catch里加log输出,记录捕获到异常的位置,异常的信息。

这回行了吧。我们把完成的程序交给新员工来维护,以便自己能抽出时间做更重要的事。等我们再去看那个程序时,天啊,函数后的throws都没了,所有的异常都在函数内catch了。于是我们跑去和负责维护的员工说,这样都catch是糟糕的设计。负责维护的员工会一头雾水,“为什么要有些catch有些throws啊?凭什么区分啊?为什么那么麻烦啊?像C那样用个返回值不是很简单么?”

于是一切返回原点:
#define ERR_IO_EXCEPTION 1
#define ERR_MY_FOO_EXCEPTION 2
#define ERR_OBJECT_NOT_INITIALIZED_EXCEPTION 3
int foo() {}

 

原创粉丝点击