Manejando los errores de Go. Simulando el Try Catch.

Eglis Alvarez
5 min readJan 7, 2023

--

Para los nostálgicos que aprendimos otros lenguajes de programación antes de Golang, tal como Python, C# o Java, algo que echamos bastante en falta son las Excepciones en Go. Podemos afirmar que el manejo de errores en Go no se asemeja al de otros lenguajes que nos resultan familiares. A continuación vamos a revisar e ilustrar con ejemplos cómo se manejan los errores en Golang.

Error vs Panic

En Golang podemos distinguir dos categorías de errores, los errors y los panics. Los primeros, podemos decir, son anticipables y por tanto manejables. Los últimos constituyen una segunda categoría de error que no son anticipables y pueden ser considerados fatales, por lo tanto provocarán que el programa finalice de manera espontánea (Tal como una Excepción).

Para tratar con los errores en Golang tenemos el tipo built-in errors, el cual es una interfaz.

type error interface {
Error() string
}

Recover y las funciones diferidas

¿Qué son las funciones diferidas y como me ayudan a lidiar con los errores de tipo panic?

Debemos saber que una instrucción precedida por la palabra clave defer no se invocará hasta el final de la función en la cual se utilizó el defer. Estas instrucciones se ejecutarán incluso cuando se esté en presencia de un panic, por esta razón nos sirven para protegernos de los errores en tiempo de ejecución y tratarlos de una manera controlada, tal como podríamos hacer con un try catch en C#.

Más fácil con ejemplos. Simulando el Try Catch.

Para facilitarnos el trabajo de manejar los errores nos hemos creado una struct la cual implementa la interfaz error al implementar la función Error(). Esta función la emplearemos para los errores predecibles y manejables.

También hemos implementado una función Throw (por nostalgia con C#) la cual será utilizada para errores impredecibles.

Y una función Catch que nos servirá para recuperarnos de los panic (errores impredecibles) empleando una función diferida y el método recover().

package main

import (
"fmt"
"log"
)

type RuntimeError struct {
err_code int
err_message string
}

func (error RuntimeError) Error() string {
return fmt.Sprintf("Error Code: %d. Message: %v", error.err_code, error.err_message)
}

func (error RuntimeError) Trhow() {
panic(fmt.Sprintf("Error Code: %d. Message: %v", error.err_code, error.err_message))
}

func (error RuntimeError) Catch(f func()) {
defer func() {
if r := recover(); r != nil {
log.Println("Exception Caught:", r)
}
}()
f()
}

A continuación vamos a utilizar nuestro tipo RuntimeError, para ello hemos creado una función que valida con una expresión regular si un número de teléfono tiene un formato válido. Dicha función retorna un booleano y un error. En caso de no existir error el valor correspondiente irá en nil.

Al comprobar que no se trata de un número colombiano (error predecible) se retornará un error.

return false, RuntimeError{2, err_message}

Y si se presentan errores inesperados durante el parseo de la regex se invocará a la función Throw(), la cual “disparará” un panic.

matched, err := regexp.MatchString(`/+[0-9]{10}`, cell_number)
if err != nil {
err_message := "Unexpected error evaluating regular expression. " + err.Error()
RuntimeError{1, err_message}.Trhow()
}

Acá podemos ver la función completa:

func validateCellPhone(cell_number string) (bool, error) {
//Dealing with expected errors (Error)
code := cell_number[0:3]
if code != "+57" {
err_message := "The phone number is not a Colombian number. "
return false, RuntimeError{2, err_message}
}
//Dealing with unexpected errors (Panic)
matched, err := regexp.MatchString(`/+[0-9]{10}`, cell_number)
//matched, err := regexp.MatchString(`^\/(?!\/)(.*?)`, cell_number)
if err != nil {
err_message := "Unexpected error evaluating regular expression. " + err.Error()
RuntimeError{1, err_message}.Trhow()
}
return matched, nil
}

Ahora invoquémosla desde la función main().

func main() {

//Catching errors
RuntimeError{}.Catch(func() {
result, err_val := validateCellPhone("+1794578454")
if err_val == nil {
fmt.Println("El resultado de la validacion es: ", result)
} else {
fmt.Println("Se produjo un error durante la validación. ", err_val)
}

})
fmt.Println("Recovered from error")
}

La salida de dicho código es:

Se produjo un error durante la validación. Error Code: 2. Message: The phone number is not a Colombian number.
Recovered from error

Como puede apreciarse, hemos anticipado un posible error, en este caso el número de teléfono no es un número colombiano, y lo hemos manejado retornando un error, el cual, es capturado en la función Catch e imprime el mensaje “Se produjo un error en la validación .” seguido del mensaje de error que enviamos en el return: return false, RuntimeError{2, err_message}

El anterior es un ejemplo de cómo podemos lidiar con los errores. Miremos a continuación que hacer para sopreponernos a un panic y cómo podemos nosotros lanzar también uno.

Solo con propósito de ilustrar el punto, cambiemos la expresión regular por una que sabemos incorrecta.

matched, err := regexp.MatchString(`^\/(?!\/)(.*?)`, cell_number)
if err != nil {
err_message := "Unexpected error evaluating regular expression. " + err.Error()
RuntimeError{1, err_message}.Trhow()
}
return matched, nil

Al intentar parsear la regex mal formada se producirá un error el cual capturamos en la variable err y al constatar que efectivamente err no se encuentra vacía (err != nil) podemos hacer algo para lidiar con el error, en este caso queremos tener un final dramático, así que, usamos nuestra función Throw() para disparar un panic.

Si la invocación a la función que dispara el panic no estuviese dentro del Catch, nuestro programa habría terminado de manera abrupta y caótica, pero al utilizar el recover y la función diferida que explicamos anteriormente, manejamos nuestro panic y nos recuperamos.

func main() {

//Catching errors
RuntimeError{}.Catch(func() {
result, err_val := validateCellPhone("+5794578454")
if err_val == nil {
fmt.Println("El resultado de la validacion es: ", result)
} else {
fmt.Println("Se produjo un error durante la validación. ", err_val)
}

})
fmt.Println("Recovered from error")
}

// Dealing with runtime errors
func validateCellPhone(cell_number string) (bool, error) {
//Dealing with expected errors (Error)
code := cell_number[0:3]
if code != "+57" {
err_message := "The phone number is not a Colombian number. "
return false, RuntimeError{2, err_message}

}
//Dealing with unexpected errors (Panic)
//matched, err := regexp.MatchString(`/+[0-9]{10}`, cell_number)
matched, err := regexp.MatchString(`^\/(?!\/)(.*?)`, cell_number)
if err != nil {
err_message := "Unexpected error evaluating regular expression. " + err.Error()
RuntimeError{1, err_message}.Trhow()
}
return matched, nil
}

Como se puede apreciar en la salida, luego de lidiar con el panic continuamos ejecutando nuestro código sin mayor dificultad, imprimiéndose el mensaje que hemos puesto fuera del Catch.

2023/01/07 17:02:01 Exception Caught: Error Code: 1. Message: Unexpected error evaluating regular expression. error parsing regexp: invalid or unsupported Perl syntax: `(?!`
Recovered from error

Conclusión.

El manejo de errores de Golang, no es el más elegante ni el más sencillo, pero hemos comprobado que es posible e incluso, con poco esfuerzo podemos realizarlo emulando el try catch de los lenguajes más tradicionales.

--

--