【问题标题】:Is it bad to use exceptions to end function? [closed]使用异常来结束函数是不是很糟糕? [关闭]
【发布时间】:2014-01-26 23:23:50
【问题描述】:

我有一个应用程序,我经常使用Exceptions

最近我听说异常在性能方面的成本很高。

我的应用如下所示:

Try
{
    Function1();
    Function2();
    Function3();
    Function4();
}

catch(Execption ex)
{
    //I return to the user the error (ex) that happen inside the 
    //function that fails
}

我的每个函数 1,2,3 等看起来都与此类似..

Void Function1()
{
    //Do something...
    //If something fail
    Throw New Exception("Fails because bla bla");

    //Do something...
    //If something fail
    Throw New Exception("Fails because bla bla bla bla");

    //If everything its ok, just continue without throwing anything
}

这是糟糕的设计吗?
这会影响我的应用程序性能吗?

【问题讨论】:

  • 不要使用异常来控制程序流程。
  • 线索在“例外”一词中。这是您可以在网上和 Microsoft 轻松找到的信息!
  • @RicardoPolo 异常,适用于异常情况,不适用于普通程序流程。
  • 某个函数失败并抛出异常之一的概率是多少?如果低于 2%(任意推测),则例外情况很好。如果它更多,那么异常可能是错误的。

标签: c# .net exception


【解决方案1】:

当抛出异常时,它们会重建堆栈跟踪等。这是相当昂贵的。如果可以的话,尽量避免像这样的已知错误情况(这里是 C 风格):

public static const int INVERSE_DIVISION_BY_ZERO = -1; // any value not possible on correct operation will do

public int Inverse(int a)
{
    if (a != 0)
    {
        return 1 / a;
    }

    return INVERSE_DIVISION_BY_ZERO;
}

现在,我知道这个例子很丑。没有多大帮助。

其实从测试开始:

if (a != 0)
{
    b = Inverse(a);
}

现在是稍后测试的问题:

b = Inverse(a);
if (b != Inverse.INVERSE_DIVISION_BY_ZERO)
{
    // safe to go
}

这有意义吗?不,这个例子很糟糕。但它表明,为了保证代码执行的安全,需要做最少的测试。以前不做,以后还得做,但又不能真正避免。

对于异常的错误情况应该抛出异常,因为您无法控制它们,例如驱动器中没有CD等。当您已经知道代码如何失败时,请阻止它!

我想添加这个:

异常处理是一个复杂的设计问题,而不仅仅是一个实现问题。在这两个极端之间,他们需要权衡取舍:

  • 干净、可读的代码,偶尔会抛出异常;
  • 高效的代码难以阅读,但可以防止异常并返回需要解释的 C 样式错误代码。

常见的 C# 实践是倾向于干净、可读的代码而不是过早的优化,我同意这个愿景。

但这并不意味着应该故意抛出异常。在某些情况下,故意投掷非常有意义,例如:

enum Gender
{
    Unspecified,
    Male,
    Female
}

// later in the code

switch (gender)
{
    case Gender.Unspecified:
        // handle
        break;
    case Gender.Male:
        // handle
        break;
    case Gender.Female:
        // handle
        break;
    default:
        throw new ArgumentException(string.Format("Unrecognized gender (was {0})", (int)gender));
}

未定义的 gender 无法呈现为字符串,因为它未定义。相反,它将呈现为 int 值。

现在,在我看来,这个示例是清晰可读的代码,当将来修改 Gender 枚举时,它也很健壮。至少它会告诉开发者他忘记了什么……

另一个例子是这样的:

// A: high performance, let it throw
for (i = 0; i < values.length; i++)
{
    values[i] = 1 / values[i];
}

// B: slow performance, test first
for (i = 0; i < values.length; i++)
{
    if (values[i] != 0)
    {
        values[i] = 1 / values[i];
    }
}

values[i] 为0 A 将失败,而B 将忽略数组中的那个点。现在我想知道这个:

  • 阵列对未处理的点有用吗?也许到时候应该扔掉它;
  • 测试真的比抛出异常更高效吗?
  • 如果0 如此罕见,它每 1000 年发生一次(真的)怎么办?这就像说从不,而是基于概率估计。
  • 也许在每个周期中测试很可能不会发生的事情是一种浪费...

