Ceci est une ancienne révision du document !
Ce tutoriel propose de créer deux modules:
Dans ce tutoriel on va :
En Go, le module est l'unité de distribution du code. Il permet de distribuer une collection de paquetages. Par exemple on peut vouloir créer un module contenant des paquetages groupant des fonctions d'analyse financière. Toutes les applications ayant besoin de faire de l'analyse financière pourront bénéficier du code. Pour plus d'informations voir la documentation officielle développement et publication des modules.
Le code en Go est regroupé en paquetages et les paquetages sont groupés et distribués dans des modules. Le module déclare les dépendances nécessaires pour l'exécution du code incluant la version de Go et l'ensemble des autres modules requis.
Lorsqu'on ajouter ou améliore les fonctionnalités d'un module, on publie une nouvelle version du module. Les développeurs qui appellent/utilisent les fonctions de votre module mettent à jour les paquetages et testent les nouvelles versions avant de passer en production.
Déclarer votre nouveau module en utilisant la commande go mod init
et en fournissant le chemin vers le module. Si vous souhaitez publier votre module se devra être un chemin depuis lequel le module sera téléchargeable par les outils Go : cela pourrait être votre dépôt de code.
# créer un repertoire pour le code mkdir greetings && cd greetings # initialise le module go mod init example.com/greetings
La commande go mod init crée le fichier go.mod qui permet le suivi des dépendances. Pour le moment le fichier ne contient que le nom du module et la version de Go supportée.
cat go.mod module example.com/greetings go 1.20
Lorsque des dépendances seront ajoutées, le fichier go.mod indiquera les versions des modules desquelles dépend votre code. Cela permet de garder la compilation reproductible et offre un contrôle direct sur les versions des modules à utiliser.
Via votre éditeur créer le fichier greetings.go avec le contenu suivant :
package greetings import "fmt" // Hello retourne un message de bienvenue à la personne désignée func Hello( name string) string { message := fmt.Sprintf("Salut, %v. Bienvenue!", name) return message }
Dans ce code on a:
La fonction Hello() prend un argument nommé de type chaîne de caractères et retourne une chaîne de caractères. En Go les fonctions commençant par une majuscule peuvent être appelées en dehors du paquetage. En Go ce comportement est désigné “export des noms”.
L'opérateur :=
est un raccourci permettant de déclarer et d'initialiser une variable en une seule ligne. Go utilise l'instruction à droite de l'opérateur pour déterminer le type de la variable.
Nous allons ici écrire le code appelant la fonction Hello() du module greetings.
Créer un répertoire hello au même niveau que greetings
cd .. mkdir hello
Après cette opération on a donc l'arborescence suivante
. ├── grettings │ ├── go.mod │ └── greetings.go └── hello
On entre dans le répertoire hello et on initialise le suivi du module :
go mod init example.com/hello
Ouvrir l'éditeur et créer le fichier hello.go avec le contenu suivant :
package main import ( "fmt" "example.com/greetings" ) func main() { msg := greetings.Hello("Yoann") fmt.Println(msg) }
Dans ce code :
On doit maintenant éditer le module “example.com/hello” pour qu'il utilise le module local “example.com/greetings”.
Dans le cas où le module est publié les outils Go peuvent le télécharger. Ici ce n'est pas le cas : il faut donc adapter le module “example.com/hello” pour qu'il puisse trouver le module “example.com/greetings” sur le système de fichier local. Depuis la ligne de commande :
go mod edit -replace example.com/greetings=../grettings
La commande spécifie que “example.com/greetings” devra être remplacé par “../grettings” lors du calcul de dépendances. Après exécution de la commande, le fichier go.mod doit contenir une directive replace :
cat go.mod module example.com/hello go 1.20 replace example.com/greetings => ../greetings
Toujours depuis la ligne de commande dans le répertoire hello, exécuter la commande go mod tidy
pour synchroniser les dépendances du module “example.com/hello”, ajoutant ce qui est requis par le code mais pas encore suivi dans le module.
go mod tidy
Après exécution de la commande le ficheir go.mod doit être de la forme :
module example.com/hello go 1.20 replace example.com/greetings => ../grettings require example.com/greetings v0.0.0-00010101000000-000000000000
La commande a bien trouvé le code dans le répertoire local ../grettings et introduit la directive require
. Cette dépendance a été créée par l'import du paquetage greetings dans hello.go
LEn complément du module on retrouve une pseudo-version générée à la place de la version semantique (semantic version number). La documentation officielle décrit en détail le versionning des modules.
go run .
Salut, Yoann. Bienvenue!
La gestion des erreurs est nécessaire à la production d' un code solide. On va maintenant aborder la génération d'une erreur depuis le module 'greetings' et sa gestion par le module appelant 'main'.
Via l'éditeur, on modifie le fichier greetings/greetings.go
comme proposé ci-dessous:
package greetings import ( "errors" "fmt" ) // Hello returns a greeting for the named person. func Hello(name string) (string, error) { // If no name was given, return an error with a message. if name == "" { return "", errors.New("empty name") } // If a name was received, return a value that embeds the name // in a greeting message. message := fmt.Sprintf("Hi, %v. Welcome!", name) return message, nil }
A propos des modifications apportées:
On peut à présent modifier le code appelant dans le fichier hello/hello.go
:
package main import ( "fmt" "log" "example.com/greetings" ) func main() { //Définition de propriétés du Logger log.Default().SetPrefix("greetings: ") //Désactive l'afficahge du timestamp, du fichier source et du numero //de ligne log.SetFlags((0)) msg, err := greetings.Hello("") //Si une erreur est retournée, elle est affichée dans la console //et le programme s'arrête if err != nil { log.Fatal(err) } //Affiche le message dans la console fmt.Println(msg) }
Dans le code ci-dessus on a :
Depuis la ligne de commande, on peut exécuter notre code:
go run . greetings: empty name exit status 1
On va modifier un peu le code pour retourner un message aléatoirement parmi un petit ensemble de messages. Pour cela on va utiliser une Go slice. Une slice peut être vu comme un tableau avec la particularité de pouvoir changer de taille dynamiquement lors des ajouts/suppressions d' éléments.
Nous allons ajouter une slice contenant trois messages et le code permettant d'en retourner aléatoirement un parmi les trois.
On ouvre l'éditeur et on modifie le fichier greetings/greetings.go
package greetings import ( "errors" "fmt" "math/rand" ) // Hello retourne un message de bienvenue à la personne désignée func Hello(name string) (string, error) { //Si aucun nom n'est donné retourne une erreur avec un message. if name == "" { return "", errors.New("empty name") } message := fmt.Sprintf(randomFormat(), name) return message, nil } // randomFormat retourne aléatoirement un message préformatté parmi l'ensemble des messages disponibles. func randomFormat() string { formats := []string{ "Hello, %v. Bienvenue!", "Heureux de vous revoir %v!", "Salut %v, enchanté!", } //retourne aléatoirement un format de message return formats[rand.Intn(len(formats))] }
Quelques remarques:
formats
contenant trois messages préformattés. Pour déclarer une slice, on ne spécifie pas le nombre d'éléments entre les crochets. Cela indique à Go que la taille du tableau contenu dans la slice peut changé ;Le fichier hello.go est légèrement modifié : on remplace la chaîne vide par une chaîne de caractères quelconque pour retrouver le comportement normal du programme.
yoann@node-7c87:~/dev/go/hello$ go run . Hello, Yoann. Bienvenue! yoann@node-7c87:~/dev/go/hello$ go run . Heureux de vous revoir Yoann! yoann@node-7c87:~/dev/go/hello$ go run . Heureux de vous revoir Yoann! yoann@node-7c87:~/dev/go/hello$ go run . Heureux de vous revoir Yoann! yoann@node-7c87:~/dev/go/hello$ go run . Hello, Yoann. Bienvenue!
Nous allons modifier une dernière fois nos modules afin d'obtenir des messages pour plusieurs personnes en une seule requête. Pour cela, nous allons devoir gérer plusieurs valeurs en entrée, associer à chacune une valeur et produire en sortie à valeurs multiples. En entrée on devra passer un ensemble de noms à la fonction qui retournera pour chacun des noms un message de bienvenue.
Ces modifications soulèvent une problématique : modifier les paramètres de la fonction Hello() change sa signature. Si le module a déjà été publié et qu'il est utilisé, ces changements conduiront à une à des erreurs dans les programmes appelants.
Dans ce cas une meilleur option est d'écrire une nouvelle fonction avec un nom différent. Cette nouvelle fonction prendra plusieurs paramètres. Cela permet de préserver l'ancienne fonction et assure la rétrocompatibilité.
Via l'éditeur, on modifie notre fichier greetings/greetings.go
package greetings import ( "errors" "fmt" "math/rand" ) // Hello retourne un message de bienvenue à la personne désignée func Hello(name string) (string, error) { //Si aucun nom n'est donné retourne une erreur avec un message. if name == "" { return "", errors.New("empty name") } message := fmt.Sprintf(randomFormat(), name) return message, nil } // HelloAll retourne un map associant un message de bienvenue à chaque personne en entrée func HelloAll(names []string) (map[string]string, error) { //un map pour associer un message à chaque nom messages := make(map[string]string) //boucle sur la slice en entrée (les noms) et appelle Hello() pour associer une message for _, name := range names { //pour chaque index(non utilisé _), valeur du paramètre 'names' msg, err := Hello(name) if err != nil { //une erreur s'est produite, on retourne un objet vide et le code erreur return nil, err } messages[name] = msg } return messages, nil } // randomFormat retourne aléatoirement un message préformatté parmi l'ensemble des messages disponibles. func randomFormat() string { formats := []string{ "Hello, %v. Bienvenue!", "Heureux de vous revoir %v!", "Salut %v, enchanté!", } //retourne aléatoirement un format de message return formats[rand.Intn(len(formats))] }
Dans ce code nous avons:
make(map[key-type]value-type)
. La fonction HelloAll() retourne le map à l'appelant.for
l'opérateur range
retourne deux valeurs : l'index de l'élément courant dans la boucle et une copie de sa valeur. Nous n'utilisons pas l'index et l'avons donc