如何设计一门编程语言(六) exception和error code

我一直以来对于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,以便于您获取更多的相关知识。

时间: 2024-10-03 19:09:06

如何设计一门编程语言(六) exception和error code的相关文章

如何设计一门编程语言(二) 什么是坑(b)

我从来没有在别的语言的粉里面看见过这么容易展示人性丑陋一面的粉,就算是从十几年前开始的C++和C对喷,GC和非GC对喷,静态类型动态类型对喷的时候,甚至是云风出来喷C++黑得那么惊天动地的时候,都没有发生过这么脑残的事情.这种事情只发生在go语言的脑残粉的身上,这究竟代表什么呢?想学go语言的人最好小心一点了,学怎么用go没关系,go学成了因为受不了跳到别的语言去也没关系,就算是抖M很喜欢被折腾所以坚持用go也没关系,但是把自己学成了脑残粉,自己的心智发生不可逆转的变换,那就不好了. 当然,上一

如何设计一门编程语言(八) 异步编程和CPS变换

关于这个话题,其实在(六)里面已经讨论了一半了.学过Haskell的都知道,这个世界上很多东西都可以用monad和comonad来把一些复杂的代码给抽象成简单的.一看就懂的形式.他们的区别,就像用js做一个复杂的带着几层循环的动画,直接写出来和用jquery的"回调"写出来的代码一样.前者能看不能用,后者能用不能看.那有没有什么又能用又能看的呢?我目前只能在Haskell.C#和F#里面看到.至于说为什么,当然是因为他们都支持了monad和comonad.只不过C#作为一门不把&quo

如何设计一门编程语言(九) 类型

类型是了解编程语言的重要一环.就算是你喜欢动态类型语言,为了想实现一个靠谱的东西,那也必须了解类型.举个简单的例子,我们都知道+和-是对称的--当然这只是我们的愿望了,在javascript里面,"1"+2和"1"-2就不是一回事.这就是由于不了解类型的操作而犯下的一些滑稽的错误.什么,你觉得因为"1"的类型是string所以"1"+2就应该是"12"?啐!"1"的类型是(string

如何设计一门编程语言(十) 正则表达式与领域特定语言(DSL)

几个月前就一直有博友关心DSL的问题,于是我想一想,我在gac.codeplex.com里面也创建了一些DSL,于是今天就来说一说这个事情. 创建DSL恐怕是很多人第一次设计一门语言的经历,很少有人一开始上来就设计通用语言的.我自己第一次做这种事情是在高中写这个傻逼ARPG的时候了.当时做了一个超简单的脚本语言,长的就跟汇编差不多,虽然每一个指令都写成了调用函数的形态.虽然这个游戏需要脚本在剧情里面控制一些人物的走动什么的,但是所幸并不复杂,于是还是完成了任务.一眨眼10年过去了,现在在写Gac

如何设计一门编程语言(一) 什么是坑(a)

这个系列的起因是这样的,王垠写了一篇喷go的博客http://www.yinwang.org/blog-cn/2013/04/24/go-language/,里面说go已经烂到无可救药了,已经懒得说了,所以让大家去看http://www.mindomo.com/view.htm?m=8cc4f95228f942f8886106d876d1b041,里面有详细的解释.然后这篇东西被发上了微博,很多博友立刻展示了人性丑陋的一面: 1.那些go的拥护者们,因为go被喷了,就觉得自己的人格受到了侮辱一样

如何设计一门编程语言(五) 面向对象和消息发送

面向对象这个抽象的特例总是有说不完的话题,更糟糕的是很多语言都错误地实现了面向对象--class居然可以当一个变量类型什么的这只是让人们写代码写的更糟糕而已.当然这个话题第三篇文章已经说过了,现在来谈谈人们喜欢拿来装逼的另一个话题--消息发送. 按照惯例先来点题外话.说到消息发送,有些人喜欢跳出来说,objective-c的消息做得多优雅啊,代码都可以写成一句话[golang screw:you you:suck]之类的.其实这个还做得不够彻底.在几年前易语言曾经火了一阵,但是为什么大家这么讨厌

如何设计一门编程语言(三) 什么是坑(面向对象和异常处理)

在所有的文字之前,我需要强调一下,我本人对structure typing持反对态度,所以就算文中的内容"看起来很像"go的interface,读者们也最好不要觉得我是在赞扬go的interface.我比较喜欢的是haskell和rust的那种手法.可惜rust跟go一样恨不得把所有的单词都缩成最短,结果代码写出来连可读性都没有了,单词都变成了符号.如果rust把那乱七八糟的指针设计和go的那种屎缩写一起干掉的话,我一定会很喜欢rust的.同理,COM这个东西设计得真是太他妈正确了,简

如何设计一门编程语言(四) 什么是坑(操作模板)

其实我在写这个系列的第三篇文章的时候就已经发现,距离机器越远,也就是抽象越高的概念,坑的数量是越少的.但是这并不是说,距离机器越近的概念就越强大或者说越接近本质.这是广大的程序员对计算理论的一种误解.大多数人理解编程的知识结构的时候,都是用还原论来理解的,这个方法其实并没有错.但问题在于,"还原"的方法并不是唯一的.很多人觉得,反正你多高级的语言编译完了无非都是机器码嘛.但是还有另一种解释,你无论多低级的语言编译完了无非也就是带CPS变换(continuation passing st

如何设计一门编程语言(十一) 删减语言的功能

大家看到这个标题肯定会欢呼雀跃了,以为功能少的语言就容易学.其实完全不是这样的.功能少的语言如果还适用范围广,那所有的概念必定是正交的,最后就会变得跟数学一样.数学的概念很正交吧,正交的东西都特别抽象,一点都不直观的.不信?出门转左看Haskell,还有抽象代数.因此删减语言的功能是需要高超的技巧的,这跟大家想的,还有跟go那帮人想的,可以断定完全不一样. 首先,我们要知道到底为什么需要删减功能.在这里我们首先要达成一个共识--人都是很贱的.一方面在发表言论的时候光面堂皇的表示,要以需求变更和可