# Manejo de Errores en Go: Errores como Valores, no Excepciones

En los posts anteriores exploramos structs, interfaces, métodos y collections en Go. Hoy abordaremos uno de los aspectos más distintivos del lenguaje: el **manejo de errores**. Si vienes de Java, donde usas `try-catch` y excepciones, Go tiene un enfoque completamente diferente que al principio puede parecer verboso, pero que en realidad te da más control y claridad.

## **Filosofía: Errores como Valores**

En Go, **los errores son valores, no excepciones**. No hay `try-catch`. En su lugar, las funciones retornan errores como parte de su valor de retorno:

```go
// Convención: error siempre es el último valor de retorno
result, err := doSomething()
if err != nil {
    // Manejar el error
    return err
}
// Continuar con el resultado
```

Esta filosofía tiene ventajas importantes:

* **Explícito**: Ves claramente qué funciones pueden fallar
    
* **Control**: Decides cómo manejar cada error
    
* **Performance**: No hay overhead de stack unwinding como en excepciones
    
* **Claridad**: El flujo de código es más predecible
    

## **Crear Errores Básicos**

### **Errores Simples con** `errors.New()`

```go
var (
    ErrNotFound     = errors.New("resource not found")
    ErrInvalidInput = errors.New("invalid input")
    ErrUnauthorized = errors.New("unauthorized")
)
```

Estos son **sentinel errors** - errores predefinidos que puedes comparar directamente. Son útiles para errores conocidos que el llamador puede manejar específicamente.

### **Errores con Formato con** `fmt.Errorf()`

```go
func createFormattedError(id int) error {
    return fmt.Errorf("user with id %d not found", id)
}
```

`fmt.Errorf()` es útil cuando necesitas incluir información dinámica en el mensaje de error.

## **Errores Personalizados**

Puedes crear tipos de error personalizados para incluir más información:

```go
type ValidationError struct {
    Field   string
    Message string
}

// Implementar la interfaz error
func (e *ValidationError) Error() string {
    return fmt.Sprintf("validation error on field '%s': %s", e.Field, e.Message)
}
```

Esto te permite:

* Agregar campos adicionales al error
    
* Extraer información específica más tarde
    
* Crear jerarquías de errores
    

## **Wrapping Errors: Agregar Contexto**

Una de las características más poderosas de Go (desde Go 1.13) es el **wrapping de errores**. Te permite agregar contexto a un error sin perder el error original.

### **El Problema sin Wrapping**

```go
// ❌ Sin contexto
func readFile(filename string) error {
    file, err := os.Open(filename)
    if err != nil {
        return err  // Solo retorna el error original sin contexto
    }
    // ...
}
```

Si este error ocurre en una función profunda, no sabes dónde ni por qué falló.

### **La Solución: Wrapping con** `%w`

```go
func readFile(filename string) error {
    file, err := os.Open(filename)
    if err != nil {
        // Wrap el error agregando contexto
        return fmt.Errorf("failed to open file %s: %w", filename, err)
    }
    defer file.Close()

    data := make([]byte, 100)
    _, err = file.Read(data)
    if err != nil && err != io.EOF {
        // Wrap el error agregando contexto
        return fmt.Errorf("failed to read file %s: %w", filename, err)
    }

    return nil
}
```

El verbo `%w` en `fmt.Errorf()` crea un error que envuelve el error original. Esto te permite:

* Agregar contexto útil en cada capa
    
* Mantener el error original para verificación
    
* Crear cadenas de errores informativas
    

## **Verificar Errores Específicos:** `errors.Is()`

`errors.Is()` verifica si un error es o contiene un error específico en su cadena de wrapping:

```go
err := readFile("nonexistent.txt")

// Verificar si el error es específicamente os.ErrNotExist
if errors.Is(err, os.ErrNotExist) {
    fmt.Println("File does not exist")
}

// Verificar con nuestro error personalizado
if errors.Is(err, ErrNotFound) {
    fmt.Println("Resource not found")
}
```

