Exception 使用异常进行流控制

Exception 使用异常进行流控制,exception,language-agnostic,exception-handling,Exception,Language Agnostic,Exception Handling,最近有人告诉我,我正在滥用异常来控制我的应用程序中的流,所以我想这是我试图以某种方式澄清这种情况 在我看来,当一个方法遇到内部无法处理或调用方可以更好地处理的情况时,它应该抛出一个异常 因此-是否存在任何特定的规则集,在开发应用程序时可用于回答以下问题: 什么时候我应该抛出异常,什么时候我应该编写带有强nothrow保证的代码,它可能只返回bool,以指示成功或失败 当方法抛出异常时,我应该尽量减少情况的数量,还是相反,应该最大化它以提供处理这些情况时的灵活性 我应该坚持开发应用程序时使用的

最近有人告诉我,我正在滥用异常来控制我的应用程序中的流,所以我想这是我试图以某种方式澄清这种情况

在我看来,当一个方法遇到内部无法处理或调用方可以更好地处理的情况时,它应该抛出一个异常


因此-是否存在任何特定的规则集,在开发应用程序时可用于回答以下问题:

  • 什么时候我应该抛出异常,什么时候我应该编写带有强nothrow保证的代码,它可能只返回
    bool
    ,以指示成功或失败

  • 当方法抛出异常时,我应该尽量减少情况的数量,还是相反,应该最大化它以提供处理这些情况时的灵活性

  • 我应该坚持开发应用程序时使用的框架/运行时设置的异常抛出约定,还是应该包装所有这些调用,以便它们与我自己的异常抛出策略相匹配

  • 还有人建议我使用错误代码进行错误处理,这似乎非常有效,但从语法的角度来看很难看(而且,当使用错误代码时,开发人员将失去为方法指定输出的能力)。你觉得这个怎么样


第三个问题的示例(我在使用I/O框架时遇到以下情况):

所描述的框架不使用异常来处理错误,但是 其他代码确实使用它们。我应该总结每一个可能的失败吗 用
“?”
指示,并在这种情况下引发异常? 或者我应该将方法的签名更改为
bool吗
准备EthereSultingOutputPath
,并仅指示操作是否正确 成功与否


另一个例子是,即使是
.netframework
也没有遵循一些严格的异常抛出策略。一些方法被记录为抛出10+种不同的异常类型,包括像
NullArgumentException
这样的普通异常类型,但其中一些方法只是返回
bool
,以指示操作的成功或失败


谢谢大家!

对异常错误使用异常是可以的。例如,如果托管环境中的内存分配失败,则抛出异常是可以的(在嵌入式环境中,最好以不同的方式处理)。同样,如果没有遵守约定,则抛出(例如,当接收到一个空指针时抛出,预期会有一个有效指针)可能是合理的(可能会中止)。对预期的错误或控制流使用异常将起作用,但会打乱可接受性能的任何希望。

异常的问题在于,它们本质上是美化的GOTO,能够释放程序的调用堆栈。因此,如果您“使用异常进行流控制”,那么您可能将它们用作GOTO,而不是异常条件的指示。这正是异常的要点,也是它们命名的原因:它们应该只在异常情况下使用。因此,除非方法被设计为不抛出异常(例如.NET的
int.TryParse
),否则在异常情况下抛出异常是可以的

与Java相反,C#的优点在于,在C#中,您可以通过返回元组类型或使用out参数来返回两个或多个值。因此,返回一个错误代码作为方法的主返回值并没有多大问题,因为您可以使用out参数来完成其余的操作。例如,调用
int.TryParse
的常见范例是

string s = /* Read a string from somewhere */;
int n;
if (int.TryParse(s, out n))
{
    // Use n somehow
}
else
{
    // Tell the user that they entered a wrong number
}
现在谈谈你的第三个问题,这似乎是最重要的。参考示例代码,您询问是否应返回
bool
以指示成功/失败,或者是否应使用异常指示失败。不过,还有第三种选择。您可以定义一个枚举来告诉方法如何失败,并将该类型的值返回给调用方。然后,调用者有很多选择:调用者不必使用一堆try/catch语句,也不必使用if语句,因为if语句对方法失败的原因知之甚少,但可以选择编写其中之一

if (PrepareTheResultingOutputFile(templateFilePath, outputFilePath) == Status.Success)
    // Do  something
else
    // It failed!


您可以在这个问题上找到更多信息,尤其是在中,我强烈推荐。

我觉得第一个问题已经得到了回答,而且非常简单。仅在特殊情况下使用异常

关于你的其他问题:

我是否应该尽量减少使用该方法时出现的情况 抛出异常,或者相反,应该将其最大化为 在处理这些情况时提供灵活性

如果我正确理解了你的问题,它可以通过问题1自行回答。抛出异常的情况是在异常情况下,这当然不是很多情况。你应该确保你的程序几乎不会出现异常,除非太阳和土星对齐。然而,您应该有测试异常情况实际引发异常的测试用例

我应该遵守 开发应用程序时使用的或应该使用的框架/运行时 我包装所有这些调用,以便它们与我自己的异常抛出匹配 策略

视情况而定。在您的示例中,这取决于未找到文件是否异常?如果您希望此文件存在,而它不存在(例如,安装在程序旁边的文件),则应引发异常。如果是用户希望打开的文件,您不能确定,必须对此进行说明。我不认为这种类型的用户错误是异常的,可能会为此使用返回代码。其他事情要考虑的是:这个成功的关键是执行程序吗?
if (PrepareTheResultingOutputFile(templateFilePath, outputFilePath) == Status.Success)
    // Do  something
else
    // It failed!
switch (PrepareTheResultingOutputFile(templateFilePath, outputFilePath))
{
    case Status.Success:
        // Do something
        break;
    case Status.FileNotPresent:
        // Do something else
        break;
    case Status.CannotMakePath:
        // Do something else
        break;
    // And so on
    default:
        // Some other reason for failure
        break;
}
var pop3Clinet=new pop3Client();

    try
    {
    pop3Client.SetServer("server-address");
    pop3Client.SetUserName("username");
    pop3Client.SetPassword("password");
    var mails=pop3Client.ReceiveMails();
    }
    catch(NullReferenceException exp)
    {
     throw new Exception("can not connect to server.server-address is wrong or the server is down",exp);
    }
    catch(UnauthorizedAccess)  //the exception is meaningful and can be rethrown or this block can be removed.
    {
    throw;
    }