Cleaner, more elegant, and wrong(翻译)
Cleaner,more elegant,and wrong
整洁,更优雅,但是错的
并不是因为你看不到错误的产生路径就意味着它不存在。
下面是C#编程书中的一个片段,摘自关于异常处理的章节。
try {
AccessDatabase accessDb = new AccessDatabase();
accessDb.GenerateDatabase();
} catch (Exception e) {
// Inspect caught exception
}
public void GenerateDatabase()
{
CreatePhysicalDatabase();
CreateTables();
CreateIndexes();
}
Notice how much cleaner and more elegant [this] solution is.
请看这个解决方案是多么地整洁和优雅。
整洁,更优雅,但是错的。
假设在CreateIndexes()中抛出异常。GenerateDatabase()函数没有捕获它,所以错误被抛给执行catch的调用者,
当异常离开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()“抛出异常怎么办?
呃哦,现在我们很麻烦我们已经把这个人加入了联盟。如果有人捕获这个异常,他们将在联盟中找到一个不属于任何球队的人。如果有一些代码遍历联盟的所有成员,并使用gue.Team成员,他们将会获得NullReferenceException,因为guy.Team尚未初始化。
在编写代码时,您是否考虑到每行代码引发异常的后果呢?如果你打算写正确的代码,你必须这样做。
好吧,怎么解决这个问题?对操作重新排序。
Guy AddNewGuy(string name)
{
Guy guy = new Guy(name);
guy.Team = ChooseRandomTeam();
AddToLeague(guy);
return guy;
}
这种看似微不足道的变化对错误恢复有很大的影响。通过延迟提交数据(将guy加入到联盟),任何人员在创建过程中所遇到的异常都没有持久的影响。发生异常时,部分构造的guy被遗弃,最终被GC回收。
当然,这个例子很简单,因为设置guy的步骤没有副作用。如果设置过程中出现错误,我们可以抛弃guy,让GC清理回收。
在现实世界中,事情是比较麻烦的。考虑以下几点:
Guy AddNewGuy(string name)
{
Guy guy = new Guy(name);
guy.Team = ChooseRandomTeam();
guy.Team.Add(guy);
AddToLeague(guy);
return guy;
}
这与我们修正的函数一样,只是有人认为:如果每个球队保留一个成员列表,将会更有效率。所以您必须将自己添加到打算加入的球队中。这样做对函数的正确性有什么样后果?