Le code robuste doit réagir correctement à des circonstances imprévues, telles qu'une saisie utilisateur incorrecte, des connexions réseau défectueuses et des disques défaillants. Error handling est le processus d'identification du moment où votre programme est dans un état inattendu et de la prise de mesures pour enregistrer les informations de diagnostic pour un débogage ultérieur.
Contrairement à d'autres langages qui obligent les développeurs à gérer les erreurs avec une syntaxe spécialisée, les erreurs dans Go sont des valeurs de typeerror
renvoyées par des fonctions comme toute autre valeur. Pour gérer les erreurs dans Go, nous devons examiner ces erreurs que les fonctions peuvent renvoyer, déterminer si une erreur s'est produite et prendre les mesures appropriées pour protéger les données et informer les utilisateurs ou les opérateurs que l'erreur est survenue.
Créer des erreurs
Avant de pouvoir gérer les erreurs, nous devons en créer d’abord. La bibliothèque standard fournit deux fonctions intégrées pour créer des erreurs:errors.New
etfmt.Errorf
. Ces deux fonctions vous permettent de spécifier un message d'erreur personnalisé que vous pourrez ensuite présenter à vos utilisateurs.
errors.New
prend un seul argument - un message d'erreur sous forme de chaîne que vous pouvez personnaliser pour alerter vos utilisateurs de ce qui ne va pas.
Essayez d'exécuter l'exemple suivant pour voir une erreur créée parerrors.New
imprimée sur la sortie standard:
package main
import (
"errors"
"fmt"
)
func main() {
err := errors.New("barnacles")
fmt.Println("Sammy says:", err)
}
OutputSammy says: barnacles
Nous avons utilisé la fonctionerrors.New
de la bibliothèque standard pour créer un nouveau message d'erreur avec la chaîne"barnacles"
comme message d'erreur. Nous avons suivi la convention ici en utilisant des minuscules pour le message d'erreur comme le suggère leGo Programming Language Style Guide.
Enfin, nous avons utilisé la fonctionfmt.Println
pour combiner notre message d'erreur avec"Sammy says:"
.
La fonctionfmt.Errorf
vous permet de créer dynamiquement un message d'erreur. Son premier argument est une chaîne contenant votre message d'erreur avec des valeurs d'espace réservé telles que%s
pour une chaîne et%d
pour un entier. fmt.Errorf
interpole les arguments qui suivent cette chaîne de formatage dans ces espaces réservés dans l'ordre:
package main
import (
"fmt"
"time"
)
func main() {
err := fmt.Errorf("error occurred at: %v", time.Now())
fmt.Println("An error happened:", err)
}
OutputAn error happened: Error occurred at: 2019-07-11 16:52:42.532621 -0400 EDT m=+0.000137103
Nous avons utilisé la fonctionfmt.Errorf
pour créer un message d'erreur qui inclurait l'heure actuelle. La chaîne de mise en forme que nous avons fournie àfmt.Errorf
contient la directive de mise en forme%v
qui indique àfmt.Errorf
d'utiliser le formatage par défaut pour le premier argument fourni après la chaîne de formatage. Cet argument sera l'heure actuelle, fournie par la fonctiontime.Now
de la bibliothèque standard. De manière similaire à l'exemple précédent, nous combinons notre message d'erreur avec un préfixe court et imprimons le résultat sur la sortie standard à l'aide de la fonctionfmt.Println
.
Gestion des erreurs
En règle générale, aucune erreur créée de la sorte ne sera utilisée immédiatement sans autre but, comme dans l'exemple précédent. En pratique, il est beaucoup plus courant de créer une erreur et de la renvoyer depuis une fonction en cas de problème. Les appelants de cette fonction utiliseront alors une instructionif
pour voir si l'erreur était présente ounil
- une valeur non initialisée.
Cet exemple suivant inclut une fonction qui renvoie toujours une erreur. Notez que lorsque vous exécutez le programme, il produit la même sortie que dans l'exemple précédent, même si une fonction renvoie l'erreur cette fois-ci. La déclaration d’une erreur dans un emplacement différent ne modifie pas le message de l’erreur.
package main
import (
"errors"
"fmt"
)
func boom() error {
return errors.New("barnacles")
}
func main() {
err := boom()
if err != nil {
fmt.Println("An error occurred:", err)
return
}
fmt.Println("Anchors away!")
}
OutputAn error occurred: barnacles
Ici, nous définissons une fonction appeléeboom()
qui renvoie un seulerror
que nous construisons en utilisanterrors.New
. Nous appelons ensuite cette fonction et capturons l'erreur avec la ligneerr := boom()
.
Une fois que nous affectons cette erreur, nous vérifions si elle était présente avec le conditionnelif err != nil
. Ici, le conditionnel sera toujours évalué àtrue
, puisque nous retournons toujours unerror
deboom()
.
Ce ne sera pas toujours le cas. Il est donc recommandé d’avoir une gestion logique des cas où une erreur n’est pas présente (nil
) et des cas où l’erreur est présente. Lorsque l'erreur est présente, nous utilisonsfmt.Println
pour afficher notre erreur avec un préfixe comme nous l'avons fait dans les exemples précédents. Enfin, nous utilisons une instructionreturn
pour ignorer l'exécution defmt.Println("Anchors away!")
, car cela ne devrait s'exécuter que lorsqu'aucune erreur ne s'est produite.
[.note] #Note: La constructionif err != nil
montrée dans le dernier exemple est le cheval de bataille de la gestion des erreurs dans le langage de programmation Go. Partout où une fonction peut produire une erreur, il est important d'utiliser une instructionif
pour vérifier si une erreur s'est produite. De cette façon, le code Go idiomatique a naturellement sa logique“happy path” au premier niveau de retrait, et toute la logique de «triste chemin» au deuxième niveau de retrait.
#
Si les instructions ont une clause d'affectation facultative qui peut être utilisée pour aider à condenser l'appel d'une fonction et la gestion de ses erreurs.
Exécutez le programme suivant pour voir la même sortie que notre exemple précédent, mais cette fois en utilisant une instruction composéeif
pour réduire un peu passe-partout:
package main
import (
"errors"
"fmt"
)
func boom() error {
return errors.New("barnacles")
}
func main() {
if err := boom(); err != nil {
fmt.Println("An error occurred:", err)
return
}
fmt.Println("Anchors away!")
}
OutputAn error occurred: barnacles
Comme précédemment, nous avons une fonction,boom()
, qui renvoie toujours une erreur. Nous affectons l'erreur renvoyée deboom()
àerr
comme première partie de l'instructionif
. Dans la deuxième partie de l'instructionif
, après le point-virgule, cette variableerr
est alors disponible. Nous vérifions si l'erreur était présente et imprimons notre erreur avec une chaîne de préfixe courte, comme nous l'avons fait précédemment.
Dans cette section, nous avons appris à gérer des fonctions ne renvoyant qu’une erreur. Ces fonctions sont courantes, mais il est également important de pouvoir gérer les erreurs provenant de fonctions pouvant renvoyer plusieurs valeurs.
Renvoi d'erreurs à côté de valeurs
Les fonctions qui renvoient une seule valeur d'erreur sont souvent celles qui effectuent des modifications avec état, telles que l'insertion de lignes dans une base de données. Il est également courant d’écrire des fonctions qui renvoient une valeur si elles aboutissent avec une erreur potentielle en cas d’échec de cette fonction. Go permet aux fonctions de renvoyer plusieurs résultats, ce qui peut être utilisé pour renvoyer simultanément une valeur et un type d'erreur.
Pour créer une fonction renvoyant plusieurs valeurs, nous répertorions les types de chaque valeur renvoyée entre parenthèses dans la signature de la fonction. Par exemple, une fonctioncapitalize
qui renvoie unstring
et unerror
serait déclarée en utilisantfunc capitalize(name string) (string, error) {}
. La partie(string, error)
indique au compilateur Go que cette fonction renverra unstring
et unerror
, dans cet ordre.
Exécutez le programme suivant pour voir la sortie d'une fonction qui renvoie à la fois unstring
et unerror
:
package main
import (
"errors"
"fmt"
"strings"
)
func capitalize(name string) (string, error) {
if name == "" {
return "", errors.New("no name provided")
}
return strings.ToTitle(name), nil
}
func main() {
name, err := capitalize("sammy")
if err != nil {
fmt.Println("Could not capitalize:", err)
return
}
fmt.Println("Capitalized name:", name)
}
OutputCapitalized name: SAMMY
Nous définissonscapitalize()
comme une fonction qui prend une chaîne (le nom à mettre en majuscule) et retourne une chaîne et une valeur d'erreur. Dansmain()
, nous appelonscapitalize()
et assignons les deux valeurs renvoyées par la fonction aux variablesname
eterr
en les séparant par des virgules sur le côté gauche du Opérateur:=
. Après cela, nous effectuons notre vérification deif err != nil
comme dans les exemples précédents, en imprimant l'erreur sur la sortie standard en utilisantfmt.Println
si l'erreur était présente. Si aucune erreur n'était présente, nous imprimonsCapitalized name: SAMMY
.
Essayez de remplacer la chaîne"sammy"
dename, err := capitalize("sammy")
par la chaîne vide("")
et vous recevrez l’erreurCould not capitalize: no name provided
à la place.
La fonctioncapitalize
renverra une erreur lorsque les appelants de la fonction fournissent une chaîne vide pour le paramètrename
. Lorsque le paramètrename
n'est pas la chaîne vide,capitalize()
utilisestrings.ToTitle
pour mettre en majuscule le paramètrename
et renvoienil
pour la valeur d'erreur.
Cet exemple suit certaines conventions subtiles qui sont typiques du code Go, mais qui ne sont pas appliquées par le compilateur Go. Lorsqu'une fonction renvoie plusieurs valeurs, y compris une erreur, la convention demande que nous retournions leserror
comme dernier élément. Lors du retour d'unerror
à partir d'une fonction avec plusieurs valeurs de retour, le code Go idiomatique définira également chaque valeur sans erreur sur unzero value. Les valeurs nulles sont, par exemple, une chaîne vide pour les chaînes,0
pour les entiers, une structure vide pour les types struct etnil
pour les types interface et pointeur, pour n'en nommer que quelques-uns. Nous couvrons les valeurs nulles plus en détail dans nostutorial on variables and constants.
Réduire le passe-partout
Le respect de ces conventions peut devenir fastidieux dans les situations où il existe de nombreuses valeurs à renvoyer d'une fonction. Nous pouvons utiliser unanonymous function pour aider à réduire le passe-partout. Les fonctions anonymes sont des procédures assignées à des variables. Contrairement aux fonctions que nous avons définies dans les exemples précédents, elles ne sont disponibles que dans les fonctions où vous les déclarez, ce qui les rend parfaites pour agir comme de courts morceaux de logique d'aide réutilisable.
Le programme suivant modifie le dernier exemple pour inclure la longueur du nom que nous capitalisons. Comme il a trois valeurs à renvoyer, les erreurs de traitement pourraient devenir encombrantes sans une fonction anonyme pour nous aider:
package main
import (
"errors"
"fmt"
"strings"
)
func capitalize(name string) (string, int, error) {
handle := func(err error) (string, int, error) {
return "", 0, err
}
if name == "" {
return handle(errors.New("no name provided"))
}
return strings.ToTitle(name), len(name), nil
}
func main() {
name, size, err := capitalize("sammy")
if err != nil {
fmt.Println("An error occurred:", err)
}
fmt.Printf("Capitalized name: %s, length: %d", name, size)
}
OutputCapitalized name: SAMMY, length: 5
Dansmain()
, nous capturons maintenant les trois arguments renvoyés parcapitalize
en tant quename
,size
eterr
, respectivement. Nous vérifions ensuite sicapitalize
a renvoyé unerror
en vérifiant si la variableerr
n'était pas égale ànil
. Ceci est important à faire avant d'essayer d'utiliser l'une des autres valeurs renvoyées parcapitalize
, car la fonction anonyme,handle
, pourrait les mettre à zéro. Comme aucune erreur ne s'est produite car nous avons fourni la chaîne"sammy"
, nous imprimons le nom en majuscule et sa longueur.
Une fois de plus, vous pouvez essayer de remplacer"sammy"
par la chaîne vide("")
pour voir le cas d'erreur imprimé (An error occurred: no name provided
).
Danscapitalize
, nous définissons la variablehandle
comme une fonction anonyme. Il prend une seule erreur et renvoie des valeurs identiques dans le même ordre que les valeurs de retour decapitalize
. handle
définit ces valeurs sur des valeurs nulles et transmet leserror
passés comme argument comme valeur de retour finale. En utilisant cela, nous pouvons ensuite retourner toutes les erreurs rencontrées danscapitalize
en utilisant l'instructionreturn
devant l'appel àhandle
avec leerror
comme paramètre.
N'oubliez pas quecapitalize
doit renvoyer trois valeurs en permanence, car c'est ainsi que nous avons défini la fonction. Parfois, nous ne voulons pas traiter de toutes les valeurs pouvant être renvoyées par une fonction. Heureusement, nous avons une certaine flexibilité dans la manière dont nous pouvons utiliser ces valeurs du côté des affectations.
Gestion des erreurs à partir de fonctions multi-retours
Lorsqu'une fonction renvoie plusieurs valeurs, Go nous oblige à les affecter à une variable. Dans le dernier exemple, nous faisons cela en fournissant des noms pour les deux valeurs renvoyées par la fonctioncapitalize
. Ces noms doivent être séparés par des virgules et apparaître sur le côté gauche de l'opérateur:=
. La première valeur renvoyée parcapitalize
sera affectée à la variablename
, et la seconde valeur (leserror
) sera affectée à la variableerr
. Parfois, nous ne sommes intéressés que par la valeur d'erreur. Vous pouvez ignorer toutes les valeurs indésirables renvoyées par les fonctions en utilisant le nom de variable spécial_
.
Dans le programme suivant, nous avons modifié notre premier exemple impliquant la fonctioncapitalize
pour produire une erreur en passant la chaîne vide("")
. Essayez d'exécuter ce programme pour voir comment nous pouvons examiner uniquement l'erreur en ignorant la première valeur renvoyée avec la variable_
:
package main
import (
"errors"
"fmt"
"strings"
)
func capitalize(name string) (string, error) {
if name == "" {
return "", errors.New("no name provided")
}
return strings.ToTitle(name), nil
}
func main() {
_, err := capitalize("")
if err != nil {
fmt.Println("Could not capitalize:", err)
return
}
fmt.Println("Success!")
}
OutputCould not capitalize: no name provided
Dans la fonctionmain()
cette fois, nous attribuons le nom en majuscule (lestring
retourné en premier) à la variable de soulignement (_
). En même temps, nous affectons leserror
renvoyés parcapitalize
à la variableerr
. Nous vérifions ensuite si l'erreur était présente dans le conditionnelif err != nil
. Puisque nous avons codé en dur une chaîne vide comme argument decapitalize
dans la ligne_, err := capitalize("")
, ce conditionnel sera toujours évalué àtrue
. Cela produit la sortie"Could not capitalize: no name provided"
imprimée par l'appel à la fonctionfmt.Println
dans le corps de l'instructionif
. Lesreturn
après cela sauteront lesfmt.Println("Success!")
.
Conclusion
Nous avons vu de nombreuses façons de créer des erreurs en utilisant la bibliothèque standard et de créer des fonctions qui renvoient les erreurs de manière idiomatique. Dans ce didacticiel, nous avons réussi à créer diverses erreurs à l'aide des fonctions de bibliothèque standarderrors.New
etfmt.Errorf
. Dans les prochains tutoriels, nous verrons comment créer nos propres types d'erreur personnalisés pour transmettre des informations plus riches aux utilisateurs.