如何处理异常:最佳实践

3

需要实现全局错误处理,也许您可以帮忙看一下以下示例...

我有这段代码:

public bool IsUserAuthorizedToSignIn(string userEMailAddress, string userPassword)
        {
            // get MD5 hash for use in the LINQ query
            string passwordSaltedHash = this.PasswordSaltedHash(userEMailAddress, userPassword);

            // check for email / password / validity
            using (UserManagementDataContext context = new UserManagementDataContext())
            {
                var users = from u in context.Users
                            where u.UserEMailAdresses.Any(e => e.EMailAddress == userEMailAddress)
                                && u.UserPasswords.Any(p => p.PasswordSaltedHash == passwordSaltedHash)
                                && u.IsActive == true
                            select u;

                // true if user found
                return (users.Count() == 1) ? true : false;
            }
        }

还有MD5:

private string PasswordSaltedHash(string userEMailAddress, string userPassword)
        {
            MD5 hasher = MD5.Create();
            byte[] data = hasher.ComputeHash(Encoding.Default.GetBytes(userPassword + userEMailAddress));

            StringBuilder stringBuilder = new StringBuilder();
            for (int i = 0; i < data.Length; i++)
            {
                stringBuilder.Append(data[i].ToString("x2"));
            }

            Trace.WriteLine(String.Empty);
            Trace.WriteLine("hash: " + stringBuilder.ToString());
            return stringBuilder.ToString();
        }

那么,我该如何处理这些函数中的异常?第一个函数是从Default.aspx页面调用的。第二个函数仅从类库的其他函数中调用。
最佳做法是什么?
- 在每个函数内部包围代码与try-catch。 - 包围函数调用与try-catch。 - 其他方法?
如果出现异常怎么办?
在本例中: 这是用户登录,因此即使发生任何故障,用户也应该获得一些有意义的信息 - 比如:登录成功(重定向),登录失败(用户名/密码错误),由于内部问题无法登录,抱歉(发生异常)。
对于第一个函数,我担心数据库访问存在问题。 不确定是否需要处理第二个函数中的任何内容。
谢谢您的信息。您会如何处理? 需要具体的信息(易于理解),但还需要关于如何处理其他任务/函数的一般信息。
我在互联网上查找了一些信息,但每个人都有不同的说法,所以不确定该怎么办...将根据此处的最多投票或最合理解释的答案进行决策 :) 谢谢。
3个回答

4

两个黄金法则:

  1. 如果方法不能完成其名称所表示的操作,则抛出异常。
  2. 除非你确切知道如何处理它们,否则不要捕获异常。

请记住,异常指示了某些问题出现了错误,并且可能并非您想象的那样。(内存不足,堆栈溢出,第三方服务已停止,部署失败导致缺少程序集,配置不当,安全异常等)。

除非有极少数例外,在代码的最高级别处应该看到Pokemon异常处理的唯一位置,其中异常应该在某处发布。例如,在全局.asax文件中的Application_Error方法中。

这里有一些链接,您可能会发现有用:


呵呵,不错的黄金法则。非常合乎逻辑。 :) - b0x0rz

