Por lo general, los programadores suelen intentar escribir software perfecto y, por lo general, no lo logran.
Las cosas salen mal, de forma inesperada, y necesitamos poder lidiar con eso.
Algunos diseñadores de lenguajes creen que la prioridad es detectar un error lo más rápido posible y luego terminar la ejecución con un mensaje informativo que ayude a depurar.
Los lenguajes de ciencia de datos tienden a adoptar un enfoque más matizado. Algunos errores son tan graves que es necesario terminar de inmediato, pero a menudo es mejor señalar un problema como algo que se resolverá más tarde y continuar con la ejecución.
Vimos en el concepto Nada que Julia ofrece varios marcadores de posición para valores problemáticos: missing, NaN e Inf.
Que estos sean un mejor enfoque que terminar el programa en una situación concreta es algo que queda a criterio de quien programa.
Una aclaración sobre la nomenclatura antes de entrar en detalles: la documentación de Julia trata las palabras «error» y «excepción» como prácticamente intercambiables. El contenido que sigue puede ser igual de inconsistente.
A esta altura del temario, ya habrás visto muchos mensajes de error de Julia. Por ejemplo:
julia> Int(3.14)
ERROR: InexactError: Int64(3.14)
Intentar convertir un número de punto flotante en un entero implica una pérdida de precisión, así que obtenemos un InexactError.
InexactError es un tipo, uno de los varios (actualmente 25) que Julia incluye de serie.
Todos son subtipos de Exception:
julia> supertype(InexactError)
Exception
throw()Puede que algunos de los tipos de error estándar te resulten útiles para generarlos en tu propio código.
Como todos los tipos concretos, los errores tienen constructores. Aceptan una variedad de argumentos, así que consulta la documentación del que quieras usar.
julia> DomainError(42, "out of range")
DomainError(42, "out of range")
Para usar el error, envuelve el constructor en una función throw():
julia> throw(DomainError(42, "out of range"))
ERROR: DomainError with 42:
out of range
error()Para un enfoque rápido y sin complicaciones, la función error() puede resultar cómoda.
Toma como argumento un string (o los componentes de un string):
julia> happy = false;
julia> happy || error("😞 something went wrong")
ERROR: 😞 something went wrong
En principio, crear nuevos tipos de error es muy fácil.
Basta con agregar otro subtipo de Exception:
julia> struct MyError <: Exception end
julia> throw(MyError)
ERROR: MyError
La idea básica de una aserción es «esta afirmación debería ser cierta, así que protesta a gritos si es falsa». Su utilidad está principalmente durante la depuración, ya que el código de producción nunca debería fallar una aserción.
Vimos en el concepto Tipos que podemos agregar aserciones de tipo, por ejemplo para comprobar el tipo de retorno de una función.
julia> 42::Number
42
julia> "two"::Number
ERROR: TypeError: in typeassert, expected Number, got a value of type String
De forma más general, la macro @assert nos permite comprobar cualquier expresión que se evalúe como un Boolean:
julia> n = 22;
julia> @assert isodd(n) "n must be odd"
ERROR: AssertionError: n must be odd
try...catch
Algunos errores son necesariamente fatales, pero a menudo esperamos que el programa se recupere con elegancia.
De forma predeterminada, un error termina de inmediato la función actual, y el error (con cualquier mensaje informativo) se pasa a la función que hizo la llamada.
Esto continúa hacia arriba por la pila de llamadas, hasta que el código de nivel superior termina con un mensaje de error.
En cualquier etapa, el error se puede interceptar con un bloque try...catch que intenta manejarlo.
julia> n = -1;
julia> try
log_n = log(n)
catch problem
if problem isa DomainError # number out of range
# See next section for more on @warn and @info
@warn "you may have supplied a negative real number: $n"
@info "trying with complex argument"
log_n = log(Complex(n)) # fallback calculation
elseif problem isa MethodError # no idea what n is
@error "please supply a valid argument"
else
rethrow() # the error could be anything else
end
end
┌ Warning: you may have supplied a negative real number: -1
└ @ Main REPL[3]:5
[ Info: trying with complex argument
0.0 + 3.141592653589793im # success
En el ejemplo anterior, log(n) necesita que n sea un valor real positivo o cualquier valor complejo.
El try ... catch atrapa los problemas con valores reales negativos y devuelve la respuesta compleja correcta iπ en notación matemática.
Si proporcionas, por ejemplo, un argumento string, no hay forma de recuperarse salvo pedirle al usuario que lo corrija.
Como último recurso general, agregamos rethrow() para cualquier cosa que no sea ni DomainError ni MethodError.
Nota: A veces un try...catch es justo lo que necesitas, pero evita usarlo en exceso.
Si en su lugar puedes usar un bloque if...else, tendrá un rendimiento mucho mejor que atrapar excepciones.
Ten en cuenta que la función error(), que vimos antes, no debe confundirse con la macro @error.
La función genera una excepción, que se propagará hacia arriba por la pila de llamadas a menos que se atrape.
La macro @error, junto con sus equivalentes @debug, @info y @warn, forma parte del módulo Logging y está pensada para generar mensajes informativos sin alterar el flujo del programa.
De forma predeterminada, la salida va a la terminal (codificada por colores según la gravedad), aunque en una aplicación real hay muchas otras posibilidades.
julia> @warn "Something looks not quite right"
┌ Warning: Something looks not quite right
└ @ Main REPL[55]:1
julia> @error "Panic!"
┌ Error: Panic!
└ @ Main REPL[56]:1
Consulta también el ejemplo anterior, en la sección try...catch.
Elena es la nueva gerente de calidad de una fábrica de periódicos. Como acaba de llegar a la empresa, ha decidido revisar algunos de los procesos de la fábrica para ver qué se podría mejorar. Descubrió que los técnicos hacen muchos controles de calidad a mano. Ve una buena oportunidad para automatizar y te pide a ti, desarrollador freelance, que crees un programa para monitorear algunas de las máquinas.
Tu primera misión es escribir un programa que monitoree el nivel de humedad de la sala de producción. Ya hay un sensor conectado al software de la empresa que devuelve periódicamente el porcentaje de humedad de la sala.
Necesitas implementar una función en el software que lance un error si el porcentaje de humedad es demasiado alto.
Si la humedad está en un nivel aceptable, se agregará un log de Info.
La función debe llamarse humiditycheck y recibir el porcentaje de humedad como argumento.
Debes detenerte con una ErrorException (el mensaje exacto no importa, pero debe contener el nivel de humedad medido) si el porcentaje supera el 70%.
De lo contrario, agrega un log de Info con el mensaje "humidity level check passed: h%", donde h es el porcentaje de humedad.
julia> humiditycheck(60)
[ Info: humidity level check passed: 60%
julia> humiditycheck(100)
ERROR: humidity check failed: 100%
Elena está muy contenta con tu primera tarea y te pide que te encargues de monitorear la temperatura de las máquinas. Mientras charlas con un técnico, Greg, este te cuenta que si la temperatura de una máquina supera los 500°C, los técnicos empiezan a preocuparse por el sobrecalentamiento.
La máquina está equipada con un sensor que mide su temperatura interna. Debes saber que el sensor es muy sensible y se rompe con frecuencia. En ese caso, los técnicos tendrán que cambiarlo.
Tu trabajo es implementar una función temperaturecheck que reciba la temperatura como argumento y que agregue un log si todo está bien, o lance un error si el sensor está roto o si la máquina empieza a sobrecalentarse.
Como más adelante necesitarás reaccionar de forma distinta según el error, necesitas un mecanismo que te permita diferenciar los dos tipos de errores.
nothing.
En ese caso, debes detenerte con un ArgumentError (el mensaje no importa).DomainError que incluya la temperatura medida."temperature check passed: t °C", donde t es la temperatura.julia> temperaturecheck(nothing)
ERROR: ArgumentError: sensor is broken
julia> temperaturecheck(800)
ERROR: DomainError with 800:
"overheating detected"
julia> temperaturecheck(500)
[ Info: temperature check passed: 500 °C
Para la siguiente tarea, necesitarás definir un error más general que abarque todo.
Los detalles de implementación no importan más allá de que sea un error y de que su nombre sea MachineError.
Puedes incluir campos y mensajes como te resulte útil.
Ahora que tu máquina puede detectar errores y tienes un error de máquina personalizado, agrega una función contenedora que informe cómo funciona todo. Además de devolver los logs de las funciones anteriores, esta función contenedora también tendrá que agregar logs según los tipos de fallos que ocurran.
ErrorException, se debe agregar un log de Error con el mensaje "humidity level check failed: h%", donde h es el porcentaje de humedad.ArgumentError, se debe agregar un log de Warn con el mensaje "sensor is broken".DomainError, se debe agregar un log de Error con el mensaje "overheating detected: t °C", donde t es la temperatura.MachineError después de agregar los logs.humiditycheck y temperaturecheck.Implementa una función machinemonitor() que reciba la humedad y la temperatura como argumentos.
julia> machinemonitor(42, 450)
[ Info: humidity level check passed: 42%
[ Info: temperature check passed: 450 °C
julia> machinemonitor(42, 550)
[ Info: humidity level check passed: 42%
┌ Error: overheating detected: 550 °C
└ @ Main # output truncated
Error: MachineError
julia> machinemonitor(82, 521)
┌ Error: humidity level check failed: 82%
└ @ Main # output truncated
┌ Error: overheating detected: 521 °C
└ @ Main # output truncated
Error: MachineError
julia> machinemonitor(42, nothing)
[ Info: humidity level check passed: 42%
┌ Warning: sensor is broken
└ @ Main # output truncated
Error: MachineError
Regístrate en Exercism para aprender y dominar Julia con 35 conceptos128 ejercicios y mentoría humana real, todo gratis.