我为我的应用程序不期望的每个条件创建了异常。UserNameNotValidException, PasswordNotCorrectException等。
然而,我被告知我不应该为这些条件创造例外。在我的UML中,那些是主要流程的异常,那么为什么它不应该是异常呢?
是否有创建异常的指导或最佳实践?
我为我的应用程序不期望的每个条件创建了异常。UserNameNotValidException, PasswordNotCorrectException等。
然而,我被告知我不应该为这些条件创造例外。在我的UML中,那些是主要流程的异常,那么为什么它不应该是异常呢?
是否有创建异常的指导或最佳实践?
当前回答
我个人的指导方针是:当发现当前代码块的基本假设为假时抛出异常。
例1:假设我有一个函数,它应该检查任意类,如果该类继承自List<>,则返回true。这个函数问一个问题:“这个对象是List的后代吗?”这个函数永远不会抛出异常,因为它的操作中没有灰色地带——每个单独的类要么继承了List<>,要么继承了List<>,所以答案总是“是”或“否”。
Example 2: say I have another function which examines a List<> and returns true if its length is more than 50, and false if the length is less. This function asks the question, "Does this list have more than 50 items?" But this question makes an assumption - it assumes that the object it is given is a list. If I hand it a NULL, then that assumption is false. In that case, if the function returns either true or false, then it is breaking its own rules. The function cannot return anything and claim that it answered the question correctly. So it doesn't return - it throws an exception.
这与“负载问题”逻辑谬误相当。每个函数都问一个问题。如果给出的输入使该问题成为谬误,则抛出异常。对于返回void的函数,这条线很难画出来,但底线是:如果函数对其输入的假设违反了,它应该抛出异常,而不是正常返回。
这个等式的另一方面是:如果你发现你的函数经常抛出异常,那么你可能需要改进它们的假设。
其他回答
我认为只有在无法摆脱当前状态时才应该抛出异常。例如,如果您正在分配内存,但没有任何内存可以分配。在您提到的情况下,您可以清楚地从这些状态中恢复,并相应地将错误代码返回给调用者。
You will see plenty of advice, including in answers to this question, that you should throw exceptions only in "exceptional" circumstances. That seems superficially reasonable, but is flawed advice, because it replaces one question ("when should I throw an exception") with another subjective question ("what is exceptional"). Instead, follow the advice of Herb Sutter (for C++, available in the Dr Dobbs article When and How to Use Exceptions, and also in his book with Andrei Alexandrescu, C++ Coding Standards): throw an exception if, and only if
没有满足先决条件(通常会出现以下情况之一 不可能的)或 替代方案将无法满足后置条件或 替代方案将无法保持不变式。
为什么这样更好呢?它不是用几个关于前置条件,后置条件和不变量的问题代替了这个问题吗?这是更好的几个相关的原因。
Preconditions, postconditions and invariants are design characteristics of our program (its internal API), whereas the decision to throw is an implementation detail. It forces us to bear in mind that we must consider the design and its implementation separately, and our job while implementing a method is to produce something that satisfies the design constraints. It forces us to think in terms of preconditions, postconditions and invariants, which are the only assumptions that callers of our method should make, and are expressed precisely, enabling loose coupling between the components of our program. That loose coupling then allows us to refactor the implementation, if necessary. The post-conditions and invariants are testable; it results in code that can be easily unit tested, because the post-conditions are predicates our unit-test code can check (assert). Thinking in terms of post-conditions naturally produces a design that has success as a post-condition, which is the natural style for using exceptions. The normal ("happy") execution path of your program is laid out linearly, with all the error handling code moved to the catch clauses.
我同意japollock的说法当你不确定手术的结果时就放弃接受。调用api、访问文件系统、数据库调用等。任何时候你都要超越编程语言的“界限”。
我想补充一点,请随意抛出一个标准异常。除非你打算做一些“不同”的事情(忽略,电子邮件,日志,显示twitter鲸鱼图片之类的东西),否则不要费心自定义异常。
You may use a little bit generic exceptions for that conditions. For e.g. ArgumentException is meant to be used when anything goes wrong with the parameters to a method (with the exception of ArgumentNullException). Generally you would not need exceptions like LessThanZeroException, NotPrimeNumberException etc. Think of the user of your method. The number of the conditions that she will want to handle specifically is equal to the number of the type of the exceptions that your method needs to throw. This way, you can determine how detailed exceptions you will have.
顺便说一下,总是尝试为库的用户提供一些避免异常的方法。TryParse就是一个很好的例子,它的存在使你不必使用int。解析并捕获异常。在您的情况下,您可能希望提供一些方法来检查用户名是否有效或密码是否正确,这样您的用户(或您)就不必进行大量异常处理。这将有望产生更易于阅读的代码和更好的性能。
异常与返回错误代码参数应该是关于流控制的,而不是哲学(错误有多“异常”):
void f1() throws ExceptionType1, ExceptionType2 {}
void catchFunction() {
try{
while(someCondition){
try{
f1();
}catch(ExceptionType2 e2){
//do something, don't break the loop
}
}
}catch(ExceptionType1 e1){
//break the loop, do something else
}
}
在决定异常是否合适时,需要考虑一些有用的事情:
what level of code you want to have run after the exception candidate occurs - that is, how many layers of the call stack should unwind. You generally want to handle an exception as close as possible to where it occurs. For username/password validation, you would normally handle failures in the same block of code, rather than letting an exception bubble up. So an exception is probably not appropriate. (OTOH, after three failed login attempts, control flow may shift elsewhere, and an exception may be appropriate here.) Is this event something you would want to see in an error log? Not every exception is written to an error log, but it's useful to ask whether this entry in an error log would be useful - i.e., you would try to do something about it, or would be the garbage you ignore.