Maison  >  Article  >  Java  >  Erreurs de gestion des mauvaises pratiques

Erreurs de gestion des mauvaises pratiques

WBOY
WBOYoriginal
2024-09-03 18:33:12576parcourir

Error Handling Bad Practices

Cet article fait suite à mon précédent article sur la gestion des erreurs en 3 étapes.

Dans cet article, je souhaite montrer quelques mauvaises pratiques en matière de gestion des erreurs et peut-être quelques solutions rapides, si vous trébuchez
à travers eux dans votre base de code.

Avertissement, cet article de blog n'est qu'une opinion personnelle et vous devrez ajuster les recommandations à votre cas d'utilisation spécifique.

1. Avaler des exceptions

Je tombe régulièrement sur des bases de code où les exceptions sont silencieusement ignorées. Peut-être que le développeur était pressé ou voulait gérer les cas d'erreur plus tard ? Quoi qu'il en soit, c'est une mauvaise pratique car si l'erreur se produit :

  • l'utilisateur ne recevra aucun retour et pourra penser que l'opération a réussi.
  • il n'y a pas de trace de pile ou de message d'erreur à enquêter.
  • Parfois, il n'y a même pas d'entrée de journal appropriée, ce qui rend presque impossible la détection de l'erreur.

Mauvais exemple :

  /**
   * Reads some data from another service or repo and returns it.
   * The repo might throw an exception, but we don't care.
   */
  public Mono<String> getHello() {
    try {
      return helloRepository.getHello()
          .map("Hello %s"::formatted);
    } catch (Exception e) {
      // do nothing
      return Mono.just("Hello World");
    }
  }

Dans cet exemple, nous pourrions nous demander pourquoi certains utilisateurs se plaignent de ne pas recevoir le bon message de bonjour, mais nous ne voyons aucune erreur, nous pourrions penser que les utilisateurs sont simplement confus.

Option 1 - Conserver le try-catch et gérer l'erreur

Gérez l'erreur là où elle se produit, par ex. déjà dans le référentiel si possible.
Si vous devez le gérer ici, enregistrez au moins l'erreur et renvoyez un message d'erreur approprié.

Refactoring minimal

  • enregistrer l'erreur (éventuellement également le stacktrace)
  • renvoie un message d'erreur approprié
  public Mono<String> getHello() {
    try {
      return helloRepository.getHello()
          .map("Hello %s"::formatted);
    } catch (Exception e) {
      log.error("Error reading hello data from repository - {}", e.getMessage(), e);
      return Mono.error(new RuntimeException("Internal error reading hello data %s".formatted(e.getMessage()), e));
    }
  }

REMARQUE :

Cela pourrait casser le code en amont puisque vous renvoyez maintenant une erreur au lieu d'une valeur par défaut.

Option 2 – Refactorisation appropriée du flux réactif

Il serait préférable de gérer l'erreur dans l'API du flux réactif

  public Mono<String> getHelloHandling() {
    return helloRepository.getHello()
      .map("Hello %s"::formatted)
      // wrap the exception in a RuntimeException with a meaningful message
      .onErrorMap(e -> new RuntimeException("Internal error reading hello data from HelloRepository` %s".
          formatted(e.getMessage()), e));
  }

1b. Avaler des exceptions - avec un flux réactif

Le modèle de l'exemple 1 apparaît également dans les flux réactifs :

  public Mono<String> getHello() {
    return helloRepository.getHello()
        .map("Hello %s"::formatted)
        // do nothing on error, just return a default value
        .onErrorReturn("Hello world");
  }

Ça a l'air très joli et propre, non ? Mais nous ne pourrons pas détecter que l'erreur est levée dans le référentiel !
S'il existe une valeur par défaut, au moins un journal des erreurs doit être rédigé.

Atténuation

Comme dans l'exemple précédent, nous encapsulons l'exception dans une autre exception, cette fois dans une exception personnalisée qui facilite même la détection de l'endroit spécifique où cette exception est levée

  public Mono<String> getHello2() {
    return helloRepository.getHello()
        .map("Hello %s"::formatted)
        .onErrorMap(
            e -> new CustomException("Error reading hello-data from repository - %s".formatted(e.getMessage()), e));
  }

2. Enregistrer à plusieurs reprises le stacktrace pour la même exception

L'opposé de la suppression silencieuse des exceptions consiste à enregistrer la même exception plusieurs fois. C'est une mauvaise pratique car elle confond les journaux avec des centaines ou des milliers de lignes de stacktraces sans apporter de signification supplémentaire.

Dans mon pire exemple, j'ai trouvé la même trace de pile cinq fois dans les journaux sans aucun message significatif.

Mauvais exemple :

Le contrôleur :

