我为我的应用程序不期望的每个条件创建了异常。UserNameNotValidException, PasswordNotCorrectException等。
然而,我被告知我不应该为这些条件创造例外。在我的UML中,那些是主要流程的异常,那么为什么它不应该是异常呢?
是否有创建异常的指导或最佳实践?
我为我的应用程序不期望的每个条件创建了异常。UserNameNotValidException, PasswordNotCorrectException等。
然而,我被告知我不应该为这些条件创造例外。在我的UML中,那些是主要流程的异常,那么为什么它不应该是异常呢?
是否有创建异常的指导或最佳实践?
当前回答
首先,如果API的用户对特定的、细粒度的故障不感兴趣,那么为他们设置特定的异常就没有任何价值。
由于通常不可能知道什么可能对用户有用,一个更好的方法是有特定的异常,但确保它们继承自一个公共类(例如,std::exception或其在c++中的派生类)。这允许您的客户端捕获特定的异常(如果他们愿意的话),或者捕获更一般的异常(如果他们不关心的话)。
其他回答
一个经验法则是在您通常无法预测的情况下使用异常。例如数据库连接、磁盘上丢失的文件等。对于您可以预测的场景,例如用户试图使用错误的密码登录,您应该使用返回布尔值的函数,并知道如何优雅地处理这种情况。您不希望仅仅因为有人输入了密码错误而抛出异常,从而突然结束执行。
我认为只有在无法摆脱当前状态时才应该抛出异常。例如,如果您正在分配内存,但没有任何内存可以分配。在您提到的情况下,您可以清楚地从这些状态中恢复,并相应地将错误代码返回给调用者。
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.
我对异常的使用有哲学问题。基本上,您期待一个特定的场景发生,但不是明确地处理它,而是将问题推到“其他地方”处理。至于“其他地方”在哪里,谁也说不准。
“PasswordNotCorrectException”不是一个使用异常的好例子。用户输入错误的密码是意料之中的,所以在我看来,这几乎不是个例外。您甚至可能从中恢复,显示一个漂亮的错误消息,因此这只是一个有效性检查。
未处理的异常将最终停止执行——这是好事。如果返回false、null或错误代码,则必须自己处理程序的状态。如果您忘记检查某个地方的条件,您的程序可能会继续使用错误的数据运行,并且您可能很难弄清楚发生了什么以及在哪里发生了什么。
当然,空的catch语句也可能导致同样的问题,但至少发现这些语句更容易,而且不需要理解逻辑。
所以根据经验:
在您不想要或无法从错误中恢复的地方使用它们。
The simple answer is, whenever an operation is impossible (because of either application OR because it would violate business logic). If a method is invoked and it impossible to do what the method was written to do, throw an Exception. A good example is that constructors always throw ArgumentExceptions if an instance cannot be created using the supplied parameters. Another example is InvalidOperationException, which is thrown when an operation cannot be performed because of the state of another member or members of the class.
在您的情况下,如果调用Login(用户名,密码)这样的方法,如果用户名无效,抛出UserNameNotValidException或PasswordNotCorrectException(密码不正确)确实是正确的。用户不能使用提供的参数登录(即,这是不可能的,因为它将违反身份验证),因此抛出异常。尽管我可能从ArgumentException继承了两个异常。
话虽如此,如果因为登录失败可能很常见而不希望抛出异常,一种策略是创建一个方法,该方法返回表示不同失败的类型。这里有一个例子:
{ // class
...
public LoginResult Login(string user, string password)
{
if (IsInvalidUser(user))
{
return new UserInvalidLoginResult(user);
}
else if (IsInvalidPassword(user, password))
{
return new PasswordInvalidLoginResult(user, password);
}
else
{
return new SuccessfulLoginResult();
}
}
...
}
public abstract class LoginResult
{
public readonly string Message;
protected LoginResult(string message)
{
this.Message = message;
}
}
public class SuccessfulLoginResult : LoginResult
{
public SucccessfulLogin(string user)
: base(string.Format("Login for user '{0}' was successful.", user))
{ }
}
public class UserInvalidLoginResult : LoginResult
{
public UserInvalidLoginResult(string user)
: base(string.Format("The username '{0}' is invalid.", user))
{ }
}
public class PasswordInvalidLoginResult : LoginResult
{
public PasswordInvalidLoginResult(string password, string user)
: base(string.Format("The password '{0}' for username '{0}' is invalid.", password, user))
{ }
}
Most developers are taught to avoid Exceptions because of the overhead caused by throwing them. It's great to be resource-conscious, but usually not at the expense of your application design. That is probably the reason you were told not to throw your two Exceptions. Whether to use Exceptions or not usually boils down to how frequently the Exception will occur. If it's a fairly common or an fairly expectable result, this is when most developers will avoid Exceptions and instead create another method to indicate failure, because of the supposed consumption of resources.
下面是一个使用Try()模式避免在类似刚刚描述的场景中使用exception的例子:
public class ValidatedLogin
{
public readonly string User;
public readonly string Password;
public ValidatedLogin(string user, string password)
{
if (IsInvalidUser(user))
{
throw new UserInvalidException(user);
}
else if (IsInvalidPassword(user, password))
{
throw new PasswordInvalidException(password);
}
this.User = user;
this.Password = password;
}
public static bool TryCreate(string user, string password, out ValidatedLogin validatedLogin)
{
if (IsInvalidUser(user) ||
IsInvalidPassword(user, password))
{
return false;
}
validatedLogin = new ValidatedLogin(user, password);
return true;
}
}