什么时候异常处理比条件检查更可取?在很多情况下我可以选择使用其中一种。
例如,这是一个使用自定义异常的求和函数:
# 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的原因是什么?为什么?
答案 0 :(得分:9)
例外更易于管理,因为它们定义了可能出错的一般事物。
在您的示例中,只有一个可能的问题,因此使用异常没有任何优势。但是如果你有另一个分裂的阶级,那么它需要发出信号表明你不能偏离零。简单地返回None
将不再有效。
另一方面,异常可以是子类,您可以捕获特定的异常,具体取决于您对底层问题的关注程度。例如,您可以拥有DoesntCompute
基本异常以及InvalidType
和InvalidArgument
等子类。如果您只想要一个结果,可以将所有计算包装在一个捕获DoesntCompute
的块中,但您仍然可以轻松地执行非常具体的错误处理。
答案 1 :(得分:6)
通常,您希望对可理解,预期和能够处理的情况使用条件检查。对于不连贯或不可处理的案例,您可以使用例外。
所以,如果你想到你的“添加”功能。它永远不应该返回null。这不是添加两件事的连贯结果。在这种情况下,传入的参数中存在错误,并且函数应该不尝试假装一切正常。这是抛出异常的完美案例。
如果您处于常规或正常执行情况,您可能希望使用条件检查并返回null。例如,IsEqual
可能是使用条件的好例子,如果您的某个条件失败,则返回false。即。
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。这意味着消费者(主叫方)无法判断比较是否失败或对象是否不等于。如果需要区分这些情况,那么您应该使用例外而不是条件。
最终,您想要问问自己,消费者是否能够专门处理您遇到的失败案例。如果您的方法/函数无法执行它需要做的事情,那么您可能想要抛出异常。
答案 2 :(得分:2)
如果你问,你可能应该使用例外。例外用于表示特殊情况,这是一种特定情况,其中事物的工作方式与其他情况不同。这就是所有错误和许多其他事情的情况。
在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中避免使用类型检查的原因。
答案 3 :(得分:2)
我更喜欢状态返回异常的主要原因与考虑如果程序员忘记完成其工作会发生什么有关。除了例外,您可能会忽略捕获异常。在这种情况下,您的系统将明显失败,您将有机会考虑在何处添加捕获。有了状态返回,如果你忘记检查返回,它将被静默忽略,你的代码将继续,可能以后以神秘的方式失败。我更喜欢看不见的那种可见的失败。
还有其他原因,我在这里解释过:Exceptions vs. Status Returns。
答案 4 :(得分:2)
实际上,使用异常的问题在于业务逻辑。如果情况是例外(即根本不应该发生),则可以使用例外。但是,如果从业务逻辑的角度来看情况是可能的,那么它应该通过条件检查来处理,即使这种情况看起来要复杂得多。
例如,这是我在准备好的语句中遇到的代码,当开发人员设置参数值(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从第一眼看起来要复杂得多,但是,它是正确的,因为从商业角度来看这种情况是可能的(可能没有指定国家)。使用异常会导致性能问题,并且会导致对代码的误解,因为它不清楚,该国是否可以为空。
可以通过在 EnterpriseBean 中使用辅助功能来改进变体B,该辅助功能将立即返回地区和国家/地区:
public RegionBean getRegion() {
if (getSubRegion() != null) {
return getSubRegion().getRegion();
} else {
return null;
}
}
public CountryBean getCountry() {
if (getRegion() != null) {
return getRegion().getCountry();
} else {
return null;
}
}
此代码使用类似链接的内容,并且每个 get 方法看起来都很简单,只使用一个前任。因此,变体B可以重写如下:
// Variant C
if (enterprise != null && enterprise.getCountry() != null) {
ps.setInt(1, enterprise.getCountry().getId());
} else {
ps.setNull(1, Types.INTEGER);
}
另外,请阅读此Joel article,了解为什么不应过度使用异常。和Raymon Chen一样essay。
答案 5 :(得分:1)
当参数包含意外值时,您应该抛出异常。
通过您的示例,我建议在两个参数属于不同类型时抛出异常。
抛出异常是一种优雅的方法,可以在不使代码混乱的情况下中止服务。
答案 6 :(得分:0)
也许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
,我期望实际价值,事情就会破裂,异常会发生在远离实际导致它的线上,而人们将诅咒你的大方向不同的语言,然后更改代码以抛出一个合适的异常(TypeError("incompatible operand type")
)。