Apprendre à gérer les erreurs dans Go
Pendant que vous écrivez vos programmes, vous devez prendre en compte les différentes façons dont vos programmes peuvent échouer, et vous devez gérer les défaillances. Vos utilisateurs n’ont pas besoin de voir une erreur de trace de pile longue et prêtant à confusion. Il est préférable qu’ils voient des informations significatives sur le problème rencontré. Comme vous l’avez constaté, Go offre des fonctions intégrées telles que panic
et recover
pour gérer les exceptions, ou un comportement inattendu, dans vos programmes. Toutefois, les erreurs sont des défaillances connues et vos programmes devraient être conçus pour les gérer.
L’approche de la gestion des erreurs de Go est simplement un mécanisme de flux de contrôle où seules des instructions if
et return
sont nécessaires. Par exemple, lorsque vous appelez une fonction pour obtenir des informations à partir d’un objet employee
, vous souhaiterez peut-être savoir si l’employé existe. Le mode de gestion strict de Go pour ce type d’erreur attendue devrait ressembler à ceci :
employee, err := getInformation(1000)
if err != nil {
// Something is wrong. Do something.
}
Notez comment la fonction getInformation
retourne le struct employee
et également une erreur comme deuxième valeur. L’erreur peut être nil
. L’erreur nil
indique la réussite. S’il ne s’agit pas d’une erreur nil
, cela signifie un échec. Une erreur non-nil
accompagne un message d’erreur que vous pouvez imprimer ou, de préférence, journaliser. Voici comment gérer les erreurs dans Go. Nous allons aborder quelques autres stratégies dans la section suivante.
Vous remarquerez probablement que la gestion des erreurs dans Go vous demande de faire plus attention à la façon dont vous signalez et gérez une erreur. C’est bien là le point essentiel. Examinons d’autres exemples qui vous aideront à mieux comprendre l’approche de la gestion des erreurs de Go.
Nous utiliserons l’extrait de code que nous avons utilisé pour les structs afin de tester diverses stratégies de gestion des erreurs :
package main
import (
"fmt"
"os"
)
type Employee struct {
ID int
FirstName string
LastName string
Address string
}
func main() {
employee, err := getInformation(1001)
if err != nil {
// Something is wrong. Do something.
} else {
fmt.Print(employee)
}
}
func getInformation(id int) (*Employee, error) {
employee, err := apiCallEmployee(1000)
return employee, err
}
func apiCallEmployee(id int) (*Employee, error) {
employee := Employee{LastName: "Doe", FirstName: "John"}
return &employee, nil
}
À partir de là, nous allons nous concentrer sur la modification des fonctions getInformation
, apiCallEmployee
et main
pour montrer comment gérer les erreurs.
Stratégies de gestion des erreurs
Quand une fonction retourne une erreur, il s’agit généralement de la dernière valeur de retour. Il incombe à l’appelant de vérifier s’il existe une erreur et de la gérer, comme vous l’avez vu dans la section précédente. Par conséquent, une stratégie courante consiste à continuer à utiliser ce modèle pour propager l’erreur dans une sous-routine. Par exemple, une sous-routine (comme getInformation
dans l’exemple précédent) peut retourner l’erreur à l’appelant sans rien faire d’autre, comme suit :
func getInformation(id int) (*Employee, error) {
employee, err := apiCallEmployee(1000)
if err != nil {
return nil, err // Simply return the error to the caller.
}
return employee, nil
}
Vous pouvez également inclure des informations supplémentaires avant de propager l’erreur. À cet effet, vous pouvez utiliser la fonction fmt.Errorf()
, qui est similaire à ce que nous avons vu précédemment, mais elle retourne une erreur. Par exemple, vous pouvez ajouter davantage de contexte à l’erreur et retourner quand même l’erreur d’origine, comme suit :
func getInformation(id int) (*Employee, error) {
employee, err := apiCallEmployee(1000)
if err != nil {
return nil, fmt.Errorf("Got an error when getting the employee information: %v", err)
}
return employee, nil
}
Une autre stratégie consiste à exécuter une logique de nouvelle tentative lorsque les erreurs sont temporaires. Par exemple, vous pouvez utiliser une stratégie de nouvelle tentative pour appeler une fonction trois fois et attendre deux secondes, comme suit :
func getInformation(id int) (*Employee, error) {
for tries := 0; tries < 3; tries++ {
employee, err := apiCallEmployee(1000)
if err == nil {
return employee, nil
}
fmt.Println("Server is not responding, retrying ...")
time.Sleep(time.Second * 2)
}
return nil, fmt.Errorf("server has failed to respond to get the employee information")
}
Enfin, au lieu d’imprimer des erreurs dans la console, vous pouvez journaliser les erreurs et cacher les détails d’implémentation aux utilisateurs finaux. Nous aborderons la journalisation dans le module suivant. Pour l’instant, voyons comment vous pouvez créer et utiliser des erreurs personnalisées.
Créer des erreurs réutilisables
Parfois, le nombre de messages d’erreur augmente et vous souhaitez maintenir l’ordre. Vous pouvez également créer une bibliothèque pour les messages d’erreur courants que vous souhaitez réutiliser. Dans Go, vous pouvez utiliser la fonction errors.New()
pour créer des erreurs et les réutiliser dans plusieurs parties, comme suit :
var ErrNotFound = errors.New("Employee not found!")
func getInformation(id int) (*Employee, error) {
if id != 1001 {
return nil, ErrNotFound
}
employee := Employee{LastName: "Doe", FirstName: "John"}
return &employee, nil
}
Le code de la fonction getInformation
semble préférable et, si vous avez besoin de modifier le message d’erreur, vous le faites à un seul endroit. Notez également que la convention consiste à inclure le préfixe Err
pour les variables d’erreur.
Enfin, lorsque vous avez une variable d’erreur, vous pouvez être plus précis quand vous gérez une erreur dans une fonction d’appelant. La fonction errors.Is()
vous permet de comparer le type d’erreur que vous obtenez, comme suit :
employee, err := getInformation(1000)
if errors.Is(err, ErrNotFound) {
fmt.Printf("NOT FOUND: %v\n", err)
} else {
fmt.Print(employee)
}
Pratiques recommandées pour la gestion des erreurs
Lorsque vous gérez des erreurs dans Go, voici quelques pratiques recommandées à garder à l’esprit :
- Recherchez toujours les erreurs, même si vous ne les attendez pas. Gérez-les ensuite correctement pour éviter d’exposer des informations inutiles aux utilisateurs finaux.
- Incluez un préfixe dans un message d’erreur afin de connaître l’origine de l’erreur. Par exemple, vous pouvez inclure le nom du package et de la fonction.
- Créez autant de variables d’erreur réutilisables que vous le pouvez.
- Comprenez la différence entre le recours au retour d’erreurs et la crise de panique. Paniquez quand vous ne pouvez rien faire d’autre. Par exemple, si une dépendance n’est pas prête, il n’est pas judicieux de faire fonctionner le programme (sauf si vous souhaitez exécuter un comportement par défaut).
- Journalisez les erreurs avec autant de détails que possible (nous allons voir comment dans la section suivante) et imprimez les erreurs qu’un utilisateur final peut comprendre.