Présentation générale
Go est un langage de programmation compilé à typage fort et statique créé chez Google par Robert Griesemer, Rob Pike et Ken Thompson. Souvent appelé “Golang” à cause de son nom de domaine golang.org, ce langage a trois objectifs principaux:
- Être simple d’utilisation
- Avoir une compilation rapide
- Être rapide lors de l’exécution
Les créateurs de Go pensaient qu’aucun langage bien établi ne vérifie ces trois points simultanément. Le C++ a une exécution très rapide, mais il reste trop long à la compilation et est complexe à utiliser. Le Python est simple d’utilisation mais manque en rapidité. C’est pourquoi Go cherche à satisfaire ces trois critères à la fois.
Go a été pensé originellement pour coder les serveurs Cloud de Google, puis s’est étendu au développement de logiciel.
Go est un langage multi-paradigme. En effet, Go est un langage fonctionnel, impératif, concurrent et orienté-objet. Il est cependant limité comme langage orienté-objet.
Spécificités
Philosophie – façon de coder
Go a été créé avec une idée principale en tête : la simplicité. Il faut faire en sorte, qu’écrire et comprendre du Go soit facile pour quelqu’un qui n’en a jamais vu. C’est pourquoi il peut sembler qu’un certain nombres d’outils manque dans Go.
Pour Rob Pike les outils de développement sont un espace vectoriel et Go serait une tentative d’en trouver une base : un ensemble, le plus petit possible, qui permet de résoudre tous les problèmes.
En Go, pour la plupart des problèmes, il y a donc une seule façon de procéder pour le résoudre. Ce qui garantit un langage simple et facile d’utilisation, mais également un langage qui compile rapidement.
Syntaxe – Typage
En ce qui concerne la syntaxe Go ressemble beaucoup à du C. Cela est bien visible sur cet exemple de Hello World:
package main
import "fmt"
func main() {
fmt.Printf("Hello, world\n")
}
Cependant Go présente quelques différences avec le C notamment le fait que le point virgule n’est pas nécessaire en fin de ligne ou encore que les parenthèses ne sont pas nécessaires pour une boucle ou un if :
func main() {
sum := 1
for ; sum < 1000; {
sum += sum
}
fmt.Println(sum)
}
Concernant le typage, go est un langage fortement typé, lors de la définition d’une fonction il faut préciser le type des paramètres et du retour de la fonction :
func add(x int, y int) int {
return x + y
}
On remarquera que contrairement à C le type des paramètres suit le nom de la variable et non l’inverse. C’est aussi vrai pour la déclaration de variables, sauf dans le cas de la déclaration de variable courte :
//déclaration classique:
var i int = 1
//déclaration courte:
k := 3
De la même façon qu’en C, les arguments de fonction sont passés par copie. Il est donc impossible de modifier les paramètres d’une fonction à l’intérieur de celle-ci sans passer par des pointeurs.
Compilation – déploiement
Puisque Go est un langage compilé, il permet d’avoir des vitesses d’exécutions très rapides. Il est cependant bien meilleur que d’autres langages compilés dans le sens ou sa compilation est extrêmement rapide. Cette vitesse de compilation est due à la syntaxe très simple du Go. Ainsi le développeur ne perd pas de temps lors du développement du logiciel.
La compilation est tellement rapide que Go peut tout à fait être utilisé comme un langage de script. Il peut ainsi remplacer du Python ou du Ruby, alors qu’initialement il était surtout pensé comme un concurrent du C++
Concurrence – Goroutine
La concurrence est une des features les plus importantes de Go. Ce langage à été créé pour gérer des serveurs de Cloud, il est logique de pouvoir servir plusieurs requêtes à la fois.
La concurrence se gère avec les goroutines. Une goroutine est un processus léger (un thread) géré par le Go Runtime. Pour créer une goroutine il faut utiliser le mot-clef go
.
go f(x, y, z)
L’instruction ci-dessus démarre une nouvelle goroutine exécutant
f(x, y, z)
L’exemple suivant affiche des “hello” et des “world” de façon concurrente. Vous pouvez remarquer qu’entre deux “hello” ou deux “world” il se passe forcément 100 ms mais que rien n’est fixé entre un “hello” et un “world” par exemple. Vous pouvez tester ce code en cliquant sur ce lien.
func say(s string) {
for i := 0; i < 5; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Println(s)
}
}
func main() {
go say("world")
say("hello")
}
Mémoire partagée – canaux
La méthode conseillée pour partager des données entre plusieurs goroutines est l’utilisation de canaux. Les canaux s’utilisent avec l’opérateur de canal <-
ch <- v // Envoyer v sur le canal ch.
v := <-ch // Recevoir de ch, et
// attribuer une valeur à v.
L’orienté objet
Struct et méthodes
L’orienté objet en Go est limité et bizarre. En effet, il n’y a pas de déclaration de classe. Sur l’exemple ci-dessous, nous déclarons une structure que nous appelons Vertex. A cette structure nous donnons une méthode appelée Abs(). En Go une méthode n’est rien de plus qu’une fonction qui prend dans un paramètre spécial l’objet sur lequel la méthode est appelée (ce qui est référencé par “this” dans d’autre langages).
Ce paramètre spécial est syntaxiquement placé avant le nom de la méthode et entre parenthèses. Ici la méthode Abs()
est une méthode de l’objet Vertex
.
type Vertex struct {
X, Y float64
}
func (v Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
Implémentation d’interfaces
Il est possible de créer une interface au sens Java en Go. Une interface est alors un ensemble de signatures de méthode. On peut également créer une variable dont le type est une interface. Comme ci-dessous nous pouvons alors affecter à cette variable tout objet qui implémente la bonne interface. Dans l’exemple ci dessous c’est l’interface Abser
:
type Abser interface {
Abs() float64
}
func main() {
var a Abser
f := MyFloat(-math.Sqrt2)
v := Vertex{3, 4}
a = f // a MyFloat implements Abser
a = &v // a *Vertex implements Abser
}
Comment faire alors pour qu’un objet implémente une interface ? Il y a une chose à retenir : les interfaces sont implémentées implicitement. Pour implémenter une interface il suffit d’écrire une méthode qui corresponde à une signature de méthode présente dans l’interface et qui prend en paramètre spécial l’objet qui doit implémenter cette interface. Sur un exemple :
type I interface {
M()
}
type T struct {
S string
}
// This method means type T implements the interface I,
// but we don't need to explicitly declare that it does so.
func (t T) M() {
fmt.Println(t.S)
}
Il est alors possible d’implémenter des interfaces qui proviennent de modules, par exemple on peut implémenter la méthode String()
de l’interface Stringer
du module fmt
. Cela permet d’avoir un print
spécifique à un type. Cela revient à redéfinir la méthode toString()
en Java.
Sur cet exemple nous implémentons l’interface Stringer
pour le type IPAddr
qui est un type représentant un tableau de bytes de longueur 4
import "fmt"
type IPAddr [4]byte
func (ipAddr IPAddr) String() string {
var result string
for _, v := range(ipAddr){
result += fmt.Sprintf("%v, ", v)
}
return result
}
Divers
Garbage Collector
Go dispose d’un garbage collector très efficace. En effet, un garbage collector ne conviendrait normalement pas pour l’usage que Google fait de Go, cela créerait trop de problèmes de performances, mais celui de Go a été spécifiquement créé pour ne pas trop impacter les performances lors de l’utilisation. La rapidité lors de l’exécution est un des trois objectifs du langage.
Les fermetures de fonction
Go permet l’écriture de fermetures de fonction (aussi appelées closures) de la même façon que le Javascript le permet:
func adder() func(int) int {
sum := 0
return func(x int) int {
sum += x
return sum
}
}
Ici la variable sum
est accessible depuis la fonction présente dans la close return
. Pour voir son fonctionnement plus en détail : https://go-tour-fr.appspot.com/moretypes/25
Proof of concept: Un back-end en Go
Ce proof of concept consiste en un serveur qui affiche des pages wiki que l’utilisateur peut modifier. Les pages du wiki sont stockées localement dans des fichiers .txt.
Pour lancer le projet il vous faudra tout d’abord installer Go : https://golang.org/
Pour cloner le projet, lancez la commande suivante :
git clone --branch v1.0 git@github.com:INGENIANCE/GO-Backend-Server.git
Une fois ceci fait, rendez-vous dans le dossier gowiki où vous trouverez les fichiers wiki.go
, view.html
et edit.html
. Notez que ce sont les seuls fichiers nécessaires à l’exécution. Lancez la commande go build wiki.go
puis exécutez le fichier wiki.exe
produit.
Une fois ceci fait, dans votre navigateur allez à l’adresse localhost:8080/view/$(nom d’une page du wiki)
pour visualiser une page du wiki. Si le nom de la page de wiki que vous précisez n’existe pas vous serez redirigé vers localhost:8080/edit/$(nom de la page du wiki)
pour que vous puissiez la créer.
Le code
Le fichier principal wiki.go :
package main
import (
"errors"
"html/template"
"io/ioutil"
"log"
"net/http"
"regexp"
)
//vérification que les fichier edit.html et view.html sont bien présents
var templates = template.Must(template.ParseFiles("edit.html", "view.html"))
//La regex qui permet de détecter du forgeage d'Url
var validPath = regexp.MustCompile("^/(edit|save|view)/([a-zA-Z0-9]+)$")
//Le modèle de notre serveur, nos données seront stockées sous ce format
type Page struct {
Title string
Body []byte
}
//Sauvegarde des données dans un fichier .txt
func (p *Page) save() error {
filename := p.Title + ".txt"
return ioutil.WriteFile(filename, p.Body, 0600)
}
//Chargement des données depuis un fichier .txt
func loadPage(title string) (*Page, error) {
filename := title + ".txt"
body, err := ioutil.ReadFile(filename)
if err != nil {
return nil, err
}
return &Page{Title: title, Body: body}, nil
}
//Application du modèle sur le template
func renderTemplate(w http.ResponseWriter, tmpl string, p *Page) {
err := templates.ExecuteTemplate(w, tmpl+".html", p)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
func getTitle(w http.ResponseWriter, r *http.Request) (string, error) {
m := validPath.FindStringSubmatch(r.URL.Path)
if m == nil {
http.NotFound(w, r)
return "", errors.New("invalid Page Title")
}
return m[2], nil // Le titre est la deuxième sous-expression.
}
//Le controlleur d'affichage des pages du wiki
func viewHandler(w http.ResponseWriter, r *http.Request) {
title, err := getTitle(w, r)
if err != nil {
return
}
p, err := loadPage(title)
if err != nil {
http.Redirect(w, r, "/edit/"+title, http.StatusFound)
return
}
renderTemplate(w, "view", p)
}
//Le controlleur qui affiche la page de modification des pages du wiki
func editHandler(w http.ResponseWriter, r *http.Request) {
title, err := getTitle(w, r)
if err != nil {
return
}
p, err := loadPage(title)
if err != nil { //Si la page n'existe pas on la crée
p = &Page{Title: title}
}
renderTemplate(w, "edit", p)
}
//Le controlleur qui sauvegarde les modifications apportées par l'utilisateur
func saveHandler(w http.ResponseWriter, r *http.Request) {
title, err := getTitle(w, r)
if err != nil {
return
}
body := r.FormValue("body")
p := &Page{Title: title, Body: []byte(body)}
err = p.save()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
http.Redirect(w, r, "/view/"+title, http.StatusFound)
}
//Le point de départ de l'exécution, ici on attache les controlleurs aux bonnes URL et on lance le serveur web
func main() {
http.HandleFunc("/view/", viewHandler)
http.HandleFunc("/edit/", editHandler)
http.HandleFunc("/save/", saveHandler)
log.Fatal(http.ListenAndServe(":8080", nil))
}
Les fichiers .html :
<h1>{{.Title}}</h1>
<p>[<a href="/edit/{{.Title}}">edit</a>]</p>
<div>{{printf "%s" .Body}}</div>
<h1>Editing {{.Title}}</h1>
<form action="/save/{{.Title}}" method="POST">
<div><textarea name="body" cols="80" rows="20">{{printf "%s" .Body}}</textarea></div>
<div><input type="submit" value="Save"></div>
</form>
Sources
- https://www.jesuisundev.com/comprendre-go-en-5-minutes/
- https://en.wikipedia.org/wiki/Go_(programming_language)#Reception
- https://www.youtube.com/watch?time_continue=1101&v=cQ7STILAS0M&feature=emb_logo
- https://go-tour-fr.appspot.com/list
- https://www.scriptol.fr/programmation/go.php
- https://golang.org/doc/articles/wiki/
0 commentaire