我为我的应用程序不期望的每个条件创建了异常。UserNameNotValidException, PasswordNotCorrectException等。

然而,我被告知我不应该为这些条件创造例外。在我的UML中,那些是主要流程的异常,那么为什么它不应该是异常呢?

是否有创建异常的指导或最佳实践?


当前回答

我同意japollock的说法当你不确定手术的结果时就放弃接受。调用api、访问文件系统、数据库调用等。任何时候你都要超越编程语言的“界限”。

我想补充一点,请随意抛出一个标准异常。除非你打算做一些“不同”的事情(忽略,电子邮件,日志,显示twitter鲸鱼图片之类的东西),否则不要费心自定义异常。

其他回答

其他人建议不应该使用异常,因为在正常流程中,如果用户输入错误,就会出现错误的登录。我不同意,我不明白其中的道理。与打开文件相比。如果该文件不存在或由于某种原因不可用,则框架将抛出异常。使用上述逻辑是微软的一个错误。他们应该返回一个错误代码。解析、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.

安全性与您的示例混为一谈:您不应该告诉攻击者用户名存在,但密码是错误的。这是你不需要分享的额外信息。只要说“用户名或密码不正确”。

我认为只有在无法摆脱当前状态时才应该抛出异常。例如,如果您正在分配内存,但没有任何内存可以分配。在您提到的情况下,您可以清楚地从这些状态中恢复,并相应地将错误代码返回给调用者。


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.

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;
    }
}

首先,如果API的用户对特定的、细粒度的故障不感兴趣,那么为他们设置特定的异常就没有任何价值。

由于通常不可能知道什么可能对用户有用,一个更好的方法是有特定的异常,但确保它们继承自一个公共类(例如,std::exception或其在c++中的派生类)。这允许您的客户端捕获特定的异常(如果他们愿意的话),或者捕获更一般的异常(如果他们不关心的话)。