install-golang.png

Creer un module Go

par Romain Barraud· ·

Créer un module Go

Dans ce tutoriel nous allons créer deux modules, Le premier sera une librairie qui sera importée par d’autres librairies ou applications.

Le second module est une application qui va appeler ce premier module.

Ce tutoriel est composé de septs parties

  • Créer un module qui peut être appelé d’un autre module
  • Appeler votre code dans un autre module
  • Retourner et gérer une erreur
  • Retourner une salutation aléatoire
  • Retourner des salutations pour plusieurs personnes
  • Ajouter un test
  • Compiler et installer l’application

Créer un module que les autres puissent utiliser

Il faut dans un premier temps créer un module dans lequel on va regrouper dans un ou plusieurs packages des fonctions.

Par exemple créer un module avec des packages qui possède des fonctions qui vont faire de l’analyse financière afin que les autres applications puissent l’utiliser. Pour développer un module vous pouvez regarder Developper et publier des modules.

Création du module

mkdir salutations
cd salutations
go mod init tokenizers/salutations

Comme vu dans un autre article la commande go mod init va créer un fichier go.mod qui va garder les dépendances de notre projet. Si jamais je publie ce module, alors son path sera tokenizers/salutations

Maintenant nous allons créer un fichier salutations.go et créer une fonction Hello qui va à partir d’un prénom nous renvoyer une salutation sur ce même prénom.

package salutations

import "fmt"

func Bonjour(name string) string {
	var message string = fmt.Sprintf("Salutation noble, %v. Bienvenue!", name)
	return message
}

Dans ce code nous on déclare un package salutations dans lequel on va écrire une ou plusieurs fonctions. Nous implémentons une fonction bonjour qui à partir d’un nom va dire bonjour.

Remarque : Nous utilisons la fonction Sprintf pour créer un message de salutations, on peut voir cela comme une concaténation.

Appeler ce code à partir d’un autre module

Nous allons créer un module bonjour qui va pouvoir aller chercher les fonctions du module salutations

<home>/
 |-- salutations/
 |-- bonjour/

Pour cela même chose on va créer le repertoire bonjour, initialiser le module et créer un fichier main qui pourra être lancé avec la commande go run

package main

import (
	"fmt"

	"tokenizers/salutations"
)

func main() {
	// Get a greeting message and print it.
	message := salutations.Bonjour("Gladys")
	fmt.Println(message)
}

Ce code ne fonctionne pas car le programme ne trouve pas salutations dans la librairie standard. Et c’est tout à fait normal car il n’est pas publié. pour adapter la résolution de ce module avec un module en local.

go mod edit -replace tokenizers/salutations=../salutations

Une fois cela fait il faut télécharger la librairie, pour cela on va synchroniser avec la command go mod tidy

go mod tidy
go run .

Résultat:

Et voilà nous venons de créer deux modules dont un qui utilise l'autre. Maintenant nous allons voir comment gérer les erreurs

Retourner et gérer une erreur

Gérer les erreurs est une fonctionnalité essentielle pour du code robuste.

Nous allons rajouter un bout de code dans le module “salutations” pour gérer ça dans l’appelant.

Pour cela on va tout d’abord importer le package errors qui va nous permettre de créer des erreurs.

Ensuite il faudra mettre à jour la fonction “Bonjour” afin qu’elle puisse retourner également une erreur en plus d’une chaine de caractères.

package salutations

import (
	"errors"
	"fmt"
)

func Bonjour(name string) (string, error) {

	// If no name was given, return an error with a message.
	if name == "" {
		return "", errors.New("Le nom est vide")
	}

	// If a name was received, return a value that embeds the name
	// in a greeting message.
	var message string = fmt.Sprintf("Salutation noble, %v. Bienvenue!", name)
	return message, nil

}

Dans le cas où nous il n’y a pas d’erreurs, nous allons retourner nil ce qui signifie pas d’erreurs.

Ensuite pour gérer l’erreur nous allons utiliser le logguer contenu dans le packagelog

En utilisant ce logger on peut afficher l’erreur et utiliser la fonction “Fatal” pour afficher l’erreur et terminer le programme.

package main

import (
	"fmt"
	"log"
	"tokenizers/salutations"
)

func main() {
	// Set properties of the predefined Logger, including
	// the log entry prefix and a flag to disable printing
	// the time, source file, and line number.
	log.SetPrefix("salutations: ")
	log.SetFlags(0)

	// Get a greeting message and print it.
	message, err := salutations.Bonjour("")

	// If an error was returned, print it to the console and
	// exit the program.
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(message)
}

Ce qui donne bien le résultat suivant:

Retourner une salutation aléatoire

Nous allons proposer une liste de salutations et en prendre une au hasard à chaque fois que la fonction sera appelée. Pour faire cela on va utiliser un slice . Un slice est comme un array, sauf que sa taille change dynamiquement quand on ajoute et supprime des éléments. Le slice est un des types de Go le plus utilisé.

On va déclarer un slice de quelques élements. Ensuire nous selectionnerons aléatoirement un élément à partir de ce même slice.

package salutations

import (
	"errors"
	"fmt"
	"math/rand"
)

func Bonjour(name string) (string, error) {
	// If no name was given, return an error with a message.
	if name == "" {
		return "", errors.New("Le nom est vide")
	}

	// If a name was received, return a value that embeds the name
	// in a greeting message.
	var message string = fmt.Sprintf(obtenirSalutations(), name)
	return message, nil

}

