web-dev-qa-db-fra.com

Le meilleur moyen de définir des codes d’erreur/des chaînes en Java?

J'écris un service Web en Java et je suis {en essayant de déterminer le meilleur moyen de définir les codes d'erreur et les chaînes d'erreur associées}. J'ai besoin d'un code d'erreur numérique et d'une chaîne d'erreur regroupés. Le code d'erreur et la chaîne d'erreur seront envoyés au client accédant au service Web. Par exemple, lorsqu'une exception SQLException se produit, je souhaiterais peut-être effectuer les opérations suivantes:

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

Le programme client peut afficher le message suivant:

"Une erreur n ° 1 s'est produite: un problème d'accès à la base de données s'est produit "

Ma première pensée a été d'utiliser une variable Enum des codes d'erreur et de remplacer les méthodes toString pour renvoyer les chaînes d'erreur. Voici ce que je suis venu avec:

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
}

_ {Ma question est la suivante:} Existe-t-il une meilleure façon de procéder? Je préférerais une solution en code, plutôt que de lire à partir d'un fichier externe. J'utilise Javadoc pour ce projet et il serait utile de pouvoir documenter les codes d'erreur en ligne et de les mettre à jour automatiquement dans la documentation.

108
William Brendel

Eh bien, il y a certainement une meilleure implémentation de la solution enum (qui est généralement assez sympa):

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

Vous voudrez peut-être remplacer toString () pour simplement renvoyer la description - pas sûr. Quoi qu'il en soit, le point essentiel est qu'il n'est pas nécessaire de remplacer séparément chaque code d'erreur. Notez également que j'ai explicitement spécifié le code au lieu d'utiliser la valeur ordinale - cela facilite la modification de la commande et l'ajout/suppression d'erreurs ultérieurement.

N'oubliez pas que cela n'est pas du tout internationalisé - mais à moins que votre client de service Web ne vous envoie une description des paramètres régionaux, vous ne pourrez pas l'internationaliser facilement de toute façon. Au moins, ils auront le code d'erreur à utiliser pour i18n côté client ...

145
Jon Skeet

En ce qui me concerne, je préfère externaliser les messages d'erreur dans un fichier de propriétés ..__ Ceci sera très utile en cas d'internationalisation de votre application (un fichier de propriétés par langue). Il est également plus facile de modifier un message d'erreur et il ne nécessite aucune recompilation des sources Java.

Sur mes projets, j'ai généralement une interface contenant des codes d'erreur (chaîne ou entier, peu importe), qui contient la clé dans les fichiers de propriétés pour cette erreur:

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

dans le fichier de propriétés:

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

Un autre problème avec votre solution est la maintenabilité: vous n’avez que 2 erreurs, et déjà 12 lignes de code . Imaginez donc votre fichier Énumération quand vous aurez des centaines d’erreurs à gérer!

33
Romain Linsolas

Surcharger toString () semble un peu dégoûtant - cela semble être une partie de l’utilisation normale de toString ().

Qu'en est-il de:

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

cela me semble beaucoup plus propre ... et moins bavard.

20
Cowan

Lors de mon dernier emploi, je suis allé un peu plus loin dans la version enum:

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

@Error, @Info, @Warning sont conservés dans le fichier de classe et sont disponibles au moment de l'exécution. (Nous avons eu quelques autres annotations pour aider à décrire la livraison du message également)

@Text est une annotation à la compilation.

J'ai écrit un processeur d'annotation pour ceci qui a fait ce qui suit:

  • Vérifiez qu'il n'y a pas de numéros de message en double (la partie avant le premier trait de soulignement)
  • Syntaxe-vérifier le texte du message
  • Générez un fichier messages.properties contenant le texte, indexé par la valeur enum.

J'ai écrit quelques routines utilitaires qui ont permis de consigner les erreurs, de les envelopper comme des exceptions (si vous le souhaitez), etc.

J'essaie de les convaincre de me laisser ouvrir le code source ......-- Scott

16
Scott Stanchfield

