惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

人人都是产品经理
人人都是产品经理
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
P
Privacy International News Feed
Simon Willison's Weblog
Simon Willison's Weblog
I
Intezer
Spread Privacy
Spread Privacy
The Hacker News
The Hacker News
P
Palo Alto Networks Blog
TaoSecurity Blog
TaoSecurity Blog
S
Secure Thoughts
Google Online Security Blog
Google Online Security Blog
H
Heimdal Security Blog
N
News | PayPal Newsroom
Attack and Defense Labs
Attack and Defense Labs
Recent Commits to openclaw:main
Recent Commits to openclaw:main
博客园 - 【当耐特】
Webroot Blog
Webroot Blog
小众软件
小众软件
Help Net Security
Help Net Security
D
Darknet – Hacking Tools, Hacker News & Cyber Security
N
News and Events Feed by Topic
Hacker News - Newest:
Hacker News - Newest: "LLM"
PCI Perspectives
PCI Perspectives
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
The Cloudflare Blog
Cloudbric
Cloudbric
AI
AI
WordPress大学
WordPress大学
博客园 - 聂微东
Jina AI
Jina AI
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - 三生石上(FineUI控件)
Hacker News: Ask HN
Hacker News: Ask HN
H
Hacker News: Front Page
博客园 - Franky
V
V2EX
Schneier on Security
Schneier on Security
G
GRAHAM CLULEY
S
SegmentFault 最新的问题
有赞技术团队
有赞技术团队
H
Help Net Security
量子位
S
Security @ Cisco Blogs
大猫的无限游戏
大猫的无限游戏
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Recorded Future
Recorded Future
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
J
Java Code Geeks
C
Cisco Blogs
S
Security Affairs

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
Tipos de errores, Wrapping e Inspección en Go
Juan Carlos · 2026-05-28 · via DEV Community

Tabla de Contenidos

Objetivo

Aprender a diseñar tipos de errores personalizados con metadatos de negocio en Go y a propagarlos correctamente a través del flujo de la aplicación sin perder su tipo ni su información de origen.

Restricciones de los Errores Basados en Texto

En programas sencillos, el uso de errors.New es perfecto. Sin embargo, en aplicaciones empresariales los errores necesitan transportar datos estructurados.

Por ejemplo, si una validación de entrada falla, el frontend no solo quiere saber que "hubo un error", necesita saber qué campo exacto falló (ej. email) y qué regla se violó (ej. formato inválido). Si solo devolvemos una cadena de texto, la capa superior de nuestra aplicación tendría que parsear el texto del error con expresiones regulares para extraer los metadatos. Esto es extremadamente ineficiente, propenso a errores de formato y acopla la lógica interna a los mensajes de texto visibles al usuario.

Tipos de Errores Comunes en Go

Antes de analizar cómo propagar y envolver errores, es fundamental comprender los dos patrones principales que se utilizan en Go:

  1. Sentinel Errors: Son variables globales predefinidas que representan un estado de error estático y específico. Se definen a nivel de paquete y se comparan directamente por valor.

    • Ejemplos estándar: io.EOF, sql.ErrNoRows.
    • Creación: Generalmente declarados con errors.New (como var ErrNotFound = errors.New("not found")).
  2. Custom Structs (Errores estructurados personalizados): Son estructuras que implementan la interfaz error y contienen campos adicionales para transportar metadatos dinámicos del fallo en tiempo de ejecución (como códigos de estado o parámetros de entrada).

    • Creación: Un struct personalizado que implementa el método Error() (ej. type ValidationError struct { Field string }).

Semáforo en rojo vs. Multa de tránsito
Para entender la diferencia:

  • Sentinel Error es como un semáforo en rojo. Es único, estático y global. No importa quién seas o cuándo pases, la luz roja siempre significa "Alto". En tu código, comparas tu error directamente contra esta señal fija: errors.Is(err, sql.ErrNoRows).
  • Custom Struct es como una multa de tránsito. Contiene datos específicos redactados para tu caso (matrícula, velocidad, hora, costo). No puedes compararla directamente con una plantilla fija; necesitas extraer su información usando errors.As.

El término "centinela" proviene de los guardias militares apostados en una caseta. Su único trabajo es dar una señal fija (como gritar "¡Alerta!" o "¡Fin!") cuando ocurre un suceso concreto.

Errores Personalizados en Go

Para entender esto, piensa en el diagnóstico de fallos en un automóvil. Si la luz de "Fallo de Motor" se enciende, el conductor solo tiene un texto general: "Algo anda mal". Esto equivale a un error básico creado con errors.New.

