【问题标题】:Best way to define error codes/strings in Java?在 Java 中定义错误代码/字符串的最佳方法?
【发布时间】:2010-10-01 14:13:09
【问题描述】:

我正在用 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)您应该小心将过多的错误信息传回给用户。有用的错误信息应写入服务器日志,但应告知客户端最低限度(例如“登录时出现问题”)。这是一个安全问题和防止欺骗者立足的问题。

标签: java enums


【解决方案1】:

嗯,肯定有更好的枚举解决方案实现(通常非常好):

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...

【讨论】:

  • 要国际化,将描述字段替换为可以在资源包中查找的字符串代码?
  • @Marcus:我喜欢这个主意。我正专注于把这件事弄出去,但是当我们考虑国际化时,我想我会按照你的建议去做。谢谢!
  • @marcus,如果 toString() 没有被覆盖(它不需要被覆盖),那么字符串代码可能只是枚举值 toString() ,这将是 DATABASE 或 DUPLICATE_USER案例。
  • @Jon Skeet!我喜欢这个解决方案,如何生成易于本地化(或翻译成其他语言等)的解决方案。考虑在 Android 中使用它,我可以使用 R.string.IDS_XXXX 代替硬编码字符串吗?
  • @A.B.:好吧,一旦你有了枚举,你就可以轻松地编写一个类,通过属性文件或其他方式从枚举值中提取相关的本地化资源。
【解决方案2】:

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

【讨论】:

  • 如果可以的话,我会超过 1。硬编码字符串很难维护。
  • 在接口中存储字符串常量是个坏主意。您可以在具有私有构造函数、每个包或相关区域的最终类中使用枚举或使用字符串常量。请约翰斯基茨用枚举回答。请检查。 stackoverflow.com/questions/320588/…
【解决方案3】:

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

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

【讨论】:

  • 在任何对象(更不用说枚举)上重载 toString() 是很正常的。
  • +1 不如 Jon Skeet 的解决方案灵活,但它仍然很好地解决了问题。谢谢!
  • 我的意思是 toString() 最常用和最有用的方法是提供足够的信息来识别对象——它通常包括类名,或者某种有意义地告诉对象类型的方式。在许多情况下,只返回“发生数据库错误”的 toString() 会令人惊讶。
  • 我同意 Cowan 的观点,以这种方式使用 toString() 似乎有点“hackish”。只是快速划算,而不是正常使用。对于枚举, toString() 应该返回枚举常量的名称。当您需要变量的值时,这在调试器中看起来会很有趣。
【解决方案4】:

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

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

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

@Text 是一个编译时注解。

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

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

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

我正试图让他们让我开源它...... -- 斯科特

【讨论】:

  • 处理错误消息的好方法。你已经开源了吗?
【解决方案5】:

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

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

【讨论】:

    【解决方案6】:

    只是为了继续鞭打这匹死马——当错误显示给最终客户时,我们很好地使用了数字错误代码,因为他们经常忘记或误读了实际的错误消息,但有时可能会保留并报告一个数值,该数值可为您提供关于实际发生的情况的线索。

    【讨论】:

      【解决方案7】:

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

      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/javaparserthe one from Eclipse,您甚至可以检查枚举的使用位置并找到未使用的枚举。

      【讨论】:

        【解决方案8】:

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

        然后错误类将定义消息。

        PS:其实也关心国际化!
        PPS:如果需要,您还可以重新定义 raise-method 并添加日志记录、过滤等(至少在异常类和朋友可扩展/可更改的环境中)

        【讨论】:

        • 对不起,罗宾,但是(至少从上面的例子来看),这应该是两个例外——“数据库错误”和“重复用户”完全不同,两个单独的错误子类应该被创建,它们是单独可捕获的(一个是系统,另一个是管理错误)
        • 如果不区分一个或另一个异常,错误代码是什么?所以至少在处理程序之上,他就是这样:处理传递和 if-switched 的错误代码。
        • 我认为异常的名称比错误代码更具说明性和自我描述性。最好多考虑一下发现好的异常名称,IMO。
        • @blabla999 啊,我的想法完全正确。为什么要捕获粗粒度异常然后测试“if errorcode == x, or y, or z”。这样的痛苦,违背了粮食。然后,您也无法在堆栈的不同级别捕获不同的异常。您必须在每个级别捕获并测试每个级别的错误代码。它使客户端代码更加冗长......如果可以的话+1 +更多。也就是说,我想我们必须回答 OP 的问题。
        • 请记住,这是针对 Web 服务的。客户端只能解析字符串。在服务器端,仍然会抛出具有 errorCode 成员的异常,可用于对客户端的最终响应。
        【解决方案9】:

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

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

        编辑: 好的,在这里使用枚举有点危险,因为您永久更改特定的枚举。 我想最好改成类并使用静态字段,但你不能再使用'=='了。所以我想这是一个很好的例子,不该做什么,(或者只在初始化期间做):)

        【讨论】:

        • 完全同意您的编辑,在运行时更改枚举字段不是一个好习惯。通过这种设计,每个人都可以编辑错误消息。这是相当危险的。 枚举字段应始终为最终字段。
        【解决方案10】:

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

        【讨论】:

          【解决方案11】:

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

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

          【讨论】:

            【解决方案12】:

            请按照以下示例:

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

            【讨论】:

              【解决方案13】:

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

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

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2011-07-29
                • 2017-01-08
                • 2013-02-26
                • 2014-01-21
                • 2016-10-19
                • 1970-01-01
                相关资源
                最近更新 更多