Clase 5: Manejo de errores y excepciones
Objetivos
- Implementar un manejo adecuado de errores y excepciones en Go para garantizar la robustez y confiabilidad del programa.
- Utilizar las sentencias "defer" y "panic/recover" de manera efectiva en Go para mejorar la legibilidad y la gestión de errores en el código.
- Crear errores personalizados en Go para proporcionar información específica y detallada sobre situaciones excepcionales o fallos en el programa.
Contenido de la clase:
1. Tratamiento de errores y excepciones en Go
La gestión de errores o tratamiento de errores es una de las cosas con la que los programadores nos encontramos todos los días y hay que darle la importancia que merece.
Mientras escribes un programa, se ha de tener en cuenta las distintas formas en que se puede producir errores en los programas y debemos administrarlos correctamente. Los usuarios no necesitan ver un error de seguimiento de la pila largo y confuso. Es mejor si ven información significativa sobre lo que salió mal.
- Go, un lenguaje sin exepciones
A diferencia de otros lenguajes de programación como Java o Python, no existe un sistema de excepciones tradicional. En su lugar, Go utiliza el enfoque de manejo de errores basado en el tipo
errory las sentenciaspanicyrecover. para administrar un comportamiento inesperado en los programas.La razón principal detrás de esta decisión de diseño en Go fue simplificar y fomentar un código más limpio y legible. En lugar de utilizar bloques de
try-catchpara manejar excepciones, Go se enfoca en el manejo explícito de errores de tipoerror.Los valores de tipo
errorpueden ser retornados por funciones y verificados utilizando la declaración condicionalif. Esto permite un manejo claro y explícito de los errores en el código.
package main
import (
"fmt"
"strconv"
)
func main() {
var age string
fmt.Println("Ingresa tu edad: ")
fmt.Scanln(&age)
// Convertimos string a entero
// la función strconv.Atoi devuelve dos valores, un entero y un error
newAge, err := strconv.Atoi(age)
// Si el error es nil no existe un error
// nil == null
if err != nil {
fmt.Println("Ocurrió un error: ")
fmt.Println(err)
} else {
fmt.Println("Tu edad en 10 años será: ", newAge+10)
}
}2. Uso de la sentencia “defer” y “panic/recover”
- Defer
Defer es una característica que permite que Go aplace la ejecución de una función. Este puede ser por ejemplo cuando otra función termina de ejecutarse.
El aplazamiento de funciones es generalmente utilizado para realizar tareas de limpieza, una vez que se han completado las actividades a realizar por parte del algoritmo, como cerrar conexiones a bases de datos, borrar archivos temporales, limpiar el caché, liberar la memoria, etc.
Supongamos ahora que deseamos conectarnos a una base de datos, y queremos que después de todas las operaciones se realice automáticamente la desconexión, independientemente del flujo de trabajo.
package main
import "fmt"
func conectar() {
fmt.Println("Se ha conectado a la base de datos")
}
func desconectar() {
fmt.Println("Se ha desconectado de la base de datos")
}
func leer() {
fmt.Println("Se han leido los registros de la base de datos")
}
func actualizar() {
fmt.Println("Se han actalizado registros de la base de datos")
}
func main() {
conectar() // Se ejecuta 1°
defer desconectar() // Se ejecuta 4° [último]
leer() // Se ejecuta 2°
actualizar() // Se ejecuta 3°
}- Panic
En Go existe una función llamda
panicque detiene el flujo de un programa e inicia un proceso de pánico. No es quizás una buena idea su uso, debido a que detener la ejecución del programa no ofrece ninguna salida posible.No se recomienda el uso de
panical menos que exista una serie de condiciones en las cuales el sistema no se pueda recuperar tales como:- Si el sistema continua su ejecución, más problemas serán generados, por lo que hay que detenerlo inmediatamente.
- Existe un escenario que no ha sido cubierto y no se puede manejar, por ende hay que evitarlo.
package main
import "fmt"
func main() {
var availableMoney float64 = 1000
var cash float64
for {
fmt.Println("¿Cuánto dinero deseas retirar?")
fmt.Scanln(&cash)
if availableMoney < cash {
// fmt.Println("No hay fondos disponibles")
panic("Su tarjeta ha sido bloqueada")
}
availableMoney -= cash
fmt.Printf("Se retiró %.2f, saldo disponible %.2f \n", cash, availableMoney)
}
}- Recover
En Go, la función
recoverse utiliza en combinación con la sentenciadeferpara capturar y manejar unpanicocurrido durante la ejecución de un programa.Cuando se produce un
panicen una función, normalmente la ejecución se detiene. La funciónrecoverpermite recuperarse delpanicy continuar con la ejecución del programa en un estado controlado.
- Refactorizamos
package main
import "fmt"
func myAccount() {
var availableMoney float64 = 1000
var cash float64
for {
fmt.Println("¿Cuánto dinero deseas retirar?")
fmt.Scanln(&cash)
if availableMoney < cash {
panic("blockCard")
}
availableMoney -= cash
fmt.Printf("Se retiró %.2f, saldo disponible %.2f \n", cash, availableMoney)
}
}
func main() {
myAccount()
fmt.Println("Finalizó la operación")
}- Creamos la función handlePanic()
func blockCard() {
r := recover()
if r != nil {
fmt.Println(r)
fmt.Println("Bloquea la tarjeta")
fmt.Println("Contacta al cliente por teléfono")
}
}- Utilizamos la función handlePanic() en la función myAccount()
func myAccount() {
defer handlePanic()
var availableMoney float64 = 1000
var cash float64
for {
fmt.Println("¿Cuánto dinero deseas retirar?")
fmt.Scanln(&cash)
if availableMoney < cash {
// fmt.Println("No hay fondos disponibles")
panic("blockCard")
}
availableMoney -= cash
fmt.Printf("Se retiró %.2f, saldo disponible %.2f \n", cash, availableMoney)
}
}- Código Final
package main
import "fmt"
func handlePanic() {
r := recover()
if r != nil {
fmt.Println(r)
fmt.Println("Bloquea la tarjeta")
fmt.Println("Contacta al cliente por teléfono")
}
}
func myAccount() {
defer handlePanic()
var availableMoney float64 = 1000
var cash float64
for {
fmt.Println("¿Cuánto dinero deseas retirar?")
fmt.Scanln(&cash)
if availableMoney < cash {
panic("blockCard")
}
availableMoney -= cash
fmt.Printf("Se retiró %.2f, saldo disponible %.2f \n", cash, availableMoney)
}
}
func main() {
myAccount()
fmt.Println("Finalizó la transacción")
}3. Creación de errores personalizados
Go ofrece dos métodos para crear errores en la biblioteca estándar: errors.New y fmt.Errorf.
La creación de errores personalizados puede ser útil en varias situaciones
- Comunicar información específica: Al crear tus propios errores personalizados, puedes proporcionar mensajes de error más descriptivos y relevantes para tu aplicación. Esto ayuda a los desarrolladores y usuarios a comprender mejor la causa del error y cómo solucionarlo.
- Tratamiento de errores específicos: Puedes tener un mayor control sobre cómo se manejan y se progagan en tu programa. Puedes utilizar distintos tipos de errores personalizados para representar situaciones diferentes y tomar decisiones específicas según el tipo de error.
- Mejorar la legibilidad y mantenibilidad del codigo: Puedes darle nombres descriptivos y utilizamos en tu código de manera semántica, lo que facilita la compresión y el seguimiento del flujo del programa.
- Ejemplo básico
La función errors.New es una función proporcionada por el paquete errors en Go. Su propósito es crear un nuevo error con un mensaje de texto específico.
Esta función recibe como argumento un string que representa el mensaje de error que se desea asociar al nuevo error creado. Devuelve un valor de tipo error, que es una interfaz en Go utilizada para representar errores.
package main
import (
"errors"
"fmt"
)
func main() {
// err := fmt.Errorf("Error generado con fmt")
err := errors.New("Error generado durante la ejecución")
if err != nil {
fmt.Println(err) // Error generado durante la ejecución
fmt.Printf("%T", err) // *errors.errorString
}
}- Ejemplo Intermedio
Utilizamos errors.New en una función que retorna dos valores de tipo int, error
Creamos una función que retorna la división de dos números enteros. Sí el divisor es 0, la función debe retornar un error.
package main
import (
"errors"
"fmt"
)
func divide(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("División por cero no es posible")
}
return a / b, nil
}
func main() {
result, err := divide(10, 0)
if err != nil {
fmt.Println("Error:", err)
} else {
fmt.Println("Resultado:", result)
}
}- Ejemplo Avanzado
La diferencia entre usar errors.New y crear tu porpio tipo de error con un método Error() radica en la flexibilidad y personalización que deseas tener al crear y manejar errores en Go.
package main
import (
"fmt"
)
/*
Interface implicito - No es necesario declarar esta interface
type error interface {
Error() string
}
*/
type MyError struct {
message string
code int
}
func (e MyError) Error() string {
return fmt.Sprintf("Message: %s.\nStatusCode: %d", e.message, e.code)
}
func divide(a, b int) (int, error) {
if b == 0 {
return 0, MyError{message: "División por cero no es posible", code: 400}
}
return a / b, nil
}
func main() {
result, err := divide(10, 0)
if err != nil {
fmt.Println("Error:", err)
} else {
fmt.Println("Resultado:", result)
}
}4. Tarea
- Escribe una función llamada
SearchNameque reciba un slice de nombres y un nombre objetivo. La función debe buscar el nombre objetivo en el slice y devolver su índice si se encuentra. Si el nombre no se encuentra, la función debe devolver un error personalizado del tipoNotFound.
// Solución
package main
import "fmt"
type NotFound struct {
message string
}
func (e *NotFound) Error() string {
return fmt.Sprintf("Error: el nombre '%s' no se encontró en la lista", e.message)
}
func SearchName(list []string, name string) (int, error) {
for i, nombre := range list {
if nombre == name {
return i, nil
}
}
return 0, &NotFound{message: name}
}
func main() {
list := []string{"Juan", "María", "Pedro", "Ana"}
name := "Pedro"
index, err := SearchName(list, name)
if err != nil {
fmt.Println(err)
} else {
fmt.Printf("El nombre '%s' se encontró en el índice %d\n", name, index)
}
}- Escribe una función llamada
validatePositiveque reciba un número entero y verifique si es un número positivo. Si el número es positivo, la función debe retornartrue. Si el número es negativo o cero, la función debe retornar un error indicando que el número no es válido.
// Solución
package main
import (
"errors"
"fmt"
)
func validatePositive(num int) (bool, error) {
if num <= 0 {
return false, errors.New("El número no es válido")
}
return true, nil
}
func main() {
number := 42
valid, err := validatePositive(number)
if err != nil {
fmt.Println("Error:", err)
} else {
fmt.Println("El número es válido:", valid)
}
}5. Recursos
- Manejo de Errores: https://apuntes.de/golang/manejo-de-errores/
- Como manejar errores en Go: https://www.digitalocean.com/community/tutorials/handling-errors-in-go-es
- Gestión de errores en Golang: https://blog.friendsofgo.tech/posts/gestion-de-errores-en-golang/
- Manejo de errores en go: https://steemit.com/cervantes/@orlmicron/manejo-de-errores-en-go-golang