Il modo migliore per definire codici / stringhe di errore in Java?


118

Sto scrivendo un servizio web in Java e sto cercando di capire il modo migliore per definire i codici di errore e le stringhe di errore associate . Devo avere un codice di errore numerico e una stringa di errore raggruppati insieme. Sia il codice di errore che la stringa di errore verranno inviati al client che accede al servizio web. Ad esempio, quando si verifica una SQLException, potrei voler fare quanto segue:

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

Al programma client potrebbe essere visualizzato il messaggio:

"Si è verificato l'errore n. 1: si è verificato un problema durante l'accesso al database."

Il mio primo pensiero è stato quello di utilizzare uno Enumdei codici di errore e sovrascrivere i toStringmetodi per restituire le stringhe di errore. Ecco cosa mi è venuto in mente:

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
}

La mia domanda è: c'è un modo migliore per farlo? Preferirei una soluzione in codice, piuttosto che leggere da un file esterno. Sto usando Javadoc per questo progetto e sarebbe utile documentare i codici di errore in linea e aggiornarli automaticamente nella documentazione.


Commento in ritardo ma vale la pena menzionare ... 1) Hai davvero bisogno di codici di errore qui nell'eccezione? Vedi la risposta blabla999 di seguito. 2) Dovresti fare attenzione a restituire troppe informazioni sull'errore all'utente. Informazioni utili sugli errori dovrebbero essere scritte nei log del server ma al client dovrebbe essere comunicato il minimo indispensabile (ad esempio "si è verificato un problema durante il login"). Questa è una questione di sicurezza e di impedire agli spoofer di prendere piede.
wmorrison365

Risposte:


161

Bene, c'è sicuramente un'implementazione migliore della soluzione enum (che in genere è abbastanza carina):

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

Potresti voler sovrascrivere toString () per restituire solo la descrizione, non sono sicuro. Ad ogni modo, il punto principale è che non è necessario eseguire l'override separatamente per ogni codice di errore. Si noti inoltre che ho specificato esplicitamente il codice invece di utilizzare il valore ordinale: ciò semplifica la modifica dell'ordine e l'aggiunta / rimozione di errori in un secondo momento.

Non dimenticare che questo non è affatto internazionalizzato, ma a meno che il tuo client del servizio web non ti invii una descrizione locale, non puoi comunque internazionalizzarlo facilmente da solo. Almeno avranno il codice di errore da usare per i18n sul lato client ...


13
Per internazionalizzare, sostituire il campo della descrizione con un codice stringa che può essere cercato in un bundle di risorse?
Marcus Downing

@ Marcus: mi piace l'idea. Mi sto concentrando sul portare questa cosa fuori dalla porta, ma quando guardiamo all'internazionalizzazione, penso che farò quello che hai suggerito. Grazie!
William Brendel

@marcus, se toString () non è sovrascritto (cosa che non è necessario che sia), il codice della stringa potrebbe essere semplicemente il valore enum toString () che sarebbe DATABASE o DUPLICATE_USER in questo caso.
rublo

@ Jon Skeet! Mi piace questa soluzione, come si potrebbe produrre una soluzione che sia facile da localizzare (o tradurre in altre lingue ecc.) Pensando di usarla in Android posso usare R.string.IDS_XXXX invece delle stringhe hard coded lì?
AB

1
@AB: Bene, una volta ottenuto l'enum, potresti facilmente scrivere una classe per estrarre la risorsa localizzata pertinente dal valore enum, tramite i file delle proprietà o qualsiasi altra cosa.
Jon Skeet

34

Per quanto mi riguarda, preferisco esternalizzare i messaggi di errore in un file delle proprietà. Questo sarà davvero utile in caso di internazionalizzazione della tua applicazione (un file delle proprietà per lingua). È anche più facile modificare un messaggio di errore e non sarà necessaria alcuna ricompilazione dei sorgenti Java.

Sui miei progetti, generalmente ho un'interfaccia che contiene codici di errore (String o integer, non interessa molto), che contiene la chiave nei file delle proprietà per questo errore:

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

nel file delle proprietà:

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

Un altro problema con la tua soluzione è la manutenibilità: hai solo 2 errori e già 12 righe di codice. Quindi immagina il tuo file di enumerazione quando avrai centinaia di errori da gestire!


2
Lo farei più di 1 se potessi. L'hardcoding delle stringhe è brutto per la manutenzione.
Robin

