Parlons des points douloureux. En raison de mes responsabilités, je dois utiliser de nombreux services différents (faire de l'édition, faire de la révision du code...) ; différentes équipes écrivent généralement tous ces services, chaque fois qu'il s'agit de gérer les erreurs et de les transmettre depuis le service, parfois mes yeux se mettent à s'émerveiller. arroser. Laissez-moi essayer de vous dire quel code présente, à mon avis, une gestion des erreurs inacceptable et comment, à mon avis, il devrait être géré.
Généralement, au départ, le problème est caché par une faille dans l’analyse du service. En général, il n'y a aucune exigence de référence sur la manière dont les erreurs sont générées. En général, cela se produit pour deux raisons : la première est la précipitation pour développer de nouveaux services, et la seconde est que les analystes font confiance à l'expérience des développeurs. Dans ce cas, l'analyste dit simplement au développeur : "D'accord, donnez-moi un exemple de message d'erreur plus tard, et je le joindrai en confluence."
Passons à l'exemple, voyons les conséquences de cette approche en développement.
La première chose à ne pas faire est de lancer une RuntimeException :
public Object badExample() {
throw new RuntimeException("Something wrong!");
}
exposition:
Un service ou un client appelant recevra une erreur 500 et n’aura aucun moyen de comprendre ce qui ne va pas avec sa demande. Combien de temps pensez-vous qu’il faudra pour trouver le problème dans ce cas ? Il augmentera proportionnellement à la quantité de votre code. Si vous disposez d’une chaîne d’appels de méthodes, il devient plus difficile de gérer de tels messages lorsque les méthodes sont appelées à l’intérieur du service.
Comment l'améliorer ? Tout d'abord, créons un gestionnaire d'erreurs ; presque tous les frameworks le fournissent immédiatement ; dans mon exemple, j'utiliserai le gestionnaire de framework Spring.
exemple:
@ControllerAdvice
public class ApiExceptionHandler {
@ExceptionHandler(value = {RuntimeException.class})
public ResponseEntity<Object> handler(RuntimeException e) {
HttpStatus badRequest = HttpStatus.INTERNAL_SERVER_ERROR;
return new ResponseEntity<>(e.getMessage(), badRequest);
}
...
exposition:
Nous voyons maintenant le message, mais le format du message est une chaîne, pas JSON. Nous allons résoudre ce problème bientôt.
Idéalement, tous les services d'un projet devraient avoir le même format de message d'erreur. Au moins deux champs contiennent le message lui-même et un code d'erreur entier interne. Je suppose qu'il n'est pas nécessaire d'expliquer pourquoi le texte du message est insuffisant et combien de ressources sont nécessaires pour traiter cette chaîne.
exemple:
public class ExampleApiError {
private String message;
private Integer code;
private LocalDateTime dateTime;
...
Nous remplirons cette classe dans notre gestionnaire d'erreurs. Mais comme je l'ai dit, lancer RuntimeException est une mauvaise pratique, vous devez donc créer votre propre classe pour générer l'erreur, dans le constructeur de cette classe, nous passerons un message et un code d'erreur.
public class ApiException extends RuntimeException {
private final int code;
public ApiException(String msg, int code) {
super(msg);
this.code = code;
}
...
Tout semble plus clair, mais même ici, les problèmes commencent : chacun crée différemment les paramètres transmis au constructeur de classe. Certains créent des messages et des codes d'erreur directement dans la méthode.
exemple:
public Object badExample() {
throw new ApiException("Something wrong!", 10);
}
Trouver de tels endroits dans le code est encore difficile. Eh bien, la première idée était de créer des classes constantes, une classe pour les messages et une seconde classe pour les codes de message.
exemple:
public static final String SOMETHING_WRONG = "Something wrong";
public static final int SOMETHING_WRONG_CODE = 10;
public Object badExample() {
throw new ApiException(SOMETHING_WRONG, SOMETHING_WRONG_CODE);
}
C’est déjà mieux, plus lisible et plus facile à trouver quand on sait où se trouve le code d’erreur.
Cependant, si vous ne disposez pas de microservices, ce n'est peut-être pas une bonne idée de tout stocker dans une seule classe. Les messages commencent à devenir plus gros, il est donc préférable de séparer les classes constantes en fonction de la fonctionnalité des méthodes qui seront utilisées avec ProductExceptionConstant, PaymentExceptionConstant, etc.
Mais ce n'est pas tout. Pour certains, cela peut sembler obsolète, mais les constantes doivent être créées sous forme d'énumérations ou d'interfaces. Les variables des interfaces sont statiques, publiques et finales par défaut. Je ne suis pas contre cela ; l'essentiel est que tout soit uniforme ; si vous commencez à faire les choses via des interfaces, continuez à le faire ; aucun mixage n'est nécessaire. Dans l'un des projets, j'ai vu trois méthodes constantes différentes utilisées par la même équipe. Vous n'êtes pas obligé. )
Je vais montrer un autre exemple d'un projet réel qui a attiré mon attention au cours du processus d'examen.
Tout d'abord, le développeur a décidé que toutes les entités qu'il renverrait contiendraient des messages d'erreur et des codes d'erreur, et que le statut serait toujours 200, ce qui, à mon avis, induit l'appelant en erreur. Eh bien, un exemple de constante.
public enum ErrorsEnum {
DEFAULT_ERROR(ErrorsConstants.DEFAULT_ERROR, "400"),
REFRESH_TOKEN_NOT_VALID(ErrorsConstants.REFRESH_TOKEN_NOT_VALID, "400.1"),
USER_NOT_FOUND(ErrorsConstants.USER_NOT_FOUND, "400.2"),
Il me semble qu'il manque le code 9 3\4. Par défaut, dans ce cas, vous pouvez utiliser le Pi numérique.
Si ma méthode vous intéresse, n'hésitez pas. La chose la plus pratique à faire est de créer une interface qui constituera un contrat contraignant pour tout le monde.
public interface ExceptionBase {
String getMsg();
Integer getCode();
}
Et le constructeur de la classe d'exception doit être modifié.
public ApiException(ExceptionBase e) {
super(e.getMsg());
this.code = e.getCode();
}
Désormais, tous ceux qui tentent de lever une exception comprendront que nous devons transmettre un objet implémentant l'interface au constructeur. Le choix le plus approprié est Enum.
exemple:
/**
* This class is intended for declaring exceptions that occur during order processing. code range
* 101-199.
*/
public enum OrderException implements ExceptionBase {
ORDER_NOT_FOUND("Order not found.", 101),
ORDER_CANNOT_BE_UPDATED("Order cannot be updated,", 102);
OrderException(String msg, Integer code) {
this.msg = msg;
this.code = code;
}
private final String msg;
private final Integer code;
@Override
public String getMsg() {
return msg;
}
@Override
public Integer getCode() {
return code;
}
}
Exemple d'utilisation :
public Object getProduct(String id) {
if (id.equals("-1")) {
throw new ApiException(OrderException.ORDER_NOT_FOUND);
}
...
Réponse du serveur :
Par conséquent, nous avons le modèle de sac d’exception suivant.
Comme vous pouvez le constater, rien de compliqué, la logique est facile à lire. Dans cet exemple, j'ai laissé un constructeur dans la classe ApiException qui accepte une chaîne, mais pour des raisons de fiabilité, il est préférable de le supprimer.
En règle générale, la plupart des incohérences dans le code sont dues au manque d’inspections du code ou à de mauvaises inspections. L'excuse la plus courante est : "C'est une solution temporaire, nous y remédierons plus tard", mais non, cela ne fonctionne pas comme ça ; personne ne prend la peine de savoir où il y a une solution temporaire et où il y a une solution permanente. . Il s’avère que « ce qui est temporaire est ce qui est permanent ».
Si de nombreux services communiquent entre eux, vous simplifierez grandement votre travail d'écriture de bibliothèques client en créant un seul format de message d'erreur. Par exemple, lors de l'utilisation de Retrofit, une fois le noyau du gestionnaire écrit, il vous suffit de modifier la méthode dans l'interface et l'objet qu'elle reçoit.
en conclusion
La gestion des erreurs est une partie très importante de votre code ; elle facilite la recherche des zones problématiques dans votre code et permet également aux clients externes de comprendre ce qu'ils font de mal lorsqu'ils utilisent un point de terminaison. Vous devriez donc avoir raison dès le début. première étape de l'écriture de votre projet. Une attention particulière y est accordée.
Exemple de code ici .
J'espère que votre vie sera un peu plus facile après avoir lu cet article. Tout a fonctionné.