Les Fondamentaux de Go : Types, Fonctions et Contrôle de Flux
Introduction
Bienvenue dans cette leçon dédiée aux fondamentaux de Go, un langage de plus en plus prisé pour le développement backend, les microservices et les systèmes distribués. Dans le cadre de notre cours "Maîtrisez Go pour le Backend : Construisez des APIs Performantes et Scalables", la compréhension des types, des fonctions et du contrôle de flux est essentielle. Ces concepts sont les piliers sur lesquels reposent toutes les applications Go, des plus simples aux plus complexes, et sont d'une importance capitale pour écrire du code propre, efficace et maintenable.
Nous explorerons comment Go gère les données avec ses systèmes de types, comment structurer votre logique avec des fonctions réutilisables, et comment diriger le flot d'exécution de votre programme avec les instructions conditionnelles et les boucles. À la fin de cette leçon, vous aurez une base solide pour commencer à écrire vos propres programmes Go.
1. Les Types en Go : Les Briques de Construction
En Go, comme dans tout langage fortement typé, les types définissent la nature des données que vous manipulez. Comprendre les types est fondamental pour éviter les erreurs, optimiser les performances et écrire du code lisible.
1.1 Qu'est-ce qu'un Type ?
Un type est une classification qui spécifie les opérations qui peuvent être effectuées sur une valeur, ainsi que la manière dont cette valeur est stockée en mémoire. Go est un langage statiquement typé, ce qui signifie que le type de chaque variable est connu à la compilation et ne change pas pendant l'exécution.
1.2 Types Primitifs Fondamentaux
Go offre un ensemble de types primitifs pour gérer les données courantes :
- Nombres entiers :
int: Le type entier le plus courant, sa taille dépend de l'architecture du système (32 ou 64 bits).int8,int16,int32,int64: Entiers signés de tailles spécifiques.uint,uint8,uint16,uint32,uint64,uintptr: Entiers non signés (positifs ou zéro) de tailles spécifiques.uintptrest utilisé pour stocker des adresses mémoire.byte: Alias pouruint8, souvent utilisé pour représenter des données binaires.rune: Alias pourint32, utilisé pour représenter un caractère Unicode.
- Nombres à virgule flottante :
float32: Nombres à virgule flottante de 32 bits.float64: Nombres à virgule flottante de 64 bits (le plus couramment utilisé pour sa précision).
- Booléens :
bool: Représente une valeur de vérité, soittruesoitfalse.
- Chaînes de caractères :
string: Une séquence immuable de caractèresbyte. Les chaînes en Go sont encodées en UTF-8.
1.3 Déclaration et Initialisation des Variables
En Go, il existe plusieurs façons de déclarer et d'initialiser des variables.
Le mot-clé var
C'est la méthode de déclaration traditionnelle, où vous spécifiez explicitement le type de la variable.
package main
import "fmt"
func main() {
var nom string // Déclaration sans initialisation
var age int = 30 // Déclaration et initialisation
var estActif bool = true
nom = "Alice" // Assignation après déclaration
fmt.Println("Nom:", nom, "Age:", age, "Actif:", estActif)
// Déclaration de plusieurs variables du même type
var x, y int
x = 10
y = 20
fmt.Println("x:", x, "y:", y)
// Déclaration de plusieurs variables de types différents
var (
prenom string = "Bob"
taille float64 = 1.75
)
fmt.Println("Prénom:", prenom, "Taille:", taille)
}
Explication :
var nom stringdéclare une variablenomde typestring. Si non initialisée, elle prend sa "zéro-valeur".var age int = 30déclareagede typeintet l'initialise à30.- Les blocs
var (...)permettent de grouper des déclarations de variables pour une meilleure lisibilité.
L'opérateur de déclaration courte :=
C'est la manière la plus idiomatique et la plus courante de déclarer et d'initialiser des variables en Go, surtout à l'intérieur des fonctions. Go infère le type de la variable à partir de la valeur assignée.
package main
import "fmt"
func main() {
message := "Bonjour Go!" // Go infère que 'message' est de type string
nombre := 123 // Go infère que 'nombre' est de type int
pi := 3.14 // Go infère que 'pi' est de type float64
estValide := true // Go infère que 'estValide' est de type bool
fmt.Println(message, nombre, pi, estValide)
// Cet opérateur ne peut être utilisé que pour la déclaration initiale.
// Pour une réassignation, utilisez simplement '=' :
nombre = 456
fmt.Println("Nouveau nombre:", nombre)
}
Explication :
message := "Bonjour Go!"déclaremessageet l'initialise. Le compilateur Go détermine automatiquement quemessageest de typestring.- L'opérateur
:=est une commodité qui combine déclaration et assignation. Il ne peut être utilisé que lorsque la variable est nouvellement déclarée dans la portée actuelle.
Les "zéro-valeurs"
Lorsqu'une variable est déclarée mais non initialisée explicitement, Go lui assigne une zéro-valeur par défaut, qui est la "valeur nulle" du type correspondant :
0pour les types numériques (int,float, etc.)falsepour lesbool""(chaîne vide) pour lesstringnilpour les pointeurs, fonctions, slices, maps, channels, et interfaces
package main
import "fmt"
func main() {
var i int
var f float64
var b bool
var s string
fmt.Printf("Zéro-valeur de int: %v\n", i) // 0
fmt.Printf("Zéro-valeur de float64: %v\n", f) // 0
fmt.Printf("Zéro-valeur de bool: %v\n", b) // false
fmt.Printf("Zéro-valeur de string: %v\n", s) // ""
}
1.4 Conversion de Types
Go est très strict sur les types. Les conversions implicites entre types différents (même s'ils semblent compatibles) ne sont pas autorisées. Vous devez effectuer des conversions explicites.
package main
import "fmt"
func main() {
var entier int = 42
var flottant float64 = float64(entier) // Convertit int en float64
fmt.Printf("Entier: %d, Flottant: %f\n", entier, flottant)
var chaine string = "123"
// var nombre int = int(chaine) // ERREUR: Impossible de convertir directement string en int
// Pour convertir string en nombre, il faut utiliser des fonctions du package strconv
import "strconv" // Ajouté pour l'exemple
valeurNumerique, err := strconv.Atoi(chaine)
if err != nil {
fmt.Println("Erreur de conversion:", err)
} else {
fmt.Printf("Chaîne '%s' convertie en int: %d\n", chaine, valeurNumerique)
}
var asciiValue byte = 'A' // 'A' est un caractère, mais byte est uint8
var intValue int = int(asciiValue)
fmt.Printf("Caractère 'A' (byte): %d, Converti en int: %d\n", asciiValue, intValue)
}
Explication :
float64(entier)convertit la valeur deentierenfloat64.- La conversion de
stringvers un type numérique nécessite des fonctions spécifiques du packagestrconv, car ce n'est pas une simple réinterprétation des bits.
2. Les Fonctions en Go : Modularité et Réutilisabilité
Les fonctions sont le cœur de l'organisation logique de votre code en Go. Elles permettent de découper des problèmes complexes en tâches plus petites et réutilisables, améliorant ainsi la lisibilité, la maintenabilité et la testabilité du code.
2.1 Définition et Syntaxe de Base
Une fonction en Go est définie en utilisant le mot-clé func, suivi du nom de la fonction, d'une liste de paramètres entre parenthèses, et optionnellement d'un type de retour. Le corps de la fonction est délimité par des accolades {}.
package main
import "fmt"
// add est une fonction qui prend deux entiers et retourne leur somme.
func add(a int, b int) int {
return a + b
}
// greet est une fonction qui prend un nom et ne retourne rien.
func greet(name string) {
fmt.Println("Bonjour,", name, "!")
}
func main() {
sum := add(5, 3)
fmt.Println("La somme est:", sum)
greet("Go Programmer")
}
Explication :
func add(a int, b int) int:func: Mot-clé pour définir une fonction.add: Nom de la fonction.(a int, b int): Liste des paramètres. Chaque paramètre est suivi de son type.int: Type de la valeur retournée par la fonction.
func greet(name string): Cette fonction ne spécifie pas de type de retour, ce qui signifie qu'elle ne retourne aucune valeur (unvoidimplicite).
2.2 Paramètres et Valeurs de Retour
Paramètres avec types omis
Si plusieurs paramètres consécutifs sont du même type, vous pouvez omettre le type pour tous sauf le dernier :
func add(a, b int) int { // Plus court que add(a int, b int) int
return a + b
}
Retours multiples : La gestion des erreurs en Go
Go se distingue par sa capacité à retourner plusieurs valeurs à partir d'une fonction. Cette fonctionnalité est cruciale, notamment pour la gestion des erreurs, où une fonction retourne souvent à la fois un résultat et une erreur.
package main
import (
"errors"
"fmt"
)
// diviser divise a par b. Elle retourne le résultat et une erreur si b est zéro.
func diviser(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("impossible de diviser par zéro")
}
return a / b, nil // nil signifie 'pas d'erreur'
}
func main() {
result, err := diviser(10, 2)
if err != nil {
fmt.Println("Erreur:", err)
} else {
fmt.Println("Résultat de la division:", result)
}
result, err = diviser(10, 0)
if err != nil {
fmt.Println("Erreur:", err) // Affiche: Erreur: impossible de diviser par zéro
} else {
fmt.Println("Résultat de la division:", result)
}
}
Explication :
func diviser(a, b float64) (float64, error): Indique que la fonction retourne unfloat64et un typeerror.return a / b, nil: En cas de succès, le résultat est retourné, etnilest retourné pour l'erreur, indiquant qu'aucune erreur ne s'est produite.return 0, errors.New(...): En cas d'échec,0est retourné comme valeur par défaut et une nouvelle erreur est créée et retournée.- L'idiome
if err != nilest très courant en Go pour vérifier les erreurs.
Fonctions variadiques (...)
Une fonction variadique peut accepter un nombre variable d'arguments du même type.
package main
import "fmt"
// sumAll calcule la somme de tous les entiers passés en argument.
func sumAll(numbers ...int) int {
total := 0
for _, num := range numbers {
total += num
}
return total
}
func main() {
fmt.Println(sumAll(1, 2, 3)) // Output: 6
fmt.Println(sumAll(10, 20, 30, 40)) // Output: 100
fmt.Println(sumAll()) // Output: 0
}
Explication :
numbers ...int: Le...indique quenumbersest un paramètre variadique, et il sera traité comme unesliced'entiers à l'intérieur de la fonction.
2.3 Portée des Variables (Scope)
La portée d'une variable détermine où elle peut être accédée et utilisée dans votre programme.
- Les variables déclarées à l'intérieur d'une fonction ont une portée locale à cette fonction.
- Les variables déclarées au niveau du package (en dehors de toute fonction) ont une portée globale au package et peuvent être exportées (rendues accessibles depuis d'autres packages) si leur nom commence par une majuscule.
package main
import "fmt"
var packageScopeVar string = "Je suis une variable au niveau du package." // Portée globale au package
func main() {
localScopeVar := "Je suis une variable locale à main." // Portée locale à main()
fmt.Println(packageScopeVar)
fmt.Println(localScopeVar)
anotherFunction()
// fmt.Println(anotherLocalVar) // ERREUR: anotherLocalVar n'est pas définie ici
}
func anotherFunction() {
anotherLocalVar := "Je suis une variable locale à anotherFunction."
fmt.Println(packageScopeVar) // Accès possible à packageScopeVar
fmt.Println(anotherLocalVar)
}
2.4 Fonctions Anonymes et Closures
Go permet de définir des fonctions sans nom, appelées fonctions anonymes. Elles sont souvent utilisées pour des opérations rapides, des fonctions passées en tant qu'arguments ou pour implémenter des closures. Une closure est une fonction anonyme qui "capture" des variables de l'environnement où elle a été définie, même après que cet environnement ait disparu.
package main
import "fmt"
func main() {
// Fonction anonyme assignée à une variable
addition := func(a, b int) int {
return a + b
}
fmt.Println("Somme (fonction anonyme):", addition(10, 5))
// Fonction anonyme exécutée immédiatement (IIFE - Immediately Invoked Function Expression)
func() {
fmt.Println("Je suis une fonction anonyme exécutée immédiatement!")
}()
// Closure: La fonction interne se souvient de 'base'
createMultiplier := func(base int) func(int) int {
return func(factor int) int {
return base * factor // 'base' est capturé de l'environnement parent
}
}
multiplyBy5 := createMultiplier(5)
fmt.Println("5 * 4 =", multiplyBy5(4)) // Output: 20
multiplyBy10 := createMultiplier(10)
fmt.Println("10 * 3 =", multiplyBy10(3)) // Output: 30
}
Explication :
- La fonction
createMultiplierretourne une autre fonction anonyme. Cette fonction anonyme "ferme" (closure) sur la variablebasede son environnement parent. Chaque appel àcreateMultipliercrée une nouvelle closure avec sa propre copie debase.
2.5 L'Instruction defer : Nettoyage Simplifié
L'instruction defer reporte l'exécution d'un appel de fonction jusqu'à ce que la fonction englobante retourne. Les appels defer sont empilés (LIFO - Last In, First Out). C'est extrêmement utile pour s'assurer que des ressources (fichiers ou connexions à la base de données) sont libérées, même en cas d'erreur.
package main
import "fmt"
func main() {
fmt.Println("Début de main")
// Les appels defer sont exécutés dans l'ordre inverse de leur définition
defer fmt.Println("Troisième defer (dernière exécutée)")
defer fmt.Println("Deuxième defer")
defer fmt.Println("Premier defer (première exécutée)")
fmt.Println("Milieu de main")
// Exemple d'utilisation pratique : fermeture de fichier
// Dans un vrai programme, on ouvrirait un fichier ici.
simulateFileOperation()
fmt.Println("Fin de main")
}
func simulateFileOperation() {
fmt.Println("\nOuverture du fichier...")
// Imaginez ici le code pour ouvrir un fichier
defer fmt.Println("Fermeture du fichier.") // Ceci sera exécuté avant que simulateFileOperation ne retourne
fmt.Println("Lecture/Écriture dans le fichier...")
// Imaginez ici le code pour lire/écrire
fmt.Println("Opération fichier terminée.")
}
Explication :
- Les appels
defersont placés sur une pile. Quand la fonctionmainousimulateFileOperationtermine son exécution (normalement ou via unpanic), les fonctionsdefersont dépilées et exécutées. Cela garantit que le nettoyage est toujours effectué.
3. Le Contrôle de Flux en Go : Diriger la Logique
Le contrôle de flux vous permet de dicter l'ordre dans lequel les instructions de votre programme sont exécutées, en fonction de conditions ou pour répéter des actions.
3.1 Instructions Conditionnelles : if, else if, else
La structure if en Go est similaire à d'autres langages, mais sans parenthèses autour de la condition. Les accolades sont obligatoires.
package main
import "fmt"
func main() {
temperature := 25
if temperature > 30 {
fmt.Println("Il fait très chaud!")
} else if temperature > 20 { // else if est facultatif
fmt.Println("Il fait bon.")
} else { // else est facultatif
fmt.Println("Il fait frais.")
}
// Initialisation courte dans `if`
// `err` est scope-limitée à ce bloc if/else
if result, err := diviser(10, 0); err != nil {
fmt.Println("Erreur de division:", err)
} else {
fmt.Println("Résultat de la division:", result)
}
// `result` et `err` ne sont pas accessibles ici
// fmt.Println(result) // Ceci provoquerait une erreur de compilation
}
// Fonction diviser réutilisée de l'exemple précédent
func diviser(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("impossible de diviser par zéro")
}
return a / b, nil
}
Explication :
- La syntaxe
if condition { ... } else if condition { ... } else { ... }est standard. - L'initialisation courte dans
if(if result, err := diviser(10, 0); err != nil) est très idiomatique en Go, permettant de déclarer des variables et de vérifier une condition en une seule ligne. Les variables déclarées ainsi sont limitées à la portée du blocif/else.
3.2 L'Instruction switch
L'instruction switch est une alternative propre et concise à de multiples if-else if pour tester plusieurs conditions basées sur une seule valeur ou expression. En Go, un switch exécute uniquement le premier cas qui correspond et ne "tombe" pas dans le cas suivant par défaut (fallthrough est désactivé implicitement).
package main
import "fmt"
func main() {
jour := "Mardi"
switch jour {
case "Lundi":
fmt.Println("C'est le début de la semaine.")
case "Mardi", "Mercredi", "Jeudi": // Plusieurs valeurs dans un même case
fmt.Println("C'est le milieu de la semaine.")
case "Vendredi":
fmt.Println("C'est presque le week-end!")
default: // Cas par défaut si aucune correspondance n'est trouvée
fmt.Println("C'est le week-end ou un jour inconnu.")
}
// switch sans condition (équivalent à des if/else if)
heure := 15
switch {
case heure < 12:
fmt.Println("Bonjour matin!")
case heure < 18:
fmt.Println("Bonne après-midi!")
default:
fmt.Println("Bonne soirée!")
}
// L'instruction `fallthrough` (rarement utilisée)
// Force l'exécution du case suivant
grade := "B"
switch grade {
case "A":
fmt.Println("Excellent!")
case "B":
fmt.Println("Très bien!")
fallthrough // Exécute aussi le case "C"
case "C":
fmt.Println("Bien.")
default:
fmt.Println("À travailler davantage.")
}
// Output:
// Très bien!
// Bien.
}
Explication :
- Le
switchen Go ne nécessite pas debreakcar il sort du blocswitchaprès la première correspondance trouvée. - Un
switchsans expression agit comme une série deif-else if, où chaquecaseest une condition booléenne. fallthroughdoit être utilisé avec parcimonie car il rend le contrôle de flux moins évident.
3.3 Les Boucles : L'Unique for
Go n'a qu'une seule instruction de boucle : for. Elle est incroyablement flexible et peut être utilisée pour implémenter tous les types de boucles que vous trouveriez dans d'autres langages (boucle for classique, boucle while, boucle infinie, etc.).
Boucle classique (for avec initialisation, condition, post-instruction)
package main
import "fmt"
func main() {
fmt.Println("Comptage de 0 à 4:")
for i := 0; i < 5; i++ {
fmt.Println(i)
}
}
Explication :
- Similaire à la boucle
foren C/Java.i := 0(initialisation),i < 5(condition),i++(post-instruction exécutée à chaque itération).
Boucle "while-like" (for avec seule condition)
package main
import "fmt"
func main() {
sum := 1
fmt.Println("\nDoublons tant que la somme est inférieure à 100:")
for sum < 100 {
sum += sum // sum = sum * 2
fmt.Println(sum)
}
}
Explication :
- L'équivalent d'une boucle
whiled'autres langages. La boucle continue tant que la conditionsum < 100est vraie.
Boucle infinie (for { })
package main
import "fmt"
func main() {
fmt.Println("\nBoucle infinie (arrêt après 3 itérations):")
counter := 0
for { // Pas de condition, la boucle est infinie
fmt.Println("Itération", counter)
counter++
if counter >= 3 {
break // Utilisez `break` pour sortir de la boucle infinie
}
}
}
Explication :
- Une boucle
for {}s'exécute indéfiniment à moins qu'une instructionbreakne soit rencontrée.
L'itération for-range
L'instruction for-range est utilisée pour itérer sur des collections comme les chaînes de caractères, les tableaux (arrays), les slices, les maps et les channels.
package main
import "fmt"
func main() {
// Itérer sur une slice
nombres := []int{10, 20, 30, 40}
fmt.Println("\nItération sur une slice:")
for index, value := range nombres {
fmt.Printf("Index: %d, Valeur: %d\n", index, value)
}
// Itérer sur une chaîne (caractères Unicode)
salutation := "Bonjour Monde!"
fmt.Println("\nItération sur une chaîne:")
for index, runeValue := range salutation {
fmt.Printf("Index: %d, Caractère: %c (Unicode: %d)\n", index, runeValue, runeValue)
}
// Itérer sur une map
fruits := map[string]string{"pomme": "rouge", "banane": "jaune"}
fmt.Println("\nItération sur une map:")
for cle, valeur := range fruits {
fmt.Printf("Clé: %s, Valeur: %s\n", cle, valeur)
}
// Ignorer l'index/clé ou la valeur
fmt.Println("\nItération sans index/clé:")
for _, value := range nombres { // Utiliser '_' pour ignorer l'index
fmt.Println("Valeur (sans index):", value)
}
}
Explication :
for index, value := range collection: À chaque itération,indexreçoit l'index (ou la clé pour les maps) etvaluereçoit l'élément.- Si vous n'avez besoin que de la valeur (ou de l'index), vous pouvez utiliser l'identifiant vide
_pour ignorer l'autre.
3.4 Instructions de Saut : break, continue, goto
Ces instructions modifient le flux normal d'exécution des boucles.
break: Termine l'exécution de la boucle la plus interne (ou duswitchouselect) et le contrôle passe à l'instruction suivant la boucle.continue: Saute le reste de l'itération actuelle de la boucle et passe à l'itération suivante.goto: Transfère le contrôle du programme à une étiquette spécifiée. L'utilisation degotoest fortement déconseillée en Go (et dans la plupart des langages modernes) car elle rend le code difficile à lire et à maintenir.
package main
import "fmt"
func main() {
fmt.Println("\nExemple de break et continue:")
for i := 0; i < 10; i++ {
if i%2 != 0 { // Si i est impair
continue // Passe à l'itération suivante
}
fmt.Println(i) // Affiche uniquement les nombres pairs
if i == 6 {
break // Sort de la boucle lorsque i est 6
}
}
// Output: 0, 2, 4, 6
// Exemple de goto (à éviter!)
fmt.Println("\nExemple de goto (à éviter!):")
i := 0
Start: // Étiquette
fmt.Println(i)
i++
if i < 3 {
goto Start // Saute à l'étiquette Start
}
// Output: 0, 1, 2
}
Exemple Pratique Complet
Combinons tout ce que nous avons appris dans un exemple qui simule la validation d'une requête API pour la création d'un utilisateur.
package main
import (
"errors"
"fmt"
"strconv" // Pour la conversion de string en int
"strings" // Pour les opérations sur les chaînes
)
// UserRequest simule une structure de données pour une requête de création d'utilisateur
// En réalité, ce serait une struct. Pour cet exemple, nous utiliserons une map pour la simplicité,
// mais les types et les structs sont préférables pour les APIs Go.
type UserRequest map[string]string
// validateAndProcessUserRequest valide les champs d'une requête utilisateur
// et renvoie un message de succès ou une erreur.
func validateAndProcessUserRequest(req UserRequest) (string, error) {
// 1. Validation des champs obligatoires
// Utilisation de la boucle for-range et if
requiredFields := []string{"name", "email", "age"}
for _, field := range requiredFields {
if _, ok := req[field]; !ok || strings.TrimSpace(req[field]) == "" {
return "", fmt.Errorf("champ manquant ou vide: '%s'", field)
}
}
// 2. Validation du format de l'e-mail (simplifiée)
email := req["email"]
if !strings.Contains(email, "@") || !strings.Contains(email, ".") {
return "", errors.New("format d'email invalide")
}
// 3. Validation de l'âge
ageStr := req["age"]
age, err := strconv.Atoi(ageStr) // Conversion de type string -> int
if err != nil {
return "", fmt.Errorf("l'âge doit être un nombre valide: %w", err)
}
if age < 18 || age > 120 {
return "", errors.New("l'âge doit être entre 18 et 120 ans")
}
// 4. Génération d'un message de bienvenue conditionnel
// Utilisation de switch et fonctions avec paramètres
welcomeMessage := createWelcomeMessage(req["name"], age)
// Simuler un enregistrement en base de données ou autre opération backend
// Utilisation de defer pour simuler la fermeture d'une ressource
fmt.Println("Ouverture de la connexion à la base de données...")
defer fmt.Println("Fermeture de la connexion à la base de données.") // S'exécutera à la fin de la fonction
fmt.Printf("Utilisateur '%s' avec email '%s' et âge %d validé.\n", req["name"], email, age)
return welcomeMessage, nil
}
// createWelcomeMessage génère un message de bienvenue basé sur le nom et l'âge.
func createWelcomeMessage(name string, age int) string {
switch { // switch sans condition
case age < 30:
return fmt.Sprintf("Bienvenue, %s ! Belle jeunesse !", name)
case age < 60:
return fmt.Sprintf("Ravi de vous voir, %s ! Votre expérience est précieuse.", name)
default:
return fmt.Sprintf("Bonjour, %s ! C'est un honneur de vous accueillir.", name)
}
}
func main() {
fmt.Println("--- Test 1: Requête valide ---")
validReq := UserRequest{
"name": "Jean Dupont",
"email": "jean.dupont@example.com",
"age": "35",
}
msg, err := validateAndProcessUserRequest(validReq)
if err != nil {
fmt.Println("Erreur lors de la validation:", err)
} else {
fmt.Println("Succès:", msg)
}
fmt.Println("\n--- Test 2: Champ manquant ---")
missingFieldReq := UserRequest{
"name": "Marie Curie",
"email": "marie.curie@example.com",
// "age" est manquant
}
msg, err = validateAndProcessUserRequest(missingFieldReq)
if err != nil {
fmt.Println("Erreur lors de la validation:", err) // Attendu: champ manquant ou vide: 'age'
} else {
fmt.Println("Succès:", msg)
}
fmt.Println("\n--- Test 3: Age invalide ---")
invalidAgeReq := UserRequest{
"name": "Pierre Martin",
"email": "pierre.martin@example.com",
"age": "abc", // Âge non numérique
}
msg, err = validateAndProcessUserRequest(invalidAgeReq)
if err != nil {
fmt.Println("Erreur lors de la validation:", err) // Attendu: l'âge doit être un nombre valide
} else {
fmt.Println("Succès:", msg)
}
}
Explication de l'exemple :
UserRequest map[string]string: Bien que les structs soient plus courantes pour les données d'API, unemapest utilisée ici pour démontrer l'itération et l'accès aux données.validateAndProcessUserRequestfonction:- Prend un
UserRequestet retourne unstring(message) et unerror(gestion standard des erreurs en Go). - Validation des champs obligatoires (
for-rangeetif): Itère sur uneslicede noms de champs. Pour chaque champ, elle vérifie s'il existe dans lamapet s'il n'est pas vide après avoir retiré les espaces. - Validation de l'e-mail (
if): Vérifie la présence de@et.(simplifié). - Validation de l'âge (
strconv.Atoi,if err != nil): Utilisestrconv.Atoipour convertir la chaîneageStrenint. C'est un exemple clé de conversion de type explicite et de la gestion des erreurs avec les retours multiples. Des conditionsifsupplémentaires vérifient la plage d'âge. - Message de bienvenue (
createWelcomeMessage): Appel à une autre fonction qui utilise unswitchsans condition pour personnaliser le message. defer: Simule l'ouverture et la fermeture d'une connexion à une base de données, garantissant que la fermeture se produit toujours, même si une erreur survient plus tôt dans la fonction.
- Prend un
createWelcomeMessagefonction: Démontre l'utilisation d'unswitchsans expression pour des logiques conditionnelles basées sur des plages de valeurs.mainfonction: AppellevalidateAndProcessUserRequestavec différentes requêtes pour illustrer les chemins de succès et d'erreur. La vérificationif err != nilest cruciale après chaque appel de fonction qui peut retourner une erreur.
Conclusion
Cette leçon vous a introduit aux fondations de la programmation en Go :
- Les types de données comme les nombres, les booléens et les chaînes, ainsi que les méthodes de déclaration et de conversion de variables.
- Les fonctions, qui sont le moyen principal d'organiser votre code, avec un accent particulier sur les retours multiples pour la gestion des erreurs et l'utilisation de
deferpour le nettoyage des ressources. - Les instructions de contrôle de flux (
if,switch,for) qui vous permettent de dicter la logique d'exécution de votre programme.
La maîtrise de ces concepts est la première étape indispensable pour construire des applications backend robustes et performantes avec Go. Dans les leçons suivantes, nous approfondirons des sujets tels que les structures (structs), les interfaces, les goroutines et les canaux, qui sont des aspects clés de Go pour le développement d'APIs performantes et scalables. Entraînez-vous avec les exemples fournis et n'hésitez pas à expérimenter pour solidifier votre compréhension !