Mettre en place les bonnes pratiques de gestion des erreurs #34

Closed
opened 2024-08-01 12:55:24 +02:00 by florian_briand · 0 comments

Il semblerait que le paquet https://docs.rs/error-chain/latest/error_chain/ soit un outil très encouragé pour faciliter une gestion propre des erreurs.

Il permet à la fois de créer facilement des erreurs customisée, accessibles de manière élégante, et permet un affichage clair de la chaine d'erreur en cas de panic.

La documentation du paquet explique comment bien mettre en place la gestion des erreurs !

Plus d'info :

Principes

Face à une erreur, 3 possibilités, par ordre de préférence :

  • Recover (Récupérer / Rétablir), selon différentes stratégies selon le contexte
    • Stop
    • Re-essayer tel quel
    • Re-essayer avec un nouvel input
    • Ignorer
    • Compenser / Contourner (fallback)
  • Propager (tel quel ou en convertissant l'erreur en une nouvelle erreur)
  • Crasher

Bonnes pratiques

Générales

  • Implémenter le Trait std::error::Error sur tous les types utilisés comme erreur ;
  • Ne pas trop utiliser les fonctions apportées par le Trait Error, car elles ne sont pas stables ; il est préférable d'utiliser à la place des librairies de gestion d'erreurs, qui font le boulot de s'adapter aux variations de la spec ;
  • Ne pas utiliser unwrap / expect à moins d'être sûr qu'on est dans une situation où on ne peut pas avoir, respectivement, de Err / None;
  • Quand on stock des informations de contexte dans une erreur (avec Result::map_err par exemple), préférer un stockage de données "structurées", plutôt qu'un stockage déstructuré (par exemple compilé dans une chaîne de texte décrivant l'erreur), pour donner + de possibilités à la partie du programme qui gèrera l'erreur de faire du recovery en ayant tout le contexte ;

