Nouveau : Datasets open source gratuits disponibles !Decouvrir →
🐹
Web 12 min intermediaire

Les erreurs a eviter en Go

Sommaire

Erreur N1 : Deadlock

Le problème

Un deadlock est une situation où deux ou plusieurs goroutines s'attendent mutuellement sur des conditions qui ne peuvent jamais être remplies. Cela peut entraîner une application qui se bloque indéfiniment.

package main

import (
    "fmt"
    "time"
)

func worker(id int, ch chan bool) {
    for {
        select {
        case <-ch:
            fmt.Println("Worker", id, "is working")
            time.Sleep(time.Second)
            ch <- true // Deadlock here
        }
    }
}

func main() {
    ch := make(chan bool)

    for i := 1; i <= 5; i++ {
        go worker(i, ch)
    }

    <-ch // This line is never reached, causing a deadlock
}

Pourquoi c'est une erreur

Un deadlock peut entraîner une perte de performance significative car il empêche les autres goroutines d'avancer. En outre, cela rend l'application imprévisible et difficile à déboguer.

La solution

Pour éviter un deadlock, vous devez vous assurer que chaque case case dans un select est couverte par une autre case ou un default. Dans ce cas, nous devons ajouter une case default pour permettre au programme de continuer même sans reçu.

package main

import (
    "fmt"
    "time"
)

func worker(id int, ch chan bool) {
    for {
        select {
        case <-ch:
            fmt.Println("Worker", id, "is working")
            time.Sleep(time.Second)
            ch <- true // Fixed deadlock here
        default:
            fmt.Println("Worker", id, "waiting for work")
        }
    }
}

func main() {
    ch := make(chan bool)

    for i := 1; i <= 5; i++ {
        go worker(i, ch)
    }

    for i := 0; i < 2; i++ {
        ch <- true
    }
}

Comment prévenir

  • Utiliser des canaux avec une taille définie pour éviter les écrabouillages.
  • Assurer que chaque case case dans un select est couverte par une autre case ou un default.
  • Éviter l'utilisation excessive de canaux bloquants dans la même fonction.

Erreur N2 : Race Condition

Le problème

Une race condition se produit lorsque deux goroutines accèdent simultanément à une variable partagée et au moins l'une d'entre elles effectue un changement. Cela peut entraîner des résultats imprévisibles ou indésirables.

package main

import (
    "fmt"
    "sync"
)

func worker(id int, wg *sync.WaitGroup) {
    defer wg.Done()
    for i := 0; i < 3; i++ {
        fmt.Println("Worker", id, "is working")
        time.Sleep(time.Millisecond)
    }
}

func main() {
    var wg sync.WaitGroup
    wg.Add(2)

    go worker(1, &wg)
    go worker(2, &wg)

    wg.Wait()
}

Pourquoi c'est une erreur

Une race condition peut entraîner des bugs difficiles à repérer et à corriger. Elle peut également causer des comportements imprévisibles ou non déterministes.

La solution

Pour éviter une race condition, vous devez utiliser les mécanismes de synchronisation fournis par Go, comme sync.Mutex, sync.RWMutex, ou sync.Cond.

package main

import (
    "fmt"
    "sync"
)

func worker(id int, wg *sync.WaitGroup, mu *sync.Mutex) {
    defer wg.Done()
    for i := 0; i < 3; i++ {
        mu.Lock()
        fmt.Println("Worker", id, "is working")
        time.Sleep(time.Millisecond)
        mu.Unlock()
    }
}

func main() {
    var wg sync.WaitGroup
    var mu sync.Mutex

    wg.Add(2)

    go worker(1, &wg, &mu)
    go worker(2, &wg, &mu)

    wg.Wait()
}

Comment prévenir

  • Utiliser des mécanismes de synchronisation comme sync.Mutex ou sync.RWMutex.
  • Éviter l'utilisation de variables partagées entre goroutines autant que possible.
  • Faire attention aux boucles et structures conditionnelles qui pourraient entraîner des conditions critiques.

Erreur N3 : Nil Pointer Exception

Le problème

Une nil pointer exception se produit lorsque vous essayez d'accéder à une méthode ou une propriété d'un pointeur nul. Cela peut entraîner un programme qui s'arrête subitement et générer des erreurs difficiles à déboguer.

package main

import "fmt"

type Person struct {
    Name string
}

func (p *Person) SayHello() {
    fmt.Println("Hello, my name is", p.Name)
}

func main() {
    var person *Person = nil
    person.SayHello()
}

Pourquoi c'est une erreur

Une nil pointer exception peut entraîner des comportements imprévisibles ou non déterministes. Elle peut également rendre l'application moins robuste et plus susceptible de plantage.

La solution

Pour éviter une nil pointer exception, vous devez vérifier si un pointeur est nul avant d'y accéder. Vous pouvez utiliser une instruction if pour effectuer cette vérification.

package main

import "fmt"

