Communitygithub.com

golang-patterns

Modèles Go idiomatiques, bonnes pratiques et conventions pour construire des applications Go robustes, efficaces et maintenables. À utiliser lors de l'écriture ou de la relecture de code Go, en cas de doute sur la structure idiomatique ou les conventions.

Qu'est-ce que golang-patterns ?

Cette compétence fournit un guide complet des modèles de conception et des bonnes pratiques de programmation en Go. Elle est conçue pour être activée lorsque vous écrivez, révisez ou refactorisez du code Go, ou lors de la conception de packages et de modules. Basée sur le fichier SKILL.md, elle couvre les principes fondamentaux comme la simplicité, l'utilité de la valeur zéro, et les interfaces vs. les structures concrètes. Elle détaille les modèles de gestion d'erreurs (wrap, types personnalisés, `errors.Is`/`As`), la concurrence (worker pools, context, shutdown gracieux, errgroup), la conception d'interfaces (petites et ciblées, définies là où elles sont utilisées), ainsi que l'organisation des packages et les astuces de performance. Le but est d'aider tout développeur utilisant des agents d'IA comme Claude Code, Cursor ou Codex à produire un code Go idiomatique, maintenable et performant.

Compatible avec~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/affaan-m/ECC/tree/main/skills/golang-patterns

Demander à votre IA préférée

Ouvre une nouvelle conversation avec cette compétence d'agent déjà préchargée.

Documentation

Modèles de développement Go

Modèles Go idiomatiques et bonnes pratiques pour construire des applications robustes, efficaces et maintenables.

Quand activer

  • Écrire du nouveau code Go
  • Relire du code Go
  • Refactoriser du code Go existant
  • Concevoir des packages/modules Go

Principes Fondamentaux

1. Simplicité et Clarté

Go favorise la simplicité au détriment de l'astuce. Le code doit être évident et facile à lire.

// Bien : Clair et direct
func GetUser(id string) (*User, error) {
    user, err := db.FindUser(id)
    if err != nil {
        return nil, fmt.Errorf("get user %s: %w", id, err)
    }
    return user, nil
}

// Mal : Trop astucieux
func GetUser(id string) (*User, error) {
    return func() (*User, error) {
        if u, e := db.FindUser(id); e == nil {
            return u, nil
        } else {
            return nil, e
        }
    }()
}

2. Rendre la valeur zéro utile

Concevez les types pour que leur valeur zéro soit immédiatement utilisable sans initialisation.

// Bien : La valeur zéro est utile
type Counter struct {
    mu    sync.Mutex
    count int // la valeur zéro est 0, prête à l'emploi
}

func (c *Counter) Inc() {
    c.mu.Lock()
    c.count++
    c.mu.Unlock()
}

// Bien : bytes.Buffer fonctionne avec la valeur zéro
var buf bytes.Buffer
buf.WriteString("hello")

// Mal : Nécessite une initialisation
type BadCounter struct {
    counts map[string]int // une map nil paniquera
}

3. Accepter des interfaces, retourner des structures

Les fonctions doivent accepter des paramètres d'interface et retourner des types concrets.

// Bien : Accepte une interface, retourne un type concret
func ProcessData(r io.Reader) (*Result, error) {
    data, err := io.ReadAll(r)
    if err != nil {
        return nil, err
    }
    return &Result{Data: data}, nil
}

// Mal : Retourne une interface (cache inutilement les détails d'implémentation)
func ProcessData(r io.Reader) (io.Reader, error) {
    // ...
}

Modèles de Gestion d'Erreurs

Encapsulation d'erreurs avec contexte

// Bien : Encapsule les erreurs avec du contexte
func LoadConfig(path string) (*Config, error) {
    data, err := os.ReadFile(path)
    if err != nil {
        return nil, fmt.Errorf("load config %s: %w", path, err)
    }

    var cfg Config
    if err := json.Unmarshal(data, &cfg); err != nil {
        return nil, fmt.Errorf("parse config %s: %w", path, err)
    }

    return &cfg, nil
}

Types d'erreur personnalisés

// Définir des erreurs spécifiques au domaine
type ValidationError struct {
    Field   string
    Message string
}

