Go 语言习得机制的错误处理机制是一个优秀的设计吗

中国领先的IT技术网站
51CTO旗下网站
Go语言的有效错误处理
中午闲暇翻看Daniel Morsing的“The Go scheduler”时,发现其另外一篇短文“Effective error handling in Go”,文章不长,但感觉对Go中错误处理方法总结的还是比较到位的,这里译之供大家参考。
作者:Tony Bai来源:Tony Bai的博客| 10:05
中午闲暇翻看的&&时,发现其另外一篇短文&&,文章不长,但感觉对中错误处理方法总结的还是比较到位的,这里译之供大家参考。
Go语言受到诟病最多的一项就是其错误处理机制。如果显式地检查和处理每个error,这恐怕的确会让人望而却步。你可以试试这里列出的几个方法,以避免你走入错误处理方法的误区当中去。
二、在缩进区处理错误
当使用Go语言编写代码时,首选下面这样的错误处理方法:
f,&err&:=&os.Open(path)&if&err&!=&nil&{&&&&&&}&&&而不是下面这样的:&&f,&err&:=&os.Open(path)&if&err&==&nil&{&&&&&&}&&
按照上面的方法处理错误,处理正常情况的代码读起来就显得通篇连贯了。
三、定义你自己的errors
做好如何正确进行错误处理的第一步就是要了解error是什么。如果你设计实现的包会因某种原因发生某种错误,你的包用户将会对错误的原因很感兴趣。为了满足用户的需求,你需要实现error接口,简单做起来就像这样:
type&Error&string&func&(e&Error)&Error()&string&{&return&string(e)&}&
现在,你的包用户通过执行一个type assertion就可以知道是否是你的包导致了这个错误:
result,&err&:=&yourpackage.Foo()&if&ype,&ok&:=&err.(yourpackage.Error);&ok&{&&&&&&}&
通过这个方法,你还可以向你的包用户暴露更多地结构化错误信息:
type&ParseError&struct&{&&&&&File&&*File&&&&&Error&string&}&&func&(oe&*ParseError)&Error()&string&{&&&&&&}&&func&ParseFiles(files&[]*File)&error&{&&&&&for&_,&f&:=&range&files&{&&&&&&&&&err&:=&f.parse()&&&&&&&&&if&err&!=&nil&{&&&&&&&&&&&&&return&&ParseError{&&&&&&&&&&&&&&&&&&File:&&f,&&&&&&&&&&&&&&&&&Error:&err.Error(),&&&&&&&&&&&&&}&&&&&&&&&}&&&&&}&}&
通过这种方法,你的用户就可以明确地知道到底哪个文件出现解析错误了。(译注:从这里看到的go语言error设计之内涵,让我想起了Rob Pike大神的一篇Blog:&&)
不过包装error时要小心,当你将一个error包装起来后,你可能会丢失一些信息:
var&c&net.Conn&f,&err&:=&DownloadFile(c,&path)&switch&e&:=&err.(type)&{&default:&&&&&&case&net.Error:&&&&&&&&&&c.Close()&&&&&return&e&case&error:&&&&&&&&&&return&err&}&&
如果你包装了net.Error,上面这段代码将无法知道是由于网络问题导致的失败,会继续使用这条无效的链接。
有一条经验规则:如果你的包中使用了一个外部interface,那么不要对这个接口中方法返回的任何错误,使用你的包的用户可能更关心这些错误,而不是你包装后的错误。
四、将错误作为状态
有时,当遇到一个错误时,你可能会停下来等等。这或是因为你将延迟报告错误,又或是因为你知道如果这次报告后,后续你会再报告同样的错误。
第一种情况的一个例子就是bufio包。当一个bufio.Reader遇到一个错误时,它将停下来保持这个状态,直到buffer已经被清空。只有在那时它才会报告错误。
第二种情况的一个例子是go/loader。当你通过某些参数调用它导致错误时,它会停下来保持这个状态,因为它知道你很可能会使用同样地参数再次调用它。
五、使用函数以避免重复代码
如果你有两段重复的错误处理代码,你可以将它们放到一个函数中去:
func&handleError(c&net.Conn,&err&error)&{&&&&&&}&&func&DoStuff(c&net.Conn)&error&{&&&&&f,&err&:=&downloadFile(c,&path)&&&&&if&err&!=&nil&{&&&&&&&&&handleError(c,&err)&&&&&&&&&return&err&&&&&}&&&&&&f,&err&:=&doOtherThing(c)&&&&&if&err&!=&nil&{&&&&&&&&&handleError(c,&err)&&&&&&&&&return&err&&&&&}&}&
优化后的实现方法如下:
func&handleError(c&net.Conn,&err&error)&{&&&&&if&err&==&nil&{&&&&&&&&&return&&&&&}&&&&&&}&&func&DoStuff(c&net.Conn)&error&{&&&&&defer&func()&{&handleError(c,&err)&}()&&&&&f,&err&:=&downloadFile(c,&path)&&&&&if&err&!=&nil&{&&&&&&&&&return&err&&&&&}&&&&&&f,&err&:=&doOtherThing(c)&&&&&if&err&!=&nil&{&&&&&&&&&return&err&&&&&}&}&
这就是全部了。就Go语言错误处理而言,我知道的就这么多了。【编辑推荐】【责任编辑: TEL:(010)】
大家都在看猜你喜欢
头条头条热点热点热点
24H热文一周话题本月最赞
讲师:1人学习过
讲师:35人学习过
讲师:0人学习过
精选博文论坛热帖下载排行
本书共10章,介绍的内容包括恶意软件(包括病毒、木马和蠕虫等)的深度防御方法,黑客的主要类型和防御方法,企业网络内、外部网络防火墙系...
订阅51CTO邮刊我也经历过2次。&br&第一次,是某款PLC。厂家声明,此PLC已经在全球用了XX万套,是很成熟的产品。但在编程阶段就显得极不成熟。PLC投运后,出现死机现象,几经折腾也弄不好。最后,厂家的工程师自己来处理了,并且告诉我:我用的是全世界第一套!&br&经验教训:原来,老外也会忽悠人。&br&第二次,是另外一款PLC,此PLC被广泛使用,是技术成熟产品。&br&但被用在地铁中,却发现了死机。检查后发现,我们将此PLC同时用于逻辑控制和通信管理控制。由于两套系统都属于0类中断处理程序,于是PLC的CPU在运行受阻时,就将两套系统全部重启。造成严重事故。&br&说来有点意思,任何PLC都有几个字节的内部存储空间,用来记录严重故障时的关键值。此内部存储空间一般不公开。我就用此技术记录了死机前最后一刻的状态,找到了事故的根源。PLC的制造商也据此修改了设计。&br&一个感觉,PLC的技术进步类似于空难,也是在故障中不断修改和完善的。&br&事后,我们把两套系统用两台PLC独立完成,两者之间不存在建立横向联系,由此彻底杜绝了问题的根源。&br&&img src=&/v2-214bbf6a0ecda394dbc2c3d_b.jpg& data-rawwidth=&879& data-rawheight=&386& class=&origin_image zh-lightbox-thumb& width=&879& data-original=&/v2-214bbf6a0ecda394dbc2c3d_r.jpg&&总之,出了问题首先要分清责任。我们要敢于承担工作压力,但不能盲目的承担事故责任,要保护好自己;第二要仔细认真地分析事故原因。&br&事实上,分析事故原因难度很大,甚至都会超过开发商的技术水平。&br&若只是简单地把责任推给供应商,则自己的技术永远不会进步。
我也经历过2次。 第一次,是某款PLC。厂家声明,此PLC已经在全球用了XX万套,是很成熟的产品。但在编程阶段就显得极不成熟。PLC投运后,出现死机现象,几经折腾也弄不好。最后,厂家的工程师自己来处理了,并且告诉我:我用的是全世界第一套! 经验教训:原…
首先,这是一个好问题。深入理解二者的区别是突破中等程度程序员的标志之一。&br&&br&先澄清一个误区,那就是异常非常慢。异常的实现需要保存异常抛出点到异常捕获点的必要信息,这是人们诟病异常性能的主要依据。但是这是错误的。第一,手工按照返回错误的风格取得行为等价于异常的实现性能绝大多数时候比不上语言内置的异常的性能。对于支持原生异常的OS,如Windows,这个差距尤其明显;第二,对于非基于栈的调用实现,异常和返回错误性能几无差别,只是风格不同而已。&br&&br&回到问题,异常和返回错误的本质区别是什么?答:异常是强类型的,类型安全的分支处理技术;而返回错误是其弱化的,不安全的版本。&br&&br&先看返回错误的特征。一般来说,需要用特别返回值来标识错误,意味着需要重载特定值得含义。假设一个操作会返回一个int型值,因为其不可能返回负数(如求绝对值),我们一般可以使用-1来作为操作失败(如函数库调用加载错误)的指示。-1在int类型中并无特别含义,然而我们这里强制施加了语义,而这种语义是弱的。没办法通过类型系统检查来确保正确性。&br&&br&一个增强的办法是同时返回两种值。有的语言内置支持,没有内置支持的可以通过定义一个含有分别表征正确返回类型和错误类型的结构来模拟。这样的好处是传递信息的通道被分离了。&br&&br&无论如何返回错误,它存在着一个显著的缺点:调用方必须显式检查错误值。当错误发生,必须终止当前操作,返回错误的时候,一般需要定义全新的错误类型。这导致操作不可任意简单组合,如模块B调用模块A发生错误的时候需要返回B定义的错误,而模块C调用A错误时则需要返回C定义的错误,而这两类错误往往是不兼容的。组合相关代码必须加入大量的粘连代码,这导致了代码膨胀,而且非常容易出错。对于接口定义明确的系统,一般定义一套通用的错误码来简化其复杂性。OS内核就是一个例子,它提供了一个适配所有OS调用的错误码表。然而其内部使用的错误码要多得多,并且往往在接口把内部错误码映射到外部错误码。&br&&br&异常是一种安全的分支处理技术。因为它和错误处理密切相关,人们容易直接把异常看成是错误处理,连名字“异常”都带着浓浓的错误的含义。但是这是一个误解。异常的理论基础是假设某些分支处理中,一个分支和其它分支比起来发生的非常不频繁。与其平等地针对常见和极其罕见的情形进行处理(想一下,正常处理代码和错误处理代码往往一样多,大部分情况下后者其实更多),不如仅仅处理正常的情形,把不常见的情形归于一处统一处理。这样我们书写代码的时候仅仅关注正常情形就可以了。发生错误的时候,特别的流程会帮助程序员直接回到定义了错误处理的地方。&br&&br&这样说有点过分简化。一般情况下,不能直接跳回。因为在异常抛出点到异常处理点之间,可能存在处于非一致状态的值,等待释放的资源等。所以一般情况下,特殊流程要回溯调用栈,确保执行的事务性。但是仅仅这个特殊流程是不够的,必须有合适的配套代码才行。这导致了额外的复杂性。C++引入的RAII概念是一个创举,然而依然没能全部消除程序员的工作。&br&&br&因为异常是一个具体的类型,一个函数的signature就不仅仅是输入参数列表,输出参数加函数名,还要包含可能抛出异常列表。因为各个模块定义的异常层次结构迥异,这导致了额外的组合困难。然而和返回错误值的组合困难不同,前者导致的是编译时类型错误,后者则是运行时错误。一般来看,错误暴露的越早越好,我们倾向于前者。&br&&br&理想情况下,一个异常结构完备的系统不会有运行时错误(大概如此,不展开)。然而因为上面提到的原因,在现有的异常支持的情况下,代码同样复杂,冗余,难以维护。&br&&br&上面就是异常和错误的基本区别。&br&&br&第二部分预告:有解决上述问题的优雅方法吗?&br&&br&我的观点是有。但是因为现有能力还不到不借助任何参考直接澄清此问题,我回留待仔细斟酌,沐浴焚香,换到非手机输入的情况下给出,尽请期待。
首先,这是一个好问题。深入理解二者的区别是突破中等程度程序员的标志之一。 先澄清一个误区,那就是异常非常慢。异常的实现需要保存异常抛出点到异常捕获点的必要信息,这是人们诟病异常性能的主要依据。但是这是错误的。第一,手工按照返回错误的风格取…
这个问题说来话长,我先表达一下我的观点,Go语言从语法层面提供区分错误和异常的机制是很好的做法,比自己用单个返回值做值判断要方便很多。&br&&br&上面看到很多知乎大牛把异常和错误混在一起说,有认为Go没有异常机制的,有认为Go纯粹只有异常机制的,我觉得这些观点都太片面了。&br&&br&具体对于错误和异常的讨论,我转发一下前阵子写的一篇日志抛砖引玉吧。&br&&br&============================&br&&br&&p&最近连续遇到朋友问我项目里错误和异常管理的事情,之前也多次跟团队强调过错误和异常管理的一些概念,所以趁今天有动力就赶紧写一篇Go语言项目错误和异常管理的经验分享。&/p&&br&&p&首先我们要理清:什么是错误、什么是异常、为什么需要管理。然后才是怎样管理。&/p&&br&&p&错误和异常从语言机制上面讲,就是error和panic的区别,放到别的语言也一样,别的语言没有error类型,但是有错误码之类的,没有panic,但是有throw之类的。&/p&&br&&p&在语言层面它们是两种概念,导致的是两种不同的结果。如果程序遇到错误不处理,那么可能进一步的产生业务上的错误,比如给用户多扣钱了,或者进一步产生了异常;如果程序遇到异常不处理,那么结果就是进程异常退出。&/p&&br&&p&在项目里面是不是应该处理所有的错误情况和捕捉所有的异常呢?我只能说,你可以这么做,但是估计效果不会太好。我的理由是:&/p&&ol&&li&如果所有东西都处理和记录,那么重要信息可能被淹没在信息的海洋里。&/li&&li&不应该处理的错误被处理了,很容易导出BUG暴露不出来,直到出现更严重错误的时候才暴露出问题,到时候排查就很困难了,因为已经不是错误的第一现场。&/li&&/ol&&p&所以错误和异常最好能按一定的规则进行分类和管理,在第一时间能暴露错误和还原现场。&/p&&br&&p&对于错误处理,Erlang有一个很好的概念叫速错,就是有错误第一时间暴露它。我们的项目从Erlang到Go一直是沿用这一设计原则。但是应用这个原则的前提是先得区分错误和异常这两个概念。&/p&&br&&p&错误和异常上面已经提到了,从语言机制层面比较容易区分它们,但是语言取决于人为,什么情况下用错误表达,什么情况下用异常表达,就得有一套规则,否则很容易出现全部靠异常来做错误处理的情况,似乎Java项目特别容易出现这样的设计。&/p&&br&&p&这里我先假想有这样一个业务:游戏玩家通过购买按钮,用铜钱购买宝石。&/p&&br&&p&在实现这个业务的时候,程序逻辑会进一步分化成客户端逻辑和服务端逻辑,客户端逻辑又进一步因为设计方式的不同分化成两种结构:胖客户端结构、瘦客户端结构。&/p&&br&&p&胖客户端结构,有更多的本地数据和懂得更多的业务逻辑,所以在胖客户端结构的应用中,以上的业务会实现成这样:客户端检查缓存中的铜钱数量,铜钱数量足够的时候购买按钮为可用的亮起状态,用户点击购买按钮后客户端发送购买请求到服务端;服务端收到请求后校验用户的铜钱数量,如果铜钱数量不足就抛出异常,终止请求过程并断开客户端的连接,如果铜钱数量足够就进一步完成宝石购买过程,这里不继续描述正常过程。&/p&&br&&p&因为正常的客户端是有一步数据校验的过程的,所以当服务端收到不合理的请求(铜钱不足以购买宝石)时,抛出异常比返回错误更为合理,因为这个请求只可能来自两种客户端:外挂或者有BUG的客户端。如果不通过抛出异常来终止业务过程和断开客户端连接,那么程序的错误就很难被第一时间发现,攻击行为也很难被发现。&/p&&br&&p&我们再回头看瘦客户端结构的设计,瘦客户端不会存有太多状态数据和用户数据也不清楚业务逻辑,所以客户端的设计会是这样:用户点击购买按钮,客户端发送购买请求;服务端收到请求后检查铜钱数量,数量不足就返回数量不足的错误码,数量足够就继续完成业务并返回成功信息;客户端收到服务端的处理结果后,在界面上做出反映。&/p&&br&&p&在这种结构下,铜钱不足就变成了业务逻辑范围内的一种失败情况,但不能提升为异常,否则铜钱不足的用户一点购买按钮都会出错掉线。&/p&&br&&p&所以,异常和错误在不同程序结构下是互相转换的,我们没办法一句话的给所有类型所有结构的程序一个统一的异常和错误分类规则。&/p&&br&&p&但是,异常和错误的分类是有迹可循的。比如上面提到的痩客户端结构,铜钱不足是业务逻辑范围内的一种失败情况,它属于业务错误,再比如程序逻辑上尝试请求某个URL,最多三次,重试三次的过程中请求失败是错误,重试到第三次,失败就被提升为异常了。&/p&&br&&p&所以我们可以这样来归类异常和错误:不会终止程序逻辑运行的归类为错误,会终止程序逻辑运行的归类为异常。&/p&&br&&p&因为错误不会终止逻辑运行,所以错误是逻辑的一部分,比如上面提到的瘦客户端结构,铜钱不足的错误就是业务逻辑处理过程中需要考虑和处理的一个逻辑分支。而异常就是那些不应该出现在业务逻辑中的东西,比如上面提到的胖客户端结构,铜钱不足已经不是业务逻辑需要考虑的一部分了,所以它应该是一个异常。&/p&&br&&p&错误和异常的分类需要通过一定的思维训练来强化分类能力,就类似于面向对象的设计方式一样的,技术实现就摆在那边,但是要用好需要不断的思维训练不断的归类和总结,以上提到的归类方式希望可以作为一个参考,期待大家能发现更多更有效的归类方式。&/p&&br&&p&接下来我们讲一下速错和Go语言里面怎么做到速错。&/p&&br&&p&速错我最早接触是在做&a href=&///?target=http%3A//ASP.NET& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://&/span&&span class=&visible&&ASP.NET&/span&&span class=&invisible&&&/span&&i class=&icon-external&&&/i&&/a&的时候就体验到的,当然跟Erlang的速错不完全一致,那时候也没有那么高大上的一个名字,但是对待异常的理念是一样的。&/p&&br&&p&在.NET项目开发的时候,有经验的程序员都应该知道,不能随便re-throw,就是catch错误再抛出,原因是异常的第一现场会被破坏,堆栈跟踪信息会丢失,因为外部最后拿到异常的堆栈跟踪信息,是最后那次throw的异常的堆栈跟踪信息;其次,不能随便try catch,随便catch很容易导出异常暴露不出来,升级为更严重的业务漏洞。&/p&&br&&p&到了Erlang时期,大家学到了速错概念,简单来讲就是:让它挂。只有挂了你才会第一时间知道错误,但是Erlang的挂,只是Erlang进程的异常退出,不会导致整个Erlang节点退出,所以它挂的影响层面比较低。&/p&&br&&p&在Go语言项目中,虽然有类似Erlang进程的Goroutine,但是Goroutine如果panic了,并且没有recover,那么整个Go进程就会异常退出。所以我们在Go语言项目中要应用速错的设计理念,就要对Goroutine做一定的管理。&/p&&br&&p&在我们的游戏服务端项目中,我把Goroutine按挂掉后的结果分为两类:1、挂掉后不影响其他业务或功能的;2、挂掉后业务就无法正常进行的。&/p&&br&&p&第一类Goroutine典型的有:处理各个玩家请求的Goroutine,因为每个玩家连接各自有一个Goroutine,所以挂掉了只会影响单个玩家,不会影响整体业务进行。&/p&&br&&p&第二类Goroutine典型的有:数据库同步用的Goroutine,如果它挂了,数据就无法同步到数据库,游戏如果继续运行下去只会导致数据回档,还不如让整个游戏都异常退出。&/p&&br&&p&这样一分类,就可以比较清楚哪些Goroutine该做recover处理,哪些不该做recover处理了。&/p&&br&&p&那么在做recover处理时,要怎样才能尽量保留第一现场来帮组开发者排查问题原因呢?我们项目中通常是会在最外层的recover中把错误和堆栈跟踪信息记进日志,同时把关键的业务信息,比如:用户ID、来源IP、请求数据等也一起记录进去。&/p&&br&&p&为此,我们还特地设计了一个库,用来格式化输出堆栈跟踪信息和对象信息,项目地址:&a href=&///?target=http%3A///funny/debug& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&funny/debug · GitHub&i class=&icon-external&&&/i&&/a&&/p&&br&&p&通篇写下来发现比我预期的长很多,所以这里我做一下归纳总结,帮组大家理解这篇文章所要表达的:&/p&&ol&&li&错误和异常需要分类和管理,不能一概而论&/li&&li&错误和异常的分类可以以是否终止业务过程作为标准&/li&&li&错误是业务过程的一部分,异常不是&/li&&li&不要随便捕获异常,更不要随便捕获再重新抛出异常&/li&&li&Go语言项目需要把Goroutine分为两类,区别处理异常&/li&&li&在捕获到异常时,需要尽可能的保留第一现场的关键数据&/li&&/ol&&p&以上仅为一家之言,抛砖引玉,希望对大家有所帮助。&/p&
这个问题说来话长,我先表达一下我的观点,Go语言从语法层面提供区分错误和异常的机制是很好的做法,比自己用单个返回值做值判断要方便很多。 上面看到很多知乎大牛把异常和错误混在一起说,有认为Go没有异常机制的,有认为Go纯粹只有异常机制的,我觉得这…
因噎废食。&br&&br&首先我需要强调一句:【&strong&应该使用异常】跟【异常是用来catch的】是两个毫不相干的命题&/strong&。我从来不觉得抛了异常就一定要catch。这也是为什么异常有那么多类型,你可以自由决定什么样的异常拿来做控制流,什么样的异常拿来打dump崩溃。如果你觉得不应该有控制流异常也没关系,那你就都打dump好了。&br&&br&把所有STL能代替C语言的那部分内容,都用STL,不要用C语言的feature来做,写异常安全的代码是很简单的,根本就不需要刻意去注意什么。题主应该更加深入的学习。&br&&br&至于说你把异常干掉了,重新捡回error code这种事情,以前早就评论过:&a href=&///?target=http%3A///vczh/archive//199974.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&如何设计一门语言(三)——什么是坑(面向对象和异常处理)&i class=&icon-external&&&/i&&/a&&br&&br&对于已经熟悉了如何“把所有STL能代替C语言的那部分内容,都用STL”的人来说,在开发一个新的程序的时候,不用异常只是徒增痛苦。&br&&br&然后说道题主的这段话:&br&&br&&blockquote&于是我就想 所谓的异常安全代码 真的有必要么&br&如果按我对异常的理解&br&完全没有必要&br&我不需要恢复现场 也不需要关心是否有东西泄露&br&因为程序必然被终止&br&我要做的只是应该去修改代码&br&完全正确的程序 我认为 不应该出现任何异常&/blockquote&&br&如果你的程序在被 政府 / 工业级最终用户 使用的时候出了翔,你也不打算去修,也不打算赔钱,也不打算派人去跪着处理,公司被告倒了也没关系的话,那你怎么挂,怎么不打dump,怎么后台不维护crash系统,怎么忽略Windows的Watson,都没关系。这跟你用不用异常没有任何关系。&br&&br&玩具程序不在评论范围内。
因噎废食。 首先我需要强调一句:【应该使用异常】跟【异常是用来catch的】是两个毫不相干的命题。我从来不觉得抛了异常就一定要catch。这也是为什么异常有那么多类型,你可以自由决定什么样的异常拿来做控制流,什么样的异常拿来打dump崩溃。如果你觉得不…
我大概知道为什么会有这种规定了,单处 return 加禁止 goto,等于要求写这样的代码:&br&&br&&div class=&highlight&&&pre&&code class=&language-c&&&span class=&kt&&void&/span& &span class=&nf&&foobar&/span&&span class=&p&&()&/span& &span class=&p&&{&/span&
&span class=&kt&&void&/span&&span class=&o&&*&/span& &span class=&n&&a&/span& &span class=&err&&=&/span& &span class=&mi&&0&/span&&span class=&p&&,&/span& &span class=&n&&b&/span& &span class=&err&&=&/span& &span class=&mi&&0&/span&&span class=&p&&,&/span& &span class=&n&&c&/span& &span class=&err&&=&/span& &span class=&mi&&0&/span&&span class=&p&&;&/span&
&span class=&k&&do&/span& &span class=&p&&{&/span&
&span class=&n&&a&/span& &span class=&o&&=&/span& &span class=&n&&createA&/span&&span class=&p&&();&/span&
&span class=&k&&if&/span& &span class=&p&&(&/span&&span class=&o&&!&/span&&span class=&n&&a&/span&&span class=&p&&)&/span& &span class=&k&&break&/span&&span class=&p&&;&/span&
&span class=&n&&b&/span& &span class=&o&&=&/span& &span class=&n&&createB&/span&&span class=&p&&();&/span&
&span class=&k&&if&/span& &span class=&p&&(&/span&&span class=&o&&!&/span&&span class=&n&&b&/span&&span class=&p&&)&/span& &span class=&k&&break&/span&&span class=&p&&;&/span&
&span class=&n&&c&/span& &span class=&o&&=&/span& &span class=&n&&createC&/span&&span class=&p&&();&/span&
&span class=&k&&if&/span& &span class=&p&&(&/span&&span class=&o&&!&/span&&span class=&n&&c&/span&&span class=&p&&)&/span& &span class=&k&&break&/span&&span class=&p&&;&/span&
&span class=&c1&&//...&/span&
&span class=&p&&}&/span& &span class=&k&&while&/span& &span class=&p&&(&/span&&span class=&mi&&0&/span&&span class=&p&&);&/span&
&span class=&k&&if&/span& &span class=&p&&(&/span&&span class=&n&&c&/span&&span class=&p&&)&/span& &span class=&n&&freeC&/span&&span class=&p&&(&/span&&span class=&n&&c&/span&&span class=&p&&);&/span&
&span class=&k&&if&/span& &span class=&p&&(&/span&&span class=&n&&b&/span&&span class=&p&&)&/span& &span class=&n&&freeB&/span&&span class=&p&&(&/span&&span class=&n&&b&/span&&span class=&p&&);&/span&
&span class=&k&&if&/span& &span class=&p&&(&/span&&span class=&n&&a&/span&&span class=&p&&)&/span& &span class=&n&&freeA&/span&&span class=&p&&(&/span&&span class=&n&&a&/span&&span class=&p&&);&/span&
&span class=&p&&}&/span&
&/code&&/pre&&/div&&br&这样写倒也不是不行,就是凭空多了一个缩进,怪别扭的。&br&&br&---- 分割线 ----&br&&br&我至今不知道谁先发明了不允许多处 return 这种规定的,动机是什么。&br&&br&是为了利于调试?增加可读性?这很有效吗?除非你在没有单步调试器的环境下,纯靠加代码打 log,单进单出可能有点意义。是为了释放资源吗?那么请看最后一段。&br&&br&不允许多处 return 有一个显而易见的坏处,它会明显增加代码的缩进层次,我相信写过 c/c++ 或者 php、js 这类类 c 语言的同学都会有感觉。手机码字不方便,明天我会给出一个简单的例子。&br&&br&另外在很多情况下,你必须使用很奇怪的流程才能实现单处 return,这样增加了思考成本不说,反而降低了代码的可读性。&br&&br&所以我的建议是,能 return 就 return,能早 return 绝不晚 return,能用 return 减少大括号层次,坚决用。这不仅是代码格式上的考虑,深层次的动机是,早 return 的写法让代码“逻辑块”变得更小(逻辑块位于 return 和 return 之间),逻辑更单纯,一段尽早 return 的代码,一定比最后 return 的代码更好懂,也更符合直觉。&br&&br&每次看到有人为了最后一行 return,在循环中记录返回值,break,然后在循环体外 return,我都觉得纯属有病。&br&&br&至于 goto,平时自然是不用为好,太难把控。但是在那种依次申请资源,其中一个失败后倒序释放资源的情况中,不用 goto 纯属给自己找麻烦。刚才有朋友在评论中说 c 语言单处 return 是为了资源释放,其实当你多次 return 发现 hold 不住的时候,就应该果断上 goto。如果规范实在不让用 goto,那就只能在 while 中用 break 跳出,在结尾处用挨个释放资源。只不过这样代码不好看。
我大概知道为什么会有这种规定了,单处 return 加禁止 goto,等于要求写这样的代码: void foobar() {
void* a = 0, b = 0, c = 0;
a = createA();
b = createB();
c = createC();
我throw出来的大部分异常其实都是为了区分各种assert,所以是不catch的。一旦抛了这种异常就代表我程序写的不对,有bug要修,这个时候就要改代码,不要去想怎么做错误恢复,挂了就挂了。有catch的那一小部分异常都是我的脚本引擎和GUI库throw出来的。&br&&br&对此我给他分别写了两个基类,一个是Error,一个是Exception。&br&&br&如果你在构造函数里面抛了异常,如果你的类成员都是通过初始化列表来初始化的,那其实不用理。如果你的类成员是在构造函数里面赋值的(多半是指针),你得catch,删掉他们,然后rethrow。这几个标准动作你只要多做几次很容易就熟练了。然后你就会真正掌握一种简单的、流畅的C++编码方法,每一行都是异常安全的。
我throw出来的大部分异常其实都是为了区分各种assert,所以是不catch的。一旦抛了这种异常就代表我程序写的不对,有bug要修,这个时候就要改代码,不要去想怎么做错误恢复,挂了就挂了。有catch的那一小部分异常都是我的脚本引擎和GUI库throw出来的。 对此…
区别就是你的无论是Ok还是Err,它们都是同一个类型Result。&br&这样有什么用呢?这样你就可以把一些调用串起来了,而不用每个都去 if err != nil。比如&br&&br&&div class=&highlight&&&pre&&code class=&language-rust&&&span class=&kd&&let&/span&&span class=&w&& &/span&&span class=&n&&result&/span&&span class=&w&& &/span&&span class=&o&&=&/span&&span class=&w&& &/span&&span class=&n&&func1&/span&&span class=&p&&()&/span&&span class=&w&&&/span&
&span class=&w&&
&/span&&span class=&p&&.&/span&&span class=&n&&and_then&/span&&span class=&p&&(&/span&&span class=&o&&|&/span&&span class=&n&&succeed&/span&&span class=&o&&|&/span&&span class=&w&& &/span&&span class=&n&&func2&/span&&span class=&p&&())&/span&&span class=&w&&&/span&
&span class=&w&&
&/span&&span class=&p&&.&/span&&span class=&n&&and_then&/span&&span class=&p&&(&/span&&span class=&o&&|&/span&&span class=&n&&xxx&/span&&span class=&o&&|&/span&&span class=&w&& &/span&&span class=&n&&func3&/span&&span class=&p&&());&/span&&span class=&w&&&/span&
&/code&&/pre&&/div&因为他们都是同一个类型Result,所以你可以对Ok和Err应用同样的操作,而不一定必须要使用match来把数据取出来然后再分别处理。&br&在上面的result它还是一个Result,中间三个函数func1、func2、func3调用只要任何一个返回了Err,那么这个Err就是最终的返回值(and_then函数的作用),没有出错的话,那么结果就是最后一个闭包函数的结果,这里面是func3的结果。&br&这些函数并没有为哪个业务代码定制过,具有普适性,因为它们他们都是Result,通过and_then从一个Result转变成另一个Result而已。&br&&br&而Go的err就不行了。&br&&div class=&highlight&&&pre&&code class=&language-go&&&span class=&nx&&ret1&/span&&span class=&p&&,&/span& &span class=&nx&&err1&/span& &span class=&o&&:=&/span& &span class=&nx&&func1&/span&&span class=&p&&();&/span&
&span class=&k&&if&/span& &span class=&nx&&err1&/span& &span class=&o&&==&/span& &span class=&kc&&nil&/span& &span class=&p&&{&/span&
&span class=&k&&return&/span& &span class=&nx&&err1&/span&&span class=&p&&;&/span&
&span class=&p&&}&/span&
&span class=&nx&&ret2&/span&&span class=&p&&,&/span& &span class=&nx&&err2&/span& &span class=&o&&:=&/span& &span class=&nx&&func2&/span&&span class=&p&&();&/span&
&span class=&k&&if&/span& &span class=&nx&&err2&/span& &span class=&o&&==&/span& &span class=&kc&&nil&/span& &span class=&p&&{&/span&
&span class=&k&&return&/span& &span class=&nx&&err2&/span&&span class=&p&&;&/span&
&span class=&p&&}&/span&
&span class=&nx&&ret3&/span&&span class=&p&&,&/span& &span class=&nx&&err3&/span& &span class=&o&&:=&/span& &span class=&nx&&func3&/span&&span class=&p&&();&/span&
&span class=&k&&if&/span& &span class=&nx&&err3&/span& &span class=&o&&==&/span& &span class=&kc&&nil&/span& &span class=&p&&{&/span&
&span class=&k&&return&/span& &span class=&nx&&err3&/span&&span class=&p&&;&/span&
&span class=&p&&}&/span&
&/code&&/pre&&/div&实际上这三个if err == nil都是同样的操作,但你却怎么都要写这么三个蛋疼的东西。&br&&br&Result这个东西是个ADT (Algebraic Data Type),是函数式编程界已经普及了的东西。可以去看看OCaml, Haskell这些语言里面ADT的相关应用来感受一下它所带来的便捷性。&br&&br&Rust里面的enum就是一个ADT,比较少用FP的同学可能会经常觉得需要用match去把数据取出来 好麻烦,其实ADT大多数时候都不是这么用的。很多时候都只需要在调用链的最后最后才去把结果取出来,中间的结果可以用各种抽象的函数去封装一些基本的操作来转换。比如标准库自带的Result和Option,初学Rust的同学就是喜欢调用unwrap()提前把它的结果取出来,这并没有什么必要。
区别就是你的无论是Ok还是Err,它们都是同一个类型Result。 这样有什么用呢?这样你就可以把一些调用串起来了,而不用每个都去 if err != nil。比如 let result = func1()
.and_then(|succeed| func2())
.and_then(|xxx| func3());因为他们都是同一个类型…
你们公司是做汽车电子的吗?&b&禁止goto和多处return是MISRA C&/b&(&a href=&///?target=https%3A//en.wikipedia.org/wiki/MISRA_C& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&https://&/span&&span class=&visible&&en.wikipedia.org/wiki/M&/span&&span class=&invisible&&ISRA_C&/span&&span class=&ellipsis&&&/span&&i class=&icon-external&&&/i&&/a&)&b&里规定的&/b&(感谢Enzo Jiang指出该条款是引用自IEC 61508)。&br&&br&以下引用自MISRA C 2004版:&br&&blockquote&Rule 14.4 (required): &br&The goto statement shall not be used.&br&禁止使用goto语句。&br&&br&Rule 14.7 (required): &br&A function shall have a single point of exit at the end of the function.&br&函数只允许有一个位于函数末尾的出口(也就是return)&/blockquote&&br&由于MISRA基本上是属于汽车嵌入式行业规范,所以如果你是混这圈的那不遵守也不行。这个规定也不是完全没有道理,goto和多处return确实容易出现资源忘记回收导致内存泄露之类的问题。&br&&br&解决方法嘛,可以用do while 0套在外面,用break来提前跳出。不过为了避免return而祭出do while 0大法有点利用语法钻规则空子的味道。&br&&div class=&highlight&&&pre&&code class=&language-c&&&span class=&kt&&int&/span& &span class=&nf&&maki&/span&&span class=&p&&()&/span& &span class=&p&&{&/span&
&span class=&kt&&int&/span& &span class=&n&&err&/span& &span class=&o&&=&/span& &span class=&mi&&0&/span&&span class=&p&&;&/span&
&span class=&k&&do&/span& &span class=&p&&{&/span&
&span class=&p&&...&/span&
&span class=&p&&...&/span&
&span class=&k&&if&/span&&span class=&p&&(...)&/span& &span class=&p&&{&/span&
&span class=&n&&err&/span& &span class=&o&&=&/span& &span class=&mi&&1&/span&&span class=&p&&;&/span&
&span class=&k&&break&/span&&span class=&p&&;&/span&
&span class=&p&&}&/span&
&span class=&p&&...&/span&
&span class=&p&&...&/span&
&span class=&k&&if&/span&&span class=&p&&(...)&/span& &span class=&p&&{&/span&
&span class=&n&&err&/span& &span class=&o&&=&/span& &span class=&mi&&2&/span&&span class=&p&&;&/span&
&span class=&k&&break&/span&&span class=&p&&;&/span&
&span class=&p&&}&/span&
&span class=&p&&...&/span&
&span class=&p&&...&/span&
&span class=&p&&}&/span& &span class=&k&&while&/span&&span class=&p&&(&/span&&span class=&mi&&0&/span&&span class=&p&&);&/span&
&span class=&p&&...&/span&
&span class=&p&&...&/span&
&span class=&k&&return&/span& &span class=&n&&err&/span&&span class=&p&&;&/span&
&span class=&p&&}&/span&
&/code&&/pre&&/div&&br&&br&要是觉得用do while 0看起来很恶心的话那就用一堆不嵌套的if吧:&br&&div class=&highlight&&&pre&&code class=&language-c&&&span class=&kt&&int&/span& &span class=&nf&&nico&/span&&span class=&p&&()&/span& &span class=&p&&{&/span&
&span class=&kt&&int&/span& &span class=&n&&err&/span& &span class=&o&&=&/span& &span class=&mi&&0&/span&&span class=&p&&;&/span&
&span class=&p&&...&/span&
&span class=&p&&...&/span&
&span class=&k&&if&/span&&span class=&p&&(...)&/span& &span class=&p&&{&/span&
&span class=&n&&err&/span& &span class=&o&&=&/span& &span class=&mi&&1&/span&&span class=&p&&;&/span&
&span class=&p&&}&/span&
&span class=&k&&if&/span&&span class=&p&&(&/span&&span class=&o&&!&/span&&span class=&n&&err&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&p&&...&/span&
&span class=&p&&...&/span&
&span class=&k&&if&/span&&span class=&p&&(...)&/span& &span class=&p&&{&/span&
&span class=&n&&err&/span& &span class=&o&&=&/span& &span class=&mi&&2&/span&&span class=&p&&;&/span&
&span class=&p&&}&/span&
&span class=&p&&}&/span&
&span class=&k&&if&/span&&span class=&p&&(&/span&&span class=&o&&!&/span&&span class=&n&&err&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&p&&...&/span&
&span class=&p&&...&/span&
&span class=&k&&if&/span&&span class=&p&&(...)&/span& &span class=&p&&{&/span&
&span class=&n&&err&/span& &span class=&o&&=&/span& &span class=&mi&&3&/span&&span class=&p&&;&/span&
&span class=&p&&}&/span&
&span class=&p&&}&/span&
&span class=&p&&...&/span&
&span class=&p&&...&/span&
&span class=&k&&return&/span& &span class=&n&&err&/span&&span class=&p&&;&/span&
&span class=&p&&}&/span&
&/code&&/pre&&/div&
你们公司是做汽车电子的吗?禁止goto和多处return是MISRA C()里规定的(感谢Enzo Jiang指出该条款是引用自IEC 61508)。 以下引用自MISRA C 2004版: Rule 14.4 (required): The goto statement shall not be used. 禁止使用goto语句…
&p&简单说就是,函数无法满足调用方的期望的时候使用异常。&/p&&br&&p&放在现实场景中就是,当上级交待给你的任务无法完成的时候,使用异常。&/p&&br&&p&异常的目的是将这个问题传递给调用方解决。&/p&&br&&p&就像在现实环境中,搞不定的时候找老板一样。&/p&
简单说就是,函数无法满足调用方的期望的时候使用异常。 放在现实场景中就是,当上级交待给你的任务无法完成的时候,使用异常。 异常的目的是将这个问题传递给调用方解决。 就像在现实环境中,搞不定的时候找老板一样。
你们啊,不要想喜欢弄个大新闻。拿Java来黑Python前别忘了你家语言里也有unchecked exception。&br&&br&原文明明是这样的:&br&&blockquote&When catching exceptions, mention specific exceptions &b&whenever possible&/b& instead of using &b&a bare except: clause&/b&. &/blockquote&这意思太明确了:&br&&ol&&li&如果可能的话,尽量捕捉更specific的exception&/li&&li&而不是用except:来捕捉所有异常。&/li&&/ol&&p&你要明白PEP8不是强制标准。即使它是强制的,except Exception:也足够满足这个条款,不知道题主『哭笑不得』在哭啥笑啥。&/p&&br&&p&原因需要结合PEP8里关于Exception的部分:&/p&&blockquote&&p&Derive exceptions from
rather than
BaseException&/p&&/blockquote&从BaseException继承的exception中,最常见的就是KeyboardInterrupt了。如果你的代码里用except:来捕捉异常,会导致长时间执行的代码无法用ctrl+c来终止。&br&&br&一个代码良好的library,应该会自己定义一个base的exception class(比如说FooException),然后再从这个exception继承更详细的exception(比如说IlligalFooException)。这样你的代码使用的时候可以except FooException捕捉这个library的所有可能异常,也可以用except IlligalFooException来捕捉更详细的异常。&br&&br&如果你用的library不是以这样的形式定义异常,而是直接重用 &a class=& wrap external& href=&///?target=https%3A//docs.python.org/2/library/exceptions.html& target=&_blank& rel=&nofollow noreferrer&&6. Built-in Exceptions&i class=&icon-external&&&/i&&/a&,建议直接扔掉。
你们啊,不要想喜欢弄个大新闻。拿Java来黑Python前别忘了你家语言里也有unchecked exception。 原文明明是这样的: When catching exceptions, mention specific exceptions whenever possible instead of using a bare except: clause. 这意思太明确了: …
不用。因为连new都new不出来了,程序已经没机会再抢救一下。
不用。因为连new都new不出来了,程序已经没机会再抢救一下。
static_assert()、assert()、return。
static_assert()、assert()、return。
&a href=&///?target=http%3A////dustins-awesome-monad-tutorial-for-humans-in-python.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Dustin Getz's blog&i class=&icon-external&&&/i&&/a&&br&&br&用monad最好。
用monad最好。
Setjmp/longjmp 最大的局限,是 longjmp 跳转时只是简单的降低栈顶,而不对抛弃的栈内容做处理。所以,它不能对栈上的数据进行复杂的 clean-up 工作(类似 C++ 的 destructor)。不过,话说回来,C++ 的机制也很烂。&br&&br&所以,归根结底,还是 C 没有 GC。而且,和 exception 配合的 GC,如果是 ref-counting,则需要在 stack unwinding 时进行分析减少相应的 ref-count。而 root-tracing 在 unwinding 时无需特别的 overhead。&br&&br&这方面,Lua 做了一个比较好的例子。它从 pcall(调用 setjmp) 到 error(调用 longjmp) 中发生的所有资源分配,都对应到自己维护的一个 stack 上,所以可以在 longjmp 时回收资源。但是归根结底,这个机制还是需要 Lua 有 GC 才得以实现,不过算是 C 中最 minimal 风格的利用 setjmp/longjmp 模拟异常的方案。&br&&br&另外,C 本身的错误处理风格是 error code。这个风格和 exception 的争论由来已久。我个人倾向于不使用 exception。参见: &a href=&///?target=http%3A//techsingular.net/%3Fp%3D2153& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://&/span&&span class=&visible&&techsingular.net/?&/span&&span class=&invisible&&p=2153&/span&&span class=&ellipsis&&&/span&&i class=&icon-external&&&/i&&/a&
Setjmp/longjmp 最大的局限,是 longjmp 跳转时只是简单的降低栈顶,而不对抛弃的栈内容做处理。所以,它不能对栈上的数据进行复杂的 clean-up 工作(类似 C++ 的 destructor)。不过,话说回来,C++ 的机制也很烂。 所以,归根结底,还是 C 没有 GC。而且…
函数式语言的一个常见设计模式就是用monad做错误处理。这里好处在于,错误处理的逻辑只要在monad instance那里实现一遍,然后就不需要在每个call site考虑错误处理,减少boilerplate code,提升代码的模块性和可读性。&br&&br&放一个F#的相关slides:&a href=&///?target=http%3A///rop/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Railway Oriented Programming&i class=&icon-external&&&/i&&/a&&br&&br&以上是最简单的“在单个monad中实现错误处理”,而函数式语言里,monad这个抽象还经常用来封装各种其他类型的计算,比如带状态的计算、非确定性的计算等等,那么为了进一步提升代码的模块性,我们会希望能够像玩拼图一样,用一个monad表达错误处理、另一个monad表达带状态计算等,然后我的函数需要用到几个功能,就把几个monad拼接到一起。这就是monad transformer,比如在Haskell的transformers库中,提供了ExceptT,可以给任意monad增添一层错误处理的功能。学习monad transformers的教程:&a href=&///?target=https%3A//www.cs.virginia.edu/%7Ewh5a/personal/Transformers.pdf& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&https://www.&/span&&span class=&visible&&cs.virginia.edu/~wh5a/p&/span&&span class=&invisible&&ersonal/Transformers.pdf&/span&&span class=&ellipsis&&&/span&&i class=&icon-external&&&/i&&/a&&br&&br&另外,借用Haskell的type class和functional dependencies扩展,我们可以进一步对monad错误处理进行抽象,也就是我的代码甚至不需要关心具体在哪个monad里面进行错误处理。参考&a href=&///?target=https%3A//hackage.haskell.org/package/mtl-2.2.1/docs/Control-Monad-Except.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Control.Monad.Except&i class=&icon-external&&&/i&&/a&中的MonadError这个class。
函数式语言的一个常见设计模式就是用monad做错误处理。这里好处在于,错误处理的逻辑只要在monad instance那里实现一遍,然后就不需要在每个call site考虑错误处理,减少boilerplate code,提升代码的模块性和可读性。 放一个F#的相关slides:
只是Go里面的Error Check比较不直观而已,其实可以封装一层让它更好看的。&br&比如Rust的Result&T, E&,它包含了一个Ok(..)和Err(..),Ok表示正确,然后带有正确的返回值,而Err则表示错误,然后带着错误信息。&br&这样处理起来就比较好看了,你可以选择忽略它,让程序直接在出错的地方挂掉,像这样&br&&div class=&highlight&&&pre&&code class=&language-text&&someFunctionMayFail().unwrap(); // 要是出错了,就直接挂掉
&/code&&/pre&&/div&或者在挂掉的时候,带一个自己的出错信息&br&&div class=&highlight&&&pre&&code class=&language-text&&someFunctionMayFail().ok().expect(&Expecting a xxxxx&);
&/code&&/pre&&/div&或者自己处理&br&&div class=&highlight&&&pre&&code class=&language-text&&let return_val = match someFunctionMayFail() {
Ok(v) =& v,
Err(err) =& {
// Deal with the error
// for example:
// fail!(&Fail!!!! {}&, err);
&/code&&/pre&&/div&当然,我不是说它和Go的那个Error Check有什么本质的区别,反正都是处理错误,但是我个人觉得Rust这样写更舒服。
只是Go里面的Error Check比较不直观而已,其实可以封装一层让它更好看的。 比如Rust的Result&T, E&,它包含了一个Ok(..)和Err(..),Ok表示正确,然后带有正确的返回值,而Err则表示错误,然后带着错误信息。 这样处理起来就比较好看了,你可以选择忽略它,…
我来答一发。&br&&br&和同事一起调试蒸汽吹灰设备,忘记了排空冷凝水,把水喷到价值近千万的催化剂上面了。&br&后果:罚一个月工资,更换了打湿部分的催化剂。&br&&br&同事调声波吹灰,忘记了烟道内还有农民工在做业,直接开起来。几个工人在一个封闭的铁盒子里被6个功率不小的超声波喇叭震耳欲聋。幸好迅速跑出来了没有造成永久性的听觉损伤。&br&后果:被农民工打一顿,我也不幸遭殃被连带打了一顿。
我来答一发。 和同事一起调试蒸汽吹灰设备,忘记了排空冷凝水,把水喷到价值近千万的催化剂上面了。 后果:罚一个月工资,更换了打湿部分的催化剂。 同事调声波吹灰,忘记了烟道内还有农民工在做业,直接开起来。几个工人在一个封闭的铁盒子里被6个功率不小…
都是错误处理的策略,OO语言更倾向异常,相比较判断一个函数可能的错误状况来说,“抛出”状态完整的异常对象被认为是更好的封装 ;你说的情况下,错误即false是最简单的情况,这种情况下,异常未必有优势;一般认为用异常可以简化复杂的错误处理代码,并且只要程序不需要终止执行,try catch比if else的代码更可读(错误处理的策略明显,状态可以更丰富,虽然这不是绝对的)。&br&&br&&div class=&highlight&&&pre&&code class=&language-text&&result = func();
if (result === ERR_NO_1) {
} else if (result === ERR_NO_2) {
&/code&&/pre&&/div&&br&&div class=&highlight&&&pre&&code class=&language-text&&try {
result = func();
} catch (CustomException1 e) {
catch (CustomException2 e) {
&/code&&/pre&&/div&&br&有些异常不会立即处理,而是往上抛,runtime需要维护一个栈,所以异常会有额外开销,但通常不重要;好处是程序员不需要特别维护多层调用的错误链,A =& B =& C,如果C调用抛出异常,程序员可以在B或者A里catch,不需要改动B和A的接口(返回同样的错误链);如果C返回错误,假如要在A或B里处理,就必须传递C的错误链;所以用异常的好处是可以简化接口设计。&br&&br&但最终,异常的好处来源于它更好的封装和runtime的支持。
都是错误处理的策略,OO语言更倾向异常,相比较判断一个函数可能的错误状况来说,“抛出”状态完整的异常对象被认为是更好的封装 ;你说的情况下,错误即false是最简单的情况,这种情况下,异常未必有优势;一般认为用异常可以简化复杂的错误处理代码,并且…
try catch&br& try {&br& 
...&br& } catch (Error0 &meow0) {&br& } catch (Error1 &meow1) {&br& } catch (...) {&br& }&br&&br&synchronous signal&br& 进程执行代码时触发异常,导致信号的立即递送,如SIGSEGV、SIGFPE、SIGILL、SIGBUS等。注册信号处理函数得到通知并进行控制。&br& 在可能触发信号的地方用sigsetjmp()保存环境,信号处理函数中使用siglongjmp()跳转到先前保存的环境&br&&br&Resource Acquisition Is Initialization&br& 使用constructor和destructor实现exception-safe resource management&br& 类似的Python with statement&br&&br&C的atexit on_exit&br& 注册exit()正常退出时需要执行的函数,通常用于释放资源等。(man 3 atexit)&br& 类似地,Go的defer &&a href=&///?target=http%3A//blog.golang.org/defer-panic-and-recover& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Defer, Panic, and Recover&i class=&icon-external&&&/i&&/a&&&br&&br&error code as return value&br& C中很常见,错误码作为返回值,或者用专设的errno等变量描述错误&br&&br&fork as checkpoint&br& gdb的checkpoint命令。这个不是很相关,作为错误恢复的一个有趣例子&br&&br&bison的error&br& 这个相关性也不大……看在它是compiler compiler的份上&br&&br& 
stmts:&br& 
%empty&br& 
| stmts '\n'&br& 
| stmts exp '\n'&br& 
| stmts error '\n'&br&&br& 引入的原义是提供更好的错误信息,这给了我们一种启示:定义异常值和正常值的交互作用&br&&br&data Maybe a = Nothing | Just a&br&data Either a b = Left a | Right b&br& 不再把异常视为out-of-band的值,视作运算中能产生的一类特殊值。提供Maybe Either的monad实例来消除语法上的噪声等。Monad transformer来和其他已有monad复合等&br&&br&λυ-calculus λυt?p?-calculus ...&br& 之前翻到这些东西是为了找实现abstract machine实现delimited control的理论根基……这些还是太艰深了,而且性能不行&br& $\text{catch}_\alpha M = \operatorname{\mu}\alpha.[\alpha]M$&br& $\text{throw}_\beta M = \operatorname{\mu}\gamma[\beta]M \text{ if } \gamma \notin FV(M)$&br& Robbert Krebbers. Classical logic, control calculi and data types. Master’s thesis, Radboud University Nijmegen, 2010.&br& 其他绚丽的:multi-prompt delimited control
try catch try { ... } catch (Error0 &meow0) { } catch (Error1 &meow1) { } catch (...) { } synchronous signal 进程执行代码时触发异常,导致信号的立即递送,如SIGSEGV、SIGFPE、SIGILL、SIGBUS等。注册信号处理函数得到通知并进行控制。 在可能触发…
已有帐号?
无法登录?
社交帐号登录

我要回帖

更多关于 语言习得机制 的文章

 

随机推荐