Skip to content
Back to skills

Golang Patterns

ASecurity

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

  • 269,914 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 21, 2026
ai-agentsgobashsqlapidatabase

Works with

  • cli
  • api

Security analysis

A100/100

Scanned September 21, 2026

npx -y skills add affaan-m/ECC --skill golang-patterns --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Golang Patterns?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Golang Patterns
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/affaan-m-golang-patterns-a3350db6/badge)](https://www.skillsdirectory.com/skills/affaan-m-golang-patterns-a3350db6)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: golang-patterns
description: Patrones idiomáticos de Go, buenas prácticas y convenciones para construir aplicaciones Go robustas, eficientes y mantenibles.
origin: ECC
---

# Patrones de Desarrollo Go

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

## Cuándo Activar

- Escribir nuevo código Go
- 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.

```go
// 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 ingenioso
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ñar tipos para que su valor cero sea inmediatamente usable sin inicialización.

```go
// 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 el valor cero
var buf bytes.Buffer
buf.WriteString("hello")

// Mal: Requiere inicialización
type BadCounter struct {
    counts map[string]int // el mapa nil causará panic
}
```

### 3. Aceptar Interfaces, Retornar Structs

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

```go
// Bien: Acepta interfaz, retorna 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: Retorna interfaz (oculta detalles de implementación innecesariamente)
func ProcessData(r io.Reader) (io.Reader, error) {
    // ...
}
```

## Patrones de Manejo de Errores

### Wrapping de Errores con Contexto

```go
// Bien: Envolver 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

```go
// Definir 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

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

    // Verificar 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

```go
// Mal: Ignorar error con identificador en blanco
result, _ := doSomething()

// Bien: Manejar o documentar 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

### Worker Pool

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

### Context para Cancelación y Timeouts

```go
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 Graceful

```go
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 Goroutines Coordinadas

```go
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 // Capturar 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 Goroutine Leaks

```go
// Mal: Goroutine leak si el context es cancelado
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 buffer
    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

```go
// 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
}

// Componer interfaces según se necesite
type ReadWriteCloser interface {
    Reader
    Writer
    Closer
}
```

### Definir Interfaces Donde Se Usan

```go
// 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 Type Assertions

```go
type Flusher interface {
    Flush() error
}

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

    // Hacer flush si está soportado
    if f, ok := w.(Flusher); ok {
        return f.Flush()
    }
    return nil
}
```

## Organización de Paquetes

### Layout Estándar del Proyecto

```text
myproject/
├── cmd/
│   └── myapp/
│       └── main.go           # Punto de entrada
├── internal/
│   ├── handler/              # Handlers 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/                 # Fixtures de prueba
├── go.mod
├── go.sum
└── Makefile
```

### Nomenclatura de Paquetes

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

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

### Evitar Estado a Nivel de Paquete

```go
// Mal: Estado global mutable
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

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

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

### Embedding para Composición

```go
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 al Logger.Log embebido
```

## Memoria y Rendimiento

### Pre-asignar Slices Cuando Se Conoce el Tamaño

```go
// 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

```go
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 Concatenación de Strings en Bucles

```go
// 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: Usar la librería estándar
func join(parts []string) string {
    return strings.Join(parts, ",")
}
```

## Integración con Herramientas Go

### Comandos Esenciales

```bash
# Build 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

# Formato
gofmt -w .
goimports -w .
```

### Configuración Recomendada de Linter (.golangci.yml)

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

linters-settings:
  errcheck:
    check-type-assertions: true
  govet:
    check-shadowing: true

issues:
  exclude-use-default: false
```

## Referencia Rápida: Modismos de Go

| Modismo | Descripción |
|-------|-------------|
| Aceptar interfaces, retornar structs | Las funciones aceptan parámetros de interfaz, retornan tipos concretos |
| Los errores son valores | Tratar errores como valores de primera clase, no como excepciones |
| No comunicarse compartiendo memoria | Usar canales para coordinación entre goroutines |
| Hacer que el valor cero sea útil | Los tipos deben funcionar sin inicialización explícita |
| Un poco de copia es mejor que una pequeña dependencia | Evitar dependencias externas innecesarias |
| Claro es mejor que inteligente | Priorizar legibilidad sobre astucia |
| gofmt no es favorito de nadie pero sí amigo de todos | Siempre formatear con gofmt/goimports |
| Retornar temprano | Manejar errores primero, mantener el camino feliz sin indentar |

## Anti-Patrones a Evitar

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

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

// Mal: Pasar context en struct
type Request struct {
    ctx context.Context // El context debería ser el primer parámetro
    ID  string
}

// Bien: Context 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
// Elegir un estilo y ser consistente
```

**Recuerda**: El código Go debe ser aburrido de la mejor manera — predecible, consistente y fácil de entender. Ante la duda, mantenerlo simple.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…