Sin embargo, el mecánico conecta un escáner OBD-II al coche para extraer el código exacto (como P0300 - fallos de encendido del cilindro). Este código viene con metadatos específicos: el cilindro afectado, las revoluciones del motor al momento del fallo y la temperatura. En Go, un Custom Struct es ese reporte estructurado del escáner que hereda la capacidad de comportarse como un simple error gracias a la interfaz error, pero permitiéndonos acceder a su información detallada.

Conceptos Clave

  • Estructura Personalizada (Custom Struct): Un struct propio donde defines campos para almacenar metadatos (ej. códigos HTTP, códigos de base de datos o listas de validaciones).
  • Type Assertion / Type Switch: Mecanismos clásicos para preguntar si una interfaz error contiene un tipo concreto bajo el capó.
  • Error Wrapping (Envoltorio): La capacidad de agregar contexto a un error existente envolviéndolo con el verbo %w en fmt.Errorf.
  • errors.Is: Función de Go 1.13+ que determina si un error específico o cualquiera de sus errores envueltos coincide con un error centinela.
  • errors.As: Función de Go 1.13+ que permite buscar un tipo de error específico en una cadena de errores envueltos y extraerlo en una variable de destino.

Implementar e Inspeccionar Errores

A continuación, implementaremos un error de validación personalizado y lo examinaremos a través del mecanismo moderno de inspección.

1. Definición del Error Estructurado

Creamos un struct llamado ValidationError e implementamos la interfaz error.

package main

import (
    "errors"
    "fmt"
)

// ValidationError estructura los detalles de un error de validación.
type ValidationError struct {
    Field   string
    Message string
}

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

Enter fullscreen mode Exit fullscreen mode

2. Retorno y Wrapping de Errores

El siguiente código muestra cómo una capa puede validar los datos de un usuario y cómo una capa superior puede envolver el error para añadir contexto (por ejemplo, saber que el fallo ocurrió durante el registro):

func ValidateEmail(email string) error {
    if email == "" {
        return &ValidationError{Field: "email", Message: "cannot be empty"}
    }
    return nil
}

func RegisterUser(email string) error {
    err := ValidateEmail(email)
    if err != nil {
        // Envolvemos el ValidationError usando %w para conservar su identidad
        return fmt.Errorf("failed to register user: %w", err)
    }
    return nil
}

Enter fullscreen mode Exit fullscreen mode

3. Inspección Avanzada con errors.As

Para recuperar los metadatos específicos del error envuelto, utilizamos errors.As:

