条件检查与异常处理
什么时候异常处理比条件检查更可取?有很多情况下我可以选择使用其中一种。条件检查与异常处理
例如,这是使用自定义异常求和函数:
# module mylibrary
class WrongSummand(Exception):
pass
def sum_(a, b):
""" returns the sum of two summands of the same type """
if type(a) != type(b):
raise WrongSummand("given arguments are not of the same type")
return a + b
# module application using mylibrary
from mylibrary import sum_, WrongSummand
try:
print sum_("A", 5)
except WrongSummand:
print "wrong arguments"
这是相同的功能,从而避免了使用异常
# module mylibrary
def sum_(a, b):
""" returns the sum of two summands if they are both of the same type """
if type(a) == type(b):
return a + b
# module application using mylibrary
from mylibrary import sum_
c = sum_("A", 5)
if c is not None:
print c
else:
print "wrong arguments"
我认为,使用条件,始终是更具可读性和可管理性。或者我错了?定义引发异常的API的原因是什么?为什么?
异常更容易管理,因为它们定义了可能出错的事情的一般家族。 在你的例子中只有一个可能的问题,所以使用异常没有任何优势。但是如果你有另一个分类的分类,那么它需要表明你不能以零分隔。简单地返回None
将不再工作。
另一方面,异常可以分类,并且可以捕获特定异常,具体取决于您对基础问题的关心程度。例如,您可以有一个DoesntCompute
基本例外和子类InvalidType
和InvalidArgument
。如果你只是想得到一个结果,你可以把所有的计算都包含在一个能够捕获DoesntCompute
的块中,但是你仍然可以很容易地完成特定的错误处理。
当参数包含意外值时,应该抛出异常。
用你的例子,当两个参数是不同类型时,我会推荐抛出异常。
抛出异常是一种优雅的方式来中止服务而不会混淆代码。
通常情况下,您希望使用条件检查来了解可以理解,预期并能够处理的情况。对于不连贯或不可处理的情况,您将使用例外。
所以,如果你想到你的“添加”功能。它应该永远不会返回null。这不是添加两件事情的一致结果。在这种情况下,传入的参数中存在错误,函数而不是试图假装一切正常。这是抛出异常的最佳例子。
如果您处于常规或正常执行情况下,您会希望使用条件检查并返回null。例如,IsEqual
可能是使用条件的好例子,如果您的某个条件失败,则返回false。 I.E.
function bool IsEqual(obj a, obj b)
{
if(a is null) return false;
if(b is null) return false;
if(a.Type != b.Type) return false;
bool result = false;
//Do custom IsEqual comparison code
return result;
}
在这种情况下,您将为异常情况和“对象不等于”情况返回false。这意味着消费者(主叫方)不能分辨比较是否失败,或者对象是简单的不等于。如果需要区分这些情况,则应该使用例外而不是条件。
最终,您想问自己,消费者是否能够专门处理您遇到的失败案例。如果你的方法/功能不能做它需要做的事情那么你可能想抛出异常。
您的建议是,能够处理的事情对于异常不适用于异常情况:这就是为什么我们有*异常处理*。在Python中使用异常的情况尤其如此,其中包括非常期望的情况,例如在循环中指示迭代结束。 – 2010-04-29 18:16:42
我的意思是“在*方法中处理*而不是在没有处理的情况下”。 “黑盒子里面”可以无缝地处理某些事情。这是我在我的“IsEqual”方法中提供的示例,它处理异常情况,因为用户不一定关心输入的有效性,只能用它们的相等性。 *当前上下文*不能或不应该处理它时应抛出异常。 – DevinB 2010-04-29 19:26:40
我很清楚存在异常处理。这里的主要区别在于WHERE。在这两种情况下,您都在处理“异常”,因为这是意外情况,但是,如果您正在使用比较,则会从用户那里隐藏它。他们不会以“例外”的意义被通知,他们将被通知一个“空”或“错误”的响应代码,这是*重载*返回参数。重载返回参数几乎总是**一个坏主意,因为这样你就会迫使主叫方在他们调用函数后进行检查。 – DevinB 2010-04-29 19:32:04
如果你问,你应该使用异常。例外情况用于表示特殊情况,一种情况下工作与其他情况不同的特定情况。这是很多所有错误以及其他许多事情的情况。
在第二次执行sum_
时,用户必须每次检查的值是多少。这让人想起C/Fortran /其他语言样板文件(以及频繁的错误来源),我们可以避免错误代码被忽略。您必须在所有级别上编写这样的代码才能传播错误。它变得混乱,特别是在Python中避免。
一对夫妇的其他注意事项:
- 你往往不需要做自己的异常。对于许多情况,内部例外如
ValueError
和TypeError
是适当的。 - 当我确实创建了一个非常有用的新异常时,我经常会尝试子类化一些比
Exception
更具体的东西。内置的例外层次是here。 -
我永远不会实现像
sum_
这样的函数,因为类型检查会让您的代码变得更加灵活,可维护和惯用。我只想写函数
def sum_(a, b): return a + b
如果对象是兼容这将工作,如果没有它会已经抛出一个异常,
TypeError
每个人都在看惯了。考虑我的实现如何工作>>> sum_(1, 4) 5 >>> sum_(4.5, 5.0) 9.5 >>> sum_([1, 2], [3, 4]) [1, 2, 3, 4] >>> sum_(3.5, 5) # This operation makes perfect sense, but would fail for you 8.5 >>> sum_("cat", 7) # This makes no sense and already is an error. Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 1, in sum_ TypeError: cannot concatenate 'str' and 'int' objects
我的代码更短,更简单,但比您的代码更加健壮和灵活。这就是为什么我们避免使用Python进行类型检查的原因。
我使用的sum_函数只是一个虚拟的例子来说明处理结果的两种可能方式。通常我不会有这样的功能。 – 2010-05-03 09:04:16
我知道这只是一个愚蠢的例子,但我注意到,类型检查是检查人们认为他们应该做的常见类型的检查,但不是你在很好的Python代码中做的很多事情。 – 2010-05-03 16:10:04
我更喜欢状态返回的例外的主要原因与考虑如果程序员忘记做他的工作会发生什么有关。有例外情况,您可能会忽略捕获异常。在这种情况下,你的系统会明显失败,你将有机会考虑在哪里添加捕获。有了状态回报,如果你忘记检查退货,它将被默默地忽略,并且你的代码将继续,可能会在后来以神秘的方式失败。我更喜欢看不见的东西。
还有其他原因,我在这里解释过:Exceptions vs. Status Returns。
也许sum_
看起来不错。如果你知道它实际上被使用了怎么办?
#foo.py
def sum_(a, b):
if type(a) == type(b):
return a + b
#egg.py
from foo import sum_:
def egg(c = 5):
return sum_(3, c)
#bar.py
from egg import egg
def bar():
return len(egg("2"))
if __name__ == "__main__":
print bar()
如果运行bar.py
你会得到:
Traceback (most recent call last):
File "bar.py", line 6, in <module>
print bar()
File "bar.py", line 4, in bar
return len(egg("2"))
TypeError: object of type 'NoneType' has no len()
看到的 - 通常是一个调用一个函数的意图行事它的输出。如果您只是“吞下”异常并返回一个虚拟值,则使用您的代码的人将很难排除故障。首先,追踪是完全无用的。这本身应该是足够的理由。
谁想要修复这个错误,首先需要重复检查bar.py
,然后分析egg.py
试图找出“无”来自哪里。在阅读egg.py
后,他们必须阅读sum_.py
,并希望注意到None
的隐含回报;只有他们明白了这个问题:由于参数egg.py
为他们输入,他们没有通过类型检查。
在这件事上放上一些实际的复杂性,事情变得很难看得很难看。
与C不同,Python与Easier to Ask Forgiveness than Permission原则一起编写:如果出现问题,我会得到一个异常。如果你给我一个None
的地方,我希望得到一个实际值,那么事情就会中断,这个例外会发生在远离实际造成的线路上,并且人们会在你的总体方向上诅咒20种不同的语言,然后改变代码扔掉一个合适的例外(TypeError("incompatible operand type")
)。
实际上,使用异常的问题在于业务逻辑。如果情况是例外(即根本不应该发生),则可以使用例外。但是,如果情况从业务逻辑的角度来看是可能的,何时应该通过条件检查来处理,即使这种情况看起来要复杂得多。
例如,下面是我在准备好的声明中遇到的代码,当开发者设置的参数值(Java的,而不是Python):
// Variant A
try {
ps.setInt(1, enterprise.getSubRegion().getRegion().getCountry().getId());
} catch (Exception e) {
ps.setNull(1, Types.INTEGER);
}
将条件检查这将是这样写的:
// Variant B
if (enterprise != null && enterprise.getSubRegion() != null
&& enterprise.getSubRegion().getRegion() != null
&& enterprise.getSubRegion().getRegion().getCountry() != null) {
ps.setInt(1, enterprise.getSubRegion().getRegion().getCountry().getId());
} else {
ps.setNull(1, Types.INTEGER);
}
变式B似乎从第一眼看到的要复杂得多,但是,它是正确的一个,因为这种情况是有可能从商业的角度出发(也可以不指定国) 。使用异常会导致性能问题,并且会导致对代码的误解,因为它不明确,国家是否空着是否可以接受。
变型B可以通过使用辅助功能EnterpriseBean将立即返回该地区和国家进行改进:
public RegionBean getRegion() {
if (getSubRegion() != null) {
return getSubRegion().getRegion();
} else {
return null;
}
}
public CountryBean getCountry() {
if (getRegion() != null) {
return getRegion().getCountry();
} else {
return null;
}
}
该代码使用类似链接,每个得到方法似乎很简单,只使用一个前任。
// Variant C
if (enterprise != null && enterprise.getCountry() != null) {
ps.setInt(1, enterprise.getCountry().getId());
} else {
ps.setNull(1, Types.INTEGER);
}
而且,阅读本Joel article为什么异常不应该被过度使用:所以B变种可如下改写。和Raymon Chen的essay。
就像在上面的gimel的链接中一样,EAFP总是比测试类型更可取,因此在编写良好的Python中你会看到很少的'type(a)== type(b)'比较(尽管它们在C++中很常见) 。如果sum的调用者使用不兼容的类型,她应该得到TypeError异常。 – msw 2010-04-29 18:13:22