Simple Enough Blog logo
  • Home 
  • Projets 
  • Tags 

  •  Langage
    • English
    • Français
  1.   Blogs
  1. Accueil
  2. Blogs
  3. Comment gérer les paramètres optionnels en Go.

Comment gérer les paramètres optionnels en Go.

Posté le 15 novembre 2025 • 7 min de lecture • 1 361 mots
Golang   Thibault   Programming  
Golang   Thibault   Programming  
Partager via
Simple Enough Blog
Lien copié dans le presse-papier

Go ne propose pas de paramètres optionnels ni de surcharge de fonctions. Voici les patterns idiomatiques, leurs avantages, leurs limites.

Sur cette page
I. Comment gérer les paramètres optionnels en Go   II. Pourquoi est-ce un sujet important ?   III. Tableau de synthèse : comment choisir le bon pattern ?   1. Variantes de fonctions (pour les cas simples)   2. La structure d’options : le plus souvent utilisé   Pourquoi est-ce efficace ?   3. Functional Options (...Option)   4. Gérer les valeurs tri-état grâce aux pointeurs   5. Les pièges du nil : attention aux interfaces   Pourquoi un nil d’interface n’est pas toujours nil ?   À retenir   IV. Tableau final : comment choisir rapidement ?   Conclusion   🔗 Liens utiles  
Comment gérer les paramètres optionnels en Go.
Photo par Thibault Deheurles

I. Comment gérer les paramètres optionnels en Go  

Dans la plupart des langages modernes, il est possible de définir des valeurs par défaut dans les fonctions ou de les surcharger pour couvrir plusieurs cas d’usage.
Go, lui, ne propose ni paramètres optionnels, ni surcharge, ni valeurs par défaut dans les signatures de fonction.
Pourtant, les besoins restent les mêmes : créer des APIs lisibles, stables et capables d’évoluer sans casser les utilisateurs.

Cet article présente toutes les approches idiomatiques pour gérer les paramètres optionnels en Go, quand les utiliser, comment éviter les pièges et comment concevoir une API propre — avec des tableaux de décision et des exemples concrets inspirés de projets DevOps, Kubernetes ou AWS.


II. Pourquoi est-ce un sujet important ?  

Sans paramètres optionnels natifs, un code Go naïf devient vite illisible :

NewAPIClient("https://service", token, 0, 0, nil, false)

Ce genre d’appel force les développeurs à mémoriser la signification de chaque argument, crée des ambiguïtés, et rend l’évolution de l’API difficile. C’est pour cette raison que toute l’écosystème Go a convergé vers quelques patterns simples et efficaces pour représenter des options de manière claire.

L’objectif n’est pas seulement d’avoir du code fonctionnel, mais une API:

  • facile à lire,
  • simple à étendre,
  • compatible dans le temps,
  • idiomatique Go.

III. Tableau de synthèse : comment choisir le bon pattern ?  

Ce tableau présente les recommandations pratiques selon les cas courants rencontrés en Go :

SituationPattern recommandé
1–2 paramètres optionnels simplesVariantes de fonctions
Plusieurs options, API standardStruct d’options
Beaucoup d’options, API publique ou évolutiveFunctional Options (...Option)
Zéro-valeur ambiguë (0, false, "")Functional Options ou pointeurs
Besoin tri-état (défaut / vrai / faux)Pointeurs sur scalaires (*bool, *int)
Ressource facultative (ex : TLS)Pointeur accepté à nil

Ces approches permettent d’obtenir du code clair tout en évitant l’explosion combinatoire des signatures.


1. Variantes de fonctions (pour les cas simples)  

Lorsque l’on n’a qu’un paramètre optionnel ou deux cas d’usage bien distincts, la forme la plus simple reste de créer deux fonctions explicites.

func NewServer(addr string) (*Server, error) { /* ... */ }

func NewServerTLS(addr string, tlsCfg *tls.Config) (*Server, error) { /* ... */ }

Cette approche est très lisible et ne nécessite aucune abstraction supplémentaire. La fonction elle-même encode l’option (NewServer vs NewServerTLS). Elle devient cependant limitée si les options se multiplient : ajouter plus de variantes entraîne rapidement une complexité inutile.