3
Memorizzare le costanti String nell'interfaccia è una cattiva idea. È possibile utilizzare enumerazioni o utilizzare costanti String nelle classi finali con costruttore privato, per pacchetto o area correlata. Per favore, John Skeets rispondi con enumerazioni. Si prega di controllare. stackoverflow.com/questions/320588/…
Anand Varkey Philips

21

Il sovraccarico di toString () sembra un po 'icky - che sembra un po' un allungamento del normale utilizzo di toString ().

Che dire:

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

mi sembra molto più pulito ... e meno prolisso.


5
Il sovraccarico di toString () su qualsiasi oggetto (per non parlare delle enumerazioni) è abbastanza normale.
cletus

+1 Non abbastanza flessibile come la soluzione di Jon Skeet, ma risolve comunque bene il problema. Grazie!
William Brendel

2
Volevo dire che toString () è più comunemente e utilmente utilizzato per fornire informazioni sufficienti per identificare l'oggetto: spesso include il nome della classe o un modo per dire in modo significativo il tipo di oggetto. Un toString () che restituisce solo "Si è verificato un errore di database" sarebbe sorprendente in molti contesti.
Cowan

1
Sono d'accordo con Cowan, usare toString () in questo modo sembra un po '"hacker". Solo un rapido colpo per il dollaro e non un utilizzo normale. Per l'enumerazione, toString () dovrebbe restituire il nome della costante enum. Questo sembrerebbe interessante in un debugger quando vuoi il valore di una variabile.
Robin

19

Al mio ultimo lavoro sono andato un po 'più a fondo nella versione enum:

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

@Error, @Info, @Warning vengono conservati nel file di classe e sono disponibili in fase di esecuzione. (Avevamo anche un paio di altre annotazioni per aiutare a descrivere la consegna dei messaggi)

@Text è un'annotazione in fase di compilazione.

Ho scritto un processore di annotazioni per questo che ha fatto quanto segue:

  • Verificare che non ci siano numeri di messaggio duplicati (la parte prima del primo trattino basso)
  • Controlla la sintassi del testo del messaggio
  • Genera un file messages.properties che contiene il testo, codificato dal valore enum.

Ho scritto alcune routine di utilità che hanno aiutato a registrare gli errori, includerli come eccezioni (se lo si desidera) e così via.

Sto cercando di convincerli a lasciarmi open-source ... - Scott


Bel modo di gestire i messaggi di errore. Lo hai già reso open source?
bobbel

5

Ti consiglio di dare un'occhiata a java.util.ResourceBundle. Dovresti preoccuparti di I18N, ma ne vale la pena anche se non lo fai. L'esternalizzazione dei messaggi è un'ottima idea. Ho scoperto che era utile poter fornire un foglio di calcolo agli uomini d'affari che permettesse loro di inserire nella lingua esatta che volevano vedere. Abbiamo scritto un'attività Ant per generare i file .properties in fase di compilazione. Rende I18N banale.

Se stai usando anche la primavera, tanto meglio. La loro classe MessageSource è utile per questo genere di cose.


4

Solo per continuare a frustare questo particolare cavallo morto, abbiamo fatto buon uso di codici di errore numerici quando gli errori vengono mostrati ai clienti finali, poiché spesso dimenticano o interpretano erroneamente il messaggio di errore effettivo, ma a volte possono conservare e segnalare un valore numerico che può fornire hai un indizio di ciò che è realmente accaduto.


3

Ci sono molti modi per risolvere questo problema. Il mio approccio preferito è avere interfacce:

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

public interface IMessage {
    ICode code();
}

Ora puoi definire un numero qualsiasi di enumerazioni che forniscono messaggi:

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

Ora hai diverse opzioni per trasformarle in stringhe. È possibile compilare le stringhe nel codice (utilizzando annotazioni o parametri del costruttore enum) oppure leggerle da un file di configurazione / proprietà o da una tabella di database o da una combinazione. Quest'ultimo è il mio approccio preferito perché avrai sempre bisogno di alcuni messaggi che puoi trasformare in testo molto presto (cioè mentre ti connetti al database o leggi la configurazione).

Sto usando unit test e framework di riflessione per trovare tutti i tipi che implementano le mie interfacce per assicurarmi che ogni codice venga utilizzato da qualche parte e che i file di configurazione contengano tutti i messaggi previsti, ecc.