func (e *ValidationError) Error() string {
    return fmt.Sprintf("validation failed on %s: %s", e.Field, e.Message)
}

// Erreurs sentinelles pour les cas courants
var (
    ErrNotFound     = errors.New("resource not found")
    ErrUnauthorized = errors.New("unauthorized")
    ErrInvalidInput = errors.New("invalid input")
)

Vérification des erreurs avec errors.Is et errors.As

func HandleError(err error) {
    // Vérifier une erreur spécifique
    if errors.Is(err, sql.ErrNoRows) {
        log.Println("No records found")
        return
    }

    // Vérifier le type d'erreur
    var validationErr *ValidationError
    if errors.As(err, &validationErr) {
        log.Printf("Validation error on field %s: %s",
            validationErr.Field, validationErr.Message)
        return
    }

    // Erreur inconnue
    log.Printf("Unexpected error: %v", err)
}

Ne jamais ignorer les erreurs

// Mal : Ignorer l'erreur avec l'identifiant vide
result, _ := doSomething()

// Bien : Gérer ou documenter explicitement pourquoi il est sûr d'ignorer
result, err := doSomething()
if err != nil {
    return err
}

// Acceptable : Quand l'erreur n'a vraiment pas d'importance (rare)
_ = writer.Close() // Nettoyage au mieux, l'erreur est journalisée ailleurs

Modèles de Concurrence

Pool de travailleurs

func WorkerPool(jobs <-chan Job, results chan<- Result, numWorkers int) {
    var wg sync.WaitGroup

    for i := 0; i < numWorkers; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            for job := range jobs {
                results <- process(job)
            }
        }()
    }

    wg.Wait()
    close(results)
}

Contexte pour annulation et délais d'attente

func FetchWithTimeout(ctx context.Context, url string) ([]byte, error) {
    ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
    defer cancel()

    req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
    if err != nil {
        return nil, fmt.Errorf("create request: %w", err)
    }

    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        return nil, fmt.Errorf("fetch %s: %w", url, err)
    }
    defer resp.Body.Close()

    return io.ReadAll(resp.Body)
}

Arrêt Gracieux

func GracefulShutdown(server *http.Server) {
    quit := make(chan os.Signal, 1)
    signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)

    <-quit
    log.Println("Shutting down server...")

    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    defer cancel()

    if err := server.Shutdown(ctx); err != nil {
        log.Fatalf("Server forced to shutdown: %v", err)
    }

    log.Println("Server exited")
}

errgroup pour goroutines coordonnées

import "golang.org/x/sync/errgroup"

func FetchAll(ctx context.Context, urls []string) ([][]byte, error) {
    g, ctx := errgroup.WithContext(ctx)
    results := make([][]byte, len(urls))

    for i, url := range urls {
        i, url := i, url // Capturer les variables de boucle
        g.Go(func() error {
            data, err := FetchWithTimeout(ctx, url)
            if err != nil {
                return err
            }
            results[i] = data
            return nil
        })
    }

    if err := g.Wait(); err != nil {
        return nil, err
    }
    return results, nil
}

Éviter les fuites de goroutines

// Mal : Fuite de goroutine si le contexte est annulé
func leakyFetch(ctx context.Context, url string) <-chan []byte {
    ch := make(chan []byte)
    go func() {
        data, _ := fetch(url)
        ch <- data // Bloque indéfiniment si aucun récepteur
    }()
    return ch
}

// Bien : Gère correctement l'annulation
func safeFetch(ctx context.Context, url string) <-chan []byte {
    ch := make(chan []byte, 1) // Channel tamponné
    go func() {
        data, err := fetch(url)
        if err != nil {
            return
        }
        select {
        case ch <- data:
        case <-ctx.Done():
        }
    }()
    return ch
}

Conception d'Interfaces

Interfaces petites et ciblées

// Bien : Interfaces à méthode unique
type Reader interface {
    Read(p []byte) (n int, err error)
}

type Writer interface {
    Write(p []byte) (n int, err error)
}

type Closer interface {
    Close() error
}

// Composer les interfaces selon les besoins
type ReadWriteCloser interface {
    Reader
    Writer
    Closer
}

