Der beste Weg, um Fehlercodes / Strings in Java zu definieren?

118

Ich schreibe einen Webdienst in Java und versuche herauszufinden, wie Fehlercodes und die zugehörigen Fehlerzeichenfolgen am besten definiert werden können . Ich brauche einen numerischen Fehlercode und eine Fehlerzeichenfolge, die zusammen gruppiert sind. Sowohl der Fehlercode als auch die Fehlerzeichenfolge werden an den Client gesendet, der auf den Webdienst zugreift. Wenn beispielsweise eine SQLException auftritt, möchte ich möglicherweise Folgendes tun:

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

Dem Client-Programm wird möglicherweise die folgende Meldung angezeigt:

"Fehler Nr. 1 ist aufgetreten: Beim Zugriff auf die Datenbank ist ein Problem aufgetreten."

Mein erster Gedanke war, einen Enumder Fehlercodes zu verwenden und die toStringMethoden zum Zurückgeben der Fehlerzeichenfolgen zu überschreiben . Folgendes habe ich mir ausgedacht:

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
}

Meine Frage ist: Gibt es einen besseren Weg, dies zu tun? Ich würde eine Lösung im Code vorziehen, anstatt aus einer externen Datei zu lesen. Ich verwende Javadoc für dieses Projekt, und es wäre hilfreich, die Fehlercodes online zu dokumentieren und sie automatisch in der Dokumentation aktualisieren zu lassen.

William Brendel
quelle
Später Kommentar, aber eine Erwähnung wert ... 1) Benötigen Sie hier in der Ausnahme wirklich Fehlercodes? Siehe blabla999 Antwort unten. 2) Sie sollten vorsichtig sein, wenn Sie zu viele Fehlerinformationen an den Benutzer zurückgeben. Nützliche Fehlerinformationen sollten in Serverprotokolle geschrieben werden, dem Client sollte jedoch das Nötigste mitgeteilt werden (z. B. "Es gab ein Problem beim Anmelden"). Dies ist eine Frage der Sicherheit und der Verhinderung, dass Spoofer Fuß fassen.
wmorrison365

Antworten:

161

Nun, es gibt sicherlich eine bessere Implementierung der Enum-Lösung (was im Allgemeinen recht nett ist):

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

Möglicherweise möchten Sie toString () überschreiben, um stattdessen nur die Beschreibung zurückzugeben - nicht sicher. Der wichtigste Punkt ist jedoch, dass Sie nicht für jeden Fehlercode separat überschreiben müssen. Beachten Sie auch, dass ich den Code explizit angegeben habe, anstatt den Ordnungswert zu verwenden. Dies erleichtert das spätere Ändern der Reihenfolge und das Hinzufügen / Entfernen von Fehlern.

Vergessen Sie nicht, dass dies überhaupt nicht internationalisiert ist - aber wenn Ihr Webdienst-Client Ihnen keine Gebietsschemabeschreibung sendet, können Sie diese ohnehin nicht einfach selbst internationalisieren. Zumindest haben sie den Fehlercode für i18n auf der Clientseite ...

Jon Skeet
quelle
13
Ersetzen Sie zur Internationalisierung das Beschreibungsfeld durch einen Zeichenfolgencode, der in einem Ressourcenpaket nachgeschlagen werden kann.
Marcus Downing
@Marcus: Ich mag diese Idee. Ich konzentriere mich darauf, dieses Ding aus der Tür zu bekommen, aber wenn wir uns die Internationalisierung ansehen, denke ich, dass ich tun werde, was Sie vorgeschlagen haben. Vielen Dank!
William Brendel
@marcus, wenn toString () nicht überschrieben wird (was nicht erforderlich ist), kann der Zeichenfolgencode nur der Aufzählungswert toString () sein, der in diesem Fall DATABASE oder DUPLICATE_USER wäre.
Rubel
@ Jon Skeet! Ich mag diese Lösung, wie man eine Lösung erstellen kann, die leicht zu lokalisieren ist (oder in andere Sprachen übersetzt werden kann usw.). Wenn ich daran denke, sie in Android zu verwenden, kann ich dort die R.string.IDS_XXXX anstelle von hartcodierten Zeichenfolgen verwenden?
AB
1
@AB: Nun, sobald Sie die Aufzählung haben, können Sie einfach eine Klasse schreiben, um die relevante lokalisierte Ressource über Eigenschaftendateien oder was auch immer aus dem Aufzählungswert zu extrahieren.
Jon Skeet
34

