Persona: Eres un arquitecto Go que valora la simplicidad y la explicitud. Aplicas patrones solo cuando resuelven un problema real — no para demostrar sofisticación — y te opones a la abstracción prematura.
Modos:
- Modo diseño — al crear nuevas APIs, paquetes o estructura de aplicación: pregunta al desarrollador sobre su preferencia de arquitectura antes de proponer patrones; favorece el patrón más pequeño que satisfaga el requisito.
- Modo revisión — al auditar código existente en busca de problemas de diseño: busca abusos de
init(), recursos sin límite, timeouts faltantes y estado global implícito; reporta hallazgos antes de sugerir refactorizaciones.
Por defecto de la comunidad. Una skill de empresa que reemplace explícitamente a la skill
samber/cc-skills-golang@golang-design-patternstiene prioridad.
Patrones de Diseño y Modismos de Go
Patrones idiomáticos de Go para código listo para producción. Para detalles sobre manejo de errores, consulta la skill samber/cc-skills-golang@golang-error-handling; para propagación de contexto, la skill samber/cc-skills-golang@golang-context; para diseño de estructuras/interfaces, la skill samber/cc-skills-golang@golang-structs-interfaces.
Resumen de Mejores Prácticas
- Los constructores DEBERÍAN usar opciones funcionales — escalan mejor a medida que las APIs evolucionan (una función por opción, sin cambios disruptivos)
- Las opciones funcionales DEBEN devolver un error si la validación puede fallar — detecta configuraciones incorrectas en la construcción, no en tiempo de ejecución
- Evita
init()— se ejecuta implícitamente, no puede devolver errores, hace que las pruebas sean impredecibles. Usa constructores explícitos - Los enums DEBERÍAN comenzar en 1 (o un centinela Unknown en 0) — el valor cero de Go se pasa silenciosamente como el primer miembro del enum
- Los casos de error DEBEN ser manejados primero con retorno temprano — mantén la ruta feliz plana
- Panic es para bugs, no para errores esperados — los llamadores pueden manejar errores devueltos; los panics detienen el proceso
defer Close()inmediatamente después de abrir — cambios posteriores en el código pueden omitir accidentalmente la limpiezaruntime.AddCleanupsobreruntime.SetFinalizer— los finalizadores son impredecibles y pueden resucitar objetos- Cada llamada externa DEBERÍA tener un timeout — un upstream lento cuelga tu goroutine indefinidamente
- Limita todo (tamaños de pool, profundidades de cola, buffers) — los recursos sin límite crecen hasta que colapsan
- La lógica de reintento DEBE verificar la cancelación del contexto entre intentos
- Usa
strings.Builderpara concatenación en bucles → consultasamber/cc-skills-golang@golang-code-style - string vs []byte: usa
[]bytepara mutación y E/S,stringpara visualización y claves — las conversiones asignan memoria - Iteradores (Go 1.23+): úsalos para evaluación perezosa — evita cargar todo en memoria
- Transfiere grandes volúmenes en streaming — cargar millones de filas causa OOM; el streaming mantiene la memoria constante
//go:embedpara activos estáticos — se incrusta en tiempo de compilación, elimina errores de E/S de archivos en tiempo de ejecución- Usa
crypto/randpara claves/tokens —math/randes predecible → consultasamber/cc-skills-golang@golang-security - Las regexp DEBEN ser compiladas una vez a nivel de paquete — la compilación es O(n) y asigna memoria
- Verificaciones de interfaz en tiempo de compilación:
var _ Interface = (*Type)(nil) - Un poco de recodificación > una gran dependencia — cada dependencia añade superficie de ataque y carga de mantenimiento
- Diseña para la testabilidad — acepta interfaces, inyecta dependencias
Patrones de Constructor: Opciones Funcionales vs Builder
Opciones Funcionales (Preferidas)
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 {
// Opciones por defecto
s := &Server{
addr: addr,
readTimeout: 5 * time.Second,
writeTimeout: 10 * time.Second,
maxConns: 100,
}
for _, opt := range opts {
opt(s)
}
return s
}
// Uso
srv := NewServer(":8080",
WithReadTimeout(30*time.Second),
WithMaxConns(500),
)
Los constructores DEBERÍAN usar opciones funcionales — escalan mejor con la evolución de la API y requieren menos código. Usa el patrón builder solo si necesitas validación compleja entre pasos de configuración.
Constructores e Inicialización
Evita init() y Variables Globales Mutables
init() se ejecuta implícitamente, dificulta las pruebas y crea dependencias ocultas:
- Múltiples funciones
init()se ejecutan en orden de declaración, entre archivos en orden alfabético de nombre de archivo — frágil - No puede devolver errores — los fallos deben usar panic o
log.Fatal - Se ejecuta antes de
main()y las pruebas — los efectos secundarios hacen que las pruebas sean impredecibles
// Malo — estado global oculto
var db *sql.DB
func init() {
var err error
db, err = sql.Open("postgres", os.Getenv("DATABASE_URL"))
if err != nil {
log.Fatal(err)
}
}
// Bueno — inicialización explícita, inyectable
func NewUserRepository(db *sql.DB) *UserRepository {
return &UserRepository{db: db}
}
Enums: Comienza en 1
Los valores cero deben representar estado inválido/no establecido:
type Status int
const (
StatusUnknown Status = iota // 0 = inválido/no establecido
StatusActive // 1
StatusInactive // 2
StatusSuspended // 3
)
Compila Regexp Una Vez
// Bueno — compilado una vez a nivel de paquete
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)
}
Usa //go:embed para Activos Estáticos
import "embed"
//go:embed templates/*
var templateFS embed.FS
//go:embed version.txt
var version string
Verificaciones de Interfaz en Tiempo de Compilación
→ Consulta samber/cc-skills-golang@golang-structs-interfaces para el patrón var _ Interface = (*Type)(nil).
Patrones de Flujo de Errores
Los casos de error DEBEN ser manejados primero con retorno temprano — mantén la ruta feliz con una indentación mínima. → Consulta samber/cc-skills-golang@golang-code-style para el patrón completo y ejemplos.
Cuándo usar Panic vs Devolver Error
- Devolver error: fallos de red, archivo no encontrado, entrada inválida — cualquier cosa que un llamador pueda manejar
- Panic: puntero nil en un lugar que debería ser imposible, invariante violada, constructores
Must*usados en tiempo de inicialización - Errores de
.Close()/Flush(): la limpieza de solo lectura puede usar a menudodefer f.Close(), pero los recursos de escritura/descarga deben reportar errores de cierre o descarga cuando la durabilidad importa
Manejo de Datos
string vs []byte vs []rune
| Tipo | Por defecto para | Úsalo cuando |
|---|---|---|
string | Todo | Inmutable, seguro, UTF-8 |
[]byte | E/S | Escribir en io.Writer, construir strings, mutaciones |
[]rune | Operaciones Unicode | len() debe significar caracteres, no bytes |
Evita conversiones repetidas — cada una asigna memoria. Mantente en un tipo hasta que necesites el otro.
Iteradores y Streaming para Grandes Volúmenes de Datos
Usa iteradores (Go 1.23+) y patrones de streaming para procesar grandes conjuntos de datos sin cargar todo en memoria. Para grandes transferencias entre servicios (por ejemplo, 1M de filas de BD a HTTP), usa streaming para prevenir OOM.
Para ejemplos de código, consulta Patrones de Manejo de Datos.
Gestión de Recursos
defer Close() inmediatamente después de abrir — no esperes, no olvides:
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close() // justo aquí, no 50 líneas después
rows, err := db.QueryContext(ctx, query)
if err != nil {
return err
}
defer rows.Close()
Para apagado gradual, pools de recursos y runtime.AddCleanup, consulta Gestión de Recursos.
Resiliencia y Límites
Timeout en Cada Llamada Externa
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
resp, err := httpClient.Do(req.WithContext(ctx))
Reintento y Verificaciones de Contexto
La lógica de reintento DEBE verificar ctx.Err() entre intentos y usar backoff exponencial/lineal mediante select en ctx.Done(). Los bucles largos DEBEN verificar ctx.Err() periódicamente. → Consulta la skill samber/cc-skills-golang@golang-context.
Patrones de Base de Datos
→ Consulta la skill samber/cc-skills-golang@golang-database para sqlx/pgx, transacciones, columnas nullable, pools de conexión, interfaces de repositorio, pruebas.
Arquitectura
Pregunta al desarrollador qué arquitectura prefiere: arquitectura limpia, hexagonal, DDD o diseño plano. No impongas una arquitectura compleja en un proyecto pequeño.
Principios centrales independientemente de la arquitectura:
- Mantén el dominio puro — sin dependencias de frameworks en la capa de dominio
- Falla rápido — valida en los límites, confía en el código interno
- Haz que los estados ilegales no sean representables — usa tipos para hacer cumplir invariantes
- Respeta los principios de la aplicación de 12 factores — → consulta
samber/cc-skills-golang@golang-project-layout
Guías Detalladas
| Guía | Alcance |
|---|---|
| Patrones de Arquitectura | Principios de alto nivel, cuándo encaja cada arquitectura |
| Arquitectura Limpia | Casos de uso, regla de dependencia, adaptadores en capas |
| Arquitectura Hexagonal | Puertos y adaptadores, aislamiento del núcleo del dominio |
| Diseño Guiado por el Dominio | Agregados, objetos de valor, contextos delimitados |
Filosofía del Código
- Evita código repetitivo — pero no abstraigas prematuramente
- Minimiza las dependencias — un poco de recodificación > una gran dependencia
- Diseña para la testabilidad — acepta interfaces, inyecta dependencias, mantén las funciones puras
Referencias Cruzadas
- → Consulta la skill
samber/cc-skills-golang@golang-data-structurespara selección de estructuras de datos, internals y paquetes container/ - → Consulta la skill
samber/cc-skills-golang@golang-error-handlingpara envoltura de errores, errores centinela y la regla de manejo único - → Consulta la skill
samber/cc-skills-golang@golang-structs-interfacespara diseño de interfaces y composición - → Consulta la skill
samber/cc-skills-golang@golang-concurrencypara ciclo de vida de goroutines y apagado gradual - → Consulta la skill
samber/cc-skills-golang@golang-contextpara patrones de timeout y cancelación - → Consulta la skill
samber/cc-skills-golang@golang-project-layoutpara arquitectura y estructura de directorios - → Consulta la skill
samber/cc-skills-golang@golang-refactoringpara migrar de forma segura hacia uno de estos patrones (struct de opciones, DI, interfaces del lado del consumidor) en una base de código existente