Définir les interfaces là où elles sont utilisées

// Dans le package consommateur, pas le fournisseur
package service

// UserStore définit ce dont ce service a besoin
type UserStore interface {
    GetUser(id string) (*User, error)
    SaveUser(user *User) error
}

type Service struct {
    store UserStore
}

// L'implémentation concrète peut être dans un autre package
// Elle n'a pas besoin de connaître cette interface

Comportement optionnel avec les assertions de type

type Flusher interface {
    Flush() error
}

func WriteAndFlush(w io.Writer, data []byte) error {
    if _, err := w.Write(data); err != nil {
        return err
    }

    // Vider si supporté
    if f, ok := w.(Flusher); ok {
        return f.Flush()
    }
    return nil
}

Organisation des Packages

Structure de projet standard

myproject/
├── cmd/
│   └── myapp/
│       └── main.go           # Point d'entrée
├── internal/
│   ├── handler/              # Gestionnaires HTTP
│   ├── service/              # Logique métier
│   ├── repository/           # Accès aux données
│   └── config/               # Configuration
├── pkg/
│   └── client/               # Client API public
├── api/
│   └── v1/                   # Définitions API (proto, OpenAPI)
├── testdata/                 # Données de test
├── go.mod
├── go.sum
└── Makefile

Nommage des packages

// Bien : Court, minuscules, pas de tirets bas
package http
package json
package user

// Mal : Verbeux, casse mélangée ou redondant
package httpHandler
package json_parser
package userService // Suffixe 'Service' redondant

Éviter l'état au niveau du package

// Mal : État mutable global
var db *sql.DB

func init() {
    db, _ = sql.Open("postgres", os.Getenv("DATABASE_URL"))
}

// Bien : Injection de dépendances
type Server struct {
    db *sql.DB
}

func NewServer(db *sql.DB) *Server {
    return &Server{db: db}
}

Conception des Structures

Modèle d'options fonctionnelles

type Server struct {
    addr    string
    timeout time.Duration
    logger  *log.Logger
}

type Option func(*Server)

func WithTimeout(d time.Duration) Option {
    return func(s *Server) {
        s.timeout = d
    }
}

func WithLogger(l *log.Logger) Option {
    return func(s *Server) {
        s.logger = l
    }
}

func NewServer(addr string, opts ...Option) *Server {
    s := &Server{
        addr:    addr,
        timeout: 30 * time.Second, // défaut
        logger:  log.Default(),    // défaut
    }
    for _, opt := range opts {
        opt(s)
    }
    return s
}

// Utilisation
server := NewServer(":8080",
    WithTimeout(60*time.Second),
    WithLogger(customLogger),
)

Embarquement pour la composition

type Logger struct {
    prefix string
}

func (l *Logger) Log(msg string) {
    fmt.Printf("[%s] %s\n", l.prefix, msg)
}

type Server struct {
    *Logger // Embarquement - Server obtient la méthode Log
    addr    string
}

func NewServer(addr string) *Server {
    return &Server{
        Logger: &Logger{prefix: "SERVER"},
        addr:   addr,
    }
}

// Utilisation
s := NewServer(":8080")
s.Log("Starting...") // Appelle Logger.Log embarqué

Mémoire et Performance

Préallouer les slices quand la taille est connue

// Mal : La slice grandit plusieurs fois
func processItems(items []Item) []Result {
    var results []Result
    for _, item := range items {
        results = append(results, process(item))
    }
    return results
}

// Bien : Allocation unique
func processItems(items []Item) []Result {
    results := make([]Result, 0, len(items))
    for _, item := range items {
        results = append(results, process(item))
    }
    return results
}

Utiliser sync.Pool pour les allocations fréquentes

var bufferPool = sync.Pool{
    New: func() interface{} {
        return new(bytes.Buffer)
    },
}

func ProcessRequest(data []byte) []byte {
    buf := bufferPool.Get().(*bytes.Buffer)
    defer func() {
        buf.Reset()
        bufferPool.Put(buf)
    }()

    buf.Write(data)
    // Traitement...
    return buf.Bytes()
}

Éviter la concaténation de chaînes dans les boucles

