Javaでエラーコード/文字列を定義する最良の方法?


118

私はJavaでWebサービスを作成していて、エラーコードとそれに関連するエラー文字列を定義する最善の方法を見つけようとしています。数値エラーコードとエラー文字列をグループ化する必要があります。エラーコードとエラー文字列の両方が、Webサービスにアクセスするクライアントに送信されます。たとえば、SQLExceptionが発生した場合、次のようにすることができます。

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

クライアントプログラムに次のメッセージが表示される場合があります。

「エラー#1が発生しました:データベースへのアクセスに問題が発生しました。」

私の最初の考えはEnum、エラーコードの1つを使用し、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

回答:


161

ええと、enumソリューションの実装は確かに優れています(これは一般に非常に優れています)。

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
国際化するには、説明フィールドをリソースバンドルで検索できる文字列コードに置き換えますか?
マーカスダウニング

@マーカス:私はその考えが好きです。私はこのことを戸外に出すことに集中していますが、国際化について考えるとき、私はあなたが提案したことをするつもりだと思います。ありがとう!
ウィリアムブレンデル、

@ marcus、toString()がオーバーライドされていない場合(オーバーライドする必要はありません)、文字列コードは、列挙型の値toString()、つまりDATABASE、またはこの場合はDUPLICATE_USERになります。
ルーブル

@ジョンスキート!私はこのソリューションが好きです。ローカライズ(または他の言語での翻訳など)が簡単なソリューションをどのように作成できますか?Androidで使用することを考えて、ハードコードされた文字列の代わりにR.string.IDS_XXXXを使用できますか?
ABの

1
@AB:列挙型を取得したら、プロパティファイルなどを使用して、列挙値から関連するローカライズされたリソースを抽出するクラスを簡単に作成できます。
Jon Skeet、

34

私に関する限り、私はプロパティファイルのエラーメッセージを外部化することを好みます。これは、アプリケーションの国際化(言語ごとに1つのプロパティファイル)の場合に非常に役立ちます。エラーメッセージを修正するのも簡単で、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.
...

ソリューションのもう1つの問題は保守性です。2つのエラーしかなく、すでに12行のコードがあります。したがって、何百ものエラーを管理する必要があるときに、列挙ファイルを想像してみてください。


2
できれば、これを1以上増やします。文字列をハードコーディングすることは、メンテナンスにとって醜いです。
Robin

3
インターフェイスに文字列定数を格納することは悪い考えです。パッケージまたは関連領域ごとに、列挙型を使用するか、プライベートコンストラクターを持つ最終クラスで文字列定数を使用できます。ジョン・スキーツがエナムスで答えてください。チェックしてください。stackoverflow.com/questions/320588/...
アナンドVarkeyフィリップス

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 2009年

+1 Jon Skeetのソリューションほど柔軟ではありませんが、問題をうまく解決します。ありがとう!
ウィリアムブレンデル、

2
toString()は、オブジェクトを識別するための十分な情報を提供するために最も一般的かつ有用に使用されることを意味しました。これには、クラス名、またはオブジェクトのタイプを意味のある形で伝える何らかの方法が含まれることがよくあります。'データベースエラーが発生しました'だけを返すtoString()は、多くのコンテキストで意外です。
Cowan、

1
私はCowanに同意します。このようにtoString()を使用することは少し「ハッキー」に見えます。通常の使用法ではなく、お金のための迅速な強打です。列挙型の場合、toString()は列挙型定数の名前を返す必要があります。変数の値が必要な場合、これはデバッガーで興味深いように見えます。
Robin

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ファイルを生成します。

エラーの記録、例外としてのラップ(必要な場合)などに役立つユーティリティルーチンをいくつか作成しました。

オープンソース化させようとしている...-スコット


エラーメッセージを処理する素晴らしい方法。あなたはすでにそれをオープンソース化しましたか?
bobbel

5

java.util.ResourceBundleをご覧になることをお勧めします。I18Nを気にする必要がありますが、気にしなくてもそれだけの価値があります。メッセージを外部化することは非常に良い考えです。見たい言語を正確に入力できるスプレッドシートをビジネスマンに提供できると便利だと思いました。コンパイル時に.propertiesファイルを生成するAntタスクを作成しました。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, ...);
}