Organisation

  • Deux styles cohabitent :
    • Enum style : stockage de différentes struct implémentant Error, dans des énums "spécialisés" (spécialisés par type d'erreur, pour par type de récupérabilité ...)
    • Single struct style : une seule struct représente toutes les erreurs, avec un paramètre enum kind
  • Choix du style (règles "souples" et pouvant cohabiter à plusieurs endroits):
    • Préférer l'Enum style pour les stratégies à base de recovery complexes
    • Préférer le Single struct style pour les stratégies à base de recovery simple (affichage de l'erreur - try again)
  • Éviter de mettre Error partout dans le nom des erreurs ; souvent, avoir Error dans le nom de l'Enum est suffisant :
    MyError::Io est préférable à MyError::IoError
  • Éviter de concentrer toutes les erreurs dans un unique module dédié : créer des enum spécialisés dans le module concerné
  • On peut utiliser #[non_exhaustive] sur les enum, pour faciliter la compatibilité ascendante
  • Quand on utilise différentes erreurs au niveau de l'API et au cœur des implémentations, implémenter le trait From pour une conversion facile

Spécificités pour le code unsafe

Bibliothèques :

Typage des erreurs

  • Anyhow : ajoute un type Error avec des helpers
    • Eyre : un fork de Anyhow visiblement un peu avancé
  • thiserror : ajoute un derive pour implémenter automatiquement le trait Error avec des helpers
  • error-stack : une version un peu + avancée de thiserror

Reporting

Logging

  • tracing la plus avancé pour le tracing
  • log standard assez simpliste ; nécessite un logger en +
    • env-logger combiné avec log, probablement le plus utilisé dans les petits projets et prototypes
    • log4rs et fern : loggers complexes à combiner avec log
Il semblerait que le paquet https://docs.rs/error-chain/latest/error_chain/ soit un outil très encouragé pour faciliter une gestion propre des erreurs. Il permet à la fois de créer facilement des erreurs customisée, accessibles de manière élégante, et permet un affichage clair de la chaine d'erreur en cas de panic. La documentation du paquet explique comment bien mettre en place la gestion des erreurs ! Plus d'info : - https://rust-lang-nursery.github.io/rust-cookbook/errors/handle.html - https://nrc.github.io/error-docs/intro.html # Principes Face à une erreur, 3 possibilités, par ordre de préférence : - Recover (Récupérer / Rétablir), selon différentes stratégies selon le contexte - Stop - Re-essayer tel quel - Re-essayer avec un nouvel input - Ignorer - Compenser / Contourner (fallback) - Propager (tel quel ou en convertissant l'erreur en une nouvelle erreur) - Crasher # Bonnes pratiques ## Générales - Implémenter le Trait [std::error::Error](https://doc.rust-lang.org/nightly/std/error/trait.Error.html) sur tous les types utilisés comme erreur ; - Ne pas trop utiliser les fonctions apportées par le Trait Error, car elles ne sont pas stables ; il est préférable d'utiliser à la place des librairies de gestion d'erreurs, qui font le boulot de s'adapter aux variations de la spec ; - Ne pas utiliser `unwrap` / `expect` à moins d'être sûr qu'on est dans une situation où on ne peut pas avoir, respectivement, de `Err` / `None`; - Quand on stock des informations de contexte dans une erreur (avec `Result::map_err` par exemple), préférer un stockage de données "structurées", plutôt qu'un stockage déstructuré (par exemple compilé dans une chaîne de texte décrivant l'erreur), pour donner + de possibilités à la partie du programme qui gèrera l'erreur de faire du recovery en ayant tout le contexte ; ## Organisation - Deux styles cohabitent : - Enum style : stockage de différentes struct implémentant Error, dans des énums "spécialisés" (spécialisés par type d'erreur, pour par type de récupérabilité ...) - Single struct style : une seule struct représente toutes les erreurs, avec un paramètre enum `kind` - Choix du style (règles "souples" et pouvant cohabiter à plusieurs endroits): - Préférer l'`Enum style` pour les stratégies à base de `recovery` complexes - Préférer le `Single struct style` pour les stratégies à base de `recovery` simple (affichage de l'erreur - try again) - Éviter de mettre `Error` partout dans le nom des erreurs ; souvent, avoir `Error` dans le nom de l'Enum est suffisant : `MyError::Io` est préférable à `MyError::IoError` - Éviter de concentrer toutes les erreurs dans un unique module dédié : créer des enum spécialisés dans le module concerné - On peut utiliser `#[non_exhaustive]` sur les enum, pour faciliter la compatibilité ascendante - Quand on utilise différentes erreurs au niveau de l'API et au cœur des implémentations, implémenter le trait `From` pour une conversion facile ## Spécificités pour le code `unsafe` - Le code `unsafe` doit être [`exception-safety`](https://doc.rust-lang.org/nomicon/exception-safety.html) - ??? # Bibliothèques : ## Typage des erreurs - [Anyhow](https://github.com/dtolnay/anyhow) : ajoute un type `Error` avec des helpers - [Eyre](https://github.com/eyre-rs/eyre) : un fork de `Anyhow` visiblement un peu avancé - [thiserror](https://github.com/dtolnay/thiserror) : ajoute un `derive` pour implémenter automatiquement le trait `Error` avec des helpers - [error-stack](https://github.com/hashintel/hash/tree/main/libs/error-stack) : une version un peu + avancée de `thiserror` ## Reporting - [miette](https://github.com/zkat/miette) - [ariadne](https://github.com/zesterer/ariadne) - [codespan](https://github.com/brendanzab/codespan) ## Logging - [tracing](https://github.com/tokio-rs/tracing) la plus avancé pour le tracing - [log](https://github.com/rust-lang/log) standard assez simpliste ; nécessite un logger en + - [env-logger](https://github.com/env-logger-rs/env_logger/) combiné avec `log`, probablement le plus utilisé dans les petits projets et prototypes - [log4rs](https://github.com/estk/log4rs) et [fern](https://github.com/daboross/fern) : loggers complexes à combiner avec `log`
florian_briand added the
enhancement
label 2024-08-01 12:55:24 +02:00
florian_briand added this to the 0 - POC project 2024-08-01 12:55:24 +02:00
florian_briand added this to the 0 - POC milestone 2024-08-02 10:42:36 +02:00
florian_briand self-assigned this 2024-08-06 22:11:18 +02:00
Sign in to join this conversation.
No Milestone
No project
No Assignees
1 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: P4Pillon/Krys4lide#34
No description provided.