// Mal : Crée de nombreuses allocations de chaînes
func join(parts []string) string {
    var result string
    for _, p := range parts {
        result += p + ","
    }
    return result
}

// Bien : Allocation unique avec strings.Builder
func join(parts []string) string {
    var sb strings.Builder
    for i, p := range parts {
        if i > 0 {
            sb.WriteString(",")
        }
        sb.WriteString(p)
    }
    return sb.String()
}

// Meilleur : Utiliser la bibliothèque standard
func join(parts []string) string {
    return strings.Join(parts, ",")
}

Intégration des Outils Go

Commandes Essentielles

# Build et exécution
go build ./...
go run ./cmd/myapp

# Tests
go test ./...
go test -race ./...
go test -cover ./...

# Analyse statique
go vet ./...
staticcheck ./...
golangci-lint run

# Gestion des modules
go mod tidy
go mod verify

# Formatage
gofmt -w .
goimports -w .

Configuration de linter recommandée (.golangci.yml)

linters:
  enable:
    - errcheck
    - gosimple
    - govet
    - ineffassign
    - staticcheck
    - unused
    - gofmt
    - goimports
    - misspell
    - unconvert
    - unparam

linters-settings:
  errcheck:
    check-type-assertions: true
  govet:
    enable:
      - shadow

issues:
  exclude-use-default: false

Référence Rapide : Expressions Idiomatiques Go

IdiomeDescription
Accepter des interfaces, retourner des structuresLes fonctions acceptent des paramètres d'interface, retournent des types concrets
Les erreurs sont des valeursTraiter les erreurs comme des valeurs de première classe, pas comme des exceptions
Ne communiquez pas en partageant la mémoireUtilisez les channels pour la coordination entre goroutines
Rendre la valeur zéro utileLes types doivent fonctionner sans initialisation explicite
Un peu de copie vaut mieux qu'une petite dépendanceÉviter les dépendances externes inutiles
Clair est mieux qu'astucieuxPrioriser la lisibilité à l'astuce
gofmt n'est le favori de personne mais l'ami de tousToujours formater avec gofmt/goimports
Retourner tôtGérer les erreurs en premier, garder le chemin heureux non indenté

Anti-patrons à éviter

// Mal : Retours nus dans des fonctions longues
func process() (result int, err error) {
    // ... 50 lignes ...
    return // Qu'est-ce qui est retourné ?
}

// Mal : Utiliser panic pour le flux de contrôle
func GetUser(id string) *User {
    user, err := db.Find(id)
    if err != nil {
        panic(err) // Ne faites pas ça
    }
    return user
}

// Mal : Passer le contexte dans une structure
type Request struct {
    ctx context.Context // Le contexte devrait être le premier paramètre
    ID  string
}

// Bien : Contexte comme premier paramètre
func ProcessRequest(ctx context.Context, id string) error {
    // ...
}

// Mal : Mélanger les récepteurs valeur et pointeur
type Counter struct{ n int }
func (c Counter) Value() int { return c.n }    // Récepteur valeur
func (c *Counter) Increment() { c.n++ }        // Récepteur pointeur
// Choisissez un style et soyez cohérent

Rappel : Le code Go doit être ennuyeux de la meilleure façon possible - prévisible, cohérent et facile à comprendre. En cas de doute, restez simple.

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

accessibility

Design, implement, and audit inclusive digital products using WCAG 2.2 Level AA. Use when building or auditing UI that must meet WCAG 2.2 Level AA, or when reviewing a change for keyboard, contrast, or screen-reader support.

affaan-m/content-engine

Create platform-native content systems for X, LinkedIn, TikTok, YouTube, newsletters, and repurposed multi-platform campaigns. Use when the user wants social posts, threads, scripts, content calendars, or one source asset adapted cleanly across platforms.

affaan-m/ECC

Multi-platform content distribution across X, LinkedIn, Threads, and Bluesky. Adapts content per platform using content-engine patterns. Never posts identical content cross-platform. Use when the user wants to distribute content across social platforms.

affaan-m/fal-ai-media