func main() {
    err := RegisterUser("")
    if err != nil {
        // Declaramos una variable del tipo concreto del error que buscamos
        var valErr *ValidationError

        // errors.As busca ValidationError en toda la cadena de errores envueltos
        if errors.As(err, &valErr) {
            fmt.Printf("Inspection successful! Failed field: %s, Reason: %s\n", 
                valErr.Field, valErr.Message)
        } else {
            fmt.Printf("Occurred another type of error: %s\n", err)
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

¿Qué pasa cuando imprimes el error final?

Para entender cómo se construye el mensaje de error que termina viendo el usuario, podemos descomponer la suma de textos paso a paso desde el origen:

  1. El error de origen (ValidationError):
    Internamente genera su string al llamar a su método Error():
    "validation error on field 'email': cannot be empty"

  2. El error contenedor (fmt.Errorf con %w):
    En RegisterUser, tomas el error anterior y lo inyectas en un nuevo mensaje de contexto:
    "failed to register user: " + [Error de origen]

  3. El resultado final:
    Al imprimir en la función main usando fmt.Println(err):

   failed to register user: validation error on field 'email': cannot be empty

Enter fullscreen mode Exit fullscreen mode

¿Cómo funciona la Cadena de Envolturas (Wrapping Chain) y el método Unwrap?

Conceptualmente, el mecanismo de envoltura en Go funciona exactamente como una lista enlazada simple (singly linked list) de una sola dirección.

En lugar de tener una propiedad física .Next, Go utiliza el método Unwrap() como el enlace para saltar al siguiente error de la cadena. El error raíz original representa el último nodo, el cual devuelve nil al ser desenvuelto.

Así es como se ve la jerarquía de memoria estructurada como nodos de una lista enlazada:

3-resources/notes/mermaid-d4b387b9.png

Cuando ejecutas errors.As(err, &valErr), Go realiza los siguientes pasos de forma automática:

  1. Comprueba si el Error Externo es de tipo *ValidationError. Como no lo es (es un error de formato estándar creado por fmt.Errorf), no puede extraerlo directamente.
  2. Al fallar, Go llama al método Unwrap() del Error Externo. Esto le devuelve el error interno: el puntero a ValidationError.
  3. Go evalúa este error interno. Como coincide con el tipo que declaraste (*ValidationError), la búsqueda tiene éxito, copia los datos en tu variable valErr y retorna true.
  4. Si llamara a Unwrap() una vez más sobre ValidationError, este devolvería nil porque es el origen del fallo y no envuelve a nadie más.

4. Inspección de Errores Centinela Envueltos con errors.Is

A diferencia de errors.As (que busca coincidencias por tipo para extraer metadatos de estructuras), errors.Is busca coincidencias por valor contra un error centinela predefinido.

Imaginemos un flujo multicapa en nuestra aplicación donde un error de la base de datos se envuelve secuencialmente mientras sube por nuestra arquitectura:

package main

import (
    "errors"
    "fmt"
)

// Definimos el error centinela global en la capa de datos
var ErrRecordNotFound = errors.New("database record not found")

// Capa de Infraestructura / Repositorio
func fetchRow() error {
    return ErrRecordNotFound
}

// Capa de Servicio de Negocio
func processUser() error {
    err := fetchRow()
    if err != nil {
        // Envolvemos el error usando %w
        return fmt.Errorf("error in user service: %w", err)
    }
    return nil
}

// Capa del Controlador HTTP / Handler
func apiController() error {
    err := processUser()
    if err != nil {
        // Volvemos a envolver agregando más contexto
        return fmt.Errorf("controller failed to register: %w", err)
    }
    return nil
}

func main() {
    err := apiController()
    if err != nil {
        // Evaluamos si el error original en el fondo de la cadena es ErrRecordNotFound
        if errors.Is(err, ErrRecordNotFound) {
            fmt.Println("Match detected! Redirecting to 404 page...")
        } else {
            fmt.Println("Generic server error:", err)
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

¿Cómo funciona la inspección recursiva bajo el capó?

Cuando ejecutas errors.Is(err, target) o errors.As(err, &target), el runtime de Go realiza una búsqueda secuencial a través de la lista enlazada de errores llamando a las siguientes reglas:

  1. Comparación Directa: Go comprueba si el error actual coincide con el objetivo. Si coincide, devuelve true.
  2. Método Unwrap: Si no hay coincidencia, Go verifica si el error actual implementa el método Unwrap() error.
  3. Paso de Lista: Si Unwrap() devuelve un error interno, Go se desplaza a ese nodo y repite la inspección.
  4. Condición de Parada: La búsqueda se detiene al llegar a un nodo que devuelve nil. Si no hubo coincidencias, retorna false.

Gráficamente, la búsqueda secuencial por la lista enlazada se ve así:

3-resources/notes/mermaid-d2daaff6.png

Casos de Uso Comunes

Tabla de Comparación de Herramientas de Inspección

Herramienta Cuándo Usarla Ejemplo de Código Soporta Wrapping ¿Es Recomendable?
Comparación Directa (==) Errores centinela simples (sin envolver) if err == sql.ErrNoRows No No (Evitar en código moderno)
errors.Is Comprobar coincidencia de un Sentinel Error errors.Is(err, sql.ErrNoRows) (Buena práctica)
Type Assertion Extraer estructuras directamente (sin envolver) valErr, ok := err.(ValidationError) No No (Evitar si puede haber wrapping)
errors.As Extraer metadatos de un Custom Struct errors.As(err, &valErr) (Buena práctica)

¿Cuándo elegir Sentinel Error frente a Custom Struct?

Para decidir qué camino tomar, analiza qué necesita hacer el consumidor del error para gestionar el fallo:

  • Elige un Sentinel Error cuando la capa superior solo necesita una confirmación binaria (saber si ocurrió esa situación específica o no) para tomar decisiones de control de flujo. No necesitas añadir información variable sobre el fallo.

    • Caso de uso real (Aplicar un cupón en un E-commerce): Cuando un usuario intenta aplicar un cupón en el checkout, el repositorio busca el código en la base de datos. Si el código no existe, el repositorio devuelve un Sentinel Error: ErrCouponNotFound. El servicio que llama al repositorio solo necesita saber de forma binaria si el cupón existe o no. Si el error es ErrCouponNotFound, el sistema responde al usuario con un mensaje amigable indicando que el cupón no es válido. No se requiere información dinámica (como el ID del cupón o la hora del servidor) dentro de la estructura del error para tomar esta decisión.
    // Definición en el repositorio
    var ErrCouponNotFound = errors.New("coupon not found in database")
    
    // Verificación en el controlador/handler HTTP usando errors.Is
    if errors.Is(err, repository.ErrCouponNotFound) {
        http.Error(w, "El cupon no es valido", http.StatusNotFound)
        return
    }
    
  • Elige un Custom Struct cuando la capa superior necesita datos estructurados concretos para poder recuperarse del error o formular una respuesta específica para el cliente.

    • Caso de uso real (Procesar un pago con Stripe/Gateway): Cuando una pasarela de pago rechaza una tarjeta, no basta con decir "falló". La pasarela devuelve información dinámica: la razón del rechazo (ej. fondos insuficientes, tarjeta expirada, CVV incorrecto) y si el error es temporal (es decir, si vale la pena reintentar el cobro). El sistema de checkout necesita acceder a estos metadatos para tomar una decisión: si el error indica que es un fallo temporal (IsRetryable == true), el sistema puede programar un reintento automático. Si el error es definitivo (ej. DeclineCode == "insufficient_funds"), el sistema aborta la compra inmediatamente y le muestra al usuario la causa específica para que intente con otra tarjeta.
    // Definición del error estructurado
    type PaymentGatewayError struct {
        DeclineCode string
        IsRetryable bool
    }
    
    func (e *PaymentGatewayError) Error() string {
        return fmt.Sprintf("payment failed: code=%s, retryable=%t", e.DeclineCode, e.IsRetryable)
    }
    
    // Extracción y toma de decisiones en el controlador usando errors.As
    var payErr *PaymentGatewayError
    if errors.As(err, &payErr) {
        if payErr.IsRetryable {
            // Reintentar transacción o sugerir reintento al usuario
            http.Error(w, "Error temporal de red en el banco. Reintente.", http.StatusServiceUnavailable)
            return
        }
        // Mostrar la razón específica del rechazo del banco
        http.Error(w, fmt.Sprintf("Pago rechazado: %s", payErr.DeclineCode), http.StatusPaymentRequired)
        return
    }
    

Para no morir en el intento y consejos que no pediste

  • El peligro de comparar directamente con == en Go moderno: Si tus dependencias externas o funciones envuelven los errores usando %w (práctica estándar hoy en día), cualquier comparación directa err == ErrCentinela fallará. Usa siempre errors.Is(err, ErrCentinela).
  • Cuidado con los punteros nil en interfaces de error: En Go, una interfaz es nula si y solo si su tipo y su valor son ambos nulos (nil). Si devuelves una variable de tipo puntero a tu struct personalizado (ej. *ValidationError) que vale nil, la interfaz devuelta no será nula, ya que contendrá un tipo asignado (*ValidationError) y un valor nil. Por lo tanto, cualquier validación del estilo if err != nil evaluará a true (verdadero), creyendo erróneamente que ocurrió un error.

Código incorrecto (simula un error inexistente):

  func Validate() error {
      var err *ValidationError = nil
      return err // Devuelve una interfaz con Tipo=*ValidationError y Valor=nil. (err != nil) es true.
  }

Enter fullscreen mode Exit fullscreen mode

Código correcto (solución 1: retornar nil directamente):

  func Validate(email string) error {
      if email == "" {
          return &ValidationError{Field: "email", Message: "cannot be empty"}
      }
      return nil // Retorna nil explícito. La interfaz tendrá Tipo=nil y Valor=nil.
  }

Enter fullscreen mode Exit fullscreen mode

Código correcto (solución 2: comprobar nulidad antes de retornar):

  func Validate() error {
      var err *ValidationError = nil
      // ... lógica de validación ...
      if err == nil {
          return nil // Retorna la interfaz nula limpia
      }
      return err
  }

Enter fullscreen mode Exit fullscreen mode

  • Cuidado con la fuga de abstracciones al usar %w: Si envuelves un error interno de infraestructura (como sql.ErrNoRows) usando %w en tu repositorio, y este error viaja hasta la API de tu paquete público, los clientes de tu biblioteca podrían comenzar a depender de errors.Is(err, sql.ErrNoRows). Si en el futuro decides cambiar tu base de datos de relacional a NoSQL (por ejemplo, MongoDB), romperás el código de tus clientes porque ya no estarás devolviendo un error que envuelve a sql.ErrNoRows. Como regla general, envuelve usando %w solo aquello que sea parte del contrato público de tu módulo o traduce los errores de infraestructura a errores semánticos en las fronteras.

Conclusiones del Tema

Las herramientas de Go para gestionar tipos de errores estructurados e inspeccionar cadenas de manera no intrusiva facilitan la separación de responsabilidades entre las capas lógicas de tu sistema. El dominio puede generar errores ricos en metadatos y la infraestructura de red (como HTTP o gRPC) puede deserializarlos sin acoplamientos rígidos.

Puntos clave a recordar:

  • Implementa la interfaz error en tus structs para transportar metadatos avanzados.
  • Usa fmt.Errorf("...: %w", err) para propagar errores agregando contexto sin perder su tipo original.
  • Prefiere siempre errors.Is y errors.As sobre comparaciones tradicionales para que tu código sea compatible con el anidamiento.