Communitygithub.com

patrones-golang

Patrones idiomáticos de Go, mejores prácticas y convenciones para construir aplicaciones Go robustas, eficientes y mantenibles. Útil al escribir o revisar código Go y dudar sobre la estructura o convenciones idiomáticas.

¿Qué es patrones-golang?

Esta skill proporciona una guía completa de patrones y mejores prácticas para desarrollar en Go de forma idiomática. Cubre principios fundamentales como simplicidad y claridad, hacer que el valor cero sea útil, y aceptar interfaces mientras se devuelven structs. Incluye secciones detalladas sobre manejo de errores (envoltura con contexto, tipos de error personalizados, uso de errors.Is y errors.As), patrones de concurrencia (worker pools, contextos para cancelación y timeouts, apagado graceful, errgroup, y prevención de fugas de goroutines), diseño de interfaces pequeñas y enfocadas, organización de paquetes, patrones de diseño de structs (opciones funcionales, composición por embedding), y optimizaciones de memoria y rendimiento (preasignación de slices, sync.Pool, strings.Builder). También integra herramientas esenciales de Go (go test, go vet, linters) y advierte sobre anti-patrones comunes. Es ideal para cualquier desarrollador que escriba, revise o refactorice código Go, asegurando que el código sea predecible, consistente y fácil de entender.

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

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

Patrones de Desarrollo en Go

Patrones idiomáticos de Go y mejores prácticas para construir aplicaciones robustas, eficientes y mantenibles.

Cuándo Activar

  • Escribir código Go nuevo
  • Revisar código Go
  • Refactorizar código Go existente
  • Diseñar paquetes/módulos Go

Principios Fundamentales

1. Simplicidad y Claridad

Go favorece la simplicidad sobre la astucia. El código debe ser obvio y fácil de leer.

// Bien: Claro y directo
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: Demasiado astuto
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. Hacer que el Valor Cero Sea Útil

Diseña tipos para que su valor cero sea inmediatamente utilizable sin inicialización.

// Bien: El valor cero es útil
type Counter struct {
    mu    sync.Mutex
    count int // el valor cero es 0, listo para usar
}

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

// Bien: bytes.Buffer funciona con valor cero
var buf bytes.Buffer
buf.WriteString("hello")

// Mal: Requiere inicialización
type BadCounter struct {
    counts map[string]int // un mapa nil provocará pánico
}

3. Acepta Interfaces, Devuelve Structs

Las funciones deben aceptar parámetros de tipo interfaz y devolver tipos concretos.

// Bien: Acepta interfaz, devuelve tipo concreto
func ProcessData(r io.Reader) (*Result, error) {
    data, err := io.ReadAll(r)
    if err != nil {
        return nil, err
    }
    return &Result{Data: data}, nil
}

// Mal: Devuelve una interfaz (oculta detalles de implementación innecesariamente)
func ProcessData(r io.Reader) (io.Reader, error) {
    // ...
}

Patrones de Manejo de Errores

Envoltura de Errores con Contexto

// Bien: Envuelve errores con contexto
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
}

Tipos de Error Personalizados

// Define errores específicos del dominio
type ValidationError struct {
    Field   string
    Message string
}

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

// Errores centinela para casos comunes
var (
    ErrNotFound     = errors.New("resource not found")
    ErrUnauthorized = errors.New("unauthorized")
    ErrInvalidInput = errors.New("invalid input")
)

Verificación de Errores con errors.Is y errors.As

func HandleError(err error) {
    // Verifica un error específico
    if errors.Is(err, sql.ErrNoRows) {
        log.Println("No records found")
        return
    }

    // Verifica el tipo de error
    var validationErr *ValidationError
    if errors.As(err, &validationErr) {
        log.Printf("Validation error on field %s: %s",
            validationErr.Field, validationErr.Message)
        return
    }

    // Error desconocido
    log.Printf("Unexpected error: %v", err)
}

Nunca Ignorar Errores

// Mal: Ignorando error con identificador en blanco
result, _ := doSomething()

// Bien: Maneja o documenta explícitamente por qué es seguro ignorar
result, err := doSomething()
if err != nil {
    return err
}

// Aceptable: Cuando el error realmente no importa (raro)
_ = writer.Close() // Limpieza de mejor esfuerzo, error registrado en otro lugar

Patrones de Concurrencia

Pool de Trabajadores

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)
}

Contexto para Cancelación y Timeouts

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)
}

Apagado Gracioso

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 para Gorutinas Coordinadas

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 // Captura variables del bucle
        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
}

Evitar Fugas de Gorutinas

// Mal: Fuga de gorutina si el contexto se cancela
func leakyFetch(ctx context.Context, url string) <-chan []byte {
    ch := make(chan []byte)
    go func() {
        data, _ := fetch(url)
        ch <- data // Bloquea para siempre si no hay receptor
    }()
    return ch
}

// Bien: Maneja correctamente la cancelación
func safeFetch(ctx context.Context, url string) <-chan []byte {
    ch := make(chan []byte, 1) // Canal con búfer
    go func() {
        data, err := fetch(url)
        if err != nil {
            return
        }
        select {
        case ch <- data:
        case <-ctx.Done():
        }
    }()
    return ch
}

Diseño de Interfaces

Interfaces Pequeñas y Enfocadas

// Bien: Interfaces de un solo método
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
}

