Création et importation de packages Go

GolangBeginner
Pratiquer maintenant

Introduction

Dans la section précédente, vous avez terminé un programme Go de base, qui incluait les lignes de code suivantes :

package main
import "fmt"

Comment comprendre ces deux lignes de code? Comment utiliser efficacement les instructions package et import?

Dans ce lab (atelier) LabEx, vous apprendrez à créer et importer des packages en Go. Cela vous permettra d'organiser votre code en modules réutilisables, rendant vos projets Go plus faciles à maintenir et à mettre à l'échelle.

Points de connaissance :

  • Définition et déclaration d'un package
  • Compréhension des identifiants exportés (publics) et non exportés (privés)
  • Différentes formes d'importation de packages : importation unique, groupée, par point, avec alias et anonyme

Déclaration et définition de packages

Dans cette étape, vous allez déclarer un package réutilisable et distinguer les identifiants exportés de ceux qui restent privés au package.

Un package en Go est similaire aux modules en Python ou aux bibliothèques en C. C'est une collection de fichiers de code source utilisée pour organiser et réutiliser le code. Chaque fichier Go doit déclarer un package au début du fichier.

Remarque : Un exécutable Go est construit à partir d’un package main contenant une fonction main. Les packages réutilisables n’ont pas besoin de ce point d’entrée. Un module peut contenir plusieurs répertoires de commandes, chacun avec son propre package main.

Points clés :

Identifiants exportés (publics) : Les identifiants (variables, fonctions, types, etc.) qui commencent par une lettre majuscule sont accessibles depuis d'autres packages. Considérez-les comme l'interface publique de votre package.

Identifiants non exportés (privés) : Les identifiants qui commencent par une lettre minuscule ne sont accessibles qu'au sein du même package. Ce sont considérés comme des détails d'implémentation internes du package.

Cohérence du package : Tous les fichiers dans le même dossier doivent appartenir au même package. Cela garantit que le code lié reste ensemble.

Conventions de nommage des packages : Les noms de packages doivent être en minuscules, courts et descriptifs, évitant les underscores ou les lettres capitales.

Créons notre propre package personnalisé :

Créez un dossier nommé propagandist et un fichier propagandist.go à l'intérieur :

mkdir ~/project/propagandist
touch ~/project/propagandist/propagandist.go

Écrivez le code suivant dans propagandist.go :

package propagandist

var Shout = "I Love LabEx" // Public variable
var secret = "I love the dress" // Private variable

func Hit() string {
    return "Don't hit me, please!"
}
  • Shout est public car il commence par une lettre majuscule. Cela signifie que vous pouvez y accéder depuis d'autres packages qui importent propagandist.
  • secret est privé car il commence par une lettre minuscule. Il ne peut être utilisé qu'au sein du package propagandist.
  • Hit est une fonction publique, accessible depuis d'autres packages.

Initialisez un module Go pour le package :

cd ~/project/propagandist
go mod init propagandist

Cette commande initialise un nouveau module Go dans le répertoire propagandist, ce qui aide à gérer les dépendances du package.

Importation d'un élément unique

Dans cette étape, vous allez importer et utiliser votre package local avec des déclarations d’importation séparées.

Pour utiliser le package propagandist, créons un nouveau programme Go. Cette étape montrera comment importer et utiliser un seul package dans votre code Go.

Créez un nouveau fichier Go nommé pacExercise.go dans le dossier du projet :

touch ~/project/pacExercise.go

Initialisez un module Go pour le programme :

cd ~/project
go mod init pacExercise

Mettez à jour le fichier go.mod pour inclure la dépendance du package local. Exécutez la commande suivante dans le terminal :

echo "replace propagandist => ./propagandist" >> go.mod

Important : Cette commande ajoute une directive replace à votre fichier go.mod. C'est crucial car cela indique à Go que le package propagandist doit être obtenu à partir du répertoire local ./propagandist au lieu d'essayer de le télécharger depuis un dépôt distant. Vous devez exécuter cette commande dans votre terminal, qui ajoutera la ligne replace propagandist => ./propagandist à votre fichier go.mod. N'écrivez pas directement cette ligne dans le fichier manuellement.

