Manejo de Errores en Go: Patrones, Wrapping y Mejores Practicas para 2026

Guia completa sobre el manejo de errores en Go: errores centinela, tipos personalizados, wrapping con fmt.Errorf, errors.Is y errors.As. Patrones profesionales para entrevistas tecnicas y desarrollo en produccion.

Diagrama de patrones de manejo de errores en Go con wrapping y errores centinela

El manejo de errores en Go representa una de las caracteristicas mas distintivas del lenguaje. A diferencia de otros lenguajes que utilizan excepciones, Go adopta un enfoque explicito donde los errores son valores que deben manejarse de forma deliberada. Esta filosofia, aunque puede parecer verbosa al principio, resulta en codigo mas predecible y facil de depurar en sistemas de produccion.

En el ecosistema Go de 2026, dominar el manejo de errores no es opcional: es un requisito fundamental para cualquier desarrollador que aspire a construir aplicaciones robustas. Las entrevistas tecnicas para posiciones de backend con Go invariablemente incluyen preguntas sobre patrones de error, wrapping y las funciones del paquete errors.

Clave para entrevistas tecnicas

Los entrevistadores evaluan tres aspectos del manejo de errores: comprension de la interfaz error, uso correcto de errors.Is vs errors.As, y capacidad para disenar jerarquias de errores en arquitecturas por capas. Este articulo cubre los tres en profundidad.

La Interfaz Error: Fundamento del Sistema

Todo el sistema de errores en Go se construye sobre una interfaz extraordinariamente simple definida en el paquete builtin:

builtin.gogo
type error interface {
    Error() string
}

Cualquier tipo que implemente el metodo Error() string satisface esta interfaz. Esta simplicidad permite una flexibilidad enorme: desde errores basicos creados con errors.New() hasta estructuras complejas que contienen metadata, stacktraces y errores anidados.

La convencion en Go establece que las funciones que pueden fallar retornan el error como ultimo valor de retorno. El codigo que llama a estas funciones debe verificar explicitamente si el error es nil antes de continuar. Este patron, aunque repetitivo, elimina la ambiguedad sobre que operaciones pueden fallar y obliga a los desarrolladores a considerar los casos de error desde el inicio.

Tipos de Error Personalizados

Cuando los mensajes de error simples resultan insuficientes, Go permite crear tipos de error personalizados que encapsulan informacion adicional:

apperror.gogo
type NotFoundError struct {
    Resource string
    ID       string
}

func (e *NotFoundError) Error() string {
    return fmt.Sprintf("%s with ID %s not found", e.Resource, e.ID)
}

Los tipos personalizados ofrecen varias ventajas significativas. Primero, permiten adjuntar campos estructurados que facilitan el logging y la observabilidad. Segundo, habilitan la inspeccion programatica mediante errors.As. Tercero, proporcionan una forma type-safe de categorizar errores sin depender de comparaciones de strings.

En aplicaciones empresariales, es comun definir una jerarquia de errores de dominio que refleje las reglas de negocio. Un ValidationError puede contener una lista de campos invalidos, mientras que un AuthorizationError puede incluir el permiso requerido y el rol del usuario.

Errores Centinela: Valores Predefinidos

Los errores centinela son valores de error predefinidos a nivel de paquete que representan condiciones especificas:

errors.gogo
var (
    ErrNotFound     = errors.New("record not found")
    ErrUnauthorized = errors.New("unauthorized access")
    ErrConflict     = errors.New("resource conflict")
)

Estos errores siguen la convencion de nomenclatura Err seguido de la condicion. La ventaja principal es que permiten comparaciones directas usando errors.Is, lo que resulta mas eficiente y legible que comparar mensajes de texto.

Sin embargo, los errores centinela tienen limitaciones. Al ser valores globales inmutables, no pueden contener informacion contextual dinamica. Por esta razon, muchos equipos combinan errores centinela con wrapping para agregar contexto mientras mantienen la capacidad de identificar el error original.

Error Wrapping: Agregando Contexto

El wrapping de errores, introducido en Go 1.13, permite envolver un error existente con informacion adicional mientras se preserva el error original:

repository.gogo
func (r *UserRepo) FindByID(ctx context.Context, id string) (*User, error) {
    user, err := r.db.QueryContext(ctx, "SELECT * FROM users WHERE id = $1", id)
    if err != nil {
        return nil, fmt.Errorf("UserRepo.FindByID(%s): %w", id, err)
    }
    return user, nil
}

