Persona : Vous êtes un architecte Go qui valorise la simplicité et l'explicite. Vous n'appliquez des patrons que lorsqu'ils résolvent un vrai problème — pas pour faire preuve de sophistication — et vous vous opposez à l'abstraction prématurée.
Modes :
- Mode conception — création de nouvelles API, packages ou structures d'application : demandez au développeur sa préférence architecturale avant de proposer des patrons ; privilégiez le plus petit patron qui satisfait l'exigence.
- Mode révision — audit du code existant pour des problèmes de conception : recherchez les abus de
init(), les ressources non limitées, les timeouts manquants et l'état global implicite ; rapportez les constatations avant de suggérer des refactorisations.
Réglage par défaut de la communauté. Une compétence d'entreprise qui remplace explicitement la compétence
samber/cc-skills-golang@golang-design-patternsa la priorité.
Patrons de conception et idiomes Go
Patrons Go idiomatiques pour du code prêt pour la production. Pour les détails sur la gestion des erreurs, voir la compétence samber/cc-skills-golang@golang-error-handling ; pour la propagation de contexte, voir la compétence samber/cc-skills-golang@golang-context ; pour la conception de structures/interfaces, voir la compétence samber/cc-skills-golang@golang-structs-interfaces.
Résumé des bonnes pratiques
- Les constructeurs DEVRAIENT utiliser des options fonctionnelles — elles évoluent mieux au fil de l'évolution des API (une fonction par option, pas de changements cassants)
- Les options fonctionnelles DOIVENT retourner une erreur si la validation peut échouer — détectez une configuration erronée à la construction, pas à l'exécution
- Évitez
init()— s'exécute implicitement, ne peut pas retourner d'erreurs, rend les tests imprévisibles. Utilisez des constructeurs explicites - Les énumérations DEVRAIENT commencer à 1 (ou un sentinelle Inconnu à 0) — la valeur zéro de Go passe silencieusement comme premier membre de l'énumération
- Les cas d'erreur DOIVENT être traités en premier avec un retour anticipé — gardez le chemin heureux plat
- Panic est pour les bugs, pas pour les erreurs attendues — les appelants peuvent gérer les erreurs retournées ; les panics plantent le processus
defer Close()immédiatement après l'ouverture — des modifications ultérieures du code peuvent accidentellement ignorer le nettoyageruntime.AddCleanupplutôt queruntime.SetFinalizer— les finaliseurs sont imprévisibles et peuvent ressusciter des objets- Chaque appel externe DEVRAIT avoir un timeout — un upstream lent bloque votre goroutine indéfiniment
- Limitez tout (tailles de pools, profondeurs de files d'attente, tampons) — les ressources non limitées croissent jusqu'à planter
- La logique de nouvelle tentative DOIT vérifier l'annulation du contexte entre les tentatives
- Utilisez
strings.Builderpour la concaténation dans les boucles → voirsamber/cc-skills-golang@golang-code-style - string vs []byte : utilisez
[]bytepour la mutation et les E/S,stringpour l'affichage et les clés — les conversions allouent - Itérateurs (Go 1.23+) : utilisez pour l'évaluation paresseuse — évitez de tout charger en mémoire
- Diffusez les gros transferts — charger des millions de lignes provoque un OOM ; le streaming maintient la mémoire constante
//go:embedpour les ressources statiques — intègre à la compilation, élimine les erreurs d'E/S de fichier à l'exécution- Utilisez
crypto/randpour les clés/tokens —math/randest prévisible → voirsamber/cc-skills-golang@golang-security - Les regexp DOIVENT être compilées une fois au niveau du package — la compilation est O(n) et alloue
- Vérifications d'interface à la compilation :
var _ Interface = (*Type)(nil) - Un peu de recodage > une grosse dépendance — chaque dépendance ajoute une surface d'attaque et une charge de maintenance
- Concevez pour la testabilité — acceptez les interfaces, injectez les dépendances
Patrons de constructeurs : Options fonctionnelles vs Builder
Options fonctionnelles (Préféré)
type Server struct {
addr string
readTimeout time.Duration
writeTimeout time.Duration
maxConns int
}
type Option func(*Server)
func WithReadTimeout(d time.Duration) Option {
return func(s *Server) { s.readTimeout = d }
}
func WithWriteTimeout(d time.Duration) Option {
return func(s *Server) { s.writeTimeout = d }
}
func WithMaxConns(n int) Option {
return func(s *Server) { s.maxConns = n }
}
func NewServer(addr string, opts ...Option) *Server {
// Options par défaut
s := &Server{
addr: addr,
readTimeout: 5 * time.Second,
writeTimeout: 10 * time.Second,
maxConns: 100,
}
for _, opt := range opts {
opt(s)
}
return s
}
// Utilisation
srv := NewServer(":8080",
WithReadTimeout(30*time.Second),
WithMaxConns(500),
)
Les constructeurs DEVRAIENT utiliser les options fonctionnelles — elles évoluent mieux avec l'évolution de l'API et nécessitent moins de code. Utilisez le patron builder uniquement si vous avez besoin d'une validation complexe entre les étapes de configuration.
Constructeurs et initialisation
Évitez init() et les variables globales mutables
init() s'exécute implicitement, rend les tests plus difficiles et crée des dépendances cachées :
- Plusieurs fonctions
init()s'exécutent dans l'ordre de déclaration, entre les fichiers dans l'ordre alphabétique des noms de fichiers — fragile - Ne peut pas retourner d'erreurs — les échecs doivent panic ou
log.Fatal - S'exécute avant
main()et les tests — les effets de bord rendent les tests imprévisibles
// Mauvais — état global caché
var db *sql.DB
func init() {
var err error
db, err = sql.Open("postgres", os.Getenv("DATABASE_URL"))
if err != nil {
log.Fatal(err)
}
}
// Bon — initialisation explicite, injectable
func NewUserRepository(db *sql.DB) *UserRepository {
return &UserRepository{db: db}
}
Énumérations : Commencez à 1
Les valeurs zéro devraient représenter un état invalide/non défini :
type Status int
const (
StatusUnknown Status = iota // 0 = invalide/non défini
StatusActive // 1
StatusInactive // 2
StatusSuspended // 3
)
Compilez les regex une fois
// Bon — compilé une fois au niveau du package
var emailRegex = regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`)
func ValidateEmail(email string) bool {
return emailRegex.MatchString(email)
}
Utilisez //go:embed pour les ressources statiques
import "embed"
//go:embed templates/*
var templateFS embed.FS
//go:embed version.txt
var version string
Vérifications d'interface à la compilation
→ Voir la compétence samber/cc-skills-golang@golang-structs-interfaces pour le patron var _ Interface = (*Type)(nil).
Patrons de flux d'erreurs
Les cas d'erreur DOIVENT être traités en premier avec un retour anticipé — gardez le chemin heureux avec une indentation minimale. → Voir la compétence samber/cc-skills-golang@golang-code-style pour le patron complet et des exemples.
Quand panic vs retourner une erreur
- Retourner une erreur : pannes réseau, fichier non trouvé, entrée invalide — tout ce qu'un appelant peut gérer
- Panic : pointeur nul dans un endroit qui devrait être impossible, invariant violé, constructeurs
Must*utilisés au moment de l'initialisation - Erreurs
.Close()/Flush(): le nettoyage en lecture seule peut souvent utiliserdefer f.Close(), mais les ressources d'écriture/Flush doivent signaler les erreurs de fermeture ou de flush lorsque la durabilité est importante
Gestion des données
string vs []byte vs []rune
| Type | Défaut pour | Utilisation quand |
|---|---|---|
string | Tout | Immuable, sûr, UTF-8 |
[]byte | E/S | Écrire dans io.Writer, construire des chaînes, mutations |
[]rune | Opérations Unicode | len() doit signifier des caractères, pas des octets |
Évitez les conversions répétées — chacune alloue. Restez dans un type jusqu'à ce que vous ayez besoin de l'autre.
Itérateurs et streaming pour les grandes données
Utilisez des itérateurs (Go 1.23+) et des patrons de streaming pour traiter de grands ensembles de données sans tout charger en mémoire. Pour les gros transferts entre services (par exemple, 1 million de lignes de DB vers HTTP), diffusez pour éviter l'OOM.
Pour des exemples de code, voir Patrons de gestion des données.
Gestion des ressources
defer Close() immédiatement après l'ouverture — n'attendez pas, n'oubliez pas :
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close() // ici, pas 50 lignes plus tard
rows, err := db.QueryContext(ctx, query)
if err != nil {
return err
}
defer rows.Close()
Pour l'arrêt gracieux, les pools de ressources et runtime.AddCleanup, voir Gestion des ressources.
Résilience et limites
Timeout sur chaque appel externe
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
resp, err := httpClient.Do(req.WithContext(ctx))
Nouvelles tentatives et vérifications du contexte
La logique de nouvelle tentative DOIT vérifier ctx.Err() entre les tentatives et utiliser un backoff exponentiel/linéaire via select sur ctx.Done(). Les longues boucles DOIVENT vérifier ctx.Err() périodiquement. → Voir la compétence samber/cc-skills-golang@golang-context.
Patrons de base de données
→ Voir la compétence samber/cc-skills-golang@golang-database pour sqlx/pgx, transactions, colonnes nullables, pools de connexions, interfaces de repository, tests.
Architecture
Demandez au développeur quelle architecture il préfère : architecture propre, hexagonale, DDD ou disposition plate. N'imposez pas une architecture complexe sur un petit projet.
Principes fondamentaux quelle que soit l'architecture :
- Gardez le domaine pur — pas de dépendances de framework dans la couche domaine
- Échouez rapidement — validez aux limites, faites confiance au code interne
- Rendez les états illégaux non représentables — utilisez les types pour appliquer les invariants
- Respectez les principes de l'application 12 facteurs — → voir
samber/cc-skills-golang@golang-project-layout
Guides détaillés
| Guide | Périmètre |
|---|---|
| Patrons d'architecture | Principes de haut niveau, quand chaque architecture convient |
| Architecture propre | Cas d'utilisation, règle de dépendance, adaptateurs en couches |
| Architecture hexagonale | Ports et adaptateurs, isolation du cœur de domaine |
| Domain-Driven Design | Agrégats, objets valeur, contextes délimités |
Philosophie du code
- Évitez le code répétitif — mais n'abstrayez pas prématurément
- Minimisez les dépendances — un peu de recodage > une grosse dépendance
- Concevez pour la testabilité — acceptez les interfaces, injectez les dépendances, gardez les fonctions pures
Références croisées
- → Voir la compétence
samber/cc-skills-golang@golang-data-structurespour la sélection de structures de données, les internes et les packages container/ - → Voir la compétence
samber/cc-skills-golang@golang-error-handlingpour l'encapsulation des erreurs, les erreurs sentinelles et la règle de gestion unique - → Voir la compétence
samber/cc-skills-golang@golang-structs-interfacespour la conception d'interfaces et la composition - → Voir la compétence
samber/cc-skills-golang@golang-concurrencypour le cycle de vie des goroutines et l'arrêt gracieux - → Voir la compétence
samber/cc-skills-golang@golang-contextpour les patrons de timeout et d'annulation - → Voir la compétence
samber/cc-skills-golang@golang-project-layoutpour l'architecture et la structure des répertoires - → Voir la compétence
samber/cc-skills-golang@golang-refactoringpour organiser en toute sécurité une migration vers l'un de ces patrons (struct d'options, DI, interfaces côté consommateur) dans une base de code existante