2. La structure d’options : le plus souvent utilisé  

La méthode la plus idiomatique en Go consiste à regrouper toutes les options dans une structure passée à la fonction. Chaque champ utilise sa zéro-valeur comme valeur par défaut, ce qui rend l’API simple et évolutive.

Exemple


type ClientOptions struct {
    Timeout     time.Duration // 0 = défaut
    Retries     int           // 0 = défaut
    Logger      *log.Logger   // nil = log.Default()
    InsecureTLS bool          // false = par défaut
}

func NewAPIClient(baseURL, token string, opts ClientOptions) (*APIClient, error) {
    if opts.Timeout == 0 {
        opts.Timeout = 5 * time.Second
    }
    if opts.Retries == 0 {
        opts.Retries = 3
    }
    if opts.Logger == nil {
        opts.Logger = log.Default()
    }

    return &APIClient{/* ... */}, nil
}

Usage

client, _ := NewAPIClient("https://api.service", token, ClientOptions{
    Timeout: 2 * time.Second,
    InsecureTLS: true,
})

Pourquoi est-ce efficace ?  

Cette approche est lisible, simple, intuitive pour l’utilisateur, compatible avec les outils d’auto-complétion, et elle supporte parfaitement l’évolution de l’API : on peut ajouter un champ dans la struct sans casser les appels existants.

La seule limite apparaît lorsque la zéro-valeur a une signification métier. Par exemple, Timeout = 0 pourrait vouloir dire “pas de timeout” — et non “utilise la valeur par défaut”. Dans ce cas, on utilise un autre pattern.


3. Functional Options (...Option)  

Ce pattern est utilisé par Kubernetes, Docker, Prometheus, Terraform, etc. Il consiste à transformer les options en fonctions configuratrices, ce qui permet d’obtenir une API très lisible et complètement extensible.

Exemple complet


type Option func(*config)

type config struct {
    timeout     time.Duration
    retries     int
    logger      *log.Logger
    insecureTLS bool
}

func WithTimeout(d time.Duration) Option {
    return func(c *config) { c.timeout = d }
}

func WithRetries(n int) Option {
    return func(c *config) { c.retries = n }
}

func WithLogger(l *log.Logger) Option {
    return func(c *config) { c.logger = l }
}

func WithInsecureTLS() Option {
    return func(c *config) { c.insecureTLS = true }
}

func NewAPIClient(baseURL, token string, opts ...Option) (*APIClient, error) {
    cfg := config{
        timeout: 5 * time.Second,
        retries: 3,
        logger:  log.Default(),
    }

    for _, o := range opts {
        o(&cfg)
    }

    return &APIClient{/* ... */}, nil
}

Usage


client, _ := NewAPIClient(
    "https://api.xyz",
    token,
    WithTimeout(1*time.Second),
    WithRetries(4),
    WithInsecureTLS(),
)