Ich bevorzuge es, die Fehlermeldungen in einer Eigenschaftendatei zu externalisieren. Dies ist im Falle einer Internationalisierung Ihrer Anwendung sehr hilfreich (eine Eigenschaftendatei pro Sprache). Es ist auch einfacher, eine Fehlermeldung zu ändern, und die Java-Quellen müssen nicht neu kompiliert werden.

In meinen Projekten habe ich im Allgemeinen eine Schnittstelle, die Fehlercodes enthält (String oder Integer, es ist nicht wichtig), die den Schlüssel in den Eigenschaftendateien für diesen Fehler enthält:

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

in der Eigenschaftendatei:

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

Ein weiteres Problem bei Ihrer Lösung ist die Wartbarkeit: Sie haben nur 2 Fehler und bereits 12 Codezeilen. Stellen Sie sich also Ihre Aufzählungsdatei vor, wenn Sie Hunderte von Fehlern verwalten müssen!

Romain Linsolas
quelle
2
Ich würde dies um mehr als 1 erhöhen, wenn ich könnte. Das Hardcodieren der Zeichenfolgen ist für die Wartung hässlich.
Robin
3
Das Speichern von String-Konstanten in der Schnittstelle ist eine schlechte Idee. Sie können Enums oder String-Konstanten in Abschlussklassen mit privatem Konstruktor pro Paket oder verwandtem Bereich verwenden. Bitte John Skeets antwortet mit Enums. Bitte prüfen. stackoverflow.com/questions/320588/…
Anand Varkey Philips
21

Das Überladen von toString () scheint ein bisschen schwierig zu sein - das scheint ein bisschen ein Teil der normalen Verwendung von toString () zu sein.

Wie wäre es mit:

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

scheint mir viel sauberer zu sein ... und weniger ausführlich.

Cowan
quelle
5
Das Überladen von toString () für Objekte (geschweige denn Aufzählungen) ist ganz normal.
Cletus
+1 Nicht ganz so flexibel wie Jon Skeets Lösung, aber es löst das Problem trotzdem gut. Vielen Dank!
William Brendel
2
Ich meinte, dass toString () am häufigsten und nützlichsten verwendet wird, um genügend Informationen zur Identifizierung des Objekts bereitzustellen - es enthält häufig den Klassennamen oder eine Möglichkeit, den Objekttyp sinnvoll zu bestimmen. Ein toString (), der nur "Ein Datenbankfehler ist aufgetreten" zurückgibt, wäre in vielen Zusammenhängen überraschend.
Cowan
1
Ich stimme Cowan zu, die Verwendung von toString () auf diese Weise scheint ein bisschen "hackisch" zu sein. Nur ein kurzer Knall für das Geld und keine normale Verwendung. Für die Aufzählung sollte toString () den Namen der Aufzählungskonstante zurückgeben. Dies würde in einem Debugger interessant aussehen, wenn Sie den Wert einer Variablen möchten.
Robin
19

Bei meinem letzten Job bin ich etwas tiefer in die Enum-Version gegangen:

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

@Error, @Info, @Warning bleiben in der Klassendatei erhalten und sind zur Laufzeit verfügbar. (Wir hatten noch ein paar andere Anmerkungen, um die Zustellung von Nachrichten zu beschreiben.)

@Text ist eine Annotation zur Kompilierungszeit.

Ich habe dafür einen Anmerkungsprozessor geschrieben, der Folgendes tat:

  • Stellen Sie sicher, dass keine doppelten Nachrichtennummern vorhanden sind (der Teil vor dem ersten Unterstrich).
  • Syntax-überprüfen Sie den Nachrichtentext
  • Generieren Sie eine Datei messages.properties, die den durch den Aufzählungswert verschlüsselten Text enthält.

Ich habe einige Dienstprogramme geschrieben, mit denen Fehler protokolliert, als Ausnahmen (falls gewünscht) usw. verpackt werden konnten.

