最佳实践:返回值vs异常vs枚举

16

我试图找出一种具有多个结果值的方法的优缺点。

例如,我正在使用登录方法。如果登录成功,则它将通过,否则我需要知道失败的原因。

1. 返回真或假(信息不足)

bool Login(string user, string password);

2. 如果成功则返回 true,否则抛出异常

public class UnknownUserException : Exception { }
public class WrongPasswordException : Exception { }
bool Login(string user, string password);

3. 不返回任何东西。如果没有成功,抛出异常。

public class UnknownUserException : Exception { }
public class WrongPasswordException : Exception { }
void Login(string user, string password);

4. 返回枚举值

enum LoginResult
{
    Successful
    UnknownUser,
    WrongPassword
}
LoginResult Login(string user, string password);

“登录”只是一个示例案例。我想知道不同实现方式的优缺点,并且它们在哪些情况下更或者不太适用。


1和2显然不值得考虑。#3是通常做事情的方式,但唯一能够认识到在你的情况下也许#4更合适的人是你自己。 - Jon
API是同步还是异步的? - Sriram Sakthivel
同步,抱歉 :) - Jan
短语“异常仅用于特殊错误”应该引导您避免使用#2或#3。在可能有多个失败原因的情况下,可以接受#4,而当只有一个失败原因时,可以接受#1。在我的自己的库中,我创建了一个名为ResultString的类,其中包含一个用于成功/失败的布尔值和一个用于向用户显示详细信息的字符串。这使得函数能够确定最佳的失败描述。 - Dr Herbie
异常 vs. 状态返回 - tchelidze
4个回答

5
肯定不是异常情况。登录失败并不算是“异常”情况,它只是应用程序逻辑的正常过程。如果您使用异常,那么您将始终需要使用异常处理程序来处理登录失败的情况,这似乎是使用异常进行逻辑流程控制的典型案例,而这是不正确的。如果您需要返回特定信息(虽然从登录函数中并不总是必要的,但在您的情况下可能会需要),那么第4项建议似乎是合理的。您可以进一步将其封装为一个对象。
public class LoginResult
{
    // an enum for the status
    // a string for a more specific message
    // a valid user object on successful login
    // etc.
}

根据逻辑,可以使用不可变的结构体代替类(请确保该结构体是不可变的,可变结构体会引发问题)。重点是您可以在结果对象本身上应用各种逻辑和功能,这似乎是您正在追求的方向。

4

如果我在做的话,我的看法会更加具有个人色彩,我会将第三点和第四点结合起来。使用一个枚举类型来表明登录失败的原因,并抛出 LoginFailedException 异常。

void Login(string user, string password);//Or return a bool(redundant though)

class LoginFailedException : ApplicationException
{
    public LoginFailReason Reason {get; private set;}
    public LoginFailedException(LoginFailReason reason)
    {
       this.Reason = reason;
    }
}

enum LoginFailReason
{
    UnknownUser,
    WrongPassword
}

选择异常选项的原因:假设您选择了仅返回值方法,您API的用户(可能是客户端或其他开发人员)有可能会忽略API的某些细节。

instance.Login(user, password);
var accountInfo = instance.GetAccountInfo();//Assuming logged in; going to explode

谁知道他们需要这样做。
if(instance.Login(user, password) == LoginResult.Successful))
{
    var accountInfo = instance.GetAccountInfo();
}

所以,在我看来,应该抛出一个异常,表明我不能处理你的登录请求,原因是这样那样的。让它简单明了。


6
我喜欢这种方法,尽管在我看来,在一个普通的场景中抛出异常会感觉不太对。我认为异常应该在“特殊”的情况下使用,即在更深层次上出现问题时。 - Moeri
4
@Moeri,我认为这是正确的方式,登录失败不是一种常见情况,而是一种异常情况。如果你只选择枚举方法,并开发了一个公共API,那么如果用户没有检查返回值,直接调用Login();并假设已经登录,会怎样呢?应该抛出错误提示,告诉用户出了问题(就像SqlException一样)。 - Sriram Sakthivel
@Moeri 异常是用来替代 C-Style 状态码和 GetLastError() 的。如果要使用这种方法实现“内部异常”的想法,应该如何操作呢?当错误发生在两个或更多层时的情况 - tchelidze

4
当然,这取决于具体情况,但我在这里提供一些主观评论:
  1. 我相信应该避免这种情况。该方法的名称表明它只执行像“登录”之类的任何操作。根据方法名称,我们不能期望在此处获得任何结果。如果您希望该方法返回bool值,则最好将其命名为IsLoggedIn(userName)。此外,您永远不会知道是否需要扩展正在返回的值集。因此,在此处使用enum更好,还考虑到enum名称反映了值的目的,而不是简单的bool

  2. 与上述情况相同。在这里,异常有助于停止整个执行层次结构(当然可以包含多个调用堆栈中的方法),而不是仅返回结果并让调用者做出适当的决定。对我来说,这是一种更灵活的解决方案。在当前情况下,我仅对参数验证使用异常。例如,“错误的用户名/密码”等情况并不异常。从用例的角度来看,它们是正常的。 null参数或错误的参数格式是异常情况。

  3. 如果您不需要该方法返回值,则应采用这种方法。不要忘记,您不应将异常用作导航。我的意思是UserSuccessfullyCreatedException之类的。

  4. 如我上面所提到的,这对我来说是最好的方法。唯一的问题是不要将验证异常放置为enum值。您有异常可以处理它。

因此,enum结果加上验证异常是正确的方法。

如果您希望在方法执行期间收集所有错误,则可能需要创建特殊的LoginOperationResult类来包装所有信息(包括方法执行期间发生的验证错误)。

class OperationResult
{
    public OperationStatus Status { get; set; }

    public IEnumerable<ValidationError> Errors { get; set; }
    // or list of exceptions
}

class LoginOperationResult : OperationResult
{
    // Login result specific data.
}

enum OperationStatus
{
    Success,
    Denied,
    ValidationFailed,
    // etc.
}

1
我通常在我的项目中使用这种方法:
签名:
bool TryLogin(string username, string password, out User user);

使用方法:

User user;
if(userService.TryLogin(username, password, out user)))
{
    // do stuff with user
}
else 
{
    // show "login failed"
}

您可以扩展此代码以返回您的枚举类型:

签名:

enum LoginResult
{
    Successful
    UnknownUser,
    WrongPassword
}

LoginResult TryLogin(string username, string password, out User user);

使用方法:

User user;
LoginResult loginResult;
if((loginResult = userService.TryLogin(username, password, out user)) == LoginResult.Successful)
{
    // do stuff with user
}
else 
{
    // do stuff with loginResult
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接