type Person struct {
    Name string
}

func (p *Person) SayHello() {
    if p != nil {
        fmt.Println("Hello, my name is", p.Name)
    } else {
        fmt.Println("Person is nil")
    }
}

func main() {
    var person *Person = nil
    person.SayHello()
}

Comment prévenir

  • Vérifier toujours si un pointeur est nul avant d'y accéder.
  • Utiliser des fonctions d'aide pour éviter les erreurs liées aux pointeurs nuls.
  • Faire attention aux initialisations de variables et à la gestion des ressources.

Erreur N4 : Memory Leak

Le problème

Un memory leak se produit lorsque vous allouez de la mémoire qui ne peut pas être libérée. Cela peut entraîner une perte progressive de la mémoire disponible pour l'application, ce qui peut finir par entraîner un plantage.

package main

import (
    "time"
)

func leak() {
    for i := 0; i < 1000000; i++ {
        go func() {
            data := make([]byte, 1024)
            time.Sleep(time.Second) // Do something with data
        }()
    }
}

func main() {
    leak()
}

Pourquoi c'est une erreur

Un memory leak peut entraîner une perte progressive de la mémoire disponible pour l'application. Cela peut finir par entraîner un plantage et rendre l'application moins robuste.

La solution

Pour éviter un memory leak, vous devez vous assurer que toutes les allocations de mémoire sont correctement libérées. Vous pouvez utiliser des outils comme pprof pour détecter les fuites de mémoire.

package main

import (
    "time"
)

func leak() {
    for i := 0; i < 1000000; i++ {
        go func() {
            data := make([]byte, 1024)
            time.Sleep(time.Second) // Do something with data
            data = nil // Free memory
        }()
    }
}

func main() {
    leak()
}

Comment prévenir

  • Libérer explicitement la mémoire allouée avec defer.
  • Utiliser des outils comme pprof pour détecter les fuites de mémoire.
  • Éviter l'utilisation excessive de structures et de canaux sans libération.

Erreur N5 : Invalid Argument

Le problème

Une invalid argument se produit lorsque vous passez un argument invalide à une fonction. Cela peut entraîner des résultats imprévisibles ou indésirables, ainsi que des erreurs difficiles à repérer et à corriger.

package main

import (
    "fmt"
)

func divide(a, b int) (int, error) {
    if b == 0 {
        return 0, fmt.Errorf("division by zero")
    }
    return a / b, nil
}

func main() {
    result, err := divide(10, 0)
    if err != nil {
        fmt.Println(err)
    } else {
        fmt.Println(result)
    }
}

Pourquoi c'est une erreur

Une invalid argument peut entraîner des comportements imprévisibles ou non déterministes. Elle peut également rendre l'application moins robuste et plus susceptible de plantage.

La solution

Pour éviter une invalid argument, vous devez vérifier les arguments passés à une fonction avant d'y utiliser. Vous pouvez utiliser des instructions if pour effectuer cette vérification.

package main

import (
    "fmt"
)

func divide(a, b int) (int, error) {
    if b == 0 {
        return 0, fmt.Errorf("division by zero")
    }
    return a / b, nil
}

func main() {
    result, err := divide(10, 0)
    if err != nil {
        fmt.Println(err)
    } else {
        fmt.Println(result)
    }
}

Comment prévenir

  • Vérifier toujours les arguments passés à une fonction.
  • Utiliser des fonctions d'aide pour éviter les erreurs liées aux arguments invalides.
  • Faire attention aux conditions spéciales et aux valeurs edge.

Erreur N6 : Timeout

Le problème

Un timeout se produit lorsque une opération prend trop de temps. Cela peut entraîner un programme qui s'arrête subitement et générer des erreurs difficiles à déboguer.

package main

import (
    "context"
    "fmt"
    "time"
)

func worker(ctx context.Context) {
    select {
    case <-ctx.Done():
        fmt.Println("Worker stopped", ctx.Err())
    case <-time.After(5 * time.Second):
        fmt.Println("Work done")
    }
}

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
    defer cancel()

    worker(ctx)
}

Pourquoi c'est une erreur

Un timeout peut entraîner des comportements imprévisibles ou non déterministes. Elle peut également rendre l'application moins robuste et plus susceptible de plantage.

La solution

Pour éviter un timeout, vous devez utiliser les mécanismes de temps limité fournis par Go, comme context.WithTimeout ou time.After. Vous pouvez également utiliser des instructions select pour gérer les cas où une opération prend trop de temps.

package main

import (
    "context"
    "fmt"
    "time"
)

func worker(ctx context.Context) {
    select {
    case <-ctx.Done():
        fmt.Println("Worker stopped", ctx.Err())
    case <-time.After(5 * time.Second):
        fmt.Println("Work done")
    }
}

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
    defer cancel()

    worker(ctx)
}

