优雅简洁但是错误的代码

蝎子
不能仅仅因为看不到错误处理流程 , 就认为错误不存在 。
有一本C#编程的书中对异常的进行了”高度评价” , 下面是该书中的例子代码 , 我们来看看:

优雅简洁但是错误的代码
文章图片
瞧瞧 , 这代码是多么优雅和简洁 , 异常确实是个好东西 。
嗯 , 的确是非常简洁 , 也非常优雅 , 但是它是错误的 。
我们假设在CreateIndexes中出现了异常 。 GenerateDatabase函数不会捕获这个异常 , 接下里这个异常会向上层传递 。
但是当异常离开GenerateDatabase函数时 , 重要信息丢失了:数据库创建状态 。 捕获异常的代码不知道数据库创建的哪一步失败 。 是否需要删除索引?是否需要删除表?是否需要删除物理数据库?它啥也不知道 。
因此 , 如果调用CreateIndexes时遇到问题 , 则会永久泄漏物理数据库文件和表 。 (由于这些文件大概是磁盘上的文件 , 因此它们会无限期地被系统挂起 。 )
从某种意义上说 , 在异常模型中编写正确的代码比在错误代码模型中编写困难 , 因为任何事情都可能失败 , 并且你必须为此做好准备 。 在错误代码模型中 , 当你必须检查错误时很明显:当你获得错误代码时 。 在异常模型中 , 你只需要知道错误可能在任何地方发生 。
换句话说 , 在错误代码模型中 , 很明显有人无法处理错误:他们没有检查错误代码 。 但是在抛出异常的模型中 , 不太容易看出是否有人处理了错误 , 因为异常不是显式的 。
让我们看看下面的例子代码:

优雅简洁但是错误的代码
文章图片
在代码中 , 我们创建了一个Guy对象 , 然后将他添加到参赛联盟 , 然后随机地将他加入到一个参赛组中 。 我们如何简化这段代码呢?
记得:每一行代码都可能发生错误
假设执行”newGuy(name)”时抛出异常?抛出这个异常时 , 我们还没有做其他的动作 , 所以 , 问题不大 。
假设执行”AddToLeague(guy)”时抛出异常?我们创建的guy对象会被抛弃 , 而稍后垃圾回收(GC)程序会清理这个对象 。
假设执行”guy.Team=ChooseRandomTeam()”时抛出异常?呃 , 现在我们有麻烦了 。 我们已经将该人加入了联盟 。 如果有人捕获了这个异常 , 他们将在联盟中找到一个不属于任何球队的人 。 如果某些代码遍历了联盟的所有成员并使用了guy.Team成员 , 则由于guy.Team尚未初始化 , 他们将收到NullReferenceException异常 。
在编写代码时 , 如果每行代码都引发异常会带来什么后果?如果你打算编写正确的代码 , 则必须执行以下操作 。 我们看看下面的代码 , 通过重新排列调用的顺序来解决问题:

优雅简洁但是错误的代码
文章图片
这种看似微不足道的更改对错误恢复有很大影响 。 通过延迟操作(将人员加入联盟) , 在人员构造期间采取的任何异常都不会产生任何持久影响 。 所有发生的事情是 , 一个部分构造的对象被抛弃了 , 最终被GC清理了 。
一般设计原则:在数据准备就绪之前 , 不要提交数据
优雅简洁但是错误的代码】当然 , 此示例非常简单 , 因为设置该人员的步骤没有副作用 。 如果在设置过程中出现问题 , 我们可以抛弃这个对象 , 让GC进行清理 。
在真实世界中 , 事情会变得复杂起来 。 我们看看下面的代码:

优雅简洁但是错误的代码
文章图片
以上代码和我们修正过的版本是一样的 , 唯一的不同时 , 有人认为 , 如果每个团队保留一份成员列表 , 效率会更高 , 因此你必须将自己添加到要加入的团队中 。 这对功能的正确性有什么影响?
总结
通过返回错误代码的方式来表达一项操作失败了 , 是比较简单和直观的 , 但有些朋友可能会忘记检查每个调用的返回值 。
所以 , 我还是比较喜欢异常这种编码模型 , 因为你很难忽略它 。
最后
RaymondChen的《TheOldNewThing》是我非常喜欢的博客之一 , 里面有很多关于Windows的小知识 , 对于广大Windows平台开发者来说 , 确实十分有帮助 。
本文来自:《Cleaner,moreelegant,andwrong》

优雅简洁但是错误的代码
文章图片