【外刊IT评论网】清晰,优雅,但却是错的

标签: 异常 批评评论 异常处理 | 发表时间:2012-11-06 00:09 | 作者:Aqee
出处:http://www.aqee.net

最近国外关于 go语言的讨论很多,其中有一个论题是关于go语言里采用的错误码的异常处理模式和Java里的try-catch的模式孰优孰劣的问题。今天的这篇文章就涉及到这两种模式的对比比较。

并不是因为你看不见错误的产生就意味着它的不存在。

下面这段代码来自《C# programming》这本书,摘自讲述异常处理的章节。

try { AccessDatabase accessDb = new AccessDatabase(); accessDb.GenerateDatabase(); } catch (Exception e) { // Inspect caught exception } public void GenerateDatabase() { CreatePhysicalDatabase(); CreateTables(); CreateIndexes(); }

你是否注意到了这中写法是多么的清晰和优雅。

清晰,优雅,但却是错的。

假设在执行CreateIndexes()这个方法时有异常抛出。GenerateDatabase()方法并不捕获它,异常就会抛回给调用者,在那里被捕获。

但当异常从GenerateDatabase()方法中抛出时,重要的信息就丢失了:数据库创建的状态。捕获到异常的代码并不知道这创建数据库的哪一步出的错。需要删除掉索引吗?需要删除表吗?需要删除物理数据库吗?它不知道。

如果是在执行CreateIndexes()时出了错,你就永远丢失了一个物理数据库文件和里面的表。(因为这些文件会存放在硬盘上,它们存在哪里并不确定。)

在一个 异常抛出模式的编程语言里写出正确的代码给人的感觉会更难,相比之下,在一个 错误码模式的编程语言里给人的感觉会容易些,因为前者中任何东西都可能抛出异常,你必须时刻准备捕捉它。而后者很显然,当你在接收到错误码时才去检查发生的错误。在异常抛出模式中,你必须意识到任何地方都可能出现异常。

换句话说,在 错误码模式中,如果有人没有捕捉到错误的发生,很显然是他没有检查错误码。但在 异常抛出模式中,你不能很直观的从代码中发现是否已经有人捕捉到了错误,因为能产生错误的地方并不明显。

思考下面的代码:

Guy AddNewGuy(string name) { Guy guy = new Guy(name); AddToLeague(guy); guy.Team = ChooseRandomTeam(); return guy; }

这个函数创建了一个新Guy,把他加入到俱乐部里,然后给他随机分配一个组。 不能再简单的操作了。

请记住:任何一行代码都可能产生错误。

如果”new Guy(name)”抛出了异常,会怎样? 哦,很幸运,我们还没有开始做什么,没有损失。 如果”AddToLeague(guy)”抛出了异常,会怎样? 我们新创建的“guy”就会被遗弃,但GC会把他清理干净。 如果”guy.Team = ChooseRandomTeam()”抛出了异常,会怎样? 哦偶,我们有麻烦了。我们已经把这个guy加入了俱乐部。如果有人捕获到了异常,他会发现俱乐部里的这个人不属于任何组。如果有一段代码是来遍历俱乐部的所有会员,发现这个guy,哪个组的?这时就会得到一个NullReferenceException异常。因为他的组还没有初始化。

当你在写代码时,如果每行代码都会抛出异常,你是否明白每个异常都会产生什么样的后果? 如果你想写出正确的代码,你就需要考虑到这些。

ok,如何修补这个问题?重新组织一下操作步骤。

Guy AddNewGuy(string name) { Guy guy = new Guy(name); guy.Team = ChooseRandomTeam(); AddToLeague(guy); return guy; }

看起来是一个很微小的改动,但却对错误恢复产生巨大的影响。通过延后提交数据(把guy加入俱乐部),在构造这个guy过程中发生任何异常都不会产生任何的后续影响。会发生的事只是一个未构造完成的guy被丢弃了,最终会被GC清理掉。

通用设计原则:除非已经完备,不要提交数据。

当然,这个例子非常简单,因为在创建guy的步骤中没有其它的关联影响。如果在创建过程中出了什么问题,丢掉它就行了,让GC处理余下的事情。

在现实生活中,情况会麻烦的多。看看下面的代码:

Guy AddNewGuy(string name) { Guy guy = new Guy(name); guy.Team = ChooseRandomTeam(); guy.Team.Add(guy); AddToLeague(guy); return guy; }

跟上面我们改正过的函数一样,只是有人认为,如果在组里保持一个成员列表的引用会更有效率些,于是,你需要把自己add到你想加入到组里。这样做又会产生什么样的后果?


本文来自 外刊IT评论网( www.aqee.net),原始地址: 清晰,优雅,但却是错的


相关 [it] 推荐: