我一直以来对于exception的态度都是很明确的。首先exception是好的,否则就不会有绝大多数的语言都支持他了。其次,error code也没什么问题,只是需要一个前提——你的语言得跟Haskell一样有monad和comonad。你看Haskell就没有exception,大家也写的很开心。为什么呢?因为只要把返回带error code结果的函数给做成一个monad/comonad,那么就可以用CPS变换把它变成exception了。所以说CPS作为跟goto同样基本的控制流语句真是当之无愧呀,只是CPS是type rich的,goto是type poor的。
其实很多人对于exception的恐惧心理在于你不知道一个函数会抛什么exception出来,然后程序一crash你就傻逼了。对于server来讲情况还好,出了问题只要杀掉快速重启就行了,如今没个replication和fault tolerance还有脸说你在写后端(所以不知道那些做web的人究竟在反对什么)?这主要的问题还是在于client。只要client上面的东西还没保存,那你一crash数据就完蛋了是不是——当然这只是你的想象啦,其实根本不是这样子的。
我们的程序抛了一个access violation出来,和抛了其它exception出来,究竟有什么区别呢?access violation是一个很奇妙的东西,一旦抛了出来就告诉你你的程序没救了,继续执行下去说不定还会有破坏作用。特别是对于C/C++/Delphi这类语言来说,你不小心把错误的东西写进了什么乱七八糟的指针里面去,那会儿什么事情都没发生,结果程序跑着跑着就错了。因为你那个算错了得到的野指针,说不定是隔壁的不知道什么object的成员变量,说不定是heap里面的数据结构,或者说别的什么东西,就这么给你写了。如果你写了别的object的成员变量那封装肯定就不管用了,这个类的不变量就给你破坏了。既然你的成员函数都是基于不变量来写的,那这个时候出错时必须的。如果你写到了heap的数据结构那就更加呵呵呵了,说不定下次一new就崩了,而且你还不知道为什么。
出了access violation以外的exception基本是没什么危害的,最严重的大概也就是网线被拔了,另一块不是装OS的硬盘突然坏了什么的这种反正你也没办法但是好歹还可以处理的事情。如果这些exception是你自己抛出来的那就更可靠了——那都是计划内的。只要程序未来不会进入access violation的状态,那证明你现在所能拿到的所有变量,还有指针指向的memory,基本上都还是靠谱的。出了你救不了的错误,至少你还可以吧数据安全的保存下来,然后让自己重启——就跟word一样。但是你有可能会说,拿出了access violation怎么就不能保存数据了呢?因为这个时候内存都毁了,指不定你保存数据的代码new点东西然后挂了,这基本上是没准的。
所以无论你喜欢exception还是喜欢error code,你所希望达到的效果本质上就是避免程序未来会进入access violation的状态。想做到这一点,方法也是很简单粗暴的——只要你在函数里面把运行前该对函数做的检查都查一遍就好了。这个无论你用exception还是用error code,写起来都是一样的。区别在于调用你的函数的那个人会怎么样。那么我来举个例子,譬如说你觉得STL的map实在是太傻比了,于是你自己写了一个,然后有了一个这样子的函数:
// exception版本 Symbol* SymbolMap::Lookup(const wstring& name); // error code版本 int SymbolMap::Lookup(const wstring& name, Symbol*& result); // 其实COM就是你们最喜欢的error code风格了,写起来应该很开心才对呀,你们的双重标准真严重 HRESULT ISymbolMap::Lookup(BSTR name, ISymbol** result);
于是拿到了Lookup函数之后,我们就要开始来完成一个任务了,譬如说拿两个key得到两个symbol然后组合出一个新的symbol。函数的错误处理逻辑是这样的,如果key失败了,因为业务的原因,我们要告诉函数外面说key不存在的。调用了一个ComposeSymbol的函数丢出什么IndexOutOfRangeException显然是不合理的。但是合并的那一步,因为业务都在同一个领域内,所以suppose里面的异常外面是可以接受的。如果出现了计划外的异常,那我们是处理不了的,只能丢给上面了,外面的代码对于不认识的异常只需要报告任务失败了就可以了。于是我们的函数就会这么写:
Symbol* ComposeSymbol(const wstring& a, const wstring& b, SymbolMap* map) { Symbol* sa=0; Symbol* sb=0; try { sa=map->Lookup(a); sa=map->Lookup(b); } catch(const IndexOutOfRangeException& ex) { throw SymbolKeyException(ex.GetIndex()); } return CreatePairSymbol(sa, sb); }
看起来还挺不错。现在我们可以开始考虑error code的版本了。于是我们需要思考几个问题。首先第一个就是Lookup失败的时候要怎么报告?直接报告key的内容是不可能的,因为error code是个int。
题外话,error code当然可以是别的什么东西,如果需要返回丰富内容的错误的话,那怎样都得是一个指针了,这个时候你们就会面临下面的问题——这已经他妈不满足谁构造谁释放的原则了呀,而且我这个指针究竟直接返回出去外面理不理呢,如果只要有一个环节不理了,那内存岂不是泄露了?如果我要求把错误返回在参数里面的话,我每次调用函数都要创建出那么个结构来保存异常,不仅有if的复杂度,还有创建空间的复杂度,整个代码都变成了屎。所以还是老老实实用int吧……
以上是小编为您精心准备的的内容,在的博客、问答、公众号、人物、课程等栏目也有的相关内容,欢迎继续使用右上角搜索按钮进行搜索函数
, exception
, 野指针
, error
, code
, 什么
, 一个
wstring
exception code、getexceptioncode、php exception code、c exception code、洛奇 exception code,以便于您获取更多的相关知识。