Go : Les bases pour développeurs Java/Python en 2026
Apprenez Go rapidement en partant de vos acquis Java ou Python. Goroutines, channels, interfaces et patterns essentiels expliqués pour une transition fluide.

Go (ou Golang) s'est imposé comme le langage de choix pour les microservices, les CLI et les systèmes distribués. Créé par Google en 2009, il combine la simplicité de Python avec les performances du C. Pour un développeur venant de Java ou Python, la transition vers Go est étonnamment fluide une fois les concepts clés assimilés.
Go alimente Docker, Kubernetes, Terraform et de nombreuses infrastructures cloud. Sa compilation rapide, sa gestion native de la concurrence et son déploiement en binaire unique en font un choix idéal pour le backend moderne.
Installation et configuration de Go
L'installation de Go est simple et uniforme sur toutes les plateformes. L'outil go intègre compilation, gestion des dépendances et tests.
# install.sh
# Installation sur macOS avec Homebrew
brew install go
# Installation sur Linux (Ubuntu/Debian)
sudo apt update && sudo apt install golang-go
# Vérification de l'installation
go version
# go version go1.22.0 linux/amd64La structure d'un projet Go suit des conventions strictes mais simples. Le fichier go.mod définit le module et ses dépendances.
# project-setup.sh
# Création d'un nouveau projet
mkdir mon-projet && cd mon-projet
go mod init github.com/user/mon-projet
# Structure générée :
# mon-projet/
# ├── go.mod # Manifeste du module
# └── main.go # Point d'entrée
# Commandes essentielles
go build # Compile le projet
go run main.go # Compile et exécute
go test ./... # Lance tous les tests
go fmt ./... # Formate le code automatiquementPremier programme Go
Voici un programme simple qui illustre la syntaxe de base de Go. La comparaison avec Java et Python aide à visualiser les différences.
package main
import "fmt"
// Point d'entrée du programme
func main() {
// Déclaration avec inférence de type
message := "Hello, Go!"
fmt.Println(message)
// Déclaration explicite
var count int = 42
fmt.Printf("Count: %d\n", count)
}Ce qui frappe immédiatement : pas de point-virgule, pas de parenthèses autour des conditions, et l'inférence de type avec :=. Go privilégie la concision sans sacrifier la lisibilité.
Variables et types fondamentaux
Go est statiquement typé mais offre une excellente inférence de types. Les types de base couvrent la majorité des besoins.
package main
import "fmt"
func main() {
// Déclaration courte (dans les fonctions uniquement)
name := "Alice" // string
age := 30 // int
height := 1.75 // float64
active := true // bool
// Déclaration explicite
var score int = 100
var rate float64 = 3.14
// Déclaration multiple
var (
firstName string = "Bob"
lastName string = "Smith"
points int = 0
)
// Zéro-valeurs (valeurs par défaut)
var count int // 0
var text string // "" (chaîne vide)
var flag bool // false
var ptr *int // nil
fmt.Println(name, age, height, active)
}Contrairement à Java ou Python, Go initialise automatiquement les variables à leur "zéro-valeur" : 0 pour les nombres, "" pour les strings, false pour les bools, nil pour les pointeurs et slices.
Fonctions et retours multiples
Go permet de retourner plusieurs valeurs, une fonctionnalité utilisée massivement pour la gestion des erreurs.
package main
import (
"errors"
"fmt"
)
// Fonction simple avec paramètres typés
func add(a, b int) int {
return a + b
}
// Retours multiples (pattern idiomatique pour les erreurs)
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
// Retours nommés
func getUser(id int) (name string, age int, err error) {
if id <= 0 {
err = errors.New("invalid ID")
return
}
name = "Alice"
age = 30
return
}
// Fonction variadique
func sum(numbers ...int) int {
total := 0
for _, n := range numbers {
total += n
}
return total
}
func main() {
// Appel simple
result := add(5, 3)
fmt.Println("5 + 3 =", result)
// Gestion d'erreur explicite
quotient, err := divide(10, 3)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Printf("10 / 3 = %.2f\n", quotient)
// Ignorer une valeur retournée avec _
name, _, _ := getUser(1)
fmt.Println("User:", name)
// Appel variadique
total := sum(1, 2, 3, 4, 5)
fmt.Println("Sum:", total)
}Structures et méthodes
Les structs sont les blocs de construction des types personnalisés en Go. Les méthodes s'attachent aux types via des receivers.
package main
import "fmt"
// Définition d'une struct
type User struct {
ID int
Username string
Email string
Active bool
}
// Constructeur (convention : NewTypeName)
func NewUser(id int, username, email string) *User {
return &User{
ID: id,
Username: username,
Email: email,
Active: true,
}
}
// Méthode avec receiver valeur (copie)
func (u User) FullInfo() string {
status := "inactive"
if u.Active {
status = "active"
}
return fmt.Sprintf("%s <%s> (%s)", u.Username, u.Email, status)
}
// Méthode avec receiver pointeur (modification possible)
func (u *User) Deactivate() {
u.Active = false
}
// Méthode avec receiver pointeur pour modification
func (u *User) UpdateEmail(newEmail string) {
u.Email = newEmail
}
func main() {
// Création avec constructeur
user := NewUser(1, "alice", "alice@example.com")
fmt.Println(user.FullInfo())
// Modification via méthode
user.Deactivate()
fmt.Println(user.FullInfo())
// Création directe
user2 := User{
ID: 2,
Username: "bob",
Email: "bob@example.com",
}
fmt.Println(user2.FullInfo())
}Utiliser un receiver pointeur (*User) quand la méthode modifie l'état ou quand la struct est volumineuse. Utiliser un receiver valeur (User) pour les méthodes en lecture seule sur des structs légères.
Interfaces : polymorphisme implicite
Les interfaces Go sont implémentées implicitement. Un type satisfait une interface s'il implémente toutes ses méthodes, sans déclaration explicite.
package main
import (
"fmt"
"math"
)
// Définition d'une interface
type Shape interface {
Area() float64
Perimeter() float64
}
// Rectangle implémente Shape implicitement
type Rectangle struct {
Width, Height float64
}
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
func (r Rectangle) Perimeter() float64 {
return 2 * (r.Width + r.Height)
}
// Circle implémente aussi Shape
type Circle struct {
Radius float64
}
func (c Circle) Area() float64 {
return math.Pi * c.Radius * c.Radius
}
func (c Circle) Perimeter() float64 {
return 2 * math.Pi * c.Radius
}
// Fonction acceptant l'interface
func PrintShapeInfo(s Shape) {
fmt.Printf("Area: %.2f, Perimeter: %.2f\n", s.Area(), s.Perimeter())
}
func main() {
rect := Rectangle{Width: 10, Height: 5}
circle := Circle{Radius: 7}
// Polymorphisme via interface
PrintShapeInfo(rect)
PrintShapeInfo(circle)
// Slice d'interfaces
shapes := []Shape{rect, circle}
for _, shape := range shapes {
PrintShapeInfo(shape)
}
}Cette approche est radicalement différente de Java où implements est obligatoire. En Go, la conformité est structurelle et non nominale.
Prêt à réussir tes entretiens Go ?
Entraîne-toi avec nos simulateurs interactifs, fiches express et tests techniques.
Slices et Maps : collections dynamiques
Les slices sont des vues dynamiques sur des arrays, et les maps sont des tables de hachage natives.
package main
import "fmt"
func main() {
// Slice : tableau dynamique
numbers := []int{1, 2, 3, 4, 5}
// Ajouter des éléments
numbers = append(numbers, 6, 7)
// Slicing (similaire à Python)
subset := numbers[1:4] // [2, 3, 4]
fmt.Println("Subset:", subset)
// Créer un slice avec make
scores := make([]int, 0, 10) // len=0, cap=10
scores = append(scores, 100, 95, 88)
// Itération avec range
for index, value := range numbers {
fmt.Printf("numbers[%d] = %d\n", index, value)
}
// Map : table de hachage
users := map[string]int{
"alice": 30,
"bob": 25,
}
// Ajouter/Modifier
users["charlie"] = 35
// Vérifier l'existence
age, exists := users["alice"]
if exists {
fmt.Println("Alice's age:", age)
}
// Supprimer
delete(users, "bob")
// Itération sur map
for name, age := range users {
fmt.Printf("%s is %d years old\n", name, age)
}
}Gestion des erreurs idiomatique
Go n'a pas d'exceptions. Les erreurs sont des valeurs retournées explicitement, ce qui force une gestion rigoureuse.
package main
import (
"errors"
"fmt"
"os"
)
// Erreur sentinelle (pour comparaison)
var ErrNotFound = errors.New("resource not found")
var ErrInvalidInput = errors.New("invalid input")
// Erreur custom avec contexte
type ValidationError struct {
Field string
Message string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("validation failed on %s: %s", e.Field, e.Message)
}
// Fonction retournant différents types d'erreurs
func GetUser(id int) (string, error) {
if id <= 0 {
return "", &ValidationError{
Field: "id",
Message: "must be positive",
}
}
if id > 1000 {
return "", ErrNotFound
}
return "Alice", nil
}
// Wrapping d'erreurs (Go 1.13+)
func ReadConfig(path string) ([]byte, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("reading config %s: %w", path, err)
}
return data, nil
}
func main() {
// Pattern de base
user, err := GetUser(-1)
if err != nil {
fmt.Println("Error:", err)
// Type assertion pour erreur custom
var valErr *ValidationError
if errors.As(err, &valErr) {
fmt.Printf("Field: %s\n", valErr.Field)
}
// Comparaison avec erreur sentinelle
if errors.Is(err, ErrNotFound) {
fmt.Println("User not found")
}
} else {
fmt.Println("User:", user)
}
}Depuis Go 1.13, utiliser errors.Is() pour comparer avec des erreurs sentinelles et errors.As() pour extraire un type d'erreur spécifique d'une chaîne d'erreurs wrappées.
Goroutines : concurrence légère
Les goroutines sont des threads légers gérés par le runtime Go. Lancer une goroutine coûte seulement quelques Ko de mémoire.
package main
import (
"fmt"
"sync"
"time"
)
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done() // Décrémente le compteur à la fin
fmt.Printf("Worker %d starting\n", id)
time.Sleep(time.Second)
fmt.Printf("Worker %d done\n", id)
}
func main() {
var wg sync.WaitGroup
// Lancer 5 goroutines
for i := 1; i <= 5; i++ {
wg.Add(1)
go worker(i, &wg) // Préfixe 'go' lance la goroutine
}
// Attendre la fin de toutes les goroutines
wg.Wait()
fmt.Println("All workers completed")
}Le sync.WaitGroup permet d'attendre la fin de plusieurs goroutines. C'est le pattern de base pour le parallélisme en Go.
Channels : communication entre goroutines
Les channels sont des conduits typés pour la communication entre goroutines. Ils permettent de synchroniser et d'échanger des données de manière sûre.
package main
import (
"fmt"
"time"
)
func producer(ch chan<- int) {
for i := 1; i <= 5; i++ {
fmt.Println("Producing:", i)
ch <- i // Envoie sur le channel
time.Sleep(100 * time.Millisecond)
}
close(ch) // Ferme le channel quand terminé
}
func consumer(ch <-chan int, done chan<- bool) {
for value := range ch { // Itère jusqu'à fermeture
fmt.Println("Consuming:", value)
}
done <- true
}
func main() {
ch := make(chan int) // Channel non bufferisé
done := make(chan bool)
go producer(ch)
go consumer(ch, done)
<-done // Attend la fin du consumer
fmt.Println("All done")
}Channels bufferisés et select
package main
import (
"fmt"
"time"
)
func main() {
// Channel bufferisé (capacité 3)
buffered := make(chan int, 3)
buffered <- 1
buffered <- 2
buffered <- 3
// buffered <- 4 // Bloquerait car buffer plein
fmt.Println(<-buffered) // 1
// Select : multiplexage de channels
ch1 := make(chan string)
ch2 := make(chan string)
go func() {
time.Sleep(100 * time.Millisecond)
ch1 <- "from ch1"
}()
go func() {
time.Sleep(200 * time.Millisecond)
ch2 <- "from ch2"
}()
// Attend le premier message disponible
for i := 0; i < 2; i++ {
select {
case msg1 := <-ch1:
fmt.Println("Received:", msg1)
case msg2 := <-ch2:
fmt.Println("Received:", msg2)
case <-time.After(500 * time.Millisecond):
fmt.Println("Timeout!")
}
}
}Go suit le modèle CSP (Communicating Sequential Processes) : "Ne communiquez pas en partageant la mémoire, partagez la mémoire en communiquant." Les channels évitent les race conditions.
Tests en Go
Go intègre un framework de test minimaliste mais efficace. Les fichiers de test se terminent par _test.go.
package calculator
func Add(a, b int) int {
return a + b
}
func Divide(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}package calculator
import (
"testing"
)
// Test de base
func TestAdd(t *testing.T) {
result := Add(2, 3)
expected := 5
if result != expected {
t.Errorf("Add(2, 3) = %d; want %d", result, expected)
}
}
// Tests table-driven (pattern recommandé)
func TestAddTableDriven(t *testing.T) {
tests := []struct {
name string
a, b int
expected int
}{
{"positive numbers", 2, 3, 5},
{"negative numbers", -2, -3, -5},
{"zero", 0, 0, 0},
{"mixed", -5, 10, 5},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := Add(tt.a, tt.b)
if result != tt.expected {
t.Errorf("Add(%d, %d) = %d; want %d",
tt.a, tt.b, result, tt.expected)
}
})
}
}
// Test d'erreur
func TestDivideByZero(t *testing.T) {
_, err := Divide(10, 0)
if err == nil {
t.Error("Expected error for division by zero")
}
}
// Benchmark
func BenchmarkAdd(b *testing.B) {
for i := 0; i < b.N; i++ {
Add(100, 200)
}
}Les tests s'exécutent avec go test ./... et les benchmarks avec go test -bench=..
HTTP : serveur web minimaliste
Go excelle dans la création de serveurs HTTP performants grâce à sa bibliothèque standard.
package main
import (
"encoding/json"
"log"
"net/http"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
}
func main() {
// Route simple
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello, Go!"))
})
// Route JSON
http.HandleFunc("/api/users", func(w http.ResponseWriter, r *http.Request) {
users := []User{
{ID: 1, Name: "Alice"},
{ID: 2, Name: "Bob"},
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(users)
})
// Route avec méthode
http.HandleFunc("/api/user", func(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case "GET":
w.Write([]byte("Get user"))
case "POST":
w.Write([]byte("Create user"))
default:
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
})
log.Println("Server starting on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}Ce serveur gère des milliers de connexions simultanées grâce aux goroutines. Chaque requête est traitée dans sa propre goroutine automatiquement.
Conclusion
Go offre une approche pragmatique du développement backend : syntaxe simple, compilation rapide, concurrence native et excellent outillage. Pour les développeurs Java ou Python, la transition demande d'accepter quelques conventions différentes (gestion explicite des erreurs, pas de génériques complexes avant Go 1.18), mais les bénéfices en performance et maintenabilité sont immédiats.
Checklist pour bien démarrer
- ✅ Installer Go via le site officiel ou gestionnaire de paquets
- ✅ Maîtriser les commandes
go build,go run,go test,go fmt - ✅ Comprendre la différence entre slices et arrays
- ✅ Adopter le pattern
if err != nilpour la gestion des erreurs - ✅ Utiliser goroutines et channels pour la concurrence
- ✅ Écrire des tests table-driven avec le package
testing
Passe à la pratique !
Teste tes connaissances avec nos simulateurs d'entretien et tests techniques.
L'écosystème Go est mature avec des frameworks populaires comme Gin, Echo et Fiber pour le web, et des outils comme Cobra pour les CLI. Avec ces bases solides, l'exploration de sujets avancés comme les génériques (Go 1.18+), le context package et les patterns de concurrence devient accessible.
Tags
Partager
Articles similaires

Concurrency Go : Goroutines et Channels - Guide Complet
Maîtrisez la concurrence en Go avec goroutines et channels. Patterns avancés, synchronisation, select, et bonnes pratiques avec exemples de code détaillés.

Entretien technique Go : Goroutines, Channels et Concurrence
Questions d'entretien Go sur les goroutines, channels et patterns de concurrence. Exemples de code, pieges courants et reponses de niveau expert pour preparer les entretiens techniques Go en 2026.

Top 25 questions d'entretien Go : Guide complet pour développeurs
Préparez vos entretiens Go avec les 25 questions les plus posées. Goroutines, channels, interfaces, concurrence et patterns avancés avec exemples de code.