El verbo %w en fmt.Errorf es la clave del wrapping. A diferencia de %v que simplemente formatea el error como string, %w crea una cadena de errores que puede recorrerse posteriormente. Cada capa de la aplicacion puede agregar su propio contexto sin perder la informacion del error subyacente.

¿Listo para aprobar tus entrevistas de Go?

Practica con nuestros simuladores interactivos, flashcards y tests técnicos.

Un patron efectivo consiste en incluir el nombre de la funcion y los parametros relevantes en el mensaje de wrapping. Esto crea un pseudo-stacktrace que facilita enormemente la depuracion en logs de produccion, especialmente cuando el error atraviesa multiples capas de la aplicacion.

errors.Is: Comparacion de Errores

La funcion errors.Is verifica si un error especifico existe en alguna parte de la cadena de errores wrapped:

handler.gogo
func handleGetUser(w http.ResponseWriter, r *http.Request) {
    user, err := userService.GetByID(r.Context(), chi.URLParam(r, "id"))
    if errors.Is(err, ErrNotFound) {
        http.Error(w, "User not found", http.StatusNotFound)
        return
    }
    if err != nil {
        http.Error(w, "Internal error", http.StatusInternalServerError)
        return
    }
    json.NewEncoder(w).Encode(user)
}

La funcion errors.Is recorre recursivamente la cadena de errores llamando al metodo Unwrap() de cada error hasta encontrar una coincidencia o llegar al final. Esto significa que incluso si el error original fue wrapped multiples veces, errors.Is puede identificarlo correctamente.

Para errores personalizados, es posible implementar el metodo Is(target error) bool para definir logica de comparacion personalizada. Esto resulta util cuando dos errores diferentes deben considerarse equivalentes bajo ciertas condiciones.

errors.As: Extraccion de Tipos

Mientras errors.Is compara valores, errors.As extrae tipos especificos de la cadena de errores:

middleware.gogo
func errorMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        defer func() {
            if r := recover(); r != nil {
                http.Error(w, "Internal error", http.StatusInternalServerError)
            }
        }()
        next.ServeHTTP(w, r)
    })
}

func mapErrorToHTTP(err error) int {
    var notFound *NotFoundError
    if errors.As(err, &notFound) {
        return http.StatusNotFound
    }
    var validationErr *ValidationError
    if errors.As(err, &validationErr) {
        return http.StatusBadRequest
    }
    return http.StatusInternalServerError
}

El segundo argumento de errors.As debe ser un puntero a la variable donde se almacenara el error extraido. Si la funcion retorna true, la variable contendra el error del tipo solicitado y se puede acceder a sus campos estructurados.

Este patron es fundamental para mapear errores de dominio a respuestas HTTP apropiadas. En lugar de depender de strings o codigos numericos, el codigo puede inspeccionar el tipo concreto del error y extraer informacion relevante para la respuesta.

Errores de Dominio en Arquitecturas por Capas

En aplicaciones con arquitectura limpia o hexagonal, es esencial definir errores de dominio que encapsulen tanto la categoria del error como informacion contextual:

domain/errors.gogo
type DomainError struct {
    Code    string
    Message string
    Err     error
}

func (e *DomainError) Error() string {
    if e.Err != nil {
        return fmt.Sprintf("%s: %s: %v", e.Code, e.Message, e.Err)
    }
    return fmt.Sprintf("%s: %s", e.Code, e.Message)
}

func (e *DomainError) Unwrap() error {
    return e.Err
}

El metodo Unwrap() es crucial: permite que errors.Is y errors.As continuen su busqueda a traves del error envuelto. Sin este metodo, la cadena de errores se romperia en el DomainError.

En la capa de servicio, estos errores de dominio se utilizan para comunicar condiciones de negocio:

service/user.gogo
func (s *UserService) Deactivate(ctx context.Context, userID string) error {
    user, err := s.repo.FindByID(ctx, userID)
    if err != nil {
        return fmt.Errorf("deactivating user %s: %w", userID, err)
    }
    if user.Status == StatusInactive {
        return &DomainError{
            Code:    "ALREADY_INACTIVE",
            Message: fmt.Sprintf("user %s is already inactive", userID),
        }
    }
    return s.repo.UpdateStatus(ctx, userID, StatusInactive)
}

Este enfoque separa claramente los errores tecnicos (problemas de base de datos, red, etc.) de los errores de dominio (reglas de negocio violadas), permitiendo que cada capa maneje los errores de forma apropiada.

