什么是好的“错误检查”模式(Java)?

时间:2010-06-10 13:28:31

标签: java error-handling

我将解释输入错误检查的含义。

假设您有一个功能doSomething(x)

如果函数成功完成,doSomething会执行某些操作并且不返回任何内容。 但是,如果有错误,我希望得到通知。这就是错误检查的意思。

一般来说,我一直在寻找检查错误的最佳方法。我想到了以下解决方案,每个解决方案都存在潜在问题。

  1. 标记错误检查。如果doSomething(x)成功完成,则返回null。否则,它返回一个布尔值或错误字符串。 问题:副作用。

  2. 抛出异常。如果doSomething(x)遇到错误,则抛出异常。 问题:如果您仅对参数执行错误检查,则抛出IllegalArgumentException似乎不合适。

  3. 在函数调用之前验证输入。如果错误检查仅用于函数的参数,则可以在调用doSomething(x)函数之前调用验证器函数。 问题:如果该类客户在调用doSomething(x)之前忘记调用验证程序函数该怎么办?

  4. 我经常遇到这个问题,非常感谢任何帮助或正确方向上的一点。

9 个答案:

答案 0 :(得分:6)

抛出异常是最好的方法。

  

如果您只对参数执行错误检查,请抛出一个   IllegalArgumentException似乎不合适。

为什么呢?这就是这个例外的目的。

答案 1 :(得分:5)

  
      
  1. 标记错误检查
  2.   

这在某些情况下是合适的,具体取决于“错误”的含义。

API的一个示例:如果您尝试将对象添加到Set,其中已包含equals新对象的另一个对象,add方法排序“失败“并通过返回false来表明这一点。 (请注意,我们处于技术上甚至不是“错误”的水平!)

  

2. 抛出异常

这是默认选项。

现在问题是,您是否应该检查已检查的异常(需要throws声明或try / catch子句)或未经检查的异常(扩展{{ 1}})。这里有一些拇指规则。

来自Java Practices -> Checked versus unchecked exceptions

  • 未经检查的例外:代表程序中的缺陷(错误) - 通常会将无效参数传递给非私有方法。

  • 已检查的例外:代表程序直接控制范围之外的区域中的无效条件(无效的用户输入,数据库问题,网络中断,缺少文件)

请注意,RuntimeException是一个未经检查的异常,非常适合在参数不符合时抛出。

如果你想抛出一个已检查的异常,你可以通过扩展IllegalArgumentException来推动自己的异常,B)使用一些现有的已检查异常或C)“链”一个运行时异常,例如{ {1}}:Exception

  

3. 在函数调用之前验证输入

依赖于客户应该在通话之前清理/检查他的论点这对我来说是一个坏主意。

答案 2 :(得分:3)

您的第二个建议(“抛出异常”)是最佳选择。另外两个选项依赖于调用者之前做某事(“在函数调用之前验证输入”)或之后(“标记错误检查”)对方法的调用。无论哪种方式,额外的任务都不是由编译器强制要求的,所以调用该函数的人不会被迫调用“额外的东西”,因此在运行时才会发现问题。

至于“抛出异常”和你建议的“问题”,答案是为代码抛出适当的异常类型。如果输入参数无效,则抛出InvalidArgumentException(因为这是相应的错误)。如果异常是针对功能的(例如,无法打开网络连接),请使用其他异常类型或创建自己的异常类型。

答案 3 :(得分:2)

我同意抛出异常。我想添加另一个结合#2和#3的选项 - 代理模式。这样,您的代码就能保持相当的凝聚力 - 在一个地方验证,在另一个地方验证业务逻如果您有大量需要验证的调用,这是有道理的。

创建proxy来处理验证。让它在验证后委托对业务逻辑接口的实际实现的所有调用,否则如果某些内容无法验证,它会抛出异常。

答案 4 :(得分:1)

我决定通常在接口类型上使用哪种方法。

  • 用户界面(GUI):我在调用业务方法之前验证,因为用户想知道出了什么问题。

  • 在组件或系统之间的技术接口上,接口应该经过测试并正常工作,在这种情况下,我会抛出异常。

答案 5 :(得分:1)

例外是要走的路。通过正确实现异常抛出/处理,可以减轻您所述的异常问题。通过在您需要的最低级别验证参数并在验证失败时抛出异常来使用例外优势。这样可以避免在调用堆栈中的多个级别上冗余地检查有效性。将异常抛到底部,让堆栈展开到适当的位置以处理错误。

答案 6 :(得分:1)

