0

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

这段代码说明了这一点:

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 是“不好的”。说明风险或不利因素以证明不使用这种简单技术的合理性。

4

7 回答 7

1

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

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

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

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

不管怎样,你最终都会返回 nil 。

于 2013-05-14T16:58:46.883 回答
0

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

枚举/值对象的概念确实感觉有点矫枉过正,但通常需求会扩大,您最终会需要类似的东西。

在可能有多个错误的情况下,我喜欢返回一个字符串列表,一个空列表显然意味着没有问题。在这种情况下,返回null,返回一个空列表。

于 2013-05-14T17:17:16.420 回答
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):
    }
}
于 2013-05-14T15:16:47.330 回答
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; }
}
于 2013-05-14T15:18:26.167 回答
0

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

似乎没有理由进行多层检查然后在一个函数足以引发错误时获取错误。

于 2013-05-14T15:18:57.590 回答
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[]的方法也可以解决问题,因为您可以返回 anint作为错误代码并将其用作索引该数组以获取您可以解释、显示和本地化的实际代码。

于 2013-05-14T15:28:18.210 回答
0

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

于 2013-05-14T15:21:47.760 回答