Percursos
/
Julia
Julia
/
Exercícios
/
Sensores da fábrica
Sensores da fábrica

Sensores da fábrica

Exercício de aprendizagem

Introdução

Os programadores normalmente tentam escrever software perfeito e normalmente falham.

As coisas correm mal, inesperadamente, e precisamos de conseguir lidar com isso.

Alguns criadores de linguagens consideram que a prioridade é detetar um erro o mais depressa possível e depois terminar a execução com uma mensagem informativa que ajude na depuração.

As linguagens de ciência de dados tendem a adotar uma abordagem mais nuançada. Alguns erros são tão graves que é necessária uma terminação imediata, mas muitas vezes é melhor assinalar um problema como algo a tratar mais tarde e continuar a execução.

Vimos no conceito Nada que a Julia disponibiliza vários marcadores de posição para valores problemáticos: missing, NaN e Inf. Se estes são uma abordagem melhor do que a terminação do programa numa determinada situação é uma questão de critério do programador.

Uma nota de nomenclatura antes de entrar nos detalhes: a documentação da Julia trata as palavras "erro" e "exceção" como praticamente intercambiáveis. O conteúdo abaixo pode ser igualmente inconsistente.

Tipos de erro padrão

A esta altura do programa, já deves ter visto muitas mensagens de erro da Julia. Por exemplo:

julia> Int(3.14)
ERROR: InexactError: Int64(3.14)

Tentar converter um número de vírgula flutuante num inteiro implica uma perda de precisão, por isso obtemos um InexactError.

O InexactError é um tipo, um dos vários (atualmente 25) que a Julia inclui de série. Todos são subtipos de Exception:

julia> supertype(InexactError)
Exception

throw()

Pode ser útil gerar alguns dos tipos de erro padrão no teu próprio código.

Como todos os tipos concretos, os erros têm construtores. Aceitam uma variedade de argumentos, por isso consulta a documentação do que queres usar.

julia> DomainError(42, "out of range")
DomainError(42, "out of range")

Para usar o erro, envolve o construtor numa função throw():

julia> throw(DomainError(42, "out of range"))
ERROR: DomainError with 42:
out of range

error()

Para uma abordagem rápida e descomplicada, a função error() pode ser prática. Recebe uma string (ou os componentes de uma string) como argumento:

julia> happy = false;
julia> happy || error("😞 something went wrong")
ERROR: 😞 something went wrong

Erros personalizados

Criar novos tipos de erro é, em princípio, muito fácil. Basta acrescentar outro subtipo de Exception:

julia> struct MyError <: Exception end

julia> throw(MyError)
ERROR: MyError

Asserções

A ideia básica de uma asserção é "esta afirmação deve ser verdadeira, por isso reclama em voz alta se for falsa". O valor disto está sobretudo na depuração, já que o código de produção nunca deve falhar uma asserção.

Vimos no conceito Tipos que podemos adicionar asserções de tipo, por exemplo para verificar o tipo devolvido por uma função.

julia> 42::Number
42

julia> "two"::Number
ERROR: TypeError: in typeassert, expected Number, got a value of type String

De forma mais geral, a macro @assert permite-nos testar qualquer expressão que seja avaliada como um valor Boolean:

julia> n = 22;
julia> @assert isodd(n) "n must be odd"
ERROR: AssertionError: n must be odd

try...catch

Alguns erros são necessariamente fatais, mas muitas vezes esperamos que o programa recupere de forma graciosa.

Por predefinição, um erro termina imediatamente a função atual, e o erro (com qualquer mensagem informativa) é passado à função que a chamou.

Isto continua a subir pela pilha de chamadas, até o código de topo terminar com uma mensagem de erro.

Em qualquer fase, o erro pode ser intercetado por um bloco try...catch que tenta tratá-lo.

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

No exemplo acima, log(n) precisa que n seja um valor real positivo ou um valor complexo qualquer. O try ... catch captura os problemas com valores reais negativos e devolve a resposta complexa correta, iπ em notação matemática.

Se forneceres, por exemplo, um argumento do tipo string, não há forma de recuperar a não ser pedir ao utilizador para o corrigir.

Como último recurso genérico, acrescentámos rethrow() para tudo o que não seja um DomainError nem um MethodError.

Nota: Às vezes, um try...catch é o que precisas, mas evita usá-lo em excesso. Se puderes usar um bloco if...else em vez disso, será muito mais eficiente do que capturar exceções.

Registos

Repara que a função error(), discutida acima, não deve ser confundida com a macro @error.

A função gera uma exceção, que vai subir a pilha de chamadas, a menos que seja capturada.

