Structurer et tester son code

Organisation d’un projet Go : packages, modules et dépendances

L’organisation d’un projet en Go est essentielle pour assurer sa maintenabilité et sa scalabilité. Go propose un système structuré basé sur les modules, les packages et la gestion des dépendances via go mod.

1. Structure d’un projet Go

L’organisation d’un projet Go suit des principes précis afin d’assurer une meilleure maintenabilité et évolutivité. Une structure claire favorise la collaboration entre les développeurs et simplifie la gestion du code source.

a. Organisation des fichiers et répertoires

Un projet Go bien structuré repose sur une hiérarchisation adaptée des fichiers et des dossiers. Voici une structure recommandée pour un projet Go modulaire :

my-project/  
├── cmd/              # Contient les points d'entrée des applications 
│   ├── app1/  
│   │   └── main.go   # Programme principal de l'application 1  
│   ├── app2/  
│   │   └── main.go   # Programme principal de l'application 2  
│  
├── internal/         # Packages internes non accessibles depuis l'extérieur 
│   ├── database/  
│   │   └── connection.go  # Gestion des connexions à la base de données 
│   ├── services/  
│   │   └── auth.go    # Service d'authentification interne  
│  
├── pkg/               # Bibliothèques réutilisables exportables  
│   ├── utils/  
│   │   └── format.go  # Fonctions de formatage diverses  ...

Documentation de code avec godoc

Une bonne documentation est essentielle pour rendre un projet Go compréhensible et maintenable. Go propose godoc, un outil intégré permettant de générer automatiquement la documentation à partir des commentaires dans le code.

1. Écrire des commentaires compatibles avec godoc

L’outil godoc permet de générer automatiquement la documentation d’un projet Go en extrayant les commentaires placés au-dessus des éléments de code.

a. Principes de base des commentaires

L’outil godoc utilise les commentaires placés immédiatement avant une déclaration de package, fonction, structure ou méthode. Ces commentaires doivent respecter quelques règles :

  • Ils doivent être rédigés en phrases complètes.

  • La première phrase doit être une description concise et explicite.

  • Le premier mot du commentaire doit correspondre au nom de l’élément documenté. 

  • Les commentaires doivent être rédigés en français pour être cohérents avec les logs et le reste du projet.

b. Commenter un package

Chaque package peut contenir un commentaire expliquant son utilité. Ce commentaire doit être placé avant la déclaration package.

// Package mathutils fournit des fonctions mathématiques avancées. 
package mathutils 

c. Commenter une fonction

Les fonctions exportées doivent toujours être documentées. Le commentaire doit expliquer l’objectif de la fonction et, si nécessaire, détailler les paramètres et la valeur de retour.

// Add calcule la somme de deux entiers et retourne le résultat.  
func Add(a, b int) int {  
    return a + b  
} 

Si la fonction retourne une erreur, elle doit être mentionnée dans le commentaire :

// OpenFile ouvre un fichier en mode lecture seule.  
// Retourne une erreur si le fichier n'existe pas ou ne peut être lu. 
func OpenFile(filename string) (File, error) {  
    // Log : tentative d'ouverture du fichier  
    fmt.Println("Tentative d'ouverture du fichier :", filename)  
    return os.Open(filename)  
} 

d. Commenter...

Introduction aux tests unitaires avec testing

Les tests unitaires sont essentiels pour garantir la fiabilité et la maintenabilité du code. Go propose un package standard, testing, qui permet d’écrire et d’exécuter facilement des tests automatisés.

1. Pourquoi écrire des tests unitaires ?

Les tests unitaires permettent de :

  • vérifier le bon fonctionnement des fonctions ;

  • détecter les régressions après des modifications de code ;

  • améliorer la qualité et la robustesse du logiciel.

2. Structure d’un test en Go

Les tests unitaires jouent un rôle essentiel dans la validation du comportement des fonctions et garantissent la stabilité du code face aux évolutions. Un test bien structuré permet d’identifier rapidement les régressions et d’assurer un bon niveau de qualité logicielle.

a. Organisation des fichiers de test

Les fichiers de test sont placés dans le même package que le code qu’ils testent et suivent une convention de nommage spécifique :

  • Un fichier contenant des tests doit se terminer par _test.go.

  • Il doit être stocké dans le même dossier que le fichier source testé.

Exemple de structure :

my_project/  
├── mathutils/  
│   ├── operations.go      # Contient la logique métier  
│   ├── operations_test.go # Contient les tests de operations.go 

b. Définition d’un test unitaire

