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


118

我正在用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,并且能够内联记录错误代码并在文档中自动更新它们将很有帮助。


晚评论但值得一提的是我的事... 1)您是否真的需要异常中的错误代码?请参阅下面的blabla999答案。2)您应该小心将过多的错误信息传回给用户。应该将有用的错误信息写入服务器日志,但应至少告知客户端(例如,“登录时出现问题”)。这是一个安全问题,如何防止踩踏者站稳脚跟。
wmorrison365

Answers:


161

好吧,当然可以更好地实现枚举解决方案(通常非常不错):

public enum Error {
  DATABASE(0, "A database error has occured."),
  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使用...


13
要进行国际化,请用可以在资源束中查找的字符串代码替换description字段?
Marcus Downing 2009年

@马库斯:我喜欢这个主意。我正在集中精力解决这个问题,但是当我们着眼于国际化时,我想我会按照您的建议去做。谢谢!
威廉·布伦德尔

@marcus,如果未重写toString()(不需要),则字符串代码可以只是枚举值toString(),在此情况下为DATABASE或DUPLICATE_USER。
卢布

@乔恩·斯基特!我喜欢这种解决方案,怎么会产生一种易于本地化(或翻译成其他语言等)的解决方案呢?考虑在Android中使用它,我可以在其中使用R.string.IDS_XXXX而不是硬编码的字符串吗?
AB

1
@AB:好了,一旦有了枚举,就可以轻松地编写一个类,以通过属性文件或其他任何东西从枚举值中提取相关的本地化资源。
乔恩·斯基特

34

就我而言,我更喜欢在属性文件中外部化错误消息。如果您的应用程序国际化(每种语言一个属性文件),这将非常有用。修改错误消息也更加容易,并且不需要任何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行代码。因此,想象一下您的枚举文件何时将要管理数百个错误!


2
如果可以的话,我会将这个比1多。硬编码字符串很难维护。
罗宾

3
在接口中存储String常量不是一个好主意。您可以在带有私有构造函数的最终类(按包或相关区域)中使用枚举或使用String常量。请John Skeets用Enums回答。请检查。stackoverflow.com/questions/320588/…–
阿南德·瓦尔基·飞利浦

21

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

对我来说似乎更干净了……而且不那么冗长。


5
在任何对象(更不用说枚举)上重载toString()是很正常的。
cletus

+1不如Jon Skeet的解决方案灵活,但它仍然可以很好地解决该问题。谢谢!
威廉·布伦德尔

2
我的意思是,toString()最常使用且最有用的方法是提供足够的信息来标识对象-它通常包括类名,或某种有意义地告诉对象类型的方法。在许多情况下,仅返回“发生数据库错误”的toString()会令人惊讶。
考恩

1
我同意Cowan的观点,以这种方式使用toString()似乎有点“骇人听闻”。只是快速赚钱而不是正常使用。对于枚举,toString()应该返回枚举常量的名称。当您想要变量的值时,这在调试器中看起来很有趣。
罗宾

19

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

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

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

@Text是编译时注释。

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

  • 确认没有重复的消息号(第一个下划线之前的部分)
  • 语法检查消息文本
  • 生成一个包含文本的messages.properties文件,该文本由枚举值键入。

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

我正在努力让他们让我开源...-Scott


处理错误消息的好方法。您已经开源了吗?
bobbel

5

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

如果您还使用Spring,那就更好了。他们的MessageSource类对于这类事情很有用。


4

只是为了制止这一特定问题,我们在错误显示给最终客户时充分利用了数字错误代码,因为它们经常忘记或误读实际的错误消息,但有时可能会保留并报告一个数字值,您可以了解实际发生的情况。


3

有很多解决方法。我的首选方法是具有接口:

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, ...);
}

现在,您有几个选择可以将它们转换为字符串。您可以将字符串编译为代码(使用批注或枚举构造函数参数),也可以从配置/属性文件或数据库表或混合表中读取它们。后者是我的首选方法,因为您将始终需要一些消息,这些消息可以很早地将其转换为文本(即,您连接到数据库或读取配置时)。

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

使用可以解析Java的框架(例如https://github.com/javaparser/javaparser来自Eclipse的框架),您甚至可以检查枚举的使用位置并找到未使用的枚举。


2

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

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

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


抱歉,Robin,但是(至少从上面的示例中),这应该是两个例外-“数据库错误”和“重复用户”是如此完全不同,以至于应该创建两个单独的错误子类,它们可以分别捕获(一个是系统,另一个是管理员错误)
blabla999

错误代码是用来做什么的,如果不区分一个或另一个例外?因此,至少在处理程序之上,他的确是这样:处理传来的错误代码并进行if切换。
blabla999

我认为异常的名称比错误代码更具说明性和自我描述性。最好将更多的精力放在发现好的异常名称上,IMO。
duffymo,2009年

@ blabla999啊,我的想法正好。为什么要捕获粗粒度的异常,然后测试“如果错误代码== x,y或z”。这样的痛苦和不利。然后,您也无法在堆栈的不同级别上捕获不同的异常。您必须抓住每个级别并在每个级别上测试错误代码。它使客户端代码更加冗长... +1 +如果可以的话。就是说,我想我们必须回答执行问题。
wmorrison365

2
请记住,这是针对Web服务的。客户端只能解析字符串。在服务器端,仍然会抛出具有errorCode成员的异常,该异常可用于对客户端的最终响应中。
pkrish

1

有点晚了,但是,我只是为自己寻找一个不错的解决方案。如果您遇到其他类型的消息错误,则可以添加简单的自定义消息工厂,以便您可以指定更多详细信息和以后要使用的格式。

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)");

编辑:好的,在这里使用枚举有点危险,因为您要永久更改特定的枚举。我猜最好是改成类并使用静态字段,但总比不能再使用'=='更好。因此,我认为这是一个很好的示例,它什么也不做(或仅在初始化期间做):)


1
完全同意您的EDIT,在运行时更改枚举字段不是一个好习惯。通过这种设计,每个人都可以编辑错误消息。这很危险。枚举字段应该始终是最终的。
b3nyc

0

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


0

使用interface的消息常数一般是一个坏主意。它将作为导出的API的一部分永久泄漏到客户端程序中。谁知道,以后的客户端程序员可能会将错误消息(公共)解析为程序的一部分。

您将永远被锁定以支持此操作,因为字符串格式的更改将/可能破坏客户端程序。


0

请遵循以下示例:

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());

0

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

查看Java文档以获取有关PropertyResourceBundle的更多信息

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.