Écrivez le code suivant dans pacExercise.go pour importer et utiliser le package propagandist :

package main

import "fmt"
import "propagandist"

func main() {
    fmt.Println(propagandist.Shout) // Access the public variable
}
  • Ce code importe le package fmt pour afficher la sortie et le package propagandist.
  • Il accède ensuite à la variable publique Shout du package propagandist en utilisant propagandist.Shout.

Exécutez le programme :

go mod tidy
go run pacExercise.go

La commande go mod tidy garantit que votre fichier go.mod est mis à jour avec toutes les nouvelles dépendances. La commande go run pacExercise.go compile et exécute le programme.

Sortie attendue :

I Love LabEx

Importations groupées

Dans cette étape, vous allez regrouper les importations de packages sans changer le comportement du programme.

Lorsque vous importez plusieurs packages, vous pouvez utiliser des importations groupées pour améliorer la lisibilité et l'organisation de votre code. Il s'agit d'un choix stylistique qui n'altère pas la fonctionnalité de votre code.

Modifiez pacExercise.go pour utiliser des importations groupées :

 package main

 import (
     "fmt"
     "propagandist"
 )

 func main() {
     fmt.Println(propagandist.Shout)
 }
 ```

Dans l’extrait de code ci-dessus, les packages `fmt` et `propagandist` sont importés dans un seul bloc `import` entouré de parenthèses. Cela facilite la lecture et la gestion de plusieurs importations de packages. Contrairement aux déclarations d’importation séparées de l’étape précédente, cette syntaxe regroupe les chemins des deux packages dans une seule déclaration. Le programme conserve le même comportement.

Exécutez le programme pour confirmer qu'il fonctionne toujours :

```bash
 go run pacExercise.go
 ```

Le programme devrait s'exécuter sans erreur et afficher le même résultat que précédemment.


<div class="pt-24 -mt-20 relative -z-10 anchor-item" id="importation-par-point"></div>

## Importation par point

Dans cette étape, vous allez examiner les importations par point et leurs compromis concernant les espaces de noms.

En utilisant une **importation par point (dot import)**, vous pouvez omettre le préfixe du nom du package lors de l'appel de ses fonctions ou variables. Cette pratique est généralement déconseillée au profit de noms de packages explicites car elle peut entraîner des conflits d'espace de noms et réduire la lisibilité. Cependant, il est bon de savoir de quoi il s’agit.

Modifiez `pacExercise.go` pour utiliser une importation par point pour `fmt` :

```go
 package main

 import . "fmt"
 import "propagandist"

 func main() {
     Println(propagandist.Shout) // No `fmt.` prefix needed
 }
 ```

- Ici, `import . "fmt"` signifie que vous pouvez utiliser directement les fonctions et variables du package `fmt` sans le préfixe `fmt.`.
- Par exemple, vous utilisez `Println` au lieu de `fmt.Println`.

Exécutez le programme :

```bash
 go run pacExercise.go
 ```

Sortie attendue :

```bash
 I Love LabEx
 ```


<div class="pt-24 -mt-20 relative -z-10 anchor-item" id="importation-avec-alias"></div>

## Importation avec alias

Dans cette étape, vous allez donner un alias local à un package importé.

Vous pouvez attribuer un alias à un package importé pour plus de clarté ou pour éviter les conflits lorsque deux packages ont des noms similaires. Cela est utile pour rendre votre code plus lisible et gérer les collisions d'espaces de noms.

Modifiez `pacExercise.go` pour attribuer l'alias `io` au package `fmt` :

```go
 package main

 import io "fmt"
 import "propagandist"

 func main() {
     io.Println(propagandist.Shout) // Use the alias `io` instead of `fmt`
 }
 ```

- `import io "fmt"` crée l'alias `io` pour le package `fmt`.
- Maintenant, vous utilisez `io.Println` au lieu de `fmt.Println`.

Exécutez le programme :

```bash
 go run pacExercise.go
 ```


<div class="pt-24 -mt-20 relative -z-10 anchor-item" id="importation-anonyme"></div>

## Importation anonyme

Dans cette étape, vous allez importer un package uniquement pour ses effets secondaires d’initialisation.

Les importations anonymes sont utilisées pour importer un package pour ses effets secondaires, comme exécuter sa fonction `init()`, sans avoir besoin de référencer directement aucun de ses identifiants exportés. Cela est utile pour les packages qui enregistrent des pilotes (drivers) ou effectuent d'autres tâches d'initialisation.

Modifiez `pacExercise.go` pour inclure une importation anonyme pour le package `time` :

```go
 package main

 import (
     "fmt"
     "propagandist"
     _ "time" // Anonymous import
 )

 func main() {
     fmt.Println(propagandist.Shout)
 }
 ```

- `import _ "time"` est une importation anonyme. Le trait de soulignement `_` est utilisé comme identifiant vide, indiquant au compilateur que vous importez le package pour ses effets secondaires et que vous n'y ferez pas référence directement dans votre code.
- La fonction `init()` du package `time` sera exécutée lorsque ce programme sera lancé. Le package `time` n'a pas d'effets secondaires particuliers visibles ici. Cependant, de nombreux packages utilisent cette méthode pour enregistrer des pilotes de base de données ou des paramètres de configuration.

Exécutez le programme :

```bash
 go run pacExercise.go
 ```

Sortie attendue :

```bash
 I Love LabEx
 ```


<div class="pt-24 -mt-20 relative -z-10 anchor-item" id="comprendre-la-fonction-init()"></div>

## Comprendre la fonction init()

Dans cette étape, vous allez suivre les variables des packages et les fonctions init() avant l’exécution de main().

La fonction `init()` est une fonction spéciale de Go qui joue un rôle essentiel dans l’initialisation des packages. Go l’exécute automatiquement lorsqu’un package est importé, avant l’exécution du code de la fonction `main`. Cette section explique en détail les fonctions `init()` et leur place dans le processus d’initialisation de Go.

**Points clés sur les fonctions `init()` :**

**Définition et objectif :**

- Une fonction `init()` ne possède ni paramètres ni valeurs de retour : `func init() {}`
- Elle sert aux tâches d’initialisation d’un package, comme la définition d’un état initial, l’enregistrement de pilotes ou la validation des prérequis.

**Ordre d’exécution :**

- Go garantit que l’initialisation d’un package n’a lieu qu’une seule fois, même s’il est importé plusieurs fois.
- L’initialisation suit un ordre bien défini :
- Les variables au niveau du package sont initialisées en premier
- Les fonctions `init()` sont ensuite exécutées
- Enfin, la fonction `main()` s’exécute (uniquement dans le package main)

**Plusieurs fonctions `init()` :**

- Un même fichier Go peut contenir plusieurs fonctions `init()`
- Plusieurs fichiers d’un même package peuvent chacun avoir leurs propres fonctions `init()`
- Toutes ces fonctions `init()` s’exécutent dans l’ordre de déclaration dans le code source, en suivant l’ordre dans lequel les fichiers sont présentés au compilateur. L’outil Go présente les fichiers du package dans l’ordre lexicographique de leurs noms ; évitez de vous appuyer sur l’ordre des fichiers pour exprimer des dépendances.

**Chaîne de dépendances :**

- Lorsqu’un package importe d’autres packages, Go veille à exécuter d’abord les fonctions `init()` des dépendances
- Cela crée une initialisation ascendante : les dépendances les plus profondes sont initialisées en premier

Créons un exemple pratique pour montrer comment fonctionnent les fonctions `init()` :

Modifions d’abord notre package `propagandist` pour y inclure une fonction `init()`. Mettez à jour `propagandist.go` :

```go
package propagandist

