抛出额外的异常以避免代码重复

时间:2012-05-09 09:47:10

标签: exception theory code-duplication

首先,我知道标准答案是永远不会将异常用于流量控制。虽然我完全同意这一点,但我一直在思考我有时会做的事情,我将用以下伪代码描述:

try
    string keyboardInput = read()
    int number = int.parse(keyboardInput)
    //the conversion succeeds
    if(number >= 1000) 
        //That's not what I asked for. The message to display to the user
        //is already in the catch-block below.
        throw new NumberFormatException() //well, there IS something wrong with the number...
 catch(NumberFormatException ex)  //the user entered text
    print("Please enter a valid number below 1000.")

首先,以非常抽象的方式采用这个例子。这不一定要发生。情况就是:

  

用户输入需要受到约束,并且可能有两种方式出错,   或       通过语言定义的抛出异常或通过检查。两个错误       用户以相同的方式报告,因为他们不需要知道       造成它的原因的技术差异。

我想到了几种解决方法。首先,抛出自定义异常会更好。我接下来面临的问题是,如果我在本地捕获它,该如何处理另一个异常?在se中,自定义异常将导致第二个catch块,其中消息也将被复制到其中。我的解决方案:

//number is wrong
throw new MyException()
catch(NumberFormatException ex) 
    throw new MyException()