3
您需要考虑方法定义的合同。如果一个方法无法履行其合同,则需要异常。在方法内部抛出的异常应该被转换为从方法合同的角度讲有意义的异常。
在第一种情况下,我会捕获语句执行可能引发的任何异常(例如数据库错误),并将它们转换成类似于AuthorizationException的异常。从方法合同的角度来看,让异常直接传播是没有意义的。例如,在方法履行是否授权的合同时,不允许数据库异常从该方法中逃逸出去是没有意义的。
使用这个方法作为合同的想法可以帮助您做出正确的决定。如果一个方法合同是登录到数据库或进行一些数据库操作,那么允许异常传播可能是有意义的。
具体来说:使用第一个例子:
public bool IsUserAuthorizedToSignIn(string userEMailAddress, string userPassword)
    {
        try
        {
            // get MD5 hash for use in the LINQ query
            string passwordSaltedHash = this.PasswordSaltedHash(userEMailAddress, userPassword);

            // check for email / password / validity
            using (UserManagementDataContext context = new UserManagementDataContext())
            {
                var users = from u in context.Users
                        where u.UserEMailAdresses.Any(e => e.EMailAddress == userEMailAddress)
                            && u.UserPasswords.Any(p => p.PasswordSaltedHash == passwordSaltedHash)
                            && u.IsActive == true
                        select u;

                // true if user found
                return (users.Count() == 1) ? true : false;
            }
        }
        catch(ThisException e)
        {
            thrown new AuthorisationException("Problem1 occurred");
        }
        catch(ThatException e)
        {
            thrown new AuthorisationException("Problem2 occurred");
        }
        catch(OtherException e)
        {
            thrown new AuthorisationException("Problem3 occurred");
        }
    }

你可能还希望在AuthorisationException上设置内部异常:
        catch(ThisException e)
        {
            thrown new AuthorisationException("Problem1 occurred", e);
        }

然后你的客户端代码可以这样做:
try
{
    if(User.IsUserAuthorizedToSignIn)
    {
        // Let the magic happen
    }
    else
    {
        // No rights
    }
}
catch(AuthorisationException e)
{
    // Let the user know there is something up with the system. 
}

你可能会决定不在直接调用的位置捕获AuthorisationException,因为你无法通知用户。所以你可以让它向上传播到一个可以处理它的级别。

希望这有所帮助!


我喜欢AuthorizationException,因为这样我只需要处理AuthorizationException的情况,而不是一堆未知的东西——它们会被卡在“其他”里。 - b0x0rz
非常正确,我正想着在泡茶的时候加上那个观点。从客户端的角度来看,代码看起来更加清晰,并且专注于客户想要实现的目标,而不是如何实现它。 - Ed Sykes
很不幸的是,现有的异常机制和层次结构并没有更好地将异常按“异常在哪些方面不符合其预期条件,调用者需要做什么才能满足其后置条件?”进行分类。这确实是需要在决定是否应该捕获和忽略异常、捕获和包装异常、捕获和重新抛出异常或不捕获异常时提出的关键问题。 - supercat

3

您的库代码或应用程序中使用的代码必须始终只抛出异常,不要担心如何处理它们。

这很重要,因为您可能在许多不同的位置使用此库进行不同的目的。

在应用程序表示层中,如果您正在使用库代码并且知道可能会出现异常,请使用try/catch捕获它们。

由于您正在使用asp.net,我建议编写一个通用的页面基类,并在Page_Error事件中加入一些错误处理机制,以捕获页面中所有未处理的异常。

甚至可以在global.asax中使用Application_Error来捕获应用程序的任何部分(模块、处理程序、服务、页面等)中发生的未处理异常。

我强烈建议不要将处理所有异常的方法作为全局应用程序错误处理的一般做法。


哇,这似乎是一个非常好的建议。我喜欢在这些库函数之外处理错误的想法,因为我希望尽可能多地重用它们(库函数)。同时,我也很喜欢使用Page_Error事件来处理未处理的错误以及使用Application_Error来处理其他所有错误的建议。 - b0x0rz
你明白了。.Net框架库也会在无法处理或不知道如何处理的情况下抛出异常。它只是会抛出异常。 "这就是使用异常的整个思想。"如果你不知道如何在给定上下文中处理错误,只需抛出异常,并在正确的上下文中潜在地捕获它即可。 - this. __curious_geek
使用框架作为异常处理模型是个好主意。框架还会在异常通过框架层时进行翻译,因为方法中的契约发生了变化。我认为,如果你有业务逻辑和数据访问层,UI 层就没有理由知道 SqlExceptions 这样的事情。异常应该用调用方法的层的语言来表述。 - Ed Sykes

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