使用异常来结束功能是不是很糟糕?

时间:2014-01-26 23:23:51

标签: c# .net exception

我有一个应用程序,我使用了很多Exceptions

最近我听说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
}

这是不好的设计吗? 这会损害我的App性能吗?

4 个答案:

答案 0 :(得分: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));
}

未定义的性别无法呈现为字符串,因为它未定义。它将呈现为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:增加了一些想法

答案 1 :(得分:0)

如果要在某个时刻停止执行某个方法,则应使用return;

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

答案 2 :(得分:0)

不,在-well- 异常状态下抛出异常是良好的设计。

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

答案 3 :(得分:0)

例外情况适用于例外个案。

例如,如果您要验证电子邮件地址,请编写电子邮件验证程序。

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

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

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

}

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

如果您使用例外,它将如下所示:

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

在上面的示例中,代码更难以阅读,如果要添加不同的情况fx,控制流程会更难。验证电子邮件,或检查一些表示应忽略它的值。

此外,筑巢大量的尝试捕获量,更难以阅读。

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

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