从"get error message"方法返回 null 是反模式吗?



这是"不好的做法"吗?在"没有发现问题"的情况下,通过返回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;
}

这会造成类膨胀,而且有点笨拙。

是"坏"吗?";overload"以这种方式返回值?
它当然"整洁"。


如果你提供了一个替代方案,说明为什么你认为返回null是"不好的"。说明不使用这种简单技术的风险或缺点。

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

  1. 返回一个表示"错误"的值(如果函数可以/确实返回值)。
  2. 在"错误指针"参数指示的位置返回错误代码给函数。
  3. 抛出异常
  4. 将控制传递给先前定义的(或参数指定的)"delegate"或"callback".
  5. 更新全局错误指示器
  6. 调用全局错误例程。

这些都不是特别吸引人——我们知道,全局是不好的,委托/回调是笨拙和冗长的,异常是缓慢的(和野兽的标志,我们都知道)。所以前两个是最常用的选项。

对于2,如果您确实从值返回函数返回错误,则仍然需要返回值,这是一个问题。而且,对于对象返回函数,nil是最符合逻辑的返回值。

最终返回nil。

我会使用:

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):
    }
}

您的初始解决方案非常好,null非常适合表示"不存在"返回值。

我决定把我的评论变成一个答案。我认为这是一个很好的枚举上下文,像这样:

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[]也可以做到这一点,因为您可以返回int作为错误代码,并将其用作该数组的索引,以获得您可以解释,显示和本地化的实际代码。

不需要检查isOK()然后获得错误消息,只需在isOK()中添加一个带有适当消息的抛出异常,然后使用try/catch来显示错误消息。

似乎没有理由有多层检查,然后得到错误,当一个函数足以抛出。

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

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

首先你可以使用异常:

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

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

enum/value对象的概念确实感觉有点夸张,但通常需求会扩展,您最终会需要这样的东西。

在可能存在多个错误的情况下,我喜欢返回一个List of Strings,空列表显然意味着没有问题。在这种情况下,do not返回null,返回空列表。

相关内容

  • 没有找到相关文章

最新更新