这是"不好的做法"吗?在"没有发现问题"的情况下,通过返回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是"不好的"。说明不使用这种简单技术的风险或缺点。
在被调用的函数中报告错误/异常情况的选项数量有限:
- 返回一个表示"错误"的值(如果函数可以/确实返回值)。
- 在"错误指针"参数指示的位置返回错误代码给函数。
- 抛出异常
- 将控制传递给先前定义的(或参数指定的)"delegate"或"callback".
- 更新全局错误指示器
- 调用全局错误例程。
这些都不是特别吸引人——我们知道,全局是不好的,委托/回调是笨拙和冗长的,异常是缓慢的(和野兽的标志,我们都知道)。所以前两个是最常用的选项。
对于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,返回空列表。