在 Java 中定义错误代码/字符串的最佳方法?

我正在用 Java 编写一个 Web 服务,我是 试图找出定义错误代码及其相关错误字符串的最佳方法。我需要有一个数字错误代码和错误字符串组合在一起。错误代码和错误字符串都将发送给访问 Web 服务的客户端。例如,当发生 SQLException 时,我可能希望执行以下操作:

// Example: errorCode = 1,
//          errorString = "There was a problem accessing the database."
throw new SomeWebServiceException(errorCode, errorString);

客户端程序可能会显示以下信息:

”出现错误 # 1: 有一个 访问数据库的问题。”

我的第一个想法是使用错误代码的 Enum并覆盖 toString方法以返回错误字符串。以下是我的想法:

public enum Errors {
DATABASE {
@Override
public String toString() {
return "A database error has occured.";
}
},


DUPLICATE_USER {
@Override
public String toString() {
return "This user already exists.";
}
},


// more errors follow
}

我的问题是: 有没有更好的方法来做到这一点?我更喜欢代码中的解决方案,而不是从外部文件中读取。我在这个项目中使用 Javadoc,能够在文档中记录错误代码并让它们在文档中自动更新将会很有帮助。

138977 次浏览

当然,枚举解决方案有一个更好的实现(通常是相当不错的) :

public enum Error {
DATABASE(0, "A database error has occurred."),
DUPLICATE_USER(1, "This user already exists.");


private final int code;
private final String description;


private Error(int code, String description) {
this.code = code;
this.description = description;
}


public String getDescription() {
return description;
}


public int getCode() {
return code;
}


@Override
public String toString() {
return code + ": " + description;
}
}

您可能希望重写 toString () ,只是返回描述-不确定。无论如何,主要的一点是您不需要为每个错误代码单独重写。还要注意的是,我已经明确指定了代码,而不是使用序号值-这使得以后更改顺序和添加/删除错误更加容易。

不要忘记,这根本不是国际化的——但是除非你的 Web 服务客户端发送给你一个地区描述,否则你无论如何也不能轻易地将它国际化。至少他们在客户端有用于 i18n 的错误代码..。

重载 toString ()似乎有点麻烦——这似乎有点延伸了 toString ()的正常用法。

那么:

public enum Errors {
DATABASE(1, "A database error has occured."),
DUPLICATE_USER(5007, "This user already exists.");
//... add more cases here ...


private final int id;
private final String message;


Errors(int id, String message) {
this.id = id;
this.message = message;
}


public int getId() { return id; }
public String getMessage() { return message; }
}

我觉得干净多了,也不那么冗长了。

就我而言,我更喜欢将错误消息外部化到属性文件中。 对于应用程序的国际化(每种语言一个属性文件) ,这将非常有帮助。修改错误消息也更容易,而且不需要对 Java 源代码进行任何重新编译。

在我的项目中,通常我有一个包含错误代码(字符串或整数,它不太关心)的接口,它包含这个错误的属性文件中的键:

public interface ErrorCodes {
String DATABASE_ERROR = "DATABASE_ERROR";
String DUPLICATE_USER = "DUPLICATE_USER";
...
}

在属性档案中:

DATABASE_ERROR=An error occurred in the database.
DUPLICATE_USER=The user already exists.
...

您的解决方案的另一个问题是可维护性: 您只有2个错误,并且已经有12行代码。 所以想象一下,当您要管理数百个错误时,您的枚举文件会是什么样子!

我(以及我公司的其他团队成员)更喜欢提出异常,而不是返回错误代码。错误代码必须到处检查、传递,并且当代码量变大时,往往会使代码不可读。

然后,错误类将定义消息。

PS: 其实也关心国际化!
PPS: 如果需要,你也可以重新定义提升方法,添加日志记录、过滤等等(至少在 Exception 类和好友是可扩展/可更改的环境中)

我建议你看看 java.util。资源包。你应该关心 I18N,但它是值得的,即使你不。将消息外部化是一个非常好的主意。我发现,能够给业务人员提供一个电子表格,让他们能够输入他们想要看到的确切语言,这是很有用的。我们编写了一个 Ant 任务来生成。编译时的属性文件。它使 I18N 变得微不足道。

如果你也在使用 Spring,那就更好了。他们的 MessageSource 类对这类事情很有用。