Preguntas Frecuentes en Entrevistas Tecnicas

Las entrevistas para posiciones de Go frecuentemente incluyen preguntas sobre manejo de errores. Estas son algunas de las mas comunes:

Cual es la diferencia entre errors.Is y errors.As? errors.Is compara un error con un valor especifico (tipicamente un error centinela), mientras que errors.As intenta extraer un error de un tipo especifico de la cadena. Se usa Is para valores conocidos y As para tipos que contienen informacion adicional.

Por que Go no usa excepciones? El equipo de Go considera que las excepciones obscurecen el flujo de control y hacen dificil razonar sobre que operaciones pueden fallar. Los errores como valores hacen explicito el manejo de errores y facilitan la revision de codigo.

Cuando usar panic vs error? panic se reserva para situaciones verdaderamente excepcionales donde la aplicacion no puede continuar de forma segura, como errores de programacion o corrupcion de estado. Para condiciones de error esperadas, siempre se prefiere retornar un error.

Antipatrones que Deben Evitarse

Existen varios patrones problematicos que los desarrolladores deben reconocer y evitar:

go
// anti-pattern: silent error swallowing
result, _ := riskyOperation()

Ignorar errores con el blank identifier es una de las practicas mas peligrosas. Si una operacion puede fallar, ese error debe manejarse o, como minimo, registrarse. El codigo que ignora errores sistematicamente genera bugs dificiles de diagnosticar.

go
// anti-pattern: double handling
if err != nil {
    log.Printf("operation failed: %v", err)
    return err
}

El manejo doble ocurre cuando un error se registra y luego se retorna sin modificar. Esto resulta en logs duplicados cuando cada capa de la aplicacion repite el patron. La solucion es elegir: o se maneja el error (logging, metricas, etc.) o se propaga hacia arriba, pero no ambos.

Concurrencia y errgroup

Cuando se procesan operaciones concurrentes, el paquete errgroup proporciona una forma elegante de manejar errores:

batch.gogo
func (s *OrderService) ProcessBatch(ctx context.Context, orderIDs []string) error {
    g, ctx := errgroup.WithContext(ctx)
    g.SetLimit(10)

    for _, id := range orderIDs {
        g.Go(func() error {
            if err := s.processOrder(ctx, id); err != nil {
                return fmt.Errorf("processing order %s: %w", id, err)
            }
            return nil
        })
    }

    return g.Wait()
}

El errgroup cancela automaticamente el contexto cuando cualquier goroutine retorna un error, deteniendo el trabajo restante de forma limpia. El metodo SetLimit controla la concurrencia maxima, previniendo la sobrecarga de recursos. Este patron es esencial para procesamiento batch en aplicaciones de produccion.

Novedades en Go 1.24

Go 1.24 introdujo mejoras en el paquete errors, incluyendo mejor soporte para multiples errores wrapped y optimizaciones de rendimiento en errors.Is y errors.As. Estas mejoras benefician especialmente a aplicaciones que manejan grandes volumenes de errores concurrentes.

Conclusion

El manejo de errores en Go, aunque inicialmente puede parecer verboso comparado con otros lenguajes, ofrece un modelo mental claro y predecible que escala bien en aplicaciones de produccion. Los puntos clave a recordar incluyen:

  • La interfaz error es simple por diseno: cualquier tipo con un metodo Error() string la satisface, permitiendo desde errores basicos hasta estructuras complejas con metadata
  • El wrapping con %w preserva la cadena de errores: cada capa puede agregar contexto sin perder informacion del error original
  • errors.Is para valores, errors.As para tipos: elegir la funcion correcta segun si se compara un valor centinela o se extrae un tipo especifico
  • Los errores de dominio separan logica de negocio de errores tecnicos: esto facilita el mapeo a respuestas HTTP y la toma de decisiones en cada capa
  • Evitar antipatrones como el swallowing silencioso y el manejo doble: estos generan bugs sutiles y logs confusos
  • errgroup para concurrencia segura: maneja errores en operaciones paralelas con cancelacion automatica

Dominar estos patrones no solo prepara a los desarrolladores para entrevistas tecnicas, sino que resulta en codigo Go mas robusto y mantenible en produccion.

¡Empieza a practicar!

Pon a prueba tu conocimiento con nuestros simuladores de entrevista y tests técnicos.

Etiquetas

#go
#error-handling
#best-practices
#interview
#golang

Compartir

Artículos relacionados