Cette année je gère mes erreurs autrement
Clairement, la gestion des erreurs, ce n’est pas forcément ce que je préfère (et en plus on a toujours tendance à oublier des cas, sans parler de la manière de les traiter).
Et puis, pendant les vacances de Noël, j’ai été amené à faire la revue de ce livre Functional Programming in Java (Il m’a fallu être très concentré - je suis plus JavaScript que Java ;) - mais c’était très instructif) où l’auteur Pierre-Yves Saumont présente notamment le type Optional
de Java 8. En parallèle, Yannick Loiseau a mis en œuvre sur le projet sur le projet Golo le type Result
et “augmenté” le type Optional
de Java 8 (ce qui m’a considérablement aidé dans la compréhension du livre sus-cité).
Tout cela m’a apporté un regard différent sur la gestion des erreurs, et je commence à changer ma façon de coder. Je vous propose, par des petits bouts de code, de vous présenter ce que je suis en train de “creuser”, à travers différents langages.
Remarque: Si vous n’êtes pas d’accord avec ce que j’écris, ou si vous avez des remarques, des conseils, des amélioration à proposer, n’hésitez pas à le faire:
- soit en postant des remarques sur le billet de blog,
- soit en postant des issues ou des en proposant des pull-requests sur le repository du blog https://github.com/k33g/k33g.github.com.
Java 8 et le type Optional
Mon cas d’usage va être tout simple, je vais avoir une fonction anonyme qui me permet de transformer une String
en Integer
. Mais au lieu de retourner directement un Integer
, je vais retourner un Optional<Integer>
.
Un Optional<T>
est un type qui peut avoir 2 types de valeur, “quelque chose” (<T>
), ou “rien” … Oui je sais, ça peut surprendre, mais avec un peu de code, ça va être plus clair:
Donc pour résumer:
- j’essaye de transformer ma
String
enInteger
:java.lang.Integer.parseInt(val)
- si ça marche, je retourne un
Optional
qui prend pour valeur le résultat:Optional.of(java.lang.Integer.parseInt(val))
- si ça foire, je retourne du vide (à l’origine
Optional
, c’est pour traiter lesnull
):Optional.empty()
L’avantage de faire ça, c’est que le type Optional
vient avec quelques méthodes bien pratiques qui vont vous permettre de faire des choses comme celles-ci:
Vous vous apercevrez qu’en utilisant ceci, votre code va gagner en clarté, mais que en plus vous allez vous poser les bonnes questions en termes d’erreurs et finalement pensez plus facilement à toutes les possibilités.
Et là vous commencez à faire du fonctionnel (enfin, il me semble ;)). C’est à ce moment là que je me suis dit, il serait peut-être temps de rejeter un coup d’œil à Scala (!!!).
Scala et les types Option et Either
Le type Option
En Scala, ma super fonction de transformation String
vers Integer
, va ressembler à ceci:
Donc pour faire court:
Option[Int]
en Scala c’est commeOptional<Integer>
en JavaSome(value)
retourne unOption
qui “contient” la valeurvalue
None
, c’est unOption
qui est “vide”
Et du coup, nous pouvons faire comme toute à l’heure:
Le type Either
Par contre à l’usage, on s’aperçoit que cela pourrait-être sympa de pouvoir stocker l’erreur au même titre que la valeur qui est stockée en cas de succès. Et là Scala marque un point (@loic_d si tu me lis, ça vaut une bière ça!) par rapport à Java. En effet, Scala propose le type Either
qui permet de faire ça.
du coup ma super fonction de transformation String
vers Integer
, va maintenant ressembler à ceci:
Donc:
Right(Integer.parseInt(value))
cela retourne unEither
avec une valeur (un contenu à droite et rien à gauche:Either[rien, valeur]
)Left("Huston? Failed!")
cela retourne unEither
sans valeur mais avec la possibilité de stocker quelque chose “à gauche” (Either[message d'erreur, rien]
)
Et je vais maintenant pouvoir l’utiliser comme ceci:
Donc vous l’avez compris, fold
prend 2 fonctions anonymes en paramètre, la 1ère (la plus à gauche) qui permet de récupérer le message d’erreur de Left
, et la 2ème (la plus à droite donc) qui permet de récupérer la valeur de Right
en cas de succès.
Et si vous voulez vous la jouer un peu, vous pouvez “faire du pattern-matching” (c’te classe!):
… presque je trouve ça sympa de faire du Scala ;) …
On continue?
Lorsque que Yannick a proposer sa pull-request, le commentaire était le suivant:
This introduce some objects, augmentations and functions to deal with
errors (null, exceptions, ...) functional style.
- `Result` type similar to Haskell `Either` or Rust `Result`
- augmentation on java `Optional`
- decorators to adapt function to return or deal with arguments of these
types
Rust? ça fait plusieurs fois que l’on me parle de ce langage, il faut que j’y jette un coup d’œil.
Rust et le type Result
Alors, Rust propose un type Result
qui ressemble beaucoup au Either
de Scala (et probablement à celui d’Haskell, mais je ne suis pas allé voir).
Mais une des spécificités de Rust, c’est qu’en standard, sa méthode de transformation de String
en entier, renvoie un Result
!
Donc, j’écris une nouvelle fois ma fonction de transformation: (mes 1ers pas en Rust ;))
C’est court! Hein! Et pour l’utiliser, nous ferons comme ceci:
Oui, oui, c’est du pattern-matching.
Maintenant, cela fait plusieurs fois que je vous parle de la PR de @yannick_loiseau pour le projet Golo. Il s’avère que Golo s’inspire de ce que nous venons de voir pour son module de gestion des erreurs gololang.Errors.
Golo avec Some, None, Result et Error
Some et None
Sur le principe du type Optional
de Java 8, ma fonction de transformation s’écrira de cette façon:
Et nous l’utiliserons de cette manière:
Ou bien de cette façon:
Ou même: (si l’on souhaite déclencher un traitement en cas d’erreur)
C’est plutôt simple à utiliser, mais dans le cadre de gestion d’erreur (allant plus loin que la gestion de valeurs nulles), il est intéressant de pouvoir stocker l’état de l’erreur (comme nous l’avons fait avec Scala et Rust). Et avec Golo, “tout est possible”!
Result et Error
J’écris une nouvelle version de ma fonction de transformation:
Donc par rapport à toute à l’heure, au lieu d’un None()
, j’ai l’opportunité de retourner un Error(quelque_chose)
, qui n’est ni plus ni moins qu’un Result
avec un Left
“contenant” une valeur et un Right
“vide” (rappelez vous le § sur Scala et Either
).
Et maintenant, je peux utiliser toInt
de cette façon:
Plutôt sympa, non? l’utilisation des paramètres nommés (mapping
et default
) n’est pas obligatoire, mais je trouve que l’on y gagne en lisibilité.
Et là, si tu es un développeur JavaScript, tu es triste, car tu voudrais bien faire ça. Pas de panique, il existe un framework topissime: Monet.js qui permet de se livrer aux joies de la gestion fonctionnelle des erreurs en JavaScript.
JavaScript: Maybe et Validation avec Monet.js
Pour faire court:
Maybe
c’est commeOptional
Validation
c’est commeResult
Maybe
Alors maintenant, vous avez l’habitude, je vais re-écrire ma fonction de transformation. Sauf que petite spécificité de JavaScript, c’est que l’équivalent du parseInt
ne “plante” pas quand on lui balance une chaîne qui ne lui convient pas, mais retourne un NaN
(not a number). (Sans compter que le parseInt
de JavaScript n’a pas tout à fait le même comportement que celui de Java).
Mais je vais garder mon cas d’usage, en forçant une exception en cas de NaN
. Donc, ma fonction ressemble à ça:
Et je l’utilserais comme ceci:
Comme avec Option
ou Optional
, je ne peux stocker l’état de l’erreur, mais toEither
me permet de transformer mon Maybe
en Either
et de passer une message d’erreur via cata(left(err), right(value))
Validation
Monet.js propose aussi le principe de Result
au travers du concept de Validation
. J’écris donc une dernière fois ma fonction anonyme toInt
:
Donc cette fois-ci, je pourrais stocker l’état de mon erreur avec monet.Validation.fail(message)
ou stocker un résultat avec monet.Validation.success(res)
, et l’utiliser de cette manière:
En fait Validation
est un Either
.
Dans cet article je n’ai pas fait le tout de toutes les possibilités de Optional
, Result
, Either
, etc… mais j’espère que cela vous donnera envie de creuser le sujet.
Sur ce, je vous souhaite un très bon week-end :)
Tweet