**Importante**: `errors.Is()` funciona incluso si el error está envuelto:

```go
// Esto funciona aunque ErrNotFound esté envuelto
if errors.Is(err, ErrNotFound) {
    // Manejar error específico
}
```

### **Ejemplo Práctico: Manejo de Errores Específicos**

```go
func (r *UserRepository) GetUser(id int) (*User, error) {
    user, exists := r.users[id]
    if !exists {
        return nil, fmt.Errorf("failed to get user: %w", ErrUserNotFound)
    }
    return user, nil
}

// En el llamador
user, err := repo.GetUser(123)
if err != nil {
    if errors.Is(err, ErrUserNotFound) {
        // Manejar específicamente el caso de usuario no encontrado
        return fmt.Errorf("user %d not found", 123)
    }
    // Otros errores
    return err
}
```

## **Extraer Errores por Tipo:** `errors.As()`

`errors.As()` es similar a `instanceof` en Java, pero para errores. Te permite extraer un error de un tipo específico de la cadena de errores:

```go
err := &ValidationError{
    Field:   "email",
    Message: "invalid format",
}

var validationErr *ValidationError
if errors.As(err, &validationErr) {
    fmt.Printf("Validation error on field: %s\n", validationErr.Field)
    fmt.Printf("Message: %s\n", validationErr.Message)
}
```

**Nota**: El segundo parámetro debe ser un puntero a un puntero del tipo de error.

### **Cuándo Usar** `errors.Is()` vs `errors.As()`

| **Situación** | **Usar** |
| --- | --- |
| Comparar con error predefinido (sentinel) | `errors.Is()` |
| Extraer información de error personalizado | `errors.As()` |
| Verificar tipo de error | `errors.As()` |

## **Combinar Múltiples Errores:** `errors.Join()`

`errors.Join()` (desde Go 1.20) combina múltiples errores en uno solo. Es perfecto para validaciones que pueden tener múltiples errores:

```go
func validateUser(name, email string) error {
    var errs []error

    if name == "" {
        errs = append(errs, &ValidationError{
            Field:   "name",
            Message: "cannot be empty",
        })
    }
    if email == "" {
        errs = append(errs, &ValidationError{
            Field:   "email",
            Message: "cannot be empty",
        })
    }
    if len(email) > 0 && !strings.Contains(email, "@") {
        errs = append(errs, &ValidationError{
            Field:   "email",
            Message: "invalid format",
        })
    }

    if len(errs) > 0 {
        return errors.Join(errs...)
    }
    return nil
}
```

Para extraer los errores individuales:

```go
if err := validateUser("", ""); err != nil {
    if joinedErr, ok := err.(interface{ Unwrap() []error }); ok {
        for _, e := range joinedErr.Unwrap() {
            fmt.Printf("  - %v\n", e)
        }
    }
}
```

## **Patrón: Sentinel Errors**

Los **sentinel errors** son errores predefinidos que representan condiciones específicas y conocidas:

```go
var (
    ErrUserNotFound    = errors.New("user not found")
    ErrInvalidPassword = errors.New("invalid password")
    ErrUserExists      = errors.New("user already exists")
)
```

**Ventajas:**

* Comparación directa con `errors.Is()`
    
* Documentación clara de errores posibles
    
* Manejo específico por tipo de error
    

**Ejemplo de uso:**

```go
func (r *UserRepository) CreateUser(id int, username, email string) error {
    if _, exists := r.users[id]; exists {
        return ErrUserExists  // Retornar sentinel error
    }
    r.users[id] = &User{ID: id, Username: username, Email: email}
    return nil
}

// En el llamador
if err := repo.CreateUser(1, "john", "john@example.com"); err != nil {
    if errors.Is(err, ErrUserExists) {
        // Manejar específicamente el caso de usuario existente
        fmt.Println("User already exists, continuing...")
    } else {
        return fmt.Errorf("failed to create user: %w", err)
    }
}
```