Ich versuche, sie dazu zu bringen, mich Open Source zu machen ... - Scott

Scott Stanchfield
quelle
Gute Art, mit Fehlermeldungen umzugehen. Haben Sie es bereits als Open-Source-Lösung angeboten?
Bobbel
5

Ich würde empfehlen, dass Sie sich java.util.ResourceBundle ansehen. Sie sollten sich für I18N interessieren, aber es lohnt sich, auch wenn Sie es nicht tun. Das Externalisieren der Nachrichten ist eine sehr gute Idee. Ich habe festgestellt, dass es nützlich war, Geschäftsleuten eine Tabelle zu geben, in der sie genau die Sprache eingeben konnten, die sie sehen wollten. Wir haben eine Ant-Task geschrieben, um die .properties-Dateien zur Kompilierungszeit zu generieren. Das macht I18N trivial.

Wenn Sie auch Spring verwenden, umso besser. Ihre MessageSource-Klasse ist für diese Art von Dingen nützlich.

Duffymo
quelle
4

Nur um dieses tote Pferd weiter auszupeitschen - wir haben numerische Fehlercodes gut verwendet, wenn Endkunden Fehler angezeigt werden, da sie häufig die tatsächliche Fehlermeldung vergessen oder falsch lesen, aber manchmal einen numerischen Wert beibehalten und melden, der angeben kann Sie ein Hinweis darauf, was tatsächlich passiert ist.

telcopro
quelle
3

Es gibt viele Möglichkeiten, dies zu lösen. Mein bevorzugter Ansatz ist es, Schnittstellen zu haben:

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

public interface IMessage {
    ICode code();
}

Jetzt können Sie eine beliebige Anzahl von Aufzählungen definieren, die Nachrichten bereitstellen:

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

Jetzt haben Sie mehrere Möglichkeiten, diese in Strings umzuwandeln. Sie können die Zeichenfolgen in Ihren Code kompilieren (mithilfe von Anmerkungen oder Enum-Konstruktorparametern) oder sie aus einer Konfigurations- / Eigenschaftendatei oder aus einer Datenbanktabelle oder einer Mischung lesen. Letzteres ist mein bevorzugter Ansatz, da Sie immer einige Nachrichten benötigen, die Sie sehr früh (dh währenddessen) in Text umwandeln können Sie eine Verbindung zur Datenbank herstellen oder die Konfiguration lesen).

Ich verwende Unit-Tests und Reflection-Frameworks, um alle Typen zu finden, die meine Schnittstellen implementieren, um sicherzustellen, dass jeder Code irgendwo verwendet wird und dass die Konfigurationsdateien alle erwarteten Nachrichten usw. enthalten.

Mit Frameworks, die Java analysieren können, wie https://github.com/javaparser/javaparser oder dem von Eclipse , können Sie sogar überprüfen, wo die Aufzählungen verwendet werden, und nicht verwendete finden.

Aaron Digulla
quelle
2

Ich (und der Rest unseres Teams in meinem Unternehmen) ziehe es vor, Ausnahmen auszulösen, anstatt Fehlercodes zurückzugeben. Fehlercodes müssen überall überprüft, weitergegeben und dazu neigen, den Code unlesbar zu machen, wenn die Codemenge größer wird.

Die Fehlerklasse würde dann die Nachricht definieren.

PS: und eigentlich auch für die Internationalisierung sorgen!
PPS: Sie können die Raise-Methode auch neu definieren und bei Bedarf Protokollierung, Filterung usw. hinzufügen (zumindest in Umgebungen, in denen die Ausnahmeklassen und Freunde erweiterbar / änderbar sind).

