从“获取错误消息”方法返回null反模式?

时间:2013-05-14 15:12:58

标签: java coding-style result-object

在“没有发现问题”的情况下,通过返回null来有效地缓存执行昂贵的无状态检查代码的结果是“不良做法”吗?好处是最小的代码,没有类/代码膨胀。

此代码说明了这一点:

public static String getErrorMessage(SomeState x) {
    // do some "expensive" processing with "x"
    if (someProblem)
        return "Some problem";
    if (someOtherProblem)
        return "Some other problem";
    return null; // no error message means "all OK"
}

调用代码:

String message = getErrorMessage(something);
if (message != null) {
    display(message);
    return; 
}
// proceed

这种模式避免了必须重复执行昂贵的代码两次,方法是返回null表示“没有错误消息,因为没有错误”。并且没有额外的“低价值”类/代码。

显而易见的替代方案是A)将检查和消息创建的问题分开:

public static boolean isOK(SomeState x) {
    // do some "expensive" processing with "x"
    return thereIsNoProblem;
}

public static String getErrorMessage(SomeState x) {
    // do some "expensive" processing with "x"
    if (someProblem)
        return "Some problem";
    if (someOtherProblem)
        return "Some other problem";
}

调用代码:

if (!isOK(something)) {
     display(getErrorMessage(something)); // expensive code called a second time here
     return;
}
// proceed

执行昂贵的代码一次以确定如果出现问题,再次确定 问题是,或者B)返回一个“结果”对象,它有一个布尔字段来回答“if”部分和一个字符串字段来回答“消息”部分,例如

class MyResult { // like a struct in C
    boolean ok;
    String message;
    // accessor methods omitted 
}

public static MyResult verify(SomeState x) { ...}

调用代码:

MyResult result = verify(something);
if (!result.ok) { // spare me the lecture on accessors - this is illustrative only
    display(result.message);
    return;
}

会造成类膨胀,并且有点笨拙恕我直言。

以这种方式“超载”返回值是“坏”吗? 它肯定比我能想到的所有替代方案都“整洁”。


如果您提供替代方案,请说明为什么您认为返回null是“坏”。说明不使用这种简单技术的风险或不利因素。

7 个答案:

答案 0 :(得分:1)

在被调用函数中报告错误/异常情况的选项数量有限:

  1. 返回一个值(如果函数可以/确实返回一个值),表示“错误”。
  2. 在函数的“错误指针”参数指示的位置返回错误代码。
  3. 抛出异常。
  4. 将控制权传递给之前定义的(或参数指定的)“委托”或“回调”。
  5. 更新全局错误指示器。
  6. 调用全局错误例程。
  7. 这些都不是特别有吸引力 - 全局是坏的,我们知道,委托/回调是笨拙和冗长的,例外是缓慢的(和野兽的标记,我们都知道)。所以前两个是最常用的选项。

    如果你确实从值返回函数返回错误,那么你仍然需要返回一个值。并且,对于对象返回函数,nil是最合理的返回值。

    无论如何,你最终都会回来。

答案 1 :(得分:0)

我会用:

public class ValidationResult {

    public final boolean isValid;

    public final String errorMessage; // may be null if isValid == true

    public ValidationResult(boolean isValid, String errorMessage) {
        if (!isValid && errorMessage == null) {
            throw new IllegalArgumentException();
        }
        this.isValid = isValid;
        this.errorMessage = errorMessage;
    }
}

然后:

public static ValidationResult validate(SomeState x) {
    // blah
    return new ValidationResult(isValidTest, errorMessageIfNot);
}

ValidationResult r = validate();
if (!r.isValid) {
    display(r.errorMessage);
}

或者,您可以让它抛出一个已检查的异常。

public class ValidationException extends Exception {
    // your code here
}

然后:

public static boolean validate(SomeState x) throws ValidationException {
    // ...
    if (isValid) {
        return true;
    } else {
        throw new ValidationException(message):
    }
}

答案 2 :(得分:0)

只要你自己使用它就可以了,但它可以以某种方式得到改善。关于为什么可以改进,让我引用Tony Hoare

  

我称之为十亿美元的错误。它是1965年空引用的发明。那时,我正在设计第一个用于面向对象语言(ALGOL W)的引用的综合类型系统。我的目标是确保所有引用的使用绝对安全,并由编译器自动执行检查。但我无法抵制引入空引用的诱惑,仅仅因为它很容易实现。这导致了无数的错误,漏洞和系统崩溃,这可能在过去四十年中造成了数十亿美元的痛苦和损害。

首先,您可以使用例外:

public static String getErrorMessage(SomeState x) throws YourException {
    // do some "expensive" processing with "x"
    if (someProblem)
        throw YourException("Some problem");
    if (someOtherProblem)
        throw YourException("Some other problem");
    return null; // no error message means "all OK"
}

然后你可以有一个自定义对象:

class ErrorState {
  boolean hasFoundError;
  String message;

  ErrorState() {
    hasFoundError = false;
  }

  ErrorState(String message) {
    hasFoundError = true;
    this.message = message;
  }

  public final static ErrorState NO_ERROR = new ErrorState();
}

最后,如果潜在错误的集合是有限的,你可以使用enum(我认为这是更好的选择):

enum ErrorState {
  NO_ERROR(""),
  SOME_ERROR("Some error"),
  SOME_OTHER_ERROR("Some other error");

  public final String message;

  ErrorState(String message) { this.message = message; }
}

答案 3 :(得分:0)

不是检查isOK()然后获取错误消息,只需使用适当的消息向isOK()添加throws异常,然后使用try / catch显示错误消息。

似乎没有理由进行多层检查,然后在一个函数满足抛出时获取错误。

答案 4 :(得分:0)

您的初始解决方案非常精细,null非常适合表示“缺少”返回值。

答案 5 :(得分:0)

我决定将我的评论作出答案。我认为这是枚举的一个很好的上下文,如下所示:

public enum OperationStatus {
    ERROR1 {
        @Override
        public String toString() {
            return "err1";
        }
    }, 
    ERROR2
    //The same as error 1
    //
    //Many error types here
    SUCCESS {
        @Override
        public String toString() {
            return "ok";
        }
    }
}

这样,您的方法可以返回此枚举的值,并使用您可以作为String获取的代码指定实际错误。特别是如果您计划本地化此类错误,例如,您可以切换本地化的捆绑包并获取每个已定义代码的关联值。

另一方面,如果您不打算在将来添加错误,或者如果出现新的错误类型,您可以承担此枚举所需的偶尔修改,则此方法可行。

一个替代方案(你仍然需要更新这个数组,但是你要防止自己有一个枚举)来避免枚举,一个简单的Stirng[]也可以做到这一点,因为你可以返回一个{{ 1}}作为错误代码,并将其用作该数组的索引,以获取您可以解释,显示和本地化的实际代码。

答案 6 :(得分:0)

我非常反NULL,而且,就个人而言,将返回空字符串""表示没有错误,或者可能是"OK"之类的常量。但是null是可以接受的。

枚举/值对象概念确实有点像矫枉过正,但通常需求会扩展,最终你需要这样的东西。

如果可能有多个错误,我想返回一个字符串列表,一个空列表显然没有问题。在这种情况下,请 not 返回null,返回一个空列表。