import "fmt"

var Shout = "I Love LabEx" // Public variable
var secret = "I love the dress" // Private variable
var initialized bool

func init() {
    fmt.Println("Initializing propagandist package...")
    initialized = true
}

func Hit() string {
    return "Don't hit me, please!"
}

func IsInitialized() bool {
    return initialized
}

Créons maintenant un autre fichier dans le package propagandist pour illustrer la présence de plusieurs fonctions init() :

touch ~/project/propagandist/second.go

Ajoutez le contenu suivant au fichier :

package propagandist

import "fmt"

func init() {
    fmt.Println("Second init function in propagandist package...")
}

Créez un nouveau package helper pour illustrer l’ordre d’initialisation :

mkdir -p ~/project/helper
touch ~/project/helper/helper.go

Ajoutez le contenu suivant au fichier :

package helper

import "fmt"

var Message = "Helper package is ready"

func init() {
    fmt.Println("Initializing helper package...")
}

func GetMessage() string {
    return Message
}

Ajoutez le fichier de module du package helper :

cd ~/project/helper
go mod init helper

Mettez à jour pacExercise.go pour utiliser les deux packages et illustrer l’ordre d’initialisation :

package main

import (
    "fmt"
    "helper"
    "propagandist"
)

func init() {
    fmt.Println("Initializing main package...")
}