## **Manejo de Errores en Capas**

En aplicaciones reales, los errores pasan por múltiples capas. Es importante agregar contexto en cada capa mientras preservas el error original:

```go
// Capa de repositorio
func (r *UserRepository) GetUser(id int) (*User, error) {
    user, exists := r.users[id]
    if !exists {
        return nil, fmt.Errorf("failed to get user: %w", ErrUserNotFound)
    }
    return user, nil
}

// Capa de servicio
func (s *UserService) Login(username, password string) (*User, error) {
    user, err := s.repo.Authenticate(username, password)
    if err != nil {
        if errors.Is(err, ErrUserNotFound) {
            return nil, fmt.Errorf("login failed: user not found")
        }
        if errors.Is(err, ErrInvalidPassword) {
            return nil, fmt.Errorf("login failed: invalid password")
        }
        return nil, fmt.Errorf("login failed: %w", err)
    }
    return user, nil
}
```

**Principios:**

1. **Wrap en cada capa** con contexto relevante
    
2. **Verifica errores específicos** con `errors.Is()` cuando sea apropiado
    
3. **No ignores errores** - siempre maneja o propaga
    
4. **Agrega contexto útil** - qué operación falló y dónde
    

## **Comparación: Java vs Go**

| **Aspecto** | **Java** | **Go** |
| --- | --- | --- |
| Mecanismo | Excepciones (`throw`/`catch`) | Valores de retorno |
| Visibilidad | Implícita (cualquier función puede lanzar) | Explícita (error en firma) |
| Performance | Overhead de stack unwinding | Sin overhead |
| Control de flujo | Puede saltar múltiples niveles | Flujo explícito |
| Contexto | Stack trace automático | Debes agregarlo manualmente |
| Verificación | `instanceof` | `errors.Is()` / `errors.As()` |

### **Ejemplo Comparativo**

**Java:**

```go
try {
    User user = repository.getUser(id);
    // ...
} catch (UserNotFoundException e) {
    // Manejar error
} catch (Exception e) {
    // Manejar otros errores
}
```

**Go:**

```go
user, err := repository.GetUser(id)
if err != nil {
    if errors.Is(err, ErrUserNotFound) {
        // Manejar error específico
        return fmt.Errorf("user %d not found", id)
    }
    // Manejar otros errores
    return fmt.Errorf("failed to get user: %w", err)
}
// Continuar con user
```

## **Mejores Prácticas**

### **1\. Siempre Verifica Errores**

```go
// ❌ MAL - Ignorar error
result, _ := doSomething()

// ✅ BIEN - Verificar error
result, err := doSomething()
if err != nil {
    return err
}
```

### **2\. Agrega Contexto con Wrapping**

```go
// ❌ MAL - Sin contexto
if err != nil {
    return err
}

// ✅ BIEN - Con contexto
if err != nil {
    return fmt.Errorf("failed to process user %d: %w", userID, err)
}
```

### **3\. Usa Sentinel Errors para Errores Conocidos**

```go
// ✅ Definir errores predefinidos
var ErrUserNotFound = errors.New("user not found")

// ✅ Retornar sentinel error
if !exists {
    return ErrUserNotFound
}

// ✅ Verificar con errors.Is()
if errors.Is(err, ErrUserNotFound) {
    // Manejar específicamente
}
```

### **4\. No Envuelvas Errores Múltiples Veces Sin Razón**

```go
// ❌ MAL - Envolver sin agregar valor
if err != nil {
    return fmt.Errorf("error: %w", err)  // No agrega contexto útil
}

// ✅ BIEN - Agregar contexto útil
if err != nil {
    return fmt.Errorf("failed to save user %s: %w", username, err)
}
```

### **5\. Usa** `errors.Join()` para Múltiples Errores