// Compón interfaces según sea necesario
type ReadWriteCloser interface {
    Reader
    Writer
    Closer
}

Define Interfaces Donde se Usan

// En el paquete consumidor, no en el proveedor
package service

// UserStore define lo que este servicio necesita
type UserStore interface {
    GetUser(id string) (*User, error)
    SaveUser(user *User) error
}

type Service struct {
    store UserStore
}

// La implementación concreta puede estar en otro paquete
// No necesita conocer esta interfaz

Comportamiento Opcional con Aserciones de Tipo

type Flusher interface {
    Flush() error
}

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

    // Vacia el búfer si es compatible
    if f, ok := w.(Flusher); ok {
        return f.Flush()
    }
    return nil
}

Organización de Paquetes

Diseño de Proyecto Estándar

myproject/
├── cmd/
│   └── myapp/
│       └── main.go           # Punto de entrada
├── internal/
│   ├── handler/              # Manejadores HTTP
│   ├── service/              # Lógica de negocio
│   ├── repository/           # Acceso a datos
│   └── config/               # Configuración
├── pkg/
│   └── client/               # Cliente de API pública
├── api/
│   └── v1/                   # Definiciones de API (proto, OpenAPI)
├── testdata/                 # Datos de prueba
├── go.mod
├── go.sum
└── Makefile

Nomenclatura de Paquetes

// Bien: Corto, minúsculas, sin guiones bajos
package http
package json
package user

// Mal: Verboso, mayúsculas mezcladas o redundante
package httpHandler
package json_parser
package userService // Sufijo 'Service' redundante

Evitar Estado a Nivel de Paquete

// Mal: Estado mutable global
var db *sql.DB

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

// Bien: Inyección de dependencias
type Server struct {
    db *sql.DB
}

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

Diseño de Structs

Patrón de Opciones Funcionales

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, // valor por defecto
        logger:  log.Default(),    // valor por defecto
    }
    for _, opt := range opts {
        opt(s)
    }
    return s
}

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

Embedding para Composición

type Logger struct {
    prefix string
}

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

type Server struct {
    *Logger // Embedding - Server obtiene el método Log
    addr    string
}

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

// Uso
s := NewServer(":8080")
s.Log("Starting...") // Llama a Logger.Log embebido

Memoria y Rendimiento

Preasignar Slices Cuando se Conoce el Tamaño

// Mal: El slice crece múltiples veces
func processItems(items []Item) []Result {
    var results []Result
    for _, item := range items {
        results = append(results, process(item))
    }
    return results
}

// Bien: Asignación única
func processItems(items []Item) []Result {
    results := make([]Result, 0, len(items))
    for _, item := range items {
        results = append(results, process(item))
    }
    return results
}

Usar sync.Pool para Asignaciones Frecuentes

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)
    // Procesar...
    return buf.Bytes()
}

Evitar la Concatenación de Strings en Bucles

// Mal: Crea muchas asignaciones de string
func join(parts []string) string {
    var result string
    for _, p := range parts {
        result += p + ","
    }
    return result
}

// Bien: Asignación única con 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()
}

// Mejor: Usa la biblioteca estándar
func join(parts []string) string {
    return strings.Join(parts, ",")
}

Integración con Herramientas Go

Comandos Esenciales

# Compilar y ejecutar
go build ./...
go run ./cmd/myapp

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

# Análisis estático
go vet ./...
staticcheck ./...
golangci-lint run

# Gestión de módulos
go mod tidy
go mod verify

# Formateo
gofmt -w .
goimports -w .

Configuración Recomendada de Linter (.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

Referencia Rápida: Modismos Go

ModismoDescripción
Acepta interfaces, devuelve structsLas funciones aceptan parámetros de tipo interfaz y devuelven tipos concretos
Los errores son valoresTrata los errores como valores de primera clase, no como excepciones
No te comuniques compartiendo memoriaUsa canales para la coordinación entre gorutinas
Haz que el valor cero sea útilLos tipos deben funcionar sin inicialización explícita
Un poco de copia es mejor que un poco de dependenciaEvita dependencias externas innecesarias
Claro es mejor que astutoPrioriza la legibilidad sobre la astucia
gofmt no es el favorito de nadie, pero es amigo de todosSiempre formatea con gofmt/goimports
Regresa tempranoManeja los errores primero, mantén la ruta feliz sin sangría

Anti-patrones a Evitar

// Mal: Retornos sin nombre en funciones largas
func process() (result int, err error) {
    // ... 50 líneas ...
    return // ¿Qué se está devolviendo?
}

// Mal: Usar panic para el flujo de control
func GetUser(id string) *User {
    user, err := db.Find(id)
    if err != nil {
        panic(err) // No hagas esto
    }
    return user
}

// Mal: Pasar contexto en un struct
type Request struct {
    ctx context.Context // El contexto debe ser el primer parámetro
    ID  string
}

// Bien: Contexto como primer parámetro
func ProcessRequest(ctx context.Context, id string) error {
    // ...
}

// Mal: Mezclar receptores de valor y puntero
type Counter struct{ n int }
func (c Counter) Value() int { return c.n }    // Receptor de valor
func (c *Counter) Increment() { c.n++ }        // Receptor de puntero
// Elige un estilo y sé coherente

Recuerda: El código Go debe ser aburrido de la mejor manera: predecible, consistente y fácil de entender. En caso de duda, mantenlo 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 relacionados