Unified media generation via fal.ai MCP — image, video, and audio. Covers text-to-image (Nano Banana), text/image-to-video (Seedance, Kling, Veo 3), text-to-speech (CSM-1B), and video-to-audio (ThinkSound). Use when the user wants to generate images, videos, or audio with AI.

affaan-m/manim-video

日本語翻訳:このファイルは manim-video 用の日本語翻訳が必要です

affaan-m/remotion-video-creation

Remotion のベストプラクティス - React で動画を作成する。3D、アニメーション、音声、字幕、チャート、トランジションなどをカバーするドメイン固有の29のルール。

affaan-m/video-editing

AI-assisted video editing workflows for cutting, structuring, and augmenting real footage. Covers the full pipeline from raw capture through FFmpeg, Remotion, ElevenLabs, fal.ai, and final polish in Descript or CapCut. Use when the user wants to edit video, cut footage, create vlogs, or build video content.

agent-architecture-audit

Full-stack diagnostic for agent and LLM applications. Audits the 12-layer agent stack for wrapper regression, memory pollution, tool discipline failures, hidden repair loops, and rendering corruption. Produces severity-ranked findings with code-first fixes. Essential for developers building agent applications, autonomous loops, or any LLM-powered feature. Use when an agent or LLM feature misbehaves and the failing layer is unknown, or before shipping an agent stack.

agent-eval

Head-to-head comparison of coding agents (Claude Code, Aider, Codex, etc.) on custom tasks with pass rate, cost, time, and consistency metrics. Use when choosing between coding agents, or when a change to an agent setup needs measured pass rate, cost, and time rather than an impression.

agent-harness-construction

Design and optimize AI agent action spaces, tool definitions, and observation formatting for higher completion rates. Use when defining or revising an agent

agentic-engineering

Operate as an agentic engineer using eval-first execution, decomposition, and cost-aware model routing. Use when planning or executing engineering work that agents will carry out end to end.

agentic-os

Build persistent multi-agent operating systems on Claude Code. Covers kernel architecture, specialist agents, slash commands, file-based memory, scheduled automation, and state management without external databases. Use when building a persistent multi-agent system on Claude Code with its own memory, commands, and scheduling.

agent-introspection-debugging

Structured self-debugging workflow for AI agent failures using capture, diagnosis, contained recovery, and introspection reports. Use when an agent run fails and you need a reproducible diagnosis instead of a retry.

agent-payment-x402

Add x402 payment execution to AI agents with per-task budgets, spending controls, and non-custodial wallets. Supports Base through agentwallet-sdk and X Layer through OKX Payments / OKX Agent Payments Protocol. Use when an agent must pay for something itself and needs per-task budgets, spending controls, and a non-custodial wallet.

agent-self-evaluation

Use after completing any non-trivial task. The agent self-rates its output on 5 axes — accuracy, completeness, clarity, actionability, conciseness — with concrete evidence per criterion. Produces a structured 1-5 scorecard with specific improvement suggestions.

agent-sort

Build an evidence-backed ECC install plan for a specific repo by sorting skills, commands, rules, hooks, and extras into DAILY vs LIBRARY buckets using parallel repo-aware review passes. Use when ECC should be trimmed to what a project actually needs instead of loading the full bundle.

ai-first-engineering

Engineering operating model for teams where AI agents generate a large share of implementation output. Use when setting team process, review gates, or ownership rules for a codebase largely written by agents.

ai-regression-testing

Regression testing strategies for AI-assisted development. Sandbox-mode API testing without database dependencies, automated bug-check workflows, and patterns to catch AI blind spots where the same model writes and reviews code. Use when adding regression coverage to AI-assisted code, or when the same model both wrote and reviewed a change.

android-clean-architecture

Clean Architecture patterns for Android and Kotlin Multiplatform projects — module structure, dependency rules, UseCases, Repositories, and data layer patterns. Use when structuring modules, layers, or data flow in an Android or KMP project.

angular-developer

Generates Angular code and provides architectural guidance. Trigger when creating projects, components, or services, or for best practices on reactivity (signals, linkedSignal, resource), forms, dependency injection, routing, SSR, accessibility (ARIA), animations, styling (component styles, Tailwind CSS), testing, or CLI tooling.

Skills associés