func main() {
    fmt.Println("Main function is running")
    fmt.Println(propagandist.Shout)
    fmt.Println(helper.Message)
    fmt.Printf("Propagandist initialized: %v\n", propagandist.IsInitialized())
}

Mettez à jour le fichier go.mod du projet principal pour y inclure le package helper local :

cd ~/project
echo "replace helper => ./helper" >> go.mod
go mod tidy

Exécutez le programme et observez l’ordre d’initialisation :

go run pacExercise.go

Sortie attendue avec les noms de fichiers fournis et l’outil Go :

Initializing helper package...
Initializing propagandist package...
Second init function in propagandist package...
Initializing main package...
Main function is running
I Love LabEx
Helper package is ready
Propagandist initialized: true

Cette sortie illustre les principaux aspects du processus d’initialisation de Go :

Les packages dont dépend un autre package sont initialisés avant celui qui les importe

Toutes les fonctions init() d’un même package s’exécutent dans l’ordre de déclaration, selon l’ordre dans lequel les fichiers sources sont présentés au compilateur

La fonction main() ne s’exécute qu’une fois l’initialisation de tous les packages terminée

Les variables au niveau du package sont initialisées avant l’exécution des fonctions init()

Cette séquence d’initialisation est un élément important dans la conception de packages Go, en particulier pour gérer les dépendances ou effectuer des opérations de préparation devant respecter un ordre précis.

Résumé

Dans ce laboratoire (lab), vous avez appris :

  1. Comment créer et définir des packages personnalisés en Go, encapsulant du code réutilisable.

  2. La différence entre les identifiants publics (exportés) et privés (non exportés) et leur impact sur l'accessibilité.

  3. Différentes méthodes pour importer des packages, chacune ayant son cas d'utilisation :

    • Importation d'un élément unique (Single-item import) : Importation d'un package à la fois.
    • Importation groupée (Grouped import) : Importation de plusieurs packages dans un seul bloc pour une meilleure organisation.
    • Importation par point (Dot import) : Importation d'un package et utilisation directe de ses identifiants sans le préfixe du nom du package. (À utiliser avec prudence)
    • Importation avec alias (Alias import) : Renommage des packages importés pour une meilleure lisibilité ou pour éviter les conflits de noms.
    • Importation anonyme (Anonymous import) : Importation d'un package uniquement pour ses effets secondaires, comme l'initialisation.
  4. Le rôle de la fonction init() dans les packages et comment les importations anonymes peuvent déclencher son exécution.

  5. Le fonctionnement détaillé du processus d’initialisation de Go, notamment :

    • L’initialisation des variables au niveau du package avant les fonctions init()
    • L’ordre d’exécution garanti des fonctions init() entre packages dépendants
    • Le fonctionnement de plusieurs fonctions init() au sein d’un package
    • Le déroulement complet de l’initialisation, des packages dépendants à la fonction main

En terminant ce laboratoire, vous êtes désormais en mesure de structurer et de gérer efficacement des projets Go en utilisant des packages. Vous pouvez créer des modules réutilisables, contrôler l'accès aux identifiants, mieux organiser votre code et comprendre le processus d’initialisation, ce qui conduit à des applications Go plus maintenables et évolutives.