Les programmeurs essaient en général d'écrire des logiciels parfaits, et en général, ils échouent.
Les choses tournent mal, sans prévenir, et il faut pouvoir y faire face.
Certains concepteurs de langages estiment que la priorité est de détecter une erreur le plus vite possible, puis de mettre fin à l'exécution avec un message informatif pour faciliter le débogage.
Les langages de science des données ont tendance à adopter une approche plus nuancée. Certaines erreurs sont si graves qu'une interruption immédiate est nécessaire, mais il est souvent préférable de signaler un problème comme quelque chose à traiter plus tard, puis de poursuivre l'exécution.
On a vu dans le concept Néant que Julia fournit divers espaces réservés pour les valeurs problématiques : missing, NaN et Inf.
Savoir si cette approche est préférable à l'arrêt du programme dans une situation donnée relève du jugement du programmeur.
Un point de nomenclature avant d'entrer dans les détails : la documentation de Julia considère les mots « erreur » et « exception » comme largement interchangeables. Le contenu ci-dessous n'est peut-être pas plus cohérent.
À ce stade du programme, tu as forcément vu beaucoup de messages d'erreur de Julia. Par exemple :
julia> Int(3.14)
ERROR: InexactError: Int64(3.14)
Convertir un nombre à virgule flottante en entier implique une perte de précision, on obtient donc une InexactError.
InexactError est un type parmi plusieurs (actuellement 25) intégrés en standard à Julia.
Tous sont des sous-types de Exception :
julia> supertype(InexactError)
Exception
throw()Certains types d'erreur standard peuvent être utiles à générer dans ton propre code.
Comme tous les types concrets, les erreurs ont des constructeurs. Ils prennent des arguments variés, alors consulte la documentation pour celui que tu veux utiliser.
julia> DomainError(42, "out of range")
DomainError(42, "out of range")
Pour utiliser l'erreur, enveloppe le constructeur dans une fonction throw() :
julia> throw(DomainError(42, "out of range"))
ERROR: DomainError with 42:
out of range
error()Pour une solution rapide et sommaire, la fonction error() peut être bien pratique.
Elle prend en argument une string (ou les composants d'une string) :
julia> happy = false;
julia> happy || error("😞 something went wrong")
ERROR: 😞 something went wrong
Créer de nouveaux types d'erreur est en principe très simple.
Il suffit d'ajouter un autre sous-type de Exception :
julia> struct MyError <: Exception end
julia> throw(MyError)
ERROR: MyError
L'idée de base d'une assertion est la suivante : « cette assertion doit être vraie, alors il faut protester bruyamment si elle est fausse ». Son intérêt se manifeste surtout pendant le débogage, car du code de production ne devrait jamais échouer à une assertion.
On a vu dans le concept Types qu'on peut ajouter des assertions de type, par exemple pour vérifier le type de retour d'une fonction.
julia> 42::Number
42
julia> "two"::Number
ERROR: TypeError: in typeassert, expected Number, got a value of type String
Plus généralement, la macro @assert permet de tester toute expression qui s'évalue en un booléen :
julia> n = 22;
julia> @assert isodd(n) "n must be odd"
ERROR: AssertionError: n must be odd
try...catch
Certaines erreurs sont forcément fatales, mais souvent on s'attend à ce que le programme s'en remette sans encombre.
Par défaut, une erreur met immédiatement fin à la fonction courante, et l'erreur (avec son éventuel message informatif) est transmise à la fonction appelante.
Cela remonte toute la pile d'appels, jusqu'à ce que le code de plus haut niveau se termine avec un message d'erreur.
À n'importe quelle étape, l'erreur peut être interceptée par un bloc try...catch qui tente de la gérer.
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
Dans l'exemple ci-dessus, log(n) a besoin que n soit une valeur réelle positive, ou n'importe quelle valeur complexe.
Le try ... catch intercepte les problèmes liés aux valeurs réelles négatives et renvoie la bonne réponse complexe iπ, en notation mathématique.
Si tu fournis, par exemple, un argument de type string, il n'y a pas d'autre recours que de demander à l'utilisateur de le corriger.
En dernier recours, on a ajouté rethrow() pour tout ce qui n'est ni DomainError ni MethodError.
Remarque : Parfois, un try...catch est ce qu'il te faut, mais essaie d'éviter d'en abuser.
Si un bloc if...else peut faire l'affaire à la place, il sera bien plus performant que d'intercepter des exceptions.
À noter que la fonction error(), dont on a parlé plus haut, ne doit pas être confondue avec la macro @error.
La fonction génère une exception, qui remontera la pile d'appels à moins d'être interceptée.
La macro @error, tout comme ses homologues @debug, @info et @warn, fait partie du module Logging et sert à générer des messages informatifs sans modifier le déroulement du programme.
Par défaut, la sortie part vers le terminal (avec un code couleur selon la gravité), même si dans une véritable application, bien d'autres possibilités existent.
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
Voir aussi l'exemple précédent, dans la section try...catch.
Elena est la nouvelle responsable qualité d'une usine de journaux. Comme elle vient tout juste d'arriver dans l'entreprise, elle a décidé de passer en revue certains processus de l'usine pour voir ce qui pourrait être amélioré. Elle a découvert que les techniciens effectuent beaucoup de contrôles qualité à la main. Elle y voit une bonne occasion d'automatiser les choses et te demande, à toi développeur freelance, de développer un logiciel pour surveiller certaines des machines.
Ta première mission est d'écrire un logiciel pour surveiller le niveau d'humidité de la salle de production. Un capteur est déjà connecté au logiciel de l'entreprise et renvoie régulièrement le pourcentage d'humidité de la pièce.
Tu dois implémenter une fonction dans le logiciel qui lèvera une erreur si le pourcentage d'humidité est trop élevé.
Si l'humidité est à un niveau acceptable, un log Info sera ajouté.
La fonction doit s'appeler humiditycheck et prendre le pourcentage d'humidité en argument.
Tu dois t'arrêter avec une ErrorException (le message exact n'a pas d'importance, mais il doit contenir le niveau d'humidité mesuré) si le pourcentage dépasse 70 %.
Sinon, ajoute un log Info avec le message "humidity level check passed: h%", où h est le pourcentage d'humidité.
julia> humiditycheck(60)
[ Info: humidity level check passed: 60%
julia> humiditycheck(100)
ERROR: humidity check failed: 100%
Elena est très satisfaite de ta première mission et te demande de t'occuper de la surveillance de la température des machines. Alors que tu discutes avec un technicien, Greg, il t'apprend que si la température d'une machine dépasse 500 °C, les techniciens commencent à s'inquiéter d'une surchauffe.
La machine est équipée d'un capteur qui mesure sa température interne. Sache que le capteur est très sensible et tombe souvent en panne. Dans ce cas, les techniciens devront le remplacer.
Ton travail consiste à implémenter une fonction temperaturecheck qui prend la température en argument et qui, soit ajoute un log si tout va bien, soit lève une erreur si le capteur est cassé ou si la machine commence à surchauffer.
Sachant que tu devras plus tard réagir différemment selon l'erreur, il te faut un mécanisme pour distinguer les deux types d'erreurs.
nothing.
Dans ce cas, tu dois t'arrêter avec une ArgumentError (le message n'a pas d'importance).DomainError qui inclut la température mesurée."temperature check passed: t °C", où t est la température.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
Pour la tâche suivante, tu devras définir une erreur plus générale, attrape-tout.
Les détails de l'implémentation n'ont pas d'importance, tant qu'il s'agit d'une erreur et que son nom est MachineError.
Tu peux inclure des champs et des messages comme tu le juges utile.
Maintenant que ta machine peut détecter les erreurs et que tu disposes d'une erreur machine personnalisée, tu ajoutes une fonction d'encapsulation qui rend compte du bon fonctionnement de l'ensemble. En plus de renvoyer les logs des fonctions précédentes, cette fonction d'encapsulation devra aussi ajouter des logs selon le ou les types de défaillances qui se produisent.
ErrorException, un log Error doit être ajouté avec le message "humidity level check failed: h%", où h est le pourcentage d'humidité.ArgumentError, un log Warn doit être ajouté avec le message "sensor is broken".DomainError, un log Error doit être ajouté avec le message "overheating detected: t °C", où t est la température.MachineError doit être levée après l'ajout des logs.humiditycheck et de temperaturecheck seront ajoutés.Implémente une fonction machinemonitor() qui prend l'humidité et la température en arguments.
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
Inscris-toi sur Exercism pour apprendre et maîtriser Julia avec 35 concepts128 exercices, et un vrai mentorat humain, le tout gratuitement.