避免异常?
这个特殊的例子涉及到的Django在Python,但应该适用于任何语言支持例外:避免异常?
try:
object = ModelClass.objects.get(search=value)
except DoesNotExist:
pass
if object:
# do stuff
Django的模型类提供了一个简单的方法得到这让我寻找有且仅有一个对象来自数据库,如果它发现或多或少会引发异常。如果能找到一种替代过滤方法,该方法返回一个列表零个或多个:
objects = ModelClass.objects.filter(search=value)
if len(objects) == 1:
object = objects[0]
# do stuff
我是过于反感异常?对我来说,这个例外似乎有点浪费,估计四分之一的时间将是'特殊'。我更喜欢在失败时返回无的函数。我会更好地使用Django的过滤器方法并自行处理列表?
线索的名称 - 例外应该是例外。
如果你总是期望该项目将存在,然后使用get
,但如果你期望它不存在一个合理的时间比例(即它不存在是一个预期的结果,而不是一个例外的结果),那么我建议使用filter
。因此,看到你指出1到2之间和1到4之间预计不存在,我肯定会写一个围绕filter
的包装,因为这绝对不是一个例外情况。
答案将取决于代码的意图。 (我不确定你的代码示例是做什么的,特殊情况下的通过会令人困惑,其他代码会如何处理object
变量?)
是否使用异常或在很多情况下,使用一种将案例视为非例外的方法是一种味道问题。当然,如果except子句中的实际代码与您必须用来避免异常的过滤器方法一样复杂,则可以使用过滤器方法。更简单的代码是更好的代码。
我会修改这个例子。 – Mat 2009-01-03 18:31:53
我同意其他答案,但我想补充说,像这样的异常传递会给你一个非常明显的性能影响。强烈建议您检查结果是否存在(如果这是过滤器),而不是传递异常。
编辑:
在回应关于这个数字要求,我跑这个简单的测试......
import time
def timethis(func, list, num):
st=time.time()
for i in xrange(0,1000000):
try:
func(list,num)
except:
pass
et = time.time()
print "Took %gs" % (et-st)
def check(list, num):
if num < len(list):
return list[num]
else:
return None
a=[1]
timethis(check, a, 1)
timethis(lambda x,y:x[y], a, 1)
输出功率为..
Took 0.772558s
Took 3.4512s
HTH。
你能提供一些比较异常和非异常版本的测量吗?我想看看这个“明显”打击真的有多大。 – 2009-01-03 19:29:34
为了记录,这两个示例都会引发StopIteration异常。海事组织表明,如果使用合理的话,例外不会对绩效产生重大影响。 – 2009-01-04 00:24:01
对烦恼的厌恶是一个意见问题 - 但是,如果有理由相信函数或方法将被多次调用或快速调用,异常会导致显着的减速。我从previous question那里了解到了这一点,我之前依靠抛出异常返回默认值,而不是通过参数检查返回该默认值。
当然,异常仍然可以以任何理由存在,如果有必要,您不应该害怕使用或抛出异常,尤其是那些可能会破坏调用函数正常流程的异常。
围绕异常使用的编程语言有一个很大的分歧。
大多数人的意见是,异常应该是例外。在大多数具有例外情况的语言中,例外情况下的控制权转让比程序回报费用要高得多。
有一个强烈的少数人认为,异常只是另一个控制流构造,它们应该便宜。 Standard ML of New Jersey和Objective Caml编译器订阅该视图。如果你有便宜的例外,你可以使用其他机制更难编码的方式编写一些奇特的回溯算法。
我已经看到这场辩论多次重复进行新的语言设计,而且几乎总是胜利者决定例外应该是昂贵且罕见的。当你关心性能时,考虑到这一点,编程是明智的。
不管你信不信,这实际上是一个在每种语言中都有些不同的问题。在Python中,对于语言本身并不例外的事件,定期抛出异常。因此我认为,“你应该只在特殊情况下抛出例外”规则并不完全适用。我认为在这个论坛上你会得到的结果将倾向于这个观点,尽管考虑到.Net程序员的数量很多(参见this question)以获得更多信息)。
在最小的情况下,我最好不要抓住任何坚持使用Python中的生成器或for循环的规则(这两种方法都涉及针对非特殊情况抛出异常)。
我不同意上面的意见,在这种情况下异常效率低下,尤其是因为它在I/O绑定操作中使用。
下面是一个更实际的例子,使用Django和内存中的sqlite数据库。运行100个不同的查询中的每一个,然后对100次运行中的每一个进行平均。虽然我怀疑这是否重要,但我也改变了执行顺序。
With ObjectDoesNotExist... 0.102783939838
Without exception ........ 0.105322141647
With ObjectDoesNotExist... 0.102762134075
Without exception ........ 0.101523952484
With ObjectDoesNotExist... 0.100004930496
Without exception ........ 0.107946784496
你可以在你自己的Django环境中使用这个工具,但是我怀疑你是否花时间避免这个异常。
不应该使用异常来将信息从代码的一部分传递到另一部分。它应该包含有关非正常情况本身的信息,并将代码返回到最安全的位置,以便继续执行。
如果您想要读取文件,文件不存在是一个例外,如果您的缓冲区太短并且读取会引发异常,则文件太长可能是一个异常。但是,这可以通过首先读取文件长度并返回状态FileTooLong来替换。
什么时候该做:你总是依靠真正的异常是多么昂贵,以及你的非正常情况发生的频率。如果您确定即使在非常不规则的系统状态下偶尔会发生异常情况,也可以使用它。如果您知道它可能会更频繁地发生,并且您有特定的参数支持它,例如我们不希望每秒在网站上出现超过50个例外,那么您不会使用它。
什么可以出错?是一个特定的软件考试任务,因为你实际上将你的代码投入到未知的,当有任何东西可以并且将会发生时。
很难在工业层面上检查这些场景中的每一个,我们通常会接受一些例外情况,这些例外情况我们可能会忽略或忽略。但是,如果我们正在考虑抛出异常,上述分析至关重要。如果您不确定异常是多么昂贵,那么对于系统而言,这是多么巨大的压力,以及在最坏的情况下它可能被抛出的频率多高,那么就不要使用它。
不要忘记,如果你抛出一个异常,有人需要抓住它。总是有一种危险,就是没有意识到代码的一部分会抛出一个异常,而实际上它可能会导致所有的事情发生并且崩溃。
所以除了抛出异常之外,还需要定义一个接收点,捕获一个异常,因为异常只是一个通道。如果一个异常没有被本地捕获,即使它被捕获到本地函数之外,也需要清楚地指示该函数抛出异常。这不是所有语言都支持的。
在任何语言中,减少异常所包含的代码量也应该是目标。
只用一个try-catch块封装部分代码然后过滤异常是很常见的。这必须避免单独尝试抓住每个细分市场。如果你有太多的try-catch块,肯定会降低可读性。如果你有一个大的try-catch,那么异常的各种原因就会以不相关的方式列出来,这就很难理解哪部分代码抛出了当前的异常,使得调试和阅读代码变得更加困难。这里的其中一个参数是代码的逻辑一致性。如果它堵塞了,你必须结合许多例外,那么这部分代码的目的就需要被检查,因为它在架构上是一个十字路口,可能会隐藏其他问题。
+1:例外情况良好 - 使用它们。只是不要滥用它们。 – 2009-01-03 19:30:13