Ce pattern est idéal pour les APIs publiques, exposées et destinées à durer longtemps. Il évite les ambiguïtés tout en restant élégant à l’appel. (WithTimeout(…), WithLogger(…)


4. Gérer les valeurs tri-état grâce aux pointeurs  

Certaines options nécessitent de distinguer 3 états:

  • non spécifié,
  • explicitement vrai,
  • explicitement faux.

Dans ce cas, les structures d’options et les functional options doivent utiliser des pointeurs sur scalaires.


type ClientOptions struct {
    EnableCache *bool // nil = défaut, true/false = explicite
}

func boolPtr(b bool) *bool { return &b }

Usage


NewAPIClient("https://api", token, ClientOptions{
    EnableCache: boolPtr(false),
})

C’est particulièrement utile dans les projets DevOps, Kubernetes et AWS où les paramètres booléens doivent parfois représenter un état complexe.


5. Les pièges du nil : attention aux interfaces  

Accepter nil pour des pointeurs est normal en Go. En revanche, avec les interfaces, un “typed nil” peut provoquer des comportements inattendus.

Pourquoi un nil d’interface n’est pas toujours nil ?  

Une interface Go contient deux informations :

  • son type dynamique
  • sa valeur dynamique

Elle n’est réellement nil que si les deux sont nil :

dynamicType = nil, dynamicValue = nil

Le piège survient lorsqu’on stocke dans une interface un pointeur nil, par exemple :

var buf *bytes.Buffer = nil
var w io.Writer = buf

Ici, w vaut :

(dynamicType = *bytes.Buffer, dynamicValue = nil)

L’interface n’est pas nil, puisqu’elle contient un type.

fmt.Println(w == nil) // false

Conséquence : risque de panic

Comme l’interface n’est pas considérée nil, du code comme :

if w != nil {
    w.Write([]byte("hello"))
}

donnera un panic :

panic: runtime error: invalid memory address or nil pointer dereference

Parce que Go tente d’appeler la méthode Write sur une valeur interne nil.

Ce qu’il ne faut pas faire

  • Utiliser nil pour une interface comme signal métier

  • Se reposer sur w == nil pour tester si un writer / logger / output existe

La bonne pratique

  • Toujours fournir une valeur par défaut sûre lorsque le paramètre interface n’est pas fourni :
func Do(w io.Writer) {
  if w == nil {
    w = io.Discard // writer neutre et sans danger
  }
w.Write([]byte("..."))
}

Ainsi, on évite :

  • les typed nil
  • les tests w == nil trompeurs
  • les panics à l’exécution

À retenir  

En Go :

  • nil pour un pointeur est autorisé
  • nil pour une interface peut être non-nil et causer un panic
  • Toujours privilégier une valeur neutre (io.Discard, logger par défaut, etc.) plutôt que de compter sur des tests de nil avec les interfaces.

IV. Tableau final : comment choisir rapidement ?  

Nombre d’optionsAmbiguïté zéro-valeurAPI publique ?Pattern recommandé
0–2 optionsnonnonVariantes de fonctions
3–10nonnonStruct d’options
3–∞ouinonPointeurs dans struct
3–∞oui ou nonouiFunctional Options
Tri-étatouioui ou nonPointeurs sur scalaires

Conclusion  

Même si Go ne propose pas de paramètres optionnels, il offre un ensemble de patterns élégants et efficaces pour modéliser des APIs flexibles et robustes.

La clé est de choisir le bon outil selon la situation :

  • privilégier les structs d’options pour la simplicité,
  • utiliser les functional options pour les APIs évolutives,
  • employer les pointeurs pour les cas tri-état,
  • rester attentif aux pièges du nil sur les interfaces,
  • et ne pas hésiter à créer des variantes de fonctions lorsque le cas d’usage est simple.

En appliquant ces principes, votre code devient non seulement plus lisible, mais aussi plus stable, plus maintenable et plus idiomatique Go — ce qui facilite la collaboration et les évolutions à long terme.


🔗 Liens utiles  

  • Effective Go (chapitre sur les paramètres)
  • Dave Cheney — Functional Options Pattern
  • Kubernetes codebase (exemples de functional options)
  • Go code review comments (zéro-valeur)
 Karpenter : l'autoscaler intelligent pour EKS
Landing Page : définition, types et bonnes pratiques pour booster vos conversions 
  • I. Comment gérer les paramètres optionnels en Go  
  • II. Pourquoi est-ce un sujet important ?  
  • III. Tableau de synthèse : comment choisir le bon pattern ?  
  • 1. Variantes de fonctions (pour les cas simples)  
  • 2. La structure d’options : le plus souvent utilisé  
  • 3. Functional Options (...Option)  
  • 4. Gérer les valeurs tri-état grâce aux pointeurs  
  • 5. Les pièges du nil : attention aux interfaces  
  • IV. Tableau final : comment choisir rapidement ?  
  • Conclusion  
  • 🔗 Liens utiles  
Suivez-nous

Nous travaillons avec vous !

   
Copyright © 2026 Simple Enough Blog Tous droits réservés. | Propulsé par Hinode.
Simple Enough Blog
Code copié dans le presse-papier