您选择的方法取决于具体情况,并且它们不是相互排斥的,因此您可以将它们全部混合在同一个解决方案中(尽管这是否真的取决于您的情况)。

  1. 如果您想要一种非常简单的方法来处理错误,请选择此方法。对于调用函数可以接受被调用函数返回的任何值的情况,此方法可能正常。可能存在业务逻辑将此视为可选的情况,例如在无法正确定位资源时返回特定的消息字符串,或者服务器没有响应。一般来说,我不会使用它或在Java中看到这种技术,因为异常是一种更好的错误处理机制。

  2. 当您的函数遇到未定义的行为时抛出异常。如果你有一个只能在正整数上运行且有人传递-1的数学函数,你应该抛出一个InvalidArguementException。如果为您的函数提供了数据库中产品的ID,但查询无法找到该产品,则可能会抛出自定义的ProductNotFound异常。

  3. 验证输入是一个好主意,我想它应该由被调用函数而不是调用者来完成 - 除非调用者可以通过在传递之前验证输入来避免来自被调用者的异常。如果您使用支持按合同设计的语言,则验证输入将作为函数的前提条件完成。

  4. 我通常使用#2和#3。我有一段时间没有用错误标志编写代码。例外情况可能是返回枚举的函数,其中一个可能的值表示错误代码。这更多地受到商业规则的驱动。

    一般来说,尽量保持简单。

答案 7 :(得分:0)

抛出自定义检查的异常。

 doSomething(WithX x ) throws BusinessRuleViolatedException 

答案 8 :(得分:0)

输入验证非常复杂,原始帖子中的所有三种建议方法都是必需的,有时甚至更多。如果输入超出业务逻辑的范围,例如它已损坏或无法读取,则异常是适当的。

如果要检查多个或两个标志,则快速标记检查将成为反模式,并且可以使用稍微专用的访问者模式替换。我不知道这个特定模式的确切名称,但我会非正式地将其称为“验证器列表模式”,并将在下面更详细地描述它。

提前检查输入并快速失败通常很好,但并非总是可行。通常会有批次输入验证,从控件外部收到的所有输入都应被视为敌对并需要验证。良好的程序设计和架构将有助于明确何时需要实现。

'验证者列表模式'

作为一个例子,让我们首先在代码中描述“验证标志”反模式,然后我们将其转换为“验证列表”模式。

public Optional<String> checkForErrorsUsingFlags( 
       ObjectToCheck objToCheck ) {
  // the small series of checks and if statements represent the
  // anti-pattern. Hard to test and many other problems crop up.
  String errMsg = checkForError1( objToCheck );
  if(errMsg != null ) {
    return Optional.of(errMsg);
  }
  errMsg = checkForError2( objToCheck );
  if(errMsg != null ) {
    return Optional.of(errMsg);
  }
  return Optional.empty();
}

/**** client usage  ****/
ObjectToCheck obj = doSomethingToReadInput(obj);
Optional<String> error = checkForErrors( obj);
if (error.isPresent()) {
  // invalid input, throw object away and request input again
} else {
  // do stuff, we have a valid input
}

要修复,首先要创建一个代表单个验证器的通用接口。然后将每个检查转换为验证器实例。最后,我们创建一个验证器列表并将其传递给验证器代码。

/** The common validator interface each validator will use */
private interface MyValidator {
  public boolean isValid(ObjectToCheck obj);
  public String getErrorMessage(ObjectToCheck obj);
}

 // this method should look familiar to the above, now we 
 // have a list of validators as an additional parameter
public Optional<String> checkForErrors( ObjectToCheck objToCheck,
     List<MyValidator> validators ) {
  for(MyValidator validator : validators ) {
    if (!validator.isValid(objToCheck)) {
      String errMsg = validator.getErrorMessage(objToCheck);
      return Optional.of(errMsg);
    }
  }
  return Optional.empty();
}

/****** client usage  *****/
// now in this pattern, the client controls when the validators
// are created, and which ones are used.
MyValidator validator1 = new MyValidator() {
  @Override
  public boolean isValid(ObjectToCheck obj) {
    return checkForError1( objToCheck ) != null;
  }
  @Override
  public boolean getErrorMessage(ObjectToCheck obj) {
    return checkForError1( objToCheck );
  }
}
// note: above we call checkForError1 twice, not optimal.
// typical in real examples this can be avoided,
// and the error message generation logic split from the detection
// logic often simplifies things.
MyValidator validator2 = new MyValidator() { ... }

List<MyValidator> validators = 
  ImmutableList.of( validator1, validator2);
Optional<String> error = checkForErrors(objToCheck, validators);
if (error.isPresent()) {
    // invalid input, throw object away and request input again
} else {
    // do stuff, we have a valid input
}

现在进行测试,创建一系列模拟验证器并检查每个验证器是否都调用了它们。您可以存根验证器结果并确保采取正确的行为。然后您还可以单独访问每个验证器,以便您可以逐个测试它们。 干杯 - 希望有帮助,快乐编码。