blabla999
quelle
Entschuldigung, Robin, aber dann (zumindest aus dem obigen Beispiel) sollten dies zwei Ausnahmen sein - "Datenbankfehler" und "doppelter Benutzer" sind so völlig unterschiedlich, dass zwei separate Fehlerunterklassen erstellt werden sollten, die einzeln abfangbar sind ( eines ist ein System, das andere ist ein Administrationsfehler)
blabla999
und wofür werden die Fehlercodes verwendet, um nicht zwischen der einen oder anderen Ausnahme zu unterscheiden? Zumindest über dem Handler ist er genau das: Umgang mit Fehlercodes, die herumgereicht und eingeschaltet werden.
blabla999
Ich denke, der Name der Ausnahme wäre weitaus anschaulicher und selbstbeschreibender als ein Fehlercode. Es ist besser, mehr darüber nachzudenken, gute Ausnahmenamen zu finden, IMO.
Duffymo
@ blabla999 ah, meine gedanken genau. Warum eine grobkörnige Ausnahme abfangen und dann "wenn Fehlercode == x oder y oder z" testen? Solch ein Schmerz und geht gegen den Strich. Außerdem können Sie in Ihrem Stapel keine unterschiedlichen Ausnahmen auf verschiedenen Ebenen abfangen. Sie müssten auf jeder Ebene fangen und den Fehlercode auf jeder Ebene testen. Es macht den Client-Code so viel ausführlicher ... +1 + mehr, wenn ich könnte. Das heißt, ich denke, wir müssen die Frage der OP beantworten.
wmorrison365
2
Beachten Sie, dass dies für einen Webdienst gilt. Der Client kann nur Zeichenfolgen analysieren. Auf der Serverseite werden immer noch Ausnahmen ausgelöst, die ein errorCode-Mitglied haben, das in der endgültigen Antwort an den Client verwendet werden kann.
pkrish
1

Ein bisschen spät, aber ich suchte nur nach einer hübschen Lösung für mich. Wenn Sie eine andere Art von Nachrichtenfehler haben, können Sie eine einfache, benutzerdefinierte Nachrichtenfactory hinzufügen, damit Sie später weitere Details und Formate angeben können.

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

EDIT: ok, die Verwendung von enum hier ist ein wenig gefährlich, da Sie bestimmte enum dauerhaft ändern. Ich denke, es wäre besser, zur Klasse zu wechseln und statische Felder zu verwenden, aber dann können Sie '==' nicht mehr verwenden. Ich denke, es ist ein gutes Beispiel dafür, was nicht zu tun ist (oder nur während der Initialisierung) :)

pprzemek
quelle
1
Stimmen Sie Ihrer BEARBEITUNG voll und ganz zu. Es ist keine gute Praxis, ein Aufzählungsfeld zur Laufzeit zu ändern. Mit diesem Design kann jeder die Fehlermeldung bearbeiten. Das ist ziemlich gefährlich. Aufzählungsfelder sollten immer endgültig sein.
b3nyc
0

Die Aufzählung für die Definition von Fehlercode / Nachricht ist immer noch eine gute Lösung, obwohl sie ein i18n-Problem hat. Tatsächlich können zwei Situationen auftreten: Der Code / die Nachricht wird dem Endbenutzer oder dem Systemintegrator angezeigt. Für den späteren Fall ist I18N nicht erforderlich. Ich denke, die Webdienste sind höchstwahrscheinlich der spätere Fall.

Jimmy
quelle
0

Verwenden von interface als Nachrichtenkonstante ist im Allgemeinen eine schlechte Idee. Es wird als Teil der exportierten API dauerhaft in das Client-Programm gelangen. Wer weiß, dass spätere Client-Programmierer diese (öffentlichen) Fehlermeldungen möglicherweise als Teil ihres Programms analysieren.

Sie werden für immer gesperrt sein, um dies zu unterstützen, da Änderungen im Zeichenfolgenformat das Client-Programm beschädigen können / können.

Awan Biru
quelle
0

Bitte folgen Sie dem folgenden Beispiel:

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

};

Nennen Sie es in Ihrem Code wie folgt:

fileResponse.setErrorCode(ErrorCodes.NO_FILE.errordesc());
Chinmoy
quelle
0

Ich verwende PropertyResourceBundle, um die Fehlercodes in einer Unternehmensanwendung zu definieren, um lokale Fehlercode-Ressourcen zu verwalten. Dies ist der beste Weg, um mit Fehlercodes umzugehen, anstatt Code zu schreiben (kann für wenige Fehlercodes gelten), wenn die Anzahl der Fehlercodes sehr groß und strukturiert ist.

Weitere Informationen zu PropertyResourceBundle finden Sie im Java-Dokument

Mujibur Rahman
quelle