在上一份工作中,我对枚举版本进行了更深入的研究:

public enum Messages {
@Error
@Text("You can''t put a {0} in a {1}")
XYZ00001_CONTAINMENT_NOT_ALLOWED,
...
}

@ Error、@Info、@Police 保留在类文件中,并且在运行时可用。(我们还有其他一些注释来帮助描述消息传递)

@ Text 是编译时注释。

我为此编写了一个注释处理器,它执行以下操作:

  • 验证是否没有重复的消息编号(第一个下划线之前的部分)
  • 语法-检查消息文本
  • 生成一个 messages.properties 文件,其中包含由枚举值键控的文本。

我编写了一些实用程序来帮助记录错误,将它们包装为异常(如果需要的话)等等。

我想让他们允许我开源。 -- Scott

有点晚了,但是,我只是在为自己寻找一个漂亮的解决方案。如果您有不同类型的消息错误,您可以添加简单的、自定义的消息工厂,以便您可以指定更多的详细信息和格式。

public enum Error {
DATABASE(0, "A database error has occured. "),
DUPLICATE_USER(1, "User already exists. ");
....
private String description = "";
public Error changeDescription(String description) {
this.description = description;
return this;
}
....
}


Error genericError = Error.DATABASE;
Error specific = Error.DUPLICATE_USER.changeDescription("(Call Admin)");

编辑: 好的,在这里使用 enum 有点危险,因为您需要永久地更改特定的 enum。 我认为更好的方法是改为类并使用静态字段,但是不能再使用’= =’了。所以我想这是一个不做什么的好例子(或者只在初始化时做) :)

枚举的错误代码/消息定义仍然是一个很好的解决方案,尽管它有一个 i18n 问题。实际上,我们可能有两种情况: 代码/消息显示给最终用户或系统集成商。对于后一种情况,不需要 I18N。我认为 Web 服务最有可能是后一种情况。

仅仅是为了不断地鞭策这匹特别的死马——当错误显示给最终客户时,我们已经很好地利用了 < em > 数字错误代码 ,因为他们经常忘记或误读实际的错误消息,但有时可能会保留并报告一个数值,这个数值可以给你一个实际发生了什么的线索。

使用 interface作为消息常量通常是一个坏主意。它将作为导出 API 的一部分永久地泄漏到客户端程序中。谁知道呢,后来的客户机程序员可能会将错误消息(公开)作为程序的一部分进行解析。

您将永远被锁定,以支持这一点,因为字符串格式的变化将/可能中断客户端程序。

请参考以下例子:

public enum ErrorCodes {
NO_File("No file found. "),
private ErrorCodes(String value) {
this.errordesc = value;
}
private String errordesc = "";
public String errordesc() {
return errordesc;
}
public void setValue(String errordesc) {
this.errordesc = errordesc;
}

};

在您的代码中,可以这样调用:

fileResponse.setErrorCode(ErrorCodes.NO_FILE.errordesc());

有很多方法可以解决这个问题,我的首选方法是使用接口:

public interface ICode {
/*your preferred code type here, can be int or string or whatever*/ id();
}


public interface IMessage {
ICode code();
}

现在您可以定义提供消息的任意数量的枚举:

public enum DatabaseMessage implements IMessage {
CONNECTION_FAILURE(DatabaseCode.CONNECTION_FAILURE, ...);
}

现在您有几个选项可以将它们转换为 String。您可以将字符串编译到代码中(使用注释或 enum 构造函数参数) ,也可以从配置/属性文件、数据库表或混合文件中读取字符串。后者是我首选的方法,因为您总是需要一些消息,您可以转换成文本非常早(即。连接到数据库或读取配置的 同时)。

我使用单元测试和反射框架来查找实现接口的所有类型,以确保每个代码都在某个地方使用,并确保配置文件包含所有预期的消息,等等。

使用像 https://github.com/javaparser/javaparser《月蚀》里的那个这样可以解析 Java 的框架,您甚至可以检查枚举的使用位置并找到未使用的枚举。

我使用 PropertyResourceBundle 定义企业应用程序中的错误代码,以管理区域设置错误代码资源。这是处理错误代码的最佳方法,而不是在错误代码数量庞大且结构化的情况下编写代码(可能适用于少数错误代码)。

有关 PropertyResourceBundle 的更多信息,请查看 java 文档