A macro @error, tal como as suas equivalentes @debug, @info e @warn, faz parte do módulo Logging e destina-se a gerar mensagens informativas sem alterar o fluxo do programa.

Por predefinição, a saída vai para o terminal (com cores consoante a gravidade), embora numa aplicação real haja muitas outras possibilidades.

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

Vê também o exemplo anterior, em try...catch.

Instruções

Elena é a nova gestora de qualidade de uma fábrica de jornais. Como acabou de chegar à empresa, decidiu rever alguns dos processos da fábrica para ver o que podia ser melhorado. Descobriu que os técnicos fazem muitos controlos de qualidade à mão. Vê aí uma boa oportunidade de automação e pede-te a ti, programador independente, que desenvolvas um software para monitorizar algumas das máquinas.

1. Verifica o nível de humidade da sala

A tua primeira missão é escrever um software para monitorizar o nível de humidade da sala de produção. Já existe um sensor ligado ao software da empresa que devolve periodicamente a percentagem de humidade da sala.

Precisas de implementar uma função no software que lance um erro se a percentagem de humidade for demasiado alta. Se a humidade estiver num nível aceitável, é adicionado um log de Info. A função deve chamar-se humiditycheck e receber a percentagem de humidade como argumento.

Deves parar com uma ErrorException (a mensagem exata não é importante, mas tem de conter o nível de humidade medido) se a percentagem exceder 70%. Caso contrário, adiciona um log de Info com a mensagem "humidity level check passed: h%", em que h é a percentagem de humidade.

julia> humiditycheck(60)
[ Info: humidity level check passed: 60%
julia> humiditycheck(100)
ERROR: humidity check failed: 100%

2. Verifica o sobreaquecimento

A Elena ficou muito satisfeita com a tua primeira tarefa e pede-te que trates da monitorização da temperatura das máquinas. Enquanto conversas com um técnico, o Greg, ele diz-te que, se a temperatura de uma máquina exceder 500°C, os técnicos começam a preocupar-se com o sobreaquecimento.

A máquina está equipada com um sensor que mede a sua temperatura interna. Deves saber que o sensor é muito sensível e avaria com frequência. Nesse caso, os técnicos terão de o substituir.

A tua tarefa é implementar uma função temperaturecheck que recebe a temperatura como argumento e ou adiciona um log se estiver tudo bem, ou lança um erro se o sensor estiver avariado ou se a máquina começar a sobreaquecer. Sabendo que mais tarde vais precisar de reagir de forma diferente consoante o erro, precisas de um mecanismo para distinguir os dois tipos de erros.

  • Se o sensor estiver avariado, a temperatura será nothing. Nesse caso, deves parar com um ArgumentError (a mensagem não é importante).
  • Quando o sensor está a funcionar, se a temperatura exceder 500°C, deves lançar um DomainError que inclua a temperatura medida.
  • Caso contrário, está tudo bem, por isso adiciona um log de Info com a mensagem "temperature check passed: t °C", em que t é a 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

3. Define um erro personalizado

Para a próxima tarefa, vais precisar de definir um erro mais geral, que abranja todos os casos. Os detalhes da implementação não são importantes, desde que seja um erro e o nome seja MachineError. Podes incluir campos e mensagens como achares útil.

4. Monitoriza a máquina

Agora que a tua máquina consegue detetar erros e tens um erro de máquina personalizado, adicionas uma função de encapsulamento que consegue reportar como está tudo a funcionar. Além de devolver os logs das funções anteriores, esta função de encapsulamento também terá de adicionar logs consoante os tipos de falhas que ocorram.

  • Verifica a humidade e a temperatura.
  • Se a verificação da humidade lançar uma ErrorException, deve ser adicionado um log de Error com a mensagem "humidity level check failed: h%", em que h é a percentagem de humidade.
  • Se a verificação da temperatura lançar um ArgumentError, deve ser adicionado um log de Warn com a mensagem "sensor is broken".
  • Se a verificação da temperatura lançar um DomainError, deve ser adicionado um log de Error com a mensagem "overheating detected: t °C", em que t é a temperatura.
  • Se uma das verificações falhar, ou as duas, deve ser lançado um único MachineError depois de os logs serem adicionados.
  • Se estiver tudo bem, só serão adicionados os logs de humiditycheck e temperaturecheck.

Implementa uma função machinemonitor() que recebe a humidade e a 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
Editar via GitHub A ligação abre numa nova janela ou separador
Julia Exercism

Estás pronto para começar Sensores da fábrica?

Inscreve-te no Exercism para aprenderes e dominares Julia com 35 conceitos128 exercícios, e mentoria humana real, tudo grátis.