```go
// ✅ Para validaciones con múltiples errores
var errs []error
if name == "" {
    errs = append(errs, ErrNameRequired)
}
if email == "" {
    errs = append(errs, ErrEmailRequired)
}
if len(errs) > 0 {
    return errors.Join(errs...)
}
```

## **Errores Comunes**

### **❌ Error 1: Ignorar Errores**

```go
// ❌ MAL
result, _ := doSomething()  // Ignora el error

// ✅ BIEN
result, err := doSomething()
if err != nil {
    return err
}
```

### **❌ Error 2: No Agregar Contexto**

```go
// ❌ MAL
if err != nil {
    return err  // Sin contexto
}

// ✅ BIEN
if err != nil {
    return fmt.Errorf("failed to process: %w", err)
}
```

### **❌ Error 3: Comparar Errores Envueltos con** `==`

```go
// ❌ MAL - No funciona con errores envueltos
if err == ErrNotFound {
    // ...
}

// ✅ BIEN - Usa errors.Is()
if errors.Is(err, ErrNotFound) {
    // ...
}
```

## **Ejemplo Completo: Sistema de Autenticación**

```go
type UserService struct {
    repo *UserRepository
}

func (s *UserService) RegisterUser(id int, username, email, password string) error {
    // Validar entrada
    if err := validateUser(username, email); err != nil {
        return fmt.Errorf("validation failed: %w", err)
    }

    // Crear usuario
    if err := s.repo.CreateUser(id, username, email); err != nil {
        if errors.Is(err, ErrUserExists) {
            return fmt.Errorf("registration failed: user already exists")
        }
        return fmt.Errorf("registration failed: %w", err)
    }

    return nil
}

func (s *UserService) Login(username, password string) (*User, error) {
    user, err := s.repo.Authenticate(username, password)
    if err != nil {
        if errors.Is(err, ErrUserNotFound) {
            return nil, fmt.Errorf("login failed: user not found")
        }
        if errors.Is(err, ErrInvalidPassword) {
            return nil, fmt.Errorf("login failed: invalid password")
        }
        return nil, fmt.Errorf("login failed: %w", err)
    }
    return user, nil
}
```

Este ejemplo muestra:

* Validación con `errors.Join()`
    
* Manejo específico de sentinel errors
    
* Agregar contexto en cada capa
    
* Preservar el error original para verificación
    

## **Conclusiones**

El manejo de errores en Go es diferente pero poderoso:

✅ **Errores como valores**: Más explícito y controlable que excepciones

✅ **Wrapping**: Agrega contexto sin perder el error original

✅ **errors.Is()**: Verifica errores específicos incluso cuando están envueltos

✅ **errors.As()**: Extrae información de errores personalizados

✅ **errors.Join()**: Combina múltiples errores para validaciones

**Conceptos clave a recordar:**

* Siempre verifica errores explícitamente
    
* Agrega contexto con `fmt.Errorf()` y `%w`
    
* Usa sentinel errors para errores conocidos
    
* Verifica con `errors.Is()`, no con `==`
    
* Extrae información con `errors.As()` para errores personalizados
    

Aunque puede parecer verboso al principio, este enfoque te da más control y hace que el código sea más predecible y fácil de depurar.

## **Próximos Pasos**

En el siguiente post exploraremos los **paquetes importantes de Go**: `context`, `io`, `fmt`, `sync`, y `reflect`. Estos paquetes son fundamentales para escribir código Go profesional y entenderás cómo se integran con todo lo que hemos aprendido hasta ahora.

---

**¿Tienes preguntas sobre el manejo de errores en Go?** Déjame saber en los comentarios. Y si quieres ver el código completo de estos ejemplos, puedes encontrarlo en mi repositorio [go-mastery-lab](https://github.com/tu-usuario/go-mastery-lab).

Enjoy!

José Díaz

+51 939965148