@RestController
@AllArgsConstructor
@Slf4j
public class HelloController {

  private final HelloService helloService;

  @GetMapping("/hello")
  public Mono<ResponseEntity<String>> hello() {
    return helloService.getHello()
        .map(ResponseEntity::ok)
        .defaultIfEmpty(ResponseEntity.notFound().build())
        .onErrorResume(e -> {
          log.error("Error:", e);
          return Mono.error(e);
        });
  }
}

Et dans le Service :

@Service
@AllArgsConstructor
@Slf4j
public class HelloService {

  private final HelloRepository helloRepository;

  /**
   * Reads some data from another service or repo and returns it.
   */
  public Mono<String> getHello() {
    return helloRepository.getHello()
        .map("Hello %s"::formatted)
        .onErrorResume(e -> {
          log.error("Error:", e);
          return Mono.error(e);
        });
  }
}

... et probablement dans d'autres endroits...

Atténuation

Ceci est expliqué dans mon article précédent Gestion des erreurs en 3 étapes, je ne montrerai donc pas le code ici, mais plutôt une recommandation :

  • Avoir un gestionnaire d'erreurs global qui enregistre l'erreur et renvoie un message d'erreur approprié à l'utilisateur.
  • Dans le code, évitez le stacktrace mais
    • enveloppez l'exception dans une exception personnalisée ou une exception d'exécution avec un message significatif
    • enregistrer le message d'erreur (pas la trace complète de la pile)
    • enregistrer la trace de pile uniquement dans le gestionnaire d'erreurs global

3. Détectez les exceptions génériques

La détection d'exceptions génériques telles que Exception ou Throwable peut entraîner un comportement involontaire et rendre le débogage assez difficile. Il est préférable d'attraper des exceptions spécifiques.

Mauvais exemple

  public Mono<String> getHello() {
    try {
      return helloRepository.getHello();
    } catch (Exception e) {
      log.error("Error while fetching hello data", e);
      return Mono.empty();
    }
  }

Atténuation

Détectez des exceptions spécifiques pour gérer différents scénarios d'erreur de manière appropriée.

  public Mono<String> getHello() {
    try {
      return helloRepository.getHello();
    } catch (SQLException e) {
      return Mono.error(new HelloDataException("Database error while getting hello-data - %s".formatted(e.getMessage()), e));
    } catch (IOException e) {
      // maybe perform a retry?
      return Mono.error(new HelloDataException("IO error while getting hello-data - %s".formatted(e.getMessage()), e));
    }
  }

et l'équivalent en utilisant l'API de flux réactif

  public Mono<String> getHello() {
    return helloRepository.getHello()
        .onErrorMap(SQLException.class, e -> new HelloDataException("Database error while getting hello-data - %s".formatted(e.getMessage()), e))
        .onErrorMap(IOException.class, e -> new HelloDataException("IO error while getting hello-data - %s".formatted(e.getMessage()), e));
  }

4. (Sur)utilisation des exceptions vérifiées

À mon avis, les exceptions vérifiées sont une erreur en Java, elles ne sont pas très utiles et conduisent souvent à de mauvaises pratiques et à un code encombré.

Mauvais exemple

public void someMethod() throws IOException, SQLException {
  // some code that might throw an exception
}

Avec les exceptions vérifiées, vous devez les gérer dans le code appelant, ce qui rend plus difficile l'utilisation d'autres modèles, par ex. programmation fonctionnelle ou flux réactifs ou au printemps le gestionnaire d'erreurs global.

Exception :
Les exceptions cochées sont utiles, par ex. dans le code de la bibliothèque, où vous souhaitez forcer l'utilisateur à gérer l'exception.

Atténuation

Utilisez des exceptions non contrôlées pour les scénarios dans lesquels on ne peut raisonnablement pas s'attendre à ce que l'appelant récupère.

public void someMethod() {
  try {
    // some code that might throw an exception
  } catch (IOException | SQLException e) {
    throw new RuntimeException("An error occurred - %s".formatted(e.getMessage()), e);
  }
}

Using Exceptions for Control Flow

Using exceptions for control flow makes the code hard to understand and can lead to performance issues.

Bad Example

try {
  int value = Integer.parseInt("abc");
} catch (NumberFormatException e) {
  // handle the case where the string is not a number
}

Mitigation

Use a regular flow control mechanism like an if-statement.

String value = "abc";
if (value.matches("\\d+")) {
  int number = Integer.parseInt(value);
} else {
  // handle the case where the string is not a number
}

Conclusion

In this article I showed some bad practices in error handling and how to mitigate them.
I hope you found this article helpful and maybe you can use some of the recommendations in your codebase and in your next refactoring.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Article précédent:API RESTCRUDArticle suivant:API RESTCRUD