これらを文字列に変換するオプションがいくつかあります。文字列をコードにコンパイル(アノテーションまたは列挙コンストラクターパラメーターを使用)するか、config / propertyファイルから、またはデータベーステーブルまたは混合から読み取ることができます。あなたはいつもあなたが非常に早いテキストに変えることができ、いくつかのメッセージが必要になりますので、(すなわち。後者は、私の好ましいアプローチである一方、データベースへの接続や設定を読んで)。

ユニットテストとリフレクションフレームワークを使用して、インターフェイスを実装するすべてのタイプを見つけ、各コードがどこかで使用されていること、および構成ファイルにすべての予期されるメッセージなどが含まれていることを確認しています。

https://github.com/javaparser/javaparserEclipseの JavaなどのJavaを解析できるフレームワークを使用して、列挙型が使用されている場所を確認し、未使用のものを見つけることもできます。


2

私(および社内の他のチーム)は、エラーコードを返すのではなく、例外を発生させることを好みます。エラーコードはどこでもチェックされ、渡されなければならず、コードの量が大きくなるとコードを読みにくくする傾向があります。

次に、エラークラスがメッセージを定義します。

PS:そして実際に国際化も気にしています!
PPS:必要に応じて、raise-methodを再定義し、ロギング、フィルタリングなどを追加することもできます(少なくとも、例外クラスとフレンドが拡張可能/変更可能な環境では)。


申し訳ありませんが、Robinですが(少なくとも上記の例では)、これらは2つの例外であるはずです。「データベースエラー」と「重複ユーザー」はまったく異なるため、2つの個別のエラーサブクラスを作成し、個別にキャッチできます( 1つはシステム、もう1つは管理エラーです)
blabla999

そして、どちらか一方の例外を区別しない場合、エラーコードは何に使用されますか?したがって、少なくともハンドラーの上では、彼はまさにそれです:渡され、if-switchedされたエラーコードを処理します。
blabla999 2009年

例外の名前は、エラーコードよりもはるかにわかりやすく説明的だと思います。優れた例外名の発見に、より多くの考慮を置く方が良い、IMO
duffymo

@ blabla999ああ、私の考えは正確に。大まかな例外をキャッチしてから、「if errorcode == x、またはy、またはz」をテストする理由。そのような痛みと穀物に逆らいます。次に、スタックのさまざまなレベルでさまざまな例外をキャッチすることもできません。すべてのレベルでキャッチし、それぞれでエラーコードをテストする必要があります。これにより、クライアントコードが非常に冗長になります。そうは言っても、OPの質問に答える必要があると思います。
wmorrison365

2
これはWebサービス用です。クライアントは文字列のみを解析できます。サーバー側では、クライアントへの最終応答で使用できるerrorCodeメンバーを持つ例外がスローされます。
pkrish 2015

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

編集:OK、ここで列挙型を使用すると、特定の列挙型を永続的に変更するため、少し危険です。クラスに変更して静的フィールドを使用する方が良いと思いますが、 '=='はもう使用できません。だから私はそれが何をしてはいけないか(または初期化中にのみそれを行う)良い例だと思います:)


1
EDITに完全に同意します。実行時に列挙型フィールドを変更することはお勧めできません。この設計により、誰もがエラーメッセージを編集できます。これはかなり危険です。列挙型フィールドは常に最終でなければなりません。
b3nyc

0

エラーコード/メッセージ定義の列挙型は国際化の懸念がありますが、それでも優れたソリューションです。実際には、2つの状況が考えられます。コード/メッセージがエンドユーザーまたはシステムインテグレーターに表示されます。後者の場合、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を使用して、エンタープライズアプリケーションでエラーコードを定義し、ロケールエラーコードリソースを管理します。これは、エラーコードの数が膨大で構造化されている場合に、コードを書く代わりにエラーコードを処理するための最良の方法です(少数のエラーコードには十分かもしれません)。

PropertyResourceBundleの詳細については、java docを参照してください

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.