如果有可靠的数据表明错误情况极为罕见,则应在异常情况下处理它们,因为平均而言测试的成本要高得多。

这并不意味着你不处理异常状态,它只是意味着你以一种非有效的方式处理它,因为它很少见,而且事先测试成本很高。

编辑:添加错误条件

编辑 2:添加了更多想法

【讨论】:

  • 很好的例子。你有一个return 声明。如果是else(或者更确切地说是a = 0),你会向调用者返回什么?
  • 或者当 a = -1 .......
【解决方案2】:

当您想在某个时刻停止执行某个方法时,您应该使用return;

当您想本地化一段代码出错的原因时,可以使用异常和异常捕获。通过找出发生了什么类型的异常,您可以采取适当的措施。

【讨论】:

  • “例外,适用于特殊情况,”。它们往往是致命的。
  • @Vasile 使用“return;” method1 结束,但 Method2 将继续。如果 Method1 失败,使用 Exceptions 所有下一个方法都将停止。您将如何处理?
  • @MitchWheat 偷了我的台词吧 ;)?
  • @MitchWheat 是的,当然,只是在开玩笑。这是一个常见的短语,从字面上看就是范式的含义。
【解决方案3】:

不,在 -well- exceptional 状态下抛出异常是 good 设计。

另一方面,我看到代码过度使用异常来基本上将函数结果传回给调用者。 是一个糟糕的设计,很容易看出这些异常不是在异常状态下抛出,而是在常规操作期间以高频率抛出。

【讨论】:

    【解决方案4】:

    例外情况除外。

    例如,如果您想验证一个电子邮件地址,您可以编写一个电子邮件验证器。

    public bool Validate(string emailAddr)
    {
        // Logic
        return false; // validation failed.
    }
    

    然后在你的主要逻辑中你可以写:

    if(Validate(emailInpunt))
    {
        // Do stuff.
    
    }
    

    这样你的代码就变得非常可读了。

    如果你使用异常,它看起来像:

    try
    {
        Validate(emailInpunt);
    }
    catch
    {
         // Something
    }
    

    已经在上面的例子中,代码更难阅读,控制流程会有点困难,如果你想添加不同的案例,fx.验证电子邮件,或者检查一些表明它应该忽略它的值。

    此外,嵌套大量的 try catch 更难阅读。

    try
    {
        Validate(emailInpunt);
    
        try
        {
            DoSomething(otherStuff);
        }
        catch
        {
             // Something
        }
    
    }
    catch
    {
         // Something
    }
    

    正如您所提到的,异常很昂贵,因为它会构建一个巨大的堆栈跟踪,并且通常会停止整个程序。

    正确使用异常的示例

    try
    {
        SqlConnection con = new SqlConnection(....);
        // Do stuff with the database connection
    
    }
    catch(SqlException sex)
    {
         // Log this
         throw new UserSomethingException("A connection to the database could not be established");
    }
    

    异常作为断言
    异常也用于告诉开发人员编程错误。

    考虑一下:

    public double Calculate(MeaningOfLifeTheUniverseAndEverythingQuestion bigQuestion)
    {
         if(bigQuestion == null)
             throw new ArgumentException("MeaningOfLifeTheUniverseAndEverythingQuestion cannot be null!!");
    
         //  7½ million years of computing
         return 42;
    }
    

    在这种情况下,我们抛出一个错误,让程序员知道他的代码中出现了错误。

    【讨论】:

    • 谢谢。如果你看到与 Try Catch 我写的 Method1,Method2,在同一级别。不是嵌套。使用 IF 方式,我必须将 Method2 嵌套在 Method1 中。我对吗?不嵌套使用非异常方式是否可行?
    • @RicardoPolo 是的,有SOLID 原则和少量Polymorphism
    • @RicardoPolo 如果您希望 cmets 了解您的特定逻辑,以及如何改进它,您需要提出一个新问题。
    猜你喜欢
    • 1970-01-01
    • 2011-10-06
    • 2019-01-13
    • 1970-01-01
    • 1970-01-01
    • 2015-10-14
    • 2015-03-09
    • 1970-01-01
    • 2021-12-06
    相关资源
    最近更新 更多