“拥抱”异常,还是,“固守”返回值?

我想,作者对于异常的性能可能有一些误会。对于 Java 或 C# 这样的语言,异常的成本无论高低,都是不可回避的,不谈了。对于 C++ 来说,未发生的异常,其成本极其微小,大体上,只是多一个地址入栈的问题。而对于已经发生的异常,这个成本确实比较高,但是,在这种时候,正确性当然压倒 所有性能要求。还有,退出作用域时析构函数必须调用,这个与抛出异常与否无关,throw 也好,return 也好,都会发生的,这本就是 C++ 至关重要的特性。

从更高的层面来思考的话,我们必须认同:如果有了错误,那么暴露得越早越好,暴露得越充分越好。以此而论:未捕捉的异常会导致程序直接 over,这一点不是缺点,而恰恰是它相对于返回值的巨大优点。一个未检查的返回值完全可以一直潜伏到火车撞上、飞机掉下、老板囧到,而一个未捕捉的异常 通常不会潜伏这么久。

最后,关于异常与返回值的选择,这个其实很简单,它们本来就是完全不相干的两个东西,只是被 C 语言带坏的我们,总想用返回值表示点结果之外的啥。其实,在绝大部分情况下,都不存在“两可”的问题:
1、只要确实是一个不可控(或者说,意外的?本不该有的?)的错误,抛出异常总是比返回一个错误码更好,因为返回值只是个处理的结果而已。比如:除0、打 开配置文件失败、硬件错、通信校验错……为什么这时候我们需要异常?因为我们要确保程序知道发生了意外的事!“确保”,恰恰是返回值所不能做到的——“有 良心”的程序员,通常也只是简单的做一个 if(good) 检测罢了。当然,什么是“不可控”,这与程序的定位有关,比如,通信校验错,对于一般的网络应用程序显然是“不可控”的错误,但对于网卡驱动就不是——只 是我不相信你会用 C 以外的语言来写驱动,呵呵。
2、只要这是一种预见中的状态,就不该抛出异常。比如,迭代会有终点、网络可能未登录、U盘可能未插入(可是文件写到一半U盘被拔出那就属于“不可控” 了)……非要从形式上说的话,一切形如 isXXX、xxxExists、hasXXX 之类的东西,当然应该用返回值而不是异常来表示它们的检查结果,对于它们来说,返回正确或错误本就是应该的事。举反例的话,python 用异常来完成迭代,这就是一个典型的设计错误。

意犹未尽举个例子,考虑一个进行编码转换的函数:
bool translate(const wchar_t* src, std::wstring& dest);
正例:编码转换,当然有成功与不成功,返回一个标识成功与否的返回值是题中应用之义。甚至,返回一个多状态值(比如 enum 值),用来说明转换失败的原因(比如源串编码错误)也是正常的设计。
反例:在编码转换的内部实现中,完全可能是用一个外部映射文件来实现的,而这个外部文件完全有可能在网络上……但是,如果因此在返回值里还有一大堆错误码诸如:网络中断、文件不存在……很明显,这些东西本该以异常的面貌出现。

又及:楼上有主张在 C++ 中使用 throw 声明的,千万不要这样做!请参看 Bjorne Stroustrup 本人的建议,这位 C++ 之父为这个特性的引入深深懊悔。



下面是交流的内容了。楼主原文没有复制过来,需要的话通过上面的链接过去看吧。
————————————————————————————————————————

楼主认为:“返回值不是狭隘的指C语言中的返回码,是泛指返回信息。如一个错误类型的对象(结构体)。”

我想,这就是我们的主要分歧了。在我看来,返回值与异常完全是两个互不相干、互为正交的东西。
“返回值”这个名字就已经清晰的表达了它是做什么的:返回值只是一段程序处理的结果,只不过结果有好有坏、有左有右、有简单有复杂……如此而已。
“异常”这个名字更加明显:异常是一段程序处理过程中出现的意外,由于这意外的干扰,处理没有结果,当然也就没有返回值,所以我们抛出异常。

曾经有一段时间,C++ 程序员们突然喜欢上了用 std::pair 来包装返回值,first 是处理结果,而 second 却是一个异常对象,两面折衷,想要两全其美。事实上,这除了在一些特定的编译平台上(这些平台出于各种原因禁止异常)用来模仿异常机制之外,实在百无一 用。

Java 和 C# 由于没有“遗产包袱”问题,它们在异常问题上就非常明确,根本没有混淆的可能。C++ 程序员的 C 惯性使他们在返回值这个载体上加载了过多的东西,实在是该纠正了。

至于“滥用异常”这个话题,这个没什么可说的,滥用可以毁掉一切好东西。但是滥用异常只会给程序员添麻烦——谁乐意干啥都 try catch 包一大堆花括号?而返回值,我们一直都在滥用,并且还试图进一步滥用。

你可能感兴趣的:(返回值,C++,异常,错误处理)