Comment prévenir

  • Utiliser des mécanismes de temps limité comme context.WithTimeout ou time.After.
  • Faire attention aux opérations qui pourraient prendre beaucoup de temps.
  • Éviter l'utilisation excessive de boucles et structures conditionnelles qui pourraient entraîner des conditions critiques.

Erreur N7 : Concurrency Bugs

Le problème

Une concurrency bug se produit lorsque deux ou plusieurs goroutines s'attendent mutuellement sur des conditions qui ne peuvent jamais être remplies. Cela peut entraîner une application qui se bloque indéfiniment.

package main

import (
    "fmt"
    "time"
)

func worker(id int, ch chan bool) {
    for {
        select {
        case <-ch:
            fmt.Println("Worker", id, "is working")
            time.Sleep(time.Second)
            ch <- true // Deadlock here
        }
    }
}

func main() {
    ch := make(chan bool)

    for i := 1; i <= 5; i++ {
        go worker(i, ch)
    }

    <-ch // This line is never reached, causing a deadlock
}

Pourquoi c'est une erreur

Une concurrency bug peut entraîner une perte de performance significative car il empêche les autres goroutines d'avancer. En outre, cela rend l'application imprévisible et difficile à déboguer.

La solution

Pour éviter un concurrency bug, vous devez vous assurer que chaque case case dans un select est couverte par une autre case ou un default. Dans ce cas, nous devons ajouter une case default pour permettre au programme de continuer même sans reçu.

package main

import (
    "fmt"
    "time"
)

func worker(id int, ch chan bool) {
    for {
        select {
        case <-ch:
            fmt.Println("Worker", id, "is working")
            time.Sleep(time.Second)
            ch <- true // Fixed deadlock here
        default:
            fmt.Println("Worker", id, "waiting for work")
        }
    }
}

func main() {
    ch := make(chan bool)

    for i := 1; i <= 5; i++ {
        go worker(i, ch)
    }

    for i := 0; i < 2; i++ {
        ch <- true
    }
}

Comment prévenir

  • Utiliser des canaux avec une taille définie pour éviter les écrabouillages.
  • Assurer que chaque case case dans un select est couverte par une autre case ou un default.
  • Éviter l'utilisation excessive de canaux bloquants dans la même fonction.

Erreur N8 : Resource Leak

Le problème

Un resource leak se produit lorsque vous allouez des ressources qui ne peuvent pas être libérées. Cela peut entraîner une perte progressive des ressources disponibles pour l'application, ce qui peut finir par entraîner un plantage.

package main

import (
    "time"
)

func leak() {
    for i := 0; i < 1000000; i++ {
        go func() {
            file, err := os.Open("file.txt")
            if err != nil {
                fmt.Println(err)
            }
            defer file.Close()
            time.Sleep(time.Second) // Do something with file
        }()
    }
}

func main() {
    leak()
}

Pourquoi c'est une erreur

Un resource leak peut entraîner une perte progressive des ressources disponibles pour l'application. Cela peut finir par entraîner un plantage et rendre l'application moins robuste.

La solution

Pour éviter un resource leak, vous devez vous assurer que toutes les allocations de ressources sont correctement libérées. Vous pouvez utiliser des instructions defer pour effectuer cette libération.

package main

import (
    "os"
    "time"
)

func leak() {
    for i := 0; i < 1000000; i++ {
        go func() {
            file, err := os.Open("file.txt")
            if err != nil {
                fmt.Println(err)
            }
            defer file.Close()
            time.Sleep(time.Second) // Do something with file
        }()
    }
}

func main() {
    leak()
}

Comment prévenir

  • Libérer explicitement la ressource allouée avec defer.
  • Utiliser des outils comme pprof pour détecter les fuites de ressources.
  • Éviter l'utilisation excessive de structures et de canaux sans libération.

Un projet tech a lancer ?

Besoin d'un accompagnement ? Decrivez votre projet pour des recommandations.

Recevoir des conseils

Questions frequentes

Quelle est la différence entre panic et error en Go ?
En Go, 'panic' indique une situation grave qui ne devrait pas arriver et peut entraîner la panne de l'exécution du programme. L'erreur ('error') est utilisé pour les conditions normales d'échec que le programme peut gérer.
Comment éviter les fuites mémoire en Go ?
Pour éviter les fuites mémoire, utilisez des ressources avec des gestionnaires de la mémoire tels que 'defer' pour s'assurer qu'une fonction est appelée à la fin d'un bloc, et libérez explicitement les ressources non nécessaires.
Quelle est une bonne pratique pour structurer un projet Go ?
Une bonne pratique consiste à organiser le code en packages logiques, à utiliser un système de gestion de dépendances comme 'go mod', et à suivre les lignes directrices du style de codage Go officiel (gofmt).

Pages liees

Chaque semaine, le meilleur de la tech francaise

Tendances, salaires, outils et opportunites — directement dans votre boite mail.

Gratuit. Desabonnement en un clic. Pas de spam.