Je vous recommande de jeter un coup d'œil à Java.util.ResourceBundle. Vous devriez vous soucier de I18N, mais cela en vaut la peine, même si vous ne le faites pas. Externaliser les messages est une très bonne idée. J'ai trouvé qu'il était utile de pouvoir fournir aux entreprises un tableur leur permettant de s'exprimer dans le langage exact souhaité. Nous avons écrit une tâche Ant pour générer les fichiers .properties au moment de la compilation. Cela rend I18N trivial. 

Si vous utilisez également le printemps, tant mieux. Leur classe MessageSource est utile pour ce genre de choses.

5
duffymo

Juste pour continuer à fouetter ce cheval mort particulier, nous avons bien utilisé les codes d’erreurnumériqueslorsque les erreurs sont signalées aux clients finaux, car ils oublient souvent ou interprètent mal le message et signaler une valeur numérique qui peut vous donner une idée de ce qui s'est réellement passé.

4
telcopro

Moi (et le reste de notre équipe dans mon entreprise) préférons lever des exceptions au lieu de renvoyer des codes d'erreur. Les codes d'erreur doivent être vérifiés partout, transmis, et ont tendance à rendre le code illisible lorsque la quantité de code augmente.

La classe d'erreur définirait alors le message.

PS: et s’occupe aussi de l’internationalisation!
PPS: vous pouvez également redéfinir la méthode de relance et ajouter la journalisation, le filtrage, etc. si nécessaire (au moins dans les environnements, où les classes et amis d'exception sont extensibles/modifiables)

1
blabla999

Un peu tard mais je cherchais juste une jolie solution pour moi-même. Si vous avez un type d'erreur de message différent, vous pouvez ajouter une fabrique de messages simple et personnalisée afin de pouvoir spécifier plus de détails et le format souhaité.

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, utiliser enum ici est un peu dangereux car vous modifiez un enum particulier de façon permanente ... Je pense que le mieux serait de changer de classe et d’utiliser des champs statiques, mais que vous ne pouvez plus utiliser '=='. Donc, je suppose que c’est un bon exemple de ce qu’il ne faut pas faire (ou le faire uniquement pendant l’initialisation) :)

1
pprzemek

Il y a plusieurs façons de résoudre ce problème. Mon approche préférée est d'avoir des interfaces:

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

public interface iMessage {
    ICode code();
}

Maintenant, vous pouvez définir autant d’énums que de messages:

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

Vous avez maintenant plusieurs options pour les transformer en chaînes. Vous pouvez compiler les chaînes dans votre code (en utilisant des annotations ou des paramètres de constructeur enum) ou les lire à partir d'un fichier de configuration/propriété, d'une table de base de données ou d'un mélange. Ce dernier est mon approche préférée car vous aurez toujours besoin de messages que vous pourrez transformer très tôt en texte (par exemple. While vous vous connectez à la base de données ou lisez la configuration).

J'utilise des tests unitaires et des structures de réflexion pour trouver tous les types qui implémentent mes interfaces afin de m'assurer que chaque code est utilisé quelque part et que les fichiers de configuration contiennent tous les messages attendus, etc.

En utilisant des frameworks qui peuvent analyser Java comme https://github.com/javaparser/javaparser ou celui d'Eclipse , vous pouvez même vérifier où les énumérations sont utilisées et trouver celles qui ne sont pas utilisées.

1
Aaron Digulla

Veuillez suivre l'exemple ci-dessous:

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

};

Dans votre code, appelez-le comme suit:

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

Utiliser interface comme constante de message est généralement une mauvaise idée. Il s'infiltrera de manière permanente dans le programme client dans le cadre de l'API exportée. Qui sait, les programmeurs clients ultérieurs pourraient analyser ces messages d'erreur (publics) dans le cadre de leur programme. 

Vous serez bloqué à jamais pour supporter cela, car les changements de format de chaîne vont/peuvent casser le programme client.

0
Awan Biru

enum for error code/message definition est toujours une solution intéressante même si elle a un souci pour i18n. En réalité, nous pouvons avoir deux situations: le code/message est affiché à l'utilisateur final ou à l'intégrateur de système. Pour le dernier cas, I18N n'est pas nécessaire. Je pense que les services Web sont très probablement le dernier cas.

0
Jimmy