Utilizzando framework in grado di analizzare Java come https://github.com/javaparser/javaparser o quello di Eclipse , puoi persino controllare dove vengono utilizzate le enumerazioni e trovare quelle inutilizzate.


2

Io (e il resto del nostro team nella mia azienda) preferiamo sollevare eccezioni invece di restituire codici di errore. I codici di errore devono essere controllati ovunque, passati in giro e tendono a rendere il codice illeggibile quando la quantità di codice diventa maggiore.

La classe di errore definirà quindi il messaggio.

PS: e in realtà tengo anche all'internazionalizzazione!
PPS: potresti anche ridefinire il metodo di rilancio e aggiungere logging, filtri ecc. Se necessario (almeno negli ambienti, dove le classi di eccezione e gli amici sono estendibili / modificabili)


scusa, Robin, ma poi (almeno dall'esempio sopra), queste dovrebbero essere due eccezioni: "errore del database" e "utente duplicato" sono così completamente diversi che dovrebbero essere create due sottoclassi di errore separate, che sono individualmente catturabili ( uno è un sistema, l'altro è un errore di amministratore)
blabla999

e a cosa servono i codici di errore, se non per distinguere tra l'una o l'altra eccezione? Quindi, almeno al di sopra del gestore, è esattamente questo: si occupa di codici di errore che vengono passati e se attivati.
blabla999

Penso che il nome dell'eccezione sarebbe molto più illustrativo e descrittivo di un codice di errore. Meglio pensare di più alla scoperta di buoni nomi di eccezioni, IMO.
duffymo

@ blabla999 ah, esattamente i miei pensieri. Perché intercettare un'eccezione a grana grossa e quindi testare "if errorcode == x, o y, or z". Un tale dolore e va contro il grano. Inoltre, non puoi catturare eccezioni diverse a livelli diversi nel tuo stack. Dovresti catturare ad ogni livello e testare il codice di errore a ciascuno. Rende il codice client molto più dettagliato ... +1 e più se potessi. Detto questo, immagino che dobbiamo rispondere alla domanda dei PO.
wmorrison365

2
Tieni presente che questo è per un servizio web. Il client può solo analizzare le stringhe. Sul lato server ci sarebbero ancora delle eccezioni che hanno un membro errorCode, che può essere utilizzato nella risposta finale al client.
pkrish

1

Un po 'tardi ma stavo solo cercando una bella soluzione per me stesso. Se si dispone di un diverso tipo di messaggio di errore, è possibile aggiungere una semplice fabbrica di messaggi personalizzata in modo da poter specificare più dettagli e formato che si desidera in seguito.

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, usare l'enum qui è un po 'pericoloso poiché si modifica in modo permanente l'enum particolare. Immagino che sarebbe meglio passare alla classe e usare campi statici, ma non puoi più usare '=='. Quindi immagino che sia un buon esempio di cosa non fare (o farlo solo durante l'inizializzazione) :)


1
Totalmente d'accordo con il tuo EDIT, non è una buona pratica alterare un campo enum in fase di esecuzione. Con questo design chiunque è in grado di modificare il messaggio di errore. Questo è piuttosto pericoloso. I campi enum dovrebbero essere sempre definitivi.
b3nyc

0

enum per il codice di errore / la definizione del messaggio è ancora una buona soluzione sebbene abbia problemi i18n. In realtà possiamo avere due situazioni: il codice / messaggio viene visualizzato all'utente finale o all'integratore di sistema. Per l'ultimo caso, I18N non è necessario. Penso che i servizi web siano molto probabilmente il caso successivo.


0

L'utilizzo interfacecome costante del messaggio è generalmente una cattiva idea. Perderà permanentemente nel programma client come parte dell'API esportata. Chissà, che i programmatori client successivi potrebbero analizzare quei messaggi di errore (pubblici) come parte del loro programma.

Sarai bloccato per sempre per supportare questo, poiché le modifiche al formato della stringa potrebbero / potrebbero interrompere il programma client.


0

Si prega di seguire l'esempio seguente:

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

};

Nel tuo codice chiamalo come:

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

0

Utilizzo PropertyResourceBundle per definire i codici di errore in un'applicazione aziendale per gestire le risorse del codice di errore locale. Questo è il modo migliore per gestire i codici di errore invece di scrivere codice (può essere valido per pochi codici di errore) quando il numero di codici di errore è enorme e strutturato.

Guarda il documento java per ulteriori informazioni su PropertyResourceBundle

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.