当走了无效的代码路径时应该抛出哪个异常?

21

我发现自己写了一些方法,其中存在一些永远不应该发生的代码路径。这里是一个简化的例子:

double Foo(double x) {
    int maxInput = 100000;
    double castMaxInput = (double)maxInput;
    if (x < 0 || x > castMaxInput || double.IsNaN(x)) {
        return double.NaN;
    }
    double r = 0;
    for (double boundary = 1; boundary<=castMaxInput; boundary++) {
        if (x <= boundary) {
            r += boundary * (x + 1 - boundary);
            return r;
        }
        else {
            r += boundary;
        }
    }

    // we should never get here.
    throw new SomeException();
}

在这里最合理的异常情况可能是像这样的:

TheAuthorOfThisMethodScrewedUpException() 

如果我们到达for循环的结尾,那么这就是正在发生的事情。不幸的是,使用上述结构化方法,编译器似乎无法聪明地推断出for循环后面的代码永远不会发生。因此,你不能什么都不写,否则编译器会抱怨“并非所有代码路径都返回值”。是的,在循环后面再加入return double.NaN是可行的。但那样会掩盖问题的来源。

我的问题是 - 是否有一个合适的异常?


1
一个自定义的吗?http://msdn.microsoft.com/zh-cn/library/87cdya3t(v=vs.110).aspx - bto.rdz
“//我们永远不应该到达这里。”的意思是什么?看起来你应该在方法开始时检查参数的有效性,如果收到意外的内容就抛出异常。 - Ian R. O'Brien
4
我会从编写正确的代码开始。 - Federico Berasategui
1
你应该重新考虑你的方法。我不确定这是否是真正的代码,但我相当确定你整个方法可以改成一行代码,并且去掉那可怕的for循环。 - Federico Berasategui
1
我的生产代码确切地为这种事情设置了NotSupposedToReachHereExeption,哈哈。 - Tamir Vered
显示剩余2条评论
5个回答

9

我使用InvalidOperationException来处理这个问题。它表示应用程序已经达到了不应该出现的状态。

throw new InvalidOperationException("Invalid state.");

您还可以使用Debug.Assert来断言某些内容是正确的,或者在执行到特定点时简单地使用Debug.Fail

Debug.Fail("This should never happen!");

但是在发布模式下,调试断言/失败不起作用,只有当定义了DEBUG条件时才起作用。是否需要视您的要求而定。
正如@AlexD正确指出的那样,还有Trace及其相应的AssertFail方法,将在运行时起作用。帮助隔离和解决问题,而不会干扰正在运行的系统,当定义了TRACE条件时(在项目属性构建选项卡中默认设置)。
顺便说一下,回答标题中的问题:如果您想创建自己的异常,可以这样做。
[Serializable]
public class TheAuthorOfThisMethodScrewedUpException : InvalidOperationException
{
    private const string DefaultMessage = "The author of this method screwed up!";

    public TheAuthorOfThisMethodScrewedUpException()
        : this(DefaultMessage, null)
    { }

    public TheAuthorOfThisMethodScrewedUpException(Exception inner)
        : base(DefaultMessage, inner)
    { }

    public TheAuthorOfThisMethodScrewedUpException(string message)
        : this(message, null)
    { }

    public TheAuthorOfThisMethodScrewedUpException(string message, Exception inner)
        : base(message, inner)
    { }

    protected TheAuthorOfThisMethodScrewedUpException(
      System.Runtime.Serialization.SerializationInfo info,
      System.Runtime.Serialization.StreamingContext context)
        : base(info, context)
    { }
}

把它扔给别人。
throw new TheAuthorOfThisMethodScrewedUpException();

在发布模式下(假设已定义TRACE),您可以使用Trace.Assert / Trace.Fail - AlexD

7

2

不要使用new Exception()语句,这会导致代码无法正确捕捉异常。你可以使用通用的、具体的异常类型:

throw new InvalidOperationException("Appplication invariants violated");

这假设你希望错误在生产中发生,认为错误比发射导弹并结束世界更好。其他开发者则更愿意使用一种方法,假设不变量可以在生产中被忽略但不能在开发时被忽略,我们无论是否结束世界都不在乎。

根据MSDN,InvalidOpertionException的意思是“当方法调用对于对象的当前状态无效时抛出的异常。”但这并不完全符合我们的情况。 - AlexD
它被用作一个万能异常处理。对于上述情况,由于代码在到达该点时已经有缺陷/不再受信任,因此所有状态下的方法调用都是无效的。据我所知,在BCL中没有其他万能异常处理,MSDN也不鼓励应用程序编写者使用。 - MatthewMartin
我也不知道一个通用的“不应该到达这里”的异常的好替代方案,寻找它也考虑了 InvalidOperationException。但是,我对语义并不完全满意。InvalidOperationException 更适合报告 客户端的误用(客户端在不满足前提条件的情况下调用方法),而不是报告内部错误,例如我们的情况。 - AlexD

1

创建自定义异常看起来很容易

public class TheAuthorOfThisMethodScrewedUpException: Exception
{
    public EmployeeListNotFoundException()
    {
    }

    public EmployeeListNotFoundException(string message)
        : base(message)
    {
    }

    public EmployeeListNotFoundException(string message, Exception inner)
        : base(message, inner)
    {
    }
}

那么

throw new TheAuthorOfThisMethodScrewedUpException("I am so sorry, this should never happen call me for more info")

0

简单!使用代码片段!

Exception + TAB + TAB

它将为您创建一个新的异常。此片段生成以下内容。

[Serializable]
    public class MyException : Exception
    {
        public MyException() { }
        public MyException(string message) : base(message) { }
        public MyException(string message, Exception inner) : base(message, inner) { }
        protected MyException(
          System.Runtime.Serialization.SerializationInfo info,
          System.Runtime.Serialization.StreamingContext context)
            : base(info, context) { }
    }

你只需要改变名称,然后就完成了!;)


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