catch(MyException ex) {
    print("Please enter...")

例外名称的含义就是这里的一切。定制异常的这种应用被广泛接受,但本质上我没有做任何与第一种方式不同的事情:我被迫进入一个捕获块,尽管抛出一个自定义异常而不是一个标准库。

应用于将异常抛出到调用方法(因此没有自定义异常的catch块)的相同方法似乎更有意义。我的方法在技术上两种方式可能出错,但基本上一种方式:错误的用户输入。因此,人们会写一个UserInputException并让方法抛出这个。新问题:如果这是应用程序的主要方法会怎样?

我目前没有在特定的应用程序中努力实现这种行为,我的问题纯粹是理论非语言特定。

解决此问题的最佳方式是什么?

6 个答案:

答案 0 :(得分:5)

我会认为第一个异常是低级别的,我会在调用时处理它(在这种情况下通过翻译)。我发现这会导致代码更容易维护和重构,因为您需要处理的异常类型较少。

try
  string keyboardInput = read()
  try
    int number = int.parse(keyboardInput)
  catch(NumberFormatException ex)
    throw MyException("Input value was not a number")

  //the conversion succeeds
  if(number >= 1000) 
    throw MyException("Input value was out of range")

catch(MyException ex)  //the user entered text
  print( ex.ToString() )
  print("Please enter a valid number below 1000.")

答案 1 :(得分:1)

在此特定示例中,您不需要任何例外。

int number;
if (int.TryParse(keyboardInput, out number) && number < 1000) // success
else // error

但是,您描述的情况在商业软件中很常见,并且抛出异常以达到统一处理程序是很常见的。

一种这样的模式是XML验证,然后是XSLT。在某些系统中,通过捕获验证异常来处理无效的XML。在这些系统中,在XSLT中重用现有的异常处理是很自然的(它可以自然地检测特定验证语言不能的某些类型的数据错误):

<xsl:if test="@required = 'yes' and @prohibited = 'yes'>
    <xsl:message terminate='yes'>Error message</xsl:message>
</xsl:if>

重要的是要看到,如果这种情况非常罕见(预计仅在早期集成测试期间发生,并且随着其他模块中的缺陷得到修复而消失),most of the typical concerns周围没有使用流控制的异常真的适用。

答案 2 :(得分:1)

我看到它的方式是:

假设没有其他方法可以解析不会抛出异常的int,那么现在的代码就是正确而优雅的。

唯一的问题是如果您的代码处于某种循环中,在这种情况下您可能会担心抛出和捕获不必要的异常的开销。在这种情况下,您将不得不妥协一些代码的美丽,而只是在必要时处理异常。

error=false;

try {
    string keyboardInput = read();
    int number = int.parse(keyboardInput);
    //the conversion succeeds
    if(number >= 1000) {
        //That's not what I asked for. The message to display to the user
        //is already in the catch-block below.
        error=true;
} catch(NumberFormatException ex) { //the user entered text
    error=true;
}

if (error)
    print("Please enter a valid number below 1000.");

此外,您可以考虑为什么要尝试将两个错误合并为一个错误。 相反,您可以告知用户他们做了什么错误,在某些情况下可能会更有帮助:

try {
    string keyboardInput = read();
    int number = int.parse(keyboardInput);
    //the conversion succeeds
    if(number >= 1000) {
        //That's not what I asked for. The message to display to the user
        //is already in the catch-block below.
        print("Please enter a number below 1000.");

} catch(NumberFormatException ex) { //the user entered text
    print("Please enter a valid number.");
}

答案 3 :(得分:1)

我认为你基本上有几种方法可以解决这个问题:

  1. 使用布尔变量/存储异常:如果您正在执行的特定任务的常规逻辑中的任何位置出现错误,则退出第一个错误标志并在单独的错误处理中处理该错误分支。

    优点:只有一个地方可以处理错误;您可以使用任何您喜欢的自定义异常/错误条件。

    缺点:你想要达到的目标的逻辑可能很难发现。

  2. 创建一个通用函数,您可以使用该函数通知用户错误(预先计算/存储描述一般错误的所有信息,例如显示用户的消息),这样您就可以制作一个发生错误情况时的函数调用。

    优点:对于代码的读者,您的意图逻辑可能更清晰;您可以使用anu自定义异常/错误条件。

    缺点:错误必须在不同的地方处理(尽管使用预先计算/存储的值,复制粘贴不多,但通知用户部分却很复杂)。

  3. 如果意图明确,我不认为明确地从你的try块中抛出异常是一个坏主意。如果您不想抛出系统提供的异常之一,您可以随时创建自己的异常,这样您只需要一个最小数量(最好是一个)的catch块。

    优点:只有一个地方可以处理错误条件 - 如果在try-block中基本上只抛出一种类型的异常。

    缺点:如果抛出多种类型的异常,则需要嵌套的try-catch块(将异常传播到最外层的)或非常一般的(例如Exception)catch块避免重复错误报告。

答案 4 :(得分:1)

如何通过编写几个接受输入并返回错误或没有错误的验证程序类来解决此验证问题。至于你与异常的斗争:将这个逻辑放入每个验证器并根据具体情况处理它。

之后你会找出用于输入的正确验证器,收集它们的错误并处理它们。

这样做的好处是:

  1. 验证者做一件事,验证一个案例
  2. 由验证函数决定如何处理错误。你是否打破了第一次验证错误,或者你是否收集它们然后处理它们?
  3. 您可以编写代码,主要验证功能可以使用相同的代码验证不同类型的输入,只需使用您喜欢的技术选择正确的验证器。
  4. 和缺点:

    1. 您最终会编写更多代码(但如果您使用的是java,则应将其放入'好处'桶中)
    2. 这里有一些伪代码示例:

      validate(input):
         validators = Validator.for(input.type)
         errors = []
         for validator in validators:
             errors.push(validator.validate(input))
      
         if errors:
             throw PoopException          
      

      和一些验证员:

      MaxValidator extends IntValidator: 
          validate(input):
              errors = []
              errors.push(super.validate(input))
              if input > 1000:
                  errors.push("bleee!!!! to big!")
              return errors
      
      IntValidator:
           validate(input):
              try:
                 int.parse(input)
              catch NumberFormatException:
                 return ['not an int']
              return []
      

      当然你需要做一些技巧来使父验证器可能返回一个有效的输入版本,在这种情况下字符串“123”转换为int所以max validator可以处理它,但这可以是通过使验证器具有状态或其他魔法来轻松完成。

答案 5 :(得分:1)

我在这里的任何地方都看不到这个答案,所以我只是将其作为另一种观点发布。

众所周知,如果您对它们了解得足够多,那么可以实际违反规则,因此如果您知道它是适合您情况的最佳解决方案,则可以使用Exception进行流量控制。从我所看到的情况来看,通常会出现一些愚蠢的框架......

那就是说,在Java 7之前(它为我们带来了强大的multicatch construct),这是我避免代码重复的方法:

try {
    someOffendingMethod();
} catch (Exception e) {
    if (e instanceof NumberFormatException || e instanceof MyException) {
        System.out.println("Please enter a valid number.");
    }
}

这也是C#中的一种有效技术。