优雅简洁但是错误的代码

优雅简洁但是错误的代码

作者:BlogUpdater |  时间:2021-02-12 |  浏览:968 |  评论已关闭 条评论

蝎子
不能仅仅因为看不到错误处理流程,就认为错误不存在。

有一本C#编程的书中对异常的进行了”高度评价”,下面是该书中的例子代码,我们来看看:

瞧瞧,这代码是多么优雅和简洁,异常确实是个好东西。

嗯,的确是非常简洁,也非常优雅,但是它是错误的。

我们假设在CreateIndexes中出现了异常。GenerateDatabase函数不会捕获这个异常,接下里这个异常会向上层传递。

但是当异常离开GenerateDatabase函数时,重要信息丢失了:数据库创建状态。捕获异常的代码不知道数据库创建的哪一步失败。是否需要删除索引?是否需要删除表?是否需要删除物理数据库?它啥也不知道。

因此,如果调用CreateIndexes时遇到问题,则会永久泄漏物理数据库文件和表。(由于这些文件大概是磁盘上的文件,因此它们会无限期地被系统挂起。)

从某种意义上说,在异常模型中编写正确的代码比在错误代码模型中编写困难,因为任何事情都可能失败,并且你必须为此做好准备。在错误代码模型中,当你必须检查错误时很明显:当您获得错误代码时。在异常模型中,你只需要知道错误可能在任何地方发生。

换句话说,在错误代码模型中,很明显有人无法处理错误:他们没有检查错误代码。但是在抛出异常的模型中,不太容易看出是否有人处理了错误,因为异常不是显式的。

让我们看看下面的例子代码:

在代码中,我们创建了一个Guy对象,然后将他添加到参赛联盟,然后随机地将他加入到一个参赛组中。我们如何简化这段代码呢?

记得:每一行代码都可能发生错误

假设执行”new Guy(name)”时抛出异常?
抛出这个异常时,我们还没有做其他的动作,所以,问题不大。

假设执行”AddToLeague(guy)”时抛出异常?
我们创建的guy对象会被抛弃,而稍后垃圾回收(GC)程序会清理这个对象。

假设执行”guy.Team = ChooseRandomTeam()”时抛出异常?
呃,现在我们有麻烦了。我们已经将该人加入了联盟。如果有人捕获了这个异常,他们将在联盟中找到一个不属于任何球队的人。如果某些代码遍历了联盟的所有成员并使用了guy.Team成员,则由于guy.Team尚未初始化,他们将收到NullReferenceException异常。

在编写代码时,如果每行代码都引发异常会带来什么后果?如果你打算编写正确的代码,则必须执行以下操作。
我们看看下面的代码,通过重新排列调用的顺序来解决问题:

这种看似微不足道的更改对错误恢复有很大影响。通过延迟操作(将人员加入联盟),在人员构造期间采取的任何异常都不会产生任何持久影响。所有发生的事情是,一个部分构造的对象被抛弃了,最终被GC清理了。

一般设计原则:在数据准备就绪之前,不要提交数据

当然,此示例非常简单,因为设置该人员的步骤没有副作用。如果在设置过程中出现问题,我们可以抛弃这个对象,让GC进行清理。

在真实世界中,事情会变得复杂起来。我们看看下面的代码:

以上代码和我们修正过的版本是一样的,唯一的不同时,有人认为,如果每个团队保留一份成员列表,效率会更高,因此你必须将自己添加到要加入的团队中。这对功能的正确性有什么影响?

总结
通过返回错误代码的方式来表达一项操作失败了,是比较简单和直观的,但有些朋友可能会忘记检查每个调用的返回值。
所以,我还是比较喜欢异常这种编码模型,因为你很难忽略它。

最后
Raymond Chen的《The Old New Thing》是我非常喜欢的博客之一,里面有很多关于Windows的小知识,对于广大Windows平台开发者来说,确实十分有帮助。
本文来自:《Cleaner, more elegant, and wrong》

评论已关闭。