func obtenirSalutations() string {
	var salutations = []string{
		"Salutation noble, %v. Bienvenue!",
		"Ravis de te rencontrer, %v. Bienvenue!",
		"Plop, %v!",
	}

	// on recupère un nombre aléatoire entre 0 et la taille max
	choix := rand.Intn(len(salutations))
	return salutations[choix]
}

Si on rejoue maintenant le code appelant.

Retourner des salutations pour plusieurs personnes

L’objectif est de pouvoir saluer plusieurs personnes en entrée, je tente sans le tutoriel

Je vais créer une fonction BonjourATous qui prendra en entrée un slice de noms, et qui retournera un slice de messages à afficher. Si jamais un des noms en entrée est vide alors il erreur surviendra.

Dans le fichier main.go

package main

import (
	"fmt"
	"log"
	"tokenizers/salutations"
)

func main() {
	// Set properties of the predefined Logger, including
	// the log entry prefix and a flag to disable printing
	// the time, source file, and line number.
	log.SetPrefix("salutations: ")
	log.SetFlags(0)

	var noms = []string{
		"Romain",
		"David",
		"Meryem",
	}

	// Get a greeting message and print it.
	messages, err := salutations.BonjourATous(noms)

	// If an error was returned, print it to the console and
	// exit the program.
	if err != nil {
		log.Fatal(err)
	}

	for i := 0; i < len(messages); i++ {
		fmt.Println(messages[i])
	}

}

Dans le module salutations.go

func BonjourATous(names []string) ([]string, error) {
	var messages []string
	var emptyError error

	for i := 0; i < len(names); i++ {

		message, err := Bonjour(names[i])
		if err != nil {
			return nil, errors.New("Un des noms en entrée est vide")
		}
		messages = append(messages, message)
	}
	return messages, emptyError
}

_Résultat : _

Et si je met un prénom à vide

var noms = []string{
		"Romain",
		"",
		"Meryem",
	}

Résultat:

// Hellos returns a map that associates each of the named people
// with a greeting message.
func BonjourATous(names []string) (map[string]string, error) {
    // A map to associate names with messages.
    messages := make(map[string]string)
    // Loop through the received slice of names, calling
    // the Hello function to get a message for each name.
    for _, name := range names {
        message, err := Hello(name)
        if err != nil {
		        // breaking loop
            return nil, err
        }
        // In the map, associate the retrieved message with
        // the name.
        messages[name] = message
    }
    return messages, nil
}

Ils ont géré le retour dans un tableau associatif, afin de pouvoir avoir une combinaison

avec le nom comme clé et le message comme valeur.

On voit grosso modo 4 choses nouvelles :

  • les Maps
  • L’opérateur make
  • La boucle for
  • Le blank identifier

Ajouter un test

On va ajouter un test pour assurer et garantir la robustesse de notre code afin d’éviter les bugs et le regressions futures.

Dans le répertoire du module “salutations” on va créer un fichier salutations_test.go

Voici ce que nous allons écrire dans le test :

package salutations

import (
	"regexp"
	"testing"
)

// TestBonjourWithName appelle salutations.Bonjour avec un nom, on
// vérifie qu'il nous renvoie bien une valeur valide
func TestBonjourWithName(t *testing.T) {
	name := "Gladys"

	// Doit contenir 'Gladys'
	want := regexp.MustCompile(`\b` + name + `\b`)
	msg, err := Bonjour(name)

	// Si ne contient pas Glady et n'a pas d'erreur alors on signale que le test ne passe pas.
	if !want.MatchString(msg) || err != nil {
		t.Fatalf(`Bonjour("Gladys") = %q, %v, want match for %#q, nil`, msg, err, want)
	}
}

func TestBonjourWithoutName(t *testing.T) {
	name := ""
	msg, err := Bonjour(name)

	// Si il contient quelquechose ou n'a pas d'erreur alors on signale que le test ne passe pas.
	if msg != "" || err == nil {
		t.Fatalf(`Hello("") = %q, %v, want "", error`, msg, err)
	}
}

Voici le package testing qui va permettre d’initier nos tests et d’attendre un objet testing qui sera certainement alimenté par la commande test et également un package de regex pour définir si le résultat attendu correspond à ce qu’on attendait. Classique.

go test

Par définition chaque fonction correspondra à un test, donc son nom doit être clair et compréhensible concernant l’attendu.

Pour avoir plus de détails il est possible ( et préférable) de mettre en verbose

go test -v

Résultat:

Compiler et installer l’application

Nous avons vu dans cet article quelques commandes Go, la commande run est un raccourci pratique pour compiler et lancer le programme quand vous faites des changements fréquents.

Cela ne génère pas d’executable.

Nous allons parler de deux commandes additionnelles, pour construire notre code.

  • La commande build
  • La commande install

Dans le répertoire bonjour, nous allons lancer la commande build pour compiler le code en executable.

go build

Nous voyons qu’il a crée un executable bonjour, nous allons donc l’executer

Maintenant nous allons rendre ce programme executable n’importe où et pour cela il faut rajouter le path de l’executable dans le path d’installation de Go. Mais où est il ?

Nous pouvons savoir cela grâce à la commande list

go list -f '{{.Target}}'

Cela doit vous afficher quelque-chose du genre : home/gopher/bin/bonjour

On va ajouter cette ligne dans le path afin que le programme soit accessible et surtout il faut l’installer

export PATH=$PATH:/path/to/your/install/directory
go install

Vous devriez avoir le résultat suivant :

Et voilà c’était très complet et long et on n’a pas pu aller en détail dans chacun des articles. J’attaquerai demain la suite.

sources :