Go Best Practices - Manejo de errores

Este es el primer artículo de una serie de lecciones que aprendí durante los dos años que trabajé con Go en producción. Estamos ejecutando una buena cantidad de servicios de Go en producción en Saltside Technologies (psst, estoy contratando para varios puestos en Bangalore para Saltside) y también dirijo mi propio negocio donde Go es una parte integral.

Cubriremos una amplia gama de temas, grandes y pequeños.

El primer tema que quería cubrir en esta serie es el manejo de errores. A menudo está causando confusión y molestia para los nuevos desarrolladores de Go.

Algunos antecedentes: la interfaz de error

Solo para que estemos en la misma página. Como puede saber, un error en Go es simplemente cualquier cosa que implemente la interfaz de error. Así es como se ve la definición de la interfaz:

interfaz de error de tipo {
    Error () cadena
}

Por lo tanto, cualquier cosa que implemente el método de cadena Error () puede usarse como un error.

Comprobando errores

Uso de estructuras de error y verificación de tipos

Cuando comencé a escribir Ir, a menudo hacía comparaciones de cadenas de mensajes de error para ver cuál era el tipo de error (sí, es vergonzoso pensar en ello, pero a veces es necesario mirar hacia atrás para seguir adelante).

Un mejor enfoque es usar tipos de error. Por lo tanto, puede (por supuesto) crear estructuras que implementen la interfaz de error y luego hacer una comparación de tipos en una declaración de cambio.

Aquí hay un ejemplo de implementación de error.

escriba ErrZeroDivision struct {
    cadena de mensaje
}
func NewErrZeroDivision (cadena de mensaje) * ErrZeroDivision {
    return & ErrZeroDivision {
        mensaje: mensaje,
    }
}
Func (e * ErrZeroDivision) Error () cadena {
    devolver mensaje electrónico
}

Ahora este error se puede usar así.

func main () {
    resultado, err: = divide (1.0, 0.0)
    if err! = nil {
        cambiar err. (tipo) {
        caso * ErrZeroDivision:
            fmt.Println (err.Error ())
        defecto:
            fmt.Println ("¿Qué demonios acaba de pasar?")
        }
    }
    fmt.Println (resultado)
}
func divide (a, b float64) (float64, error) {
    si b == 0.0 {
        return 0.0, NewErrZeroDivision ("No se puede dividir por cero")
    }
    devolver a / b, nulo
}

Aquí está el enlace Go Play para ver el ejemplo completo. Observe el patrón de error err. (Tipo), que permite verificar diferentes tipos de error en lugar de otra cosa (como la comparación de cadenas o algo similar).

Usando el paquete de errores y la comparación directa

El enfoque anterior se puede manejar alternativamente utilizando el paquete de errores. Este enfoque es recomendable para verificaciones de errores dentro del paquete donde necesita una representación rápida de errores.

var errNotFound = errors.New ("Elemento no encontrado")
func main () {
    err: = getItem (123) // Esto arrojaría errNotFound
    if err! = nil {
        cambiar err {
        caso errNotFound:
            log.Println ("Elemento solicitado no encontrado")
        defecto:
            log.Println ("Se produjo un error desconocido")
        }
    }
}

Este enfoque es menos bueno cuando necesita objetos de error más complejos con, p. códigos de error, etc. En ese caso, debe crear su propio tipo que implemente la interfaz de error.

Manejo inmediato de errores

A veces me encuentro con un código como el siguiente (pero generalmente con más pelusa ...):

Func ejemplo1 () error {
    err: = call1 ()
    volver err
}

El punto aquí es que el error no se maneja de inmediato. Este es un enfoque frágil ya que alguien puede insertar código entre err: = call1 () y return err, lo que rompería la intención, ya que eso puede ocultar el primer error. Dos enfoques alternativos:

// Contraer la devolución y el error.
Func ejemplo2 () error {
    devolver llamada1 ()
}
// Hacer un manejo explícito de errores justo después de la llamada.
error de ejemplo 3 () de func {
    err: = call1 ()
    if err! = nil {
        volver err
    }
    volver nulo
}

Los dos enfoques anteriores están bien conmigo. Logran lo mismo, que es; si alguien necesita agregar algo después de call1 (), debe encargarse del manejo de errores.

Eso es todo por hoy

Estén atentos para el próximo artículo sobre Go Best Practices. Ir fuerte :).

func main () {
    err: = readArticle ("Go Best Practices - Manejo de errores")
    if err! = nil {
        ping ("@ sebdah")
    }
}