backend / go
Fonctions et valeurs multiples
Explication
Ce que vous allez apprendre
- Écrire des fonctions avec plusieurs paramètres et plusieurs valeurs de retour
- Comprendre pourquoi
(valeur, error)est LE pattern central de Go pour signaler un échec - Utiliser les valeurs de retour nommées pour documenter l'intention d'une fonction
- Écrire une fonction variadique (
...int) et déballer un slice avecslice... - Passer une fonction en paramètre et écrire des closures qui capturent une variable
Dans quel contexte ?
Un développeur qui vient de Python attaque une fonction Go qui interroge une base de données et s'attend à devoir écrire un try/except. Il découvre à la place une fonction qui retourne (*Utilisateur, error), et doit vérifier if err != nil juste après chaque appel. Comprendre ce pattern dès maintenant, avant d'écrire la moindre logique métier, évite de coder des heures avec des erreurs silencieusement ignorées.
D'abord, une fonction Go peut retourner plus d'une valeur
C'est une différence structurelle avec la majorité des langages mainstream : func diviser(a, b float64) (float64, error) retourne à la fois un résultat et une éventuelle erreur, sans avoir besoin d'un tuple explicite ou d'une exception.
Cette possibilité native de retour multiple est ce qui rend possible l'idiome de gestion d'erreurs de Go : au lieu de lever une exception qui remonte silencieusement la pile d'appel, chaque fonction qui peut échouer retourne explicitement son erreur, que l'appelant est obligé de considérer (même s'il peut techniquement l'ignorer avec _).
Une fois ce principe acquis, il faut connaître une variante utile : les retours nommés
Une fonction peut nommer ses valeurs de retour directement dans sa signature, comme func statistiques(notes []int) (min, max, somme int). Ces noms servent de documentation vivante et créent automatiquement les variables correspondantes, initialisées à leur zero value.
| Style de retour | Exemple | Avantage |
|---|---|---|
| Retour simple | func carre(x int) int | Le plus lisible pour un cas simple |
| Retour multiple | func diviser(a, b float64) (float64, error) | Standard Go pour signaler un échec |
| Retour nommé | func stats(n []int) (min, max int) | Documente l'intention, permet un return "naked" |
Prérequis
Il faut être à l'aise avec les conditions et boucles (leçon précédente) pour suivre les exemples utilisant for/range à l'intérieur des fonctions présentées ici.
Ensuite, un mécanisme qui étonne au début : les fonctions variadiques
func somme(nombres ...int) int accepte un nombre quelconque d'arguments entiers, qui arrivent à l'intérieur de la fonction sous forme de slice. C'est le mécanisme qui rend possible des appels comme fmt.Println(a, b, c) avec un nombre variable d'arguments.
Pour "déballer" un slice existant dans une fonction variadique, on utilise la syntaxe nombres... au moment de l'appel — un détail syntaxique facile à oublier au début, qui provoque une erreur de compilation claire si omis.
Piège fréquent
Une fonction avec des valeurs de retour nommées peut faire un return "naked" (sans rien après le mot-clé), ce qui retourne implicitement les valeurs actuelles des variables nommées. C'est pratique mais peut rendre le code difficile à suivre si la fonction est longue : réserve cette forme aux fonctions courtes où l'intention reste limpide.
Enfin, les fonctions sont des valeurs comme les autres
Une fonction peut être stockée dans une variable, passée en paramètre à une autre fonction, ou retournée depuis une fonction. C'est ce qui permet d'écrire appliquer(nombres, carre) où carre est elle-même une fonction.
Une closure va plus loin : une fonction anonyme peut "capturer" une variable de son environnement et continuer à l'utiliser même après que la fonction englobante s'est terminée, comme dans l'exemple creerCompteur() qui retourne un compteur qui s'incrémente à chaque appel.
Bonne pratique
Vérifie systématiquement if err != nil immédiatement après chaque appel de fonction qui retourne une erreur, sans attendre. Un err ignoré est la source numéro un de bugs difficiles à diagnostiquer en production sur un projet Go.
Maintenant que tu sais organiser une logique en fonctions, l'étape suivante consiste à structurer de vraies données métier avec des types composés : place aux structs et aux méthodes.
Commandes & code
Fonctions et valeurs multiples
Les fonctions Go peuvent retourner plusieurs valeurs : c'est la base de l'idiome de gestion d'erreurs.
package main
import (
"errors"
"fmt"
)
// Fonction simple
func additionner(a, b int) int {
return a + b
}
// Types de parametres identiques groupes : (a, b int)
func multiplier(a, b int) int {
return a * b
}
// Retour multiple : le pattern (valeur, error) est omnipresent en Go
func diviser(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("division par zero")
}
return a / b, nil
}
// Valeurs de retour nommees : documentent l'intention, retour "naked"
func statistiques(notes []int) (min, max, somme int) {
if len(notes) == 0 {
return // retourne les zero values : 0, 0, 0
}
min, max = notes[0], notes[0]
for _, n := range notes {
somme += n
if n < min {
min = n
}
if n > max {
max = n
}
}
return // retourne min, max, somme implicitement
}
// Fonction variadique : nombre d'arguments variable
func somme(nombres ...int) int {
total := 0
for _, n := range nombres {
total += n
}
return total
}
// Fonction comme parametre (les fonctions sont des valeurs de premiere classe)
func appliquer(nombres []int, f func(int) int) []int {
resultat := make([]int, len(nombres))
for i, n := range nombres {
resultat[i] = f(n)
}
return resultat
}
func main() {
fmt.Println(additionner(2, 3), multiplier(4, 5))
resultat, err := diviser(10, 2)
if err != nil {
fmt.Println("erreur:", err)
} else {
fmt.Println("resultat:", resultat)
}
_, err = diviser(10, 0)
fmt.Println("erreur attendue:", err)
min, max, total := statistiques([]int{4, 8, 15, 16, 23, 42})
fmt.Println(min, max, total)
fmt.Println(somme(1, 2, 3, 4, 5))
nombres := []int{1, 2, 3}
fmt.Println(somme(nombres...)) // spread d'un slice
// Fonction anonyme / closure
carre := func(x int) int { return x * x }
fmt.Println(appliquer([]int{1, 2, 3, 4}, carre))
// Closure capturant une variable externe
compteur := creerCompteur()
fmt.Println(compteur(), compteur(), compteur()) // 1 2 3
}
func creerCompteur() func() int {
n := 0
return func() int {
n++
return n
}
}Résumé
- Retourner
(valeur, error)est LE pattern Go pour signaler un échec. - Les valeurs de retour nommées documentent le contrat de la fonction.
...Taccepte un nombre variable d'arguments,slice...les déballe.- Les fonctions sont des valeurs : elles se passent en paramètre, se retournent, capturent (closures).
Exercices pratiques
Mission : des validateurs qui contrôlent tous la mauvaise règle
Objectif : Corriger un bug classique de closures dans une boucle, et sécuriser l'usage d'une fonction retournant (valeur, error).
Contexte
Tu écris creerValidateurs(regles []int) []func(int) bool, une fonction qui doit construire une closure de validation par règle de la slice regles. En production, les tests montrent que TOUTES les closures retournées valident avec la même règle : la dernière de la liste, jamais les précédentes.