我为我的应用程序不期望的每个条件创建了异常。UserNameNotValidException, PasswordNotCorrectException等。
然而,我被告知我不应该为这些条件创造例外。在我的UML中,那些是主要流程的异常,那么为什么它不应该是异常呢?
是否有创建异常的指导或最佳实践?
我为我的应用程序不期望的每个条件创建了异常。UserNameNotValidException, PasswordNotCorrectException等。
然而,我被告知我不应该为这些条件创造例外。在我的UML中,那些是主要流程的异常,那么为什么它不应该是异常呢?
是否有创建异常的指导或最佳实践?
当前回答
最终,决定取决于是使用异常处理更有助于处理此类应用程序级错误,还是通过您自己的机制(如返回状态代码)更有帮助。我不认为哪个更好有一个严格的规则,但我会考虑:
Who's calling your code? Is this a public API of some sort or an internal library? What language are you using? If it's Java, for example, then throwing a (checked) exception puts an explicit burden on your caller to handle this error condition in some way, as opposed to a return status which could be ignored. That could be good or bad. How are other error conditions in the same application handled? Callers won't want to deal with a module that handles errors in an idiosyncratic way unlike anything else in the system. How many things can go wrong with the routine in question, and how would they be handled differently? Consider the difference between a series of catch blocks that handle different errors and a switch on an error code. Do you have structured information about the error you need to return? Throwing an exception gives you a better place to put this information than just returning a status.
其他回答
其他人建议不应该使用异常,因为在正常流程中,如果用户输入错误,就会出现错误的登录。我不同意,我不明白其中的道理。与打开文件相比。如果该文件不存在或由于某种原因不可用,则框架将抛出异常。使用上述逻辑是微软的一个错误。他们应该返回一个错误代码。解析、webrequest等也一样。
I don't consider a bad login part of a normal flow, it's exceptional. Normally the user types the correct password, and the file does exist. The exceptional cases are exceptional and it's perfectly fine to use exceptions for those. Complicating your code by propagating return values through n levels up the stack is a waste of energy and will result in messy code. Do the simplest thing that could possibly work. Don't prematurely optimize by using error codes, exceptional stuff by definition rarely happens, and exceptions don't cost anything unless you throw them.
在决定异常是否合适时,需要考虑一些有用的事情:
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.
一般来说,你想要为应用程序中可能发生的任何异常抛出一个"异常"
在您的示例中,这两个异常看起来都是通过密码/用户名验证调用的。在这种情况下,有人会输入错误的用户名/密码并不是什么例外。
它们是UML主要流程的“例外”,但在处理过程中是更多的“分支”。
如果您试图访问您的passwd文件或数据库,但无法访问,这将是一个异常情况,并需要抛出异常。
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;
}
}
我个人的指导方针是:当发现当前代码块的基本假设为假时抛出异常。
例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的函数,这条线很难画出来,但底线是:如果函数对其输入的假设违反了,它应该抛出异常,而不是正常返回。
这个等式的另一方面是:如果你发现你的函数经常抛出异常,那么你可能需要改进它们的假设。