Un test en Go est une fonction ayant les caractéristiques suivantes :

  • Son nom doit commencer par Test et être suivi d’un nom descriptif.

  • Elle doit appartenir au même package que la fonction testée.

  • Elle prend un paramètre *testing.T fourni par le package testing.

Exemple de test unitaire pour une fonction Add :

package mathutils  
 
import (  
    "testing"  
)  
 
// TestAdd vérifie que la fonction Add retourne le bon résultat.  
func TestAdd(t *testing.T) {  
    result := Add(2, 3)  
    expected := 5  
 
    if result != expected {  
        t.Errorf("Échec du test...

Benchmarks et optimisation des performances

L’optimisation des performances est un aspect essentiel du développement logiciel. Go propose un outil intégré pour mesurer l’efficacité du code à l’aide de benchmarks via le package testing.

1. Qu’est-ce qu’un benchmark ?

Un benchmark est un test de performance qui mesure le temps d’exécution d’une fonction. Cela permet d’identifier les goulets d’étranglement et d’améliorer l’efficacité du code.

2. Écrire un benchmark en Go

Les benchmarks permettent de mesurer la performance des fonctions d’un programme. L’objectif est d’identifier les goulots d’étranglement et d’optimiser le code en conséquence. Le package testing fournit un support natif pour l’écriture et l’exécution des benchmarks.

a. Structure d’un benchmark

Un benchmark est une fonction qui suit une structure précise :

  • Son nom doit commencer par Benchmark suivi du nom de la fonction testée.

  • Elle prend un paramètre *testing.B fourni par le package testing.

  • La fonction testée est exécutée en boucle pour obtenir une mesure fiable.

  • b.N est défini dynamiquement par le moteur de test et ajuste automatiquement le nombre d’itérations.

Exemple de benchmark pour une fonction Sum :

package mathutils  
 
import "testing"  
 
// BenchmarkSum mesure la performance de la fonction Sum.  
func BenchmarkSum(b *testing.B) {  
    for i := 0; i < b.N; i++ {  
        Sum(10, 20)  
    }  
} 

b. Comparer plusieurs implémentations

Pour identifier la meilleure version d’une fonction, il est possible de comparer plusieurs implémentations dans un même fichier de benchmark.

// BenchmarkSumLoop évalue une implémentation basée sur une boucle. 
func BenchmarkSumLoop(b *testing.B) {  
    for i := 0; i < b.N; i++ {  
        SumLoop(10, 20)  
    }  
}  
 
// BenchmarkSumOptimized évalue une version optimisée de la somme.  
func BenchmarkSumOptimized(b *testing.B)...

Debugging et profiling avec pprof

Le debugging et le profiling sont des étapes essentielles pour optimiser et améliorer la performance d’une application Go. Go fournit pprof, un outil puissant permettant d’analyser l’utilisation du CPU, de la mémoire et d’autres ressources du programme.

1. Activer pprof dans un serveur Go

Le package pprof permet d’analyser les performances d’une application en cours d’exécution. Il collecte des informations sur l’utilisation du CPU, de la mémoire, des goroutines et d’autres métriques importantes. L’activation de pprof dans un serveur HTTP facilite l’accès aux profils et permet de diagnostiquer les problèmes de performance.

a. Ajouter pprof à un serveur HTTP

L’intégration de pprof dans une application web est simple et ne nécessite que quelques lignes de code. Il suffit d’importer le package net/http/pprof et de lancer un serveur HTTP.

package main  
 
import (  
    "fmt"  
    "log"  
    "net/http"  
    _ "net/http/pprof"  
)  
 
func main() {  
    fmt.Println("Serveur démarré sur : http://localhost:6060") 
    log.Fatal(http.ListenAndServe("localhost:6060", nil))  
} 

b. Accéder aux endpoints de pprof

Une fois le serveur démarré, plusieurs endpoints sont disponibles pour explorer les profils en cours d’exécution :

  • http://localhost:6060/debug/pprof/ : page principale listant les profils disponibles.

  • http://localhost:6060/debug/pprof/profile : capture un profil CPU de 30 secondes.

  • http://localhost:6060/debug/pprof/heap : analyse l’utilisation de la mémoire.

  • http://localhost:6060/debug/pprof/goroutine : affiche le nombre de goroutines en cours.

  • http://localhost:6060/debug/pprof/block : identifie les blocages dans l’exécution du code.

c. Capturer un profil CPU via la ligne de commande

Il est possible de récupérer un profil CPU directement en utilisant go tool pprof.

curl -o cpu.prof http://localhost:6060/debug/pprof/profile 

Puis analyser le fichier généré :

go tool pprof cpu.prof 

Pour afficher les fonctions les plus consommatrices...