SlideShare una empresa de Scribd logo
1 de 98
Descargar para leer sin conexión
Librairies Java qui
changent la vie
Joseph Pachod
https://twitter.com/joeclueless
Disclaimer
Beaucoup plus de contenu que prévu…
Uniquement vavr et concordion dans un premier temps...
Une présentation java.Time ?
Qui connaît ?
Qui utilise ?
Equivalent du jdk8 et plus de la librairie JodaTime,
java.Time adresse enfin correctement les problématiques
de fuseau horaire, décalage, date sans horaire et autres
horaires sans date, le tout avec ce qu’il faut d’utilitaires et
de conversions pour un usage courant au top :)
Qui aimerait une présentation ?
Une présentation Lombok ?
Qui connaît ?
Qui utilise ?
Lombok : génération de code lors de la compilation via des
annotations, éviter de toujours coder/maintenir soit même
les accesseurs, toString, equals, logger…
Notion très pratique d’objets de données, via un
regroupement de plein de choses utiles dans une même
implémentation : @Data
Qui aimerait une présentation ?
Une présentation jOOQ ?
Qui connaît ?
Qui utilise ?
JOOQ considère que le SQL est un langage très riche et
intéressant.
Aussi, pour faciliter son usage depuis Java il propose un
DSL pour écrire du SQL mettant en avant toutes les
possibilités de SQL.
Ainsi on apprend toujours quelque chose mais surtout notre
SQL est fortement type et peut aller d’un select à un
create table…
Dès que possible jOOQ implémente les fonctionnalités de
façon sur plusieurs implémentations de SQL, sinon il fait
quand même et précise les limites de portabilité (en
permettant de s’assurer de la compatibilité via annotation
si besoin).
JOOQ peut s’utiliser seul ou via Hibernate.
Qui aimerait une présentation ?
Présentation forte en lambda...
Si vous ne savez pas lire du code contenant des lambdas,
ça va être dur. Révisez un coup puis revenez ^^
L’association IPA
API anyone ? Rien à voir!
Informaticiens Protecteurs des Animaux
En anglais : "Yeeepa !"
C’est débile clairement, mais c’est pour le contexte
fonctionnel ^^
Good code?
Le code doit être comme un bon chien. Du good code quoi.
Certains disent Clean code, mais on lave tous nos
animaux.
Anyway, c'est quoi du good code?
Un bon chien aide son propriétaire, maintenant et
dans le futur.
Le good code, c'est comme un bon chien.
Permettre les développements futurs.
Voir même les rendre plus aisés.
Plus on avance, plus on doit aller vite.
Idée folle ?
objectif : enregistrement des propriétaires
d'animaux
nom et date de naissance
(doivent être adultes)
Commençons par la date de naissance
LocalDate parse(String date){
return LocalDate.parse(
date,
DateTimeFormatter.ofPattern("yyyy/MM/dd"));
}
Un problème?
Est ce du good code?
Quid d’une erreur de parsing ? Exception !
LocalDate parse(String date){
try {
return LocalDate.parse(
date,
DateTimeFormatter.ofPattern("yyyy/MM/dd"));
} catch (Exception e) {
return ???;
}
}
Rethrow
- si unchecked, c'est l'origine du problème
- si checked, c'est un autre problème… Et personne n’a
recopié cette fonctionnalité Java, les exceptions vérifiées,
depuis son invention… Passons donc !
Bref, que retourner ?
Option to the rescue!
Option<LocalDate> parse(String date){
try {
return Option.of(
LocalDate.parse(
date,
DateTimeFormatter
.ofPattern("yyyy/MM/dd")));
} catch (Exception e) {
return Option.none();
}
}
Pour l’instant, disons que l’Option présente dans le code
fait la même chose que l’Optional de la librairie standard
Java.
Et l’essentiel est que le boulot est fait !
Option<Integer> validateAge(String birthdate) {
return parse(birthdate)
.map(
d ->
Period.between(
d,
LocalDate.now())
.getYears())
.filter(age -> age >= MIN_AGE);
}
All good?
I may want to log the exception...
Optional is not enough, let's keep the exception.
En fait, il s'agit d'un essai de parsing n'est ce pas ?
Try parse(String date) {
try {
return new Success(
LocalDate.parse(
date,
DateTimeFormatter
.ofPattern("yyyy/MM/dd")));
} catch (Exception t) {
return new Failure(t);
}
}
Success et Failure implémentent tous deux une interface
Try reprenant l'essentiel d'Option(al).
On stocke désormais l'exception si une est présente et on
indique qu'il s'agit d'un échec.
Et on permet d'accéder au contenu de l'erreur.
Try onFailure(Consumer<? super Throwable> action)
{
Objects.requireNonNull(
action,
"action is null");
if (isFailure()) {
action.accept(getCause());
}
return this;
}
onFailure retourne le try lui même, permettant de chaîner
Et si pas failure l’action communiquée n’est pas exécutée.
Facile !
parse(birthdate)
.onFailure(
t -> log.debug("Parsing failure for
birthdate '" + birthdate + "'", t))
.map(
d -> Period.between(d,
LocalDate.now()).getYears())
.filter(age -> age >= MIN_AGE);
// gives a Try
Le code reste grosso modo le même : on ne gère que ce
qui se passe bien. On ajoute juste le log. Une ligne. Pas
de if.
How great is that ?
Lancer des exceptions est fréquent!
Division (par zéro)
Integer.parseInt(string)
Code business...
File...
static <T> Try<T> of(
CheckedFunction0<? extends T> supplier) {
Objects.requireNonNull(
supplier,
"supplier is null");
try {
return new Success<>(supplier.apply());
} catch (Throwable t) {
return new Failure<>(t);
}
}
Le Try de vavr :
- générique pour un usage plus large :)
- un supplier pour passer l’action pouvant lancer une
exception :)
Try<T> onSuccess(Consumer<? super T> action)
<U> Try<U> flatMap(
Function<? super T,? extends Try<? extends U>>
mapper)
Try<T> andFinally(Runnable runnable)
...
Bien plus dans le Try de vavr !
onSuccess, le pendant d'onFailure
flatMap : permet de gérer un autre bout de code pouvant
lancer des exceptions et permet de tenter autre chose si
réussite du premier Try
andFinally pour une action toujours réalisée quelque soit le
résutlat du Try
List<T> toJavaList()
Optional<T> toJavaOptional()
List<T> toList()
Option<T> toOption()
...
Et en bonus plein de fonctions de conversions, juste un
échantillon là.
Et oui vavr a sa propre List et Option, on en parle plus
tard :)
Try<LocalDate> parse(String date) {
return Try.of(
() ->
LocalDate.parse(
date,
DateTimeFormatter
.ofPattern("yyyy/MM/dd")));
}
Parse avec Try de vavr
Try<Integer> validateAge(String birthdate) {
return parse(birthdate)
.onFailure(
t -> log.debug("Parsing failure for
birthdate " + birthdate, t))
.map(d -> Period.between(
d,
LocalDate.now()).getYears())
.filter(age -> age >= MIN_AGE);
}
Validation de l’âge avec le Try de vavr
Question de l'âge réglée, passons à la suite... :)
Contrôle du nom
/**
* Retourne un message si erreur.
*/
Option<String> checkName(String name)
On doit utiliser une méthode pré existante qui retourne (ou
pas) une erreur
Option<String> nomValide = Option.of(nom)
.filter(n -> !n.trim().isEmpty())
.filter(n -> checkName(n).isEmpty();
Problème dans le code ci dessus : on perd les erreurs. Dur
de les remonter à l’utilisateur !
Tuple2<String, String> t =
Tuple.of("foo","bar");
t._1;// foo
t._2;// bar
Un tuple associe plusieurs valeurs, comme une Entry de
Map en Java mais en plus souple (on peut créer soit
même des entrées).
Il existe de Tuple0 à Tuple8
Très pratique en plein d'occasions
Tuple2<String, Option<String>> validateNom(
String nom) {
return Option.of(nom)
.filter(n -> !n.trim().isEmpty())
.map(n -> Tuple.of(n, Option.<String>none()))
.getOrElse(
Tuple
.of(
nom,
Option.of("Le nom doit être
renseigné avec des caractères alpha
numériques")));
}
On peut retourner la valeur et les éventuelles erreurs :)
Mais… pas forcément évident de distinguer les deux…
Et puis l’erreur à la fin est sacrément décorrélée de son
origine...
Either<L,R>
R pour Right : valeur correcte
L pour Left : alternative(s)
Transporte la bonne valeur, Right, ainsi que les éventuelles
erreurs ou messages associés.
Option<Either<L, R>> filter(Predicate<? super R>
predicate)
<U> Either<L, U> map(Function<? super R, ? extends U>
mapper)
<U> Either<U, R> mapLeft(
Function<? super L, ? extends U> leftMapper)
<U> Either<L, U> flatMap(
Function<
? super R,
? extends Either<L, ? extendsU>> mapper)
Les signatures complexes ne doivent pas effrayer, il s’agit
toujours des bonnes vieilles mêmes méthodes : filter,
map et flatMap.
Seule mapLeft est un peu différente, afin de mapper sur la
valeur « left ».
static <L, R> Either<L, R> right(R right)
static <L, R> Either<L, R> left(L left)
On a également des constructeurs pour créer directement
la valeur droite ou gauche
Conversions depuis Option/Try/...
<L> Either<L, T> toEither(L left)
<L> Either<L, T> toEither(Supplier<? extends L>
leftSupplier)
<R> Either<T, R> toLeft(R right)
<R> Either<T, R> toLeft(Supplier<? extends R> right)
<L> Either<L, T> toRight(L left)
<L> Either<L, T> toRight(Supplier<? extends L> left)
Either<String, String> validateNom(String nom) {
return Option.of(nom)
.filter(n -> !n.trim().isEmpty())
.toEither("Le nom doit être renseigné avec
des caractères alpha numériques")
.flatMap(s -> {
Option<String> errors = checkName(s);
return errors.toLeft(s);
});
}
On utilise flatMap afin de pouvoir utiliser la valeur Right
pour déduire la nouvelle valeur Left
Comme précédemment, ce flatMap n’est appelé que si
Either a la valeur Right. A défaut rien ne se passe et on
demeure sur la valeur Left définie plus haut, à savoir « Le
nom doit être renseigné avec des ... »
Either<String, Integer> validateAge(String
Birthdate) {
return parse(birthdate)
.onFailure(t -> log.debug("Parsing failure
for birthdate " + birthdate, t))
.toEither("La date de naissance doit être au
format " + DATE_PATTERN + ".")
.map(d -> Period.between(
d,
LocalDate.now()).getYears())
.flatMap(age ->
(age >= MIN_AGE) ?
Either.right(age) :
Either.left("Il faut avoir plus de " +
MIN_AGE));
}
Ici le flatMap se fait sur une either, vu qu’on a besoin
de l’age pour déterminer s’il est correct.
Either<String, Person> validatePerson(
String name,
String birthdate) {
return validateNom(name)
.flatMap(nomValide ->
validateAge(birthdate)
.map(age ->
new Person(nomValide, age)));
}
Là on ne lève pas de nouveaux cas d’erreurs, du
coup on se contente du flux normal. Joli non ?
A noter qu’on peut aussi se servir d’Either pour une
alternative (indépendamment de toute gestion
d’erreur).
Par exemple « Either<Cat,Dog> ». Autre usage
souvent pratique :)
Option, Try et Either
Beaucoup de ressemblances
Plusieurs fois le même principe: une ou des valeurs sur
lesquelles on applique des opérations.
Expliciter le comportement du code
Option → Absence
Try → Exception
Either → Alternative
Et tout cela à côté du cas normal
Option, Try et Either explicitent des situations parfois
cachées dans notre code
Ainsi le code devient bien plus clair.
Qui plus est, la façon d’encapsuler les éléments mis en
avant, cad de n’appliquer les map ou flatMap que si la
valeur est ok (cad non absente, sans exception et sans
l’alternative) permet de faire un code très lisible, concis et
sans if imbriqués. Du bonheur pour le lecteur :)
Monade
un état
des opérations dans la monade
des opérations de sortie de la monade
La ressemblance est plus profonde que juste des
similitudes de comportement, ces 3 notions sont trois
déclinaisons d’un même concept : la monade.
- un état (un élément ou plusieurs)
- des opérations "dans" la monade (map, filter....)
-- "happy path" dans les monades vues jusqu'ici, on ne
décrit que le "chemin nominal", on ignore soit les autres
soit ils sont un état à part embarqué en même temps
-- permet de différer le traitement des erreurs quand on
veut, une fois à la fin généralement
- des opérations de sortie de la monade (get...)
Monade
Méthodes essentielles
constructeur
flatMap
Avec le constructeur et flatMap on peut faire map, filter...
En Haskell on appelle ces méthodes unit et bind
IO Monad :)
En programmation fonctionnelle, on utilise une monade
pour décrire les opérations d'IO : description dans la
monade des opérations, déclenchement à la demande en
sortant de la monade
Plus proche de nous,
d’autre monades se cachent…
Avez vous connaissance d’une autre Monade que vous
utilisez au quotidien ?
En Java..
List/Set/Map… du moins en Vavr !
Outils de programmation fonctionnelle... en Java!
Avec vavr, c’est java à l’envers. Littéralement : inverser le
logo !
On reste toutefois à des niveaux « simples » de PF, du fait
notamment des limites du langage
Mais c’est déjà beaucoup et permet d’améliorer
sensiblement notre code.
Inspiré de Scala... en mieux!
Découle de la lib standard Scala
L'API de collection Scala se décline en version mutable ou
immuable pour une même interface
API à mi chemin, implémentation partiellement partagée
source de bugs...
Vavr ayant choisi de ne faire que de l'immuable, tout est
grandement simplifié
Précédemment javaslang...
Actuellement en 0.9.2
Changement de nom car pb de copyright, Java étant la
marque d’Oracle.
Le nom Vavr a été trouvé en regardant le reflet d’un sticker
« javaslang ».
En profite pour un "reset", pas mal de modif non
rétrocompatible prévues pour la 1.0.0
Optional.map différent
Transforme les retours null en empty()
Option/Try/Either/List/...
map() retourne les nulls
Transformer null en None toujours possible
flatMap(x -> Option.of(x))
Déjà, retourner les nulls n’empêche pas d’atteindre le
même résultat qu’Optional si on le désire
map() retournant les nulls
uniforme
null != None
associatif
Mais au-delà, transformer les null en None serait étrange
dans un Try ou un Either… Sans parler d’une List où null
est une valeur valide !
D’ailleurs, dans une Map, avoir une clé avec une valeur
nulle est différente de l’absence de valeur. Optional perd
donc de l’information.
Cette perte d’information est en fait dramatique : suivant
l’ordre dans lequel on applique les map, Optional peut
avoir différents résultats. En effet, nombreuses sont les
méthodes pouvant retourner un résultat pour une valeur
nulle. Or celles ci ne seront jamais appelée dans le cas
d’Optional.
Cet aspect, l’associativité, est une des lois des monades.
map() retournant les nulls
Risque de NullPointerException ?
On risque toujours des NPE, même avec Optional. Au sein
d’un map on met du code. Et là le risque de NPE
persiste. Null est bel et bien une valeur possible en Java,
Optional ne pourra rien y changer.
A propos, qui a tenté de retourner null dans le flatMap
d’Optional ?
Du coup le gain de transformer les nulls en empty est très
faible, alors que les pertes sont énormes.
Retour au sujet
Où en étions nous ?
Pas simple de revenir sur un sujet après une distraction
hein ?
Vrai problème...
Quid d’une doc pour avoir un récap ?
Une petite doc dans l’idée devrait bien faire le job non ?
On n’a pas 200 règles, on pourrait facilement avoir un
aperçu…
Une doc toujours à jour ?
Seul hic, 99 % des docs ne sont pas à jour…
Et le 1 % restant est souvent infâme de relents de
procédures qualité ^^
Mais ce serait tellement bien...
Une doc interactive ?
Encore mieux qu’à jour, quid d’une doc qu’on pourrait
manipuler ?
Ajouter des cas, voir ce qu’ils retournent… A la volée,
comme ça, parce qu’on le veut ?
Informaticiens Protecteurs des Animaux
On sait aussi teaser hein ?
Vous pensez que c’est du rêve tout ça ? Vous voulez la
réponse ?
Elements validés en vert, éléments en erreur en rouge,
avec résultats effectifs affichés et ceux erronés barrés.
Spécifications par l’exemple
L’idée des spécifications par l’exemple est qu’un exemple
vaut beaucoup d’explications.
C’est en discutant d’exemples concrets que l’on parvient à
affiner. Il est donc important de matérialiser et mettre en
conserver ces exemples.
Affichage via html ou markdown
Java ou .Net
L’html (ou le markdown) donne une liberté de présentation
infinie fortement appréciable, que ce soit en termes de
contenu (choix de la langue) ou présentation.
Communication possible de l’html ou du
markdown
Avec ou sans exécution du test
On peut passer les fichiers bruts à n’importe qui.
Là l’html a un avantage à mon sens : tout le monde a un
navigateur a même de l’ouvrir, un outil pour visualiser du
markdown est plus compliqué à avoir.
Je me suis occupé de la mise en forme…
Bien sûr qu’on en passe l’html d’origine sans exécution du
test, on ne sait pas ce qui valide ou pas.
Ceci dit, cela ne devrait casser que dans des feature
branchs, pas en prod ou develop. Au développeur de
s’assurer qu’il passe la bonne version.
On peut aussi directement accéder à ces fichiers, en tant
que non codeurs, via un outil de navigation de repo
(github…).
Instrumentation de l’affichage
&
Appels à la classe de test
Évidemment ça ne marche pas tout seul ^^
<table concordion:execute="#result =
validate(#name,#birthdate)">
<tr>
<th concordion:set="#name">Nom</th>
<th concordion:set="#birthdate">Date de
naissance <br/>(yyyy/MM/dd)</th>
<th concordion:assert-
equals="#result">Résultat</th>
</tr>
<tr>
<td>Dupont</td>
<td>1999/03/21</td>
<td>Person(Dupont, 18)</td>
</tr>
</table>
Coté htlm, on ajoute des éléments qui ne perturbent pas
l’affichage, indiquant :
- ce qu’on capture
- ce qu’on exécute
- ce qu’on vérifie.
Dans le cas d’un tableau, on définit une fois le
comportement au niveau de l’header. Cela s’applique
alors sur chaque ligne.
@RunWith(ConcordionRunner.class)
public class PersonValidatorTest {
public String validate(
String name,
String birthdate) {
Either<String, String> strings =
new PersonValidator()
.validatePerson(name, birthdate)
.map(Person::toString);
return strings
.getOrElse(() -> strings.getLeft());
}
}
La classe de test fait le lien entre les méthodes appelées
depuis l’affichage et le code testé.
Ici on « aplatit » les sorties pour les passer toutes en String.
Exécution avec les tests unitaires
Embarqué avec le code
Le coût d’exécution d’un concordion est minime. On peut
les exécuter en même temps que les tests unitaires (ce
qui est le comportement par défaut).
Le fait que tout soit avec le code est génial. En effet, si on
utilise un gestionnaire de sources alors on a l’historique
des changements et on peut adapter les concordion dans
une feature branch quand cela s’impose, sans impacter
les autres branches.
Exemples hors tables possibles
Également intéressant, mais plus limité en portée et plus
coûteux en instrumentation.
Nombreuses extensions
Import d’xls
Captures d’écran
...
Non content de décrire les spécifications, on peut aussi
faire une « vraie doc ».
Documentation vivante !
Une documentation vivante :
- s’adapte aux changements (captures actualisées)
- dit si elle est périmée (tests qui plantent)
- s’adapte aux différentes versions (est stockée par
branche, avec le code)
Du (good) code
Attention toutefois, la facilité d’instrumentation, via du code,
peut parfois conduire à des usines à gaz, comme tout
code.
Toujours penser à limiter le scope, à refactorer, à découper
si cela devient trop complexe.
On est vite embarqué dans un cycle perfectionniste de
l’affichage, mais ce n’est pas une fin en soi !
Jouons un peu avec les spécifications
Souvent en ajoutant des exemples on découvre des
choses...
Arrêt à la première erreur de validation...
Validation<E, T>
<E> value type in the case of invalid
<T> value type in the case of valid
Très utile pour toutes les validations, notamment d’une
IHM.
Validation<String, String> nomValidation(
String nom) {
return Option.of(nom)
.filter(n -> !n.trim().isEmpty())
.toValidation("Le nom doit être renseigné
avec des caractères alpha numériques")
.flatMap(s -> checkName(s).toInvalid(s));
}
On indique le type d’erreur à remonter, là on a pris String,
pour l’exemple, mais dans la vraie vie ça peut être bien
plus évolué.
On transforme l’état stocké dans la monade, ici le nom
validé..
On décide alors s l’état de la monade est valide ou non puis
on fournit l’autre information pour faire la validation.
Validation<Seq<String>, Person>
personValidation(
String name,
String birthdate) {
return Validation
.combine(
nomValidation(name),
ageValidation(birthdate))
.ap(Person::new);
}
A plus haut niveau, soit tout est valide et on construit une
instance de ce qui est validé, soit on retourne les erreurs.
Retour à Concordion :)
Conversion en validation possible pour toutes les
monades
<E> Validation<E, T> toValid(E error)
<U> Validation<T, U> toInvalid(U value)
Du moins en vavr ;)
Validation via du code
Si besoin de différentes validations suivant le contexte,
easy :)
Validation<Seq<String>, Person> personValidation(..)
Un problème là qq’un ?
Collections immuables ?
Et la mutabilité alors ?
Partage de Stream de Java 8 possible ?
- Oui, mais le premier qui collecte plante le Stream
pour tous les autres consommateurs!
- Oh, ce partage est il thread safe?
Plein de choses à avoir à l’esprit !
Vivre avec java.util.ArrayList<E>...
Risque de changement dans la liste sans qu'on le
sache...
Que ce soit dans le code écrit dès à présent ou ...
dans tout code écrit dans le futur!!
Retour à la définition de code propre : Est ce le cas
avec une list muable? Si oui, pouvez vous en être
sûrs?
Bien sûr, des palliatifs sont possibles: copies
défensives, Collections.immutableList...
Mettez vous toujours en oeuvre ces palliatifs ? A quel
coût ?
A propos, les éléments d'une immutableList peuvent
ils être modifiés ?
Immuabilité == tranquillité
Quand tout est immuable, pas d’inquiétude. On peut
passer son objet à tout le monde, ils ne pourront
pas faire de mal. Contexte multi threadé ou non.
Plus besoin de programmation défensive, plus de
bug de folies dus à un changement non prévu.
Que du bonheur :)
Et les perfs alors ?
Persistent data structure
Ensemble d’algorithmes pour rendre efficace les structures
immuables, que ce soit en coût de garbage collection ou
rapidité de modification/consultation.
List<Integer> list1 = List.of(1, 2, 3);
// List(1, 2, 3)
List<Integer> list2 = list1.tail().prepend(0);
// List(0, 2, 3)
On parle là de « structural sharing » : partage de données.
On évite ainsi de créer de nouveaux tableaux et de faire
pleins de nouvelles références à chaque action, voir
même de dupliquer les objets référencés « par
précaution »..
Très peu de garbage collection
SortedSet<Integer> xs = TreeSet.of(6, 1, 3, 2, 4,
7, 8);
// TreeSet(1, 2, 3, 4, 6, 7, 8)
SortedSet<Integer> ys = xs.add(5);
// TreeSet(1, 2, 3, 4, 5, 6, 7, 8)
La structure en arbre permet toujours le « structural
sharing » mais en plus des mises à jour et parcours
rapides.
Lors des changements on change juste les « branches »
affectées, le reste peut être réutilisé :)
Utilisation de Tree
Accès en temps constant
Performances !
Au delà du partage de structure, des algoritmes tels que le
Red/Black Tree permet de s’assurer de l’accès à chaque
donnée en 4 sauts maximum, soit un temps d’accès
constant quelque soit la taille du Set…
D’ailleurs ces techniques ont été repris dans pour les
collections de java.util.concurrent depuis java 7.
Il y a même des discussions pour passer la taille d’une
ConcurrentHashMap à long : désormais int et ses 4
milliards peuvent être atteints sans problème.
Y a pas que les perfs !
Et l’IPA ?
Au delà du partage de structure, des algoritmes tels que le
Red/Black Tree permet de s’assurer de l’accès à chaque
donnée en 4 sauts maximum, soit un temps d’accès
constant quelque soit la taille du Set…
D’ailleurs ces techniques ont été repris dans pour les
collections de java.util.concurrent depuis java 7.
Il y a même des discussions pour passer la taille d’une
ConcurrentHashMap à long : désormais int et ses 4
milliards peuvent être atteints sans problème.
List<Integer> ints = List.of(1, 22);
// List(1, 22)
ints.head();
// 1
ints.headOption();
// Some(1)
tail();
// List(22)
ints.tailOption();
// Some(List(22))
En termes d’accesseurs, on peut aisément avoir le
premier élément, head, et les suivants, tail.
ints;
// List(1, 22)
ints.map(i -> "Mon int est " + i);
// List(Mon int est 1, Mon int est 22)
ints.flatMap(i -> List.of(i));
// List(1, 22)
Bien sûr, map et flatMap sont toujours là :)
ints;
// toujours List(1, 22)!
ints.fold(0, (acc, val) -> acc + val);
// 23
ints.foldLeft("acc", (acc, val) -> acc + val);
// acc122
ints.foldRight("acc", (val, acc) -> acc + val);
// acc221
Fold permet de parcourir tous les éléments de la liste
et d’accumuler le résultat au fur et à mesure
FoldLeft parcours la liste en commençant par la
gauche
FoldRight parcours la liste en commençant par la
droite
Ces deux derniers folds permettent d’avoir un
accumulateur de type différent que les éléments.
ints;
// toujours List(1, 22)!
ints.zipWithIndex();
// List((1, 0), (22, 1))
ints.intersperse(5);
// List(1, 5, 22)
ints.distinct();
// List(1, 22)
ints.find(i -> i > 10);
// Some(22)
Avec les folds et les méthodes précédentes, on
devine vite qu’on peut faire plein de choses, mais
vavr a la gentillesse de nous proposer les plus
courantes et/ou les plus pratiques !
De façon générale, rares sont les cas où vavr n’a pas
de quoi grandement simplifier notre quotidien :)
Pas de .stream() et autres .collect()
Offre en fait bien plus !
Pattern matching, stream (collection infinies)...
Vivement la 1.0 :)
That’s all folks… until next time !

Más contenido relacionado

La actualidad más candente

Javascript un langage supérieur
Javascript un langage supérieurJavascript un langage supérieur
Javascript un langage supérieur
Fredy Fadel
 
Présentation de ECMAScript 6
Présentation de ECMAScript 6Présentation de ECMAScript 6
Présentation de ECMAScript 6
Julien CROUZET
 
Cours python avancé
Cours python avancéCours python avancé
Cours python avancé
pierrepo
 
20140123 java8 lambdas_jose-paumard-soat
20140123 java8 lambdas_jose-paumard-soat20140123 java8 lambdas_jose-paumard-soat
20140123 java8 lambdas_jose-paumard-soat
SOAT
 

La actualidad más candente (20)

Introduction à Python - Achraf Kacimi El Hassani
Introduction à Python - Achraf Kacimi El HassaniIntroduction à Python - Achraf Kacimi El Hassani
Introduction à Python - Achraf Kacimi El Hassani
 
Java 8-streams-collectors-patterns
Java 8-streams-collectors-patternsJava 8-streams-collectors-patterns
Java 8-streams-collectors-patterns
 
Interface fonctionnelle, Lambda expression, méthode par défaut, référence de...
Interface fonctionnelle, Lambda expression, méthode par défaut,  référence de...Interface fonctionnelle, Lambda expression, méthode par défaut,  référence de...
Interface fonctionnelle, Lambda expression, méthode par défaut, référence de...
 
Javascript un langage supérieur
Javascript un langage supérieurJavascript un langage supérieur
Javascript un langage supérieur
 
Programmation Fonctionnelle
Programmation FonctionnelleProgrammation Fonctionnelle
Programmation Fonctionnelle
 
Développement informatique : Algorithmique I : Récursion et arbre
Développement informatique : Algorithmique I : Récursion et arbreDéveloppement informatique : Algorithmique I : Récursion et arbre
Développement informatique : Algorithmique I : Récursion et arbre
 
Python avancé : Qualité de code et convention de codage
Python avancé : Qualité de code et convention de codagePython avancé : Qualité de code et convention de codage
Python avancé : Qualité de code et convention de codage
 
Présentation de ECMAScript 6
Présentation de ECMAScript 6Présentation de ECMAScript 6
Présentation de ECMAScript 6
 
Tests unitaires : Utilisation de la librairie CUnit
Tests unitaires : Utilisation de la librairie CUnitTests unitaires : Utilisation de la librairie CUnit
Tests unitaires : Utilisation de la librairie CUnit
 
Introduction à JavaScript
Introduction à JavaScriptIntroduction à JavaScript
Introduction à JavaScript
 
Programmation fonctionnelle
Programmation fonctionnelleProgrammation fonctionnelle
Programmation fonctionnelle
 
Développement informatique : Chaines de caractères et expressions regulières
Développement informatique : Chaines de caractères et expressions regulièresDéveloppement informatique : Chaines de caractères et expressions regulières
Développement informatique : Chaines de caractères et expressions regulières
 
Cours python avancé
Cours python avancéCours python avancé
Cours python avancé
 
Type abstrait de données
Type abstrait de donnéesType abstrait de données
Type abstrait de données
 
Python avancé : Tuple et objet
Python avancé : Tuple et objetPython avancé : Tuple et objet
Python avancé : Tuple et objet
 
Formation python micro club.net
Formation python micro club.netFormation python micro club.net
Formation python micro club.net
 
Change mind about JS
Change mind about JSChange mind about JS
Change mind about JS
 
20140123 java8 lambdas_jose-paumard-soat
20140123 java8 lambdas_jose-paumard-soat20140123 java8 lambdas_jose-paumard-soat
20140123 java8 lambdas_jose-paumard-soat
 
Scala : programmation fonctionnelle
Scala : programmation fonctionnelleScala : programmation fonctionnelle
Scala : programmation fonctionnelle
 
Introduction à Python
Introduction à PythonIntroduction à Python
Introduction à Python
 

Similar a Librairies Java qui changent la vie

Similar a Librairies Java qui changent la vie (20)

Voxxdays luxembourg 2016 retours java 8
Voxxdays luxembourg 2016 retours java 8Voxxdays luxembourg 2016 retours java 8
Voxxdays luxembourg 2016 retours java 8
 
Java 5, un blian
Java 5, un blianJava 5, un blian
Java 5, un blian
 
Java 5, un bilan
Java 5,  un bilanJava 5,  un bilan
Java 5, un bilan
 
Nouveautés JavaScript dans le monde Microsoft
Nouveautés JavaScript dans le monde MicrosoftNouveautés JavaScript dans le monde Microsoft
Nouveautés JavaScript dans le monde Microsoft
 
Fondamentaux portée - contexte - function ms tech days
Fondamentaux   portée - contexte - function ms tech daysFondamentaux   portée - contexte - function ms tech days
Fondamentaux portée - contexte - function ms tech days
 
Algorithmique Amp Programmation (R Sum
Algorithmique  Amp  Programmation (R SumAlgorithmique  Amp  Programmation (R Sum
Algorithmique Amp Programmation (R Sum
 
Découverte du moteur de rendu du projet Spartan
Découverte du moteur de rendu du projet SpartanDécouverte du moteur de rendu du projet Spartan
Découverte du moteur de rendu du projet Spartan
 
C++ 11/14
C++ 11/14C++ 11/14
C++ 11/14
 
Algo poo ts
Algo poo tsAlgo poo ts
Algo poo ts
 
Ruby Pour RoR
Ruby Pour RoRRuby Pour RoR
Ruby Pour RoR
 
Algorithmique programmation2018
Algorithmique programmation2018Algorithmique programmation2018
Algorithmique programmation2018
 
De java à swift en 2 temps trois mouvements
De java à swift en 2 temps trois mouvementsDe java à swift en 2 temps trois mouvements
De java à swift en 2 temps trois mouvements
 
Retours sur java 8 devoxx fr 2016
Retours sur java 8 devoxx fr 2016Retours sur java 8 devoxx fr 2016
Retours sur java 8 devoxx fr 2016
 
Audits php
Audits phpAudits php
Audits php
 
Université des langages scala
Université des langages   scalaUniversité des langages   scala
Université des langages scala
 
Javascript Json artchitecture
Javascript  Json artchitecture Javascript  Json artchitecture
Javascript Json artchitecture
 
Theme 7
Theme 7Theme 7
Theme 7
 
C# 7 - Nouveautés
C# 7 - NouveautésC# 7 - Nouveautés
C# 7 - Nouveautés
 
50 nouvelles choses que l'on peut faire avec Java 8
50 nouvelles choses que l'on peut faire avec Java 850 nouvelles choses que l'on peut faire avec Java 8
50 nouvelles choses que l'on peut faire avec Java 8
 
Hibernate
HibernateHibernate
Hibernate
 

Librairies Java qui changent la vie

  • 1. Librairies Java qui changent la vie Joseph Pachod https://twitter.com/joeclueless
  • 2. Disclaimer Beaucoup plus de contenu que prévu… Uniquement vavr et concordion dans un premier temps...
  • 3. Une présentation java.Time ? Qui connaît ? Qui utilise ? Equivalent du jdk8 et plus de la librairie JodaTime, java.Time adresse enfin correctement les problématiques de fuseau horaire, décalage, date sans horaire et autres horaires sans date, le tout avec ce qu’il faut d’utilitaires et de conversions pour un usage courant au top :) Qui aimerait une présentation ?
  • 4. Une présentation Lombok ? Qui connaît ? Qui utilise ? Lombok : génération de code lors de la compilation via des annotations, éviter de toujours coder/maintenir soit même les accesseurs, toString, equals, logger… Notion très pratique d’objets de données, via un regroupement de plein de choses utiles dans une même implémentation : @Data Qui aimerait une présentation ?
  • 5. Une présentation jOOQ ? Qui connaît ? Qui utilise ? JOOQ considère que le SQL est un langage très riche et intéressant. Aussi, pour faciliter son usage depuis Java il propose un DSL pour écrire du SQL mettant en avant toutes les possibilités de SQL. Ainsi on apprend toujours quelque chose mais surtout notre SQL est fortement type et peut aller d’un select à un create table… Dès que possible jOOQ implémente les fonctionnalités de façon sur plusieurs implémentations de SQL, sinon il fait quand même et précise les limites de portabilité (en permettant de s’assurer de la compatibilité via annotation si besoin). JOOQ peut s’utiliser seul ou via Hibernate. Qui aimerait une présentation ?
  • 6. Présentation forte en lambda... Si vous ne savez pas lire du code contenant des lambdas, ça va être dur. Révisez un coup puis revenez ^^
  • 8. Informaticiens Protecteurs des Animaux En anglais : "Yeeepa !" C’est débile clairement, mais c’est pour le contexte fonctionnel ^^
  • 9. Good code? Le code doit être comme un bon chien. Du good code quoi. Certains disent Clean code, mais on lave tous nos animaux. Anyway, c'est quoi du good code?
  • 10. Un bon chien aide son propriétaire, maintenant et dans le futur. Le good code, c'est comme un bon chien.
  • 11. Permettre les développements futurs. Voir même les rendre plus aisés. Plus on avance, plus on doit aller vite. Idée folle ?
  • 12. objectif : enregistrement des propriétaires d'animaux nom et date de naissance (doivent être adultes) Commençons par la date de naissance
  • 13. LocalDate parse(String date){ return LocalDate.parse( date, DateTimeFormatter.ofPattern("yyyy/MM/dd")); } Un problème? Est ce du good code? Quid d’une erreur de parsing ? Exception !
  • 14. LocalDate parse(String date){ try { return LocalDate.parse( date, DateTimeFormatter.ofPattern("yyyy/MM/dd")); } catch (Exception e) { return ???; } } Rethrow - si unchecked, c'est l'origine du problème - si checked, c'est un autre problème… Et personne n’a recopié cette fonctionnalité Java, les exceptions vérifiées, depuis son invention… Passons donc ! Bref, que retourner ?
  • 15. Option to the rescue! Option<LocalDate> parse(String date){ try { return Option.of( LocalDate.parse( date, DateTimeFormatter .ofPattern("yyyy/MM/dd"))); } catch (Exception e) { return Option.none(); } } Pour l’instant, disons que l’Option présente dans le code fait la même chose que l’Optional de la librairie standard Java. Et l’essentiel est que le boulot est fait !
  • 16. Option<Integer> validateAge(String birthdate) { return parse(birthdate) .map( d -> Period.between( d, LocalDate.now()) .getYears()) .filter(age -> age >= MIN_AGE); } All good? I may want to log the exception... Optional is not enough, let's keep the exception. En fait, il s'agit d'un essai de parsing n'est ce pas ?
  • 17. Try parse(String date) { try { return new Success( LocalDate.parse( date, DateTimeFormatter .ofPattern("yyyy/MM/dd"))); } catch (Exception t) { return new Failure(t); } } Success et Failure implémentent tous deux une interface Try reprenant l'essentiel d'Option(al). On stocke désormais l'exception si une est présente et on indique qu'il s'agit d'un échec. Et on permet d'accéder au contenu de l'erreur.
  • 18. Try onFailure(Consumer<? super Throwable> action) { Objects.requireNonNull( action, "action is null"); if (isFailure()) { action.accept(getCause()); } return this; } onFailure retourne le try lui même, permettant de chaîner Et si pas failure l’action communiquée n’est pas exécutée. Facile !
  • 19. parse(birthdate) .onFailure( t -> log.debug("Parsing failure for birthdate '" + birthdate + "'", t)) .map( d -> Period.between(d, LocalDate.now()).getYears()) .filter(age -> age >= MIN_AGE); // gives a Try Le code reste grosso modo le même : on ne gère que ce qui se passe bien. On ajoute juste le log. Une ligne. Pas de if. How great is that ?
  • 20. Lancer des exceptions est fréquent! Division (par zéro) Integer.parseInt(string) Code business... File...
  • 21. static <T> Try<T> of( CheckedFunction0<? extends T> supplier) { Objects.requireNonNull( supplier, "supplier is null"); try { return new Success<>(supplier.apply()); } catch (Throwable t) { return new Failure<>(t); } } Le Try de vavr : - générique pour un usage plus large :) - un supplier pour passer l’action pouvant lancer une exception :)
  • 22. Try<T> onSuccess(Consumer<? super T> action) <U> Try<U> flatMap( Function<? super T,? extends Try<? extends U>> mapper) Try<T> andFinally(Runnable runnable) ... Bien plus dans le Try de vavr ! onSuccess, le pendant d'onFailure flatMap : permet de gérer un autre bout de code pouvant lancer des exceptions et permet de tenter autre chose si réussite du premier Try andFinally pour une action toujours réalisée quelque soit le résutlat du Try
  • 23. List<T> toJavaList() Optional<T> toJavaOptional() List<T> toList() Option<T> toOption() ... Et en bonus plein de fonctions de conversions, juste un échantillon là. Et oui vavr a sa propre List et Option, on en parle plus tard :)
  • 24. Try<LocalDate> parse(String date) { return Try.of( () -> LocalDate.parse( date, DateTimeFormatter .ofPattern("yyyy/MM/dd"))); } Parse avec Try de vavr
  • 25. Try<Integer> validateAge(String birthdate) { return parse(birthdate) .onFailure( t -> log.debug("Parsing failure for birthdate " + birthdate, t)) .map(d -> Period.between( d, LocalDate.now()).getYears()) .filter(age -> age >= MIN_AGE); } Validation de l’âge avec le Try de vavr
  • 26. Question de l'âge réglée, passons à la suite... :)
  • 27. Contrôle du nom /** * Retourne un message si erreur. */ Option<String> checkName(String name) On doit utiliser une méthode pré existante qui retourne (ou pas) une erreur
  • 28. Option<String> nomValide = Option.of(nom) .filter(n -> !n.trim().isEmpty()) .filter(n -> checkName(n).isEmpty(); Problème dans le code ci dessus : on perd les erreurs. Dur de les remonter à l’utilisateur !
  • 29. Tuple2<String, String> t = Tuple.of("foo","bar"); t._1;// foo t._2;// bar Un tuple associe plusieurs valeurs, comme une Entry de Map en Java mais en plus souple (on peut créer soit même des entrées). Il existe de Tuple0 à Tuple8 Très pratique en plein d'occasions
  • 30. Tuple2<String, Option<String>> validateNom( String nom) { return Option.of(nom) .filter(n -> !n.trim().isEmpty()) .map(n -> Tuple.of(n, Option.<String>none())) .getOrElse( Tuple .of( nom, Option.of("Le nom doit être renseigné avec des caractères alpha numériques"))); } On peut retourner la valeur et les éventuelles erreurs :) Mais… pas forcément évident de distinguer les deux… Et puis l’erreur à la fin est sacrément décorrélée de son origine...
  • 31. Either<L,R> R pour Right : valeur correcte L pour Left : alternative(s) Transporte la bonne valeur, Right, ainsi que les éventuelles erreurs ou messages associés.
  • 32. Option<Either<L, R>> filter(Predicate<? super R> predicate) <U> Either<L, U> map(Function<? super R, ? extends U> mapper) <U> Either<U, R> mapLeft( Function<? super L, ? extends U> leftMapper) <U> Either<L, U> flatMap( Function< ? super R, ? extends Either<L, ? extendsU>> mapper) Les signatures complexes ne doivent pas effrayer, il s’agit toujours des bonnes vieilles mêmes méthodes : filter, map et flatMap. Seule mapLeft est un peu différente, afin de mapper sur la valeur « left ».
  • 33. static <L, R> Either<L, R> right(R right) static <L, R> Either<L, R> left(L left) On a également des constructeurs pour créer directement la valeur droite ou gauche
  • 34. Conversions depuis Option/Try/... <L> Either<L, T> toEither(L left) <L> Either<L, T> toEither(Supplier<? extends L> leftSupplier) <R> Either<T, R> toLeft(R right) <R> Either<T, R> toLeft(Supplier<? extends R> right) <L> Either<L, T> toRight(L left) <L> Either<L, T> toRight(Supplier<? extends L> left)
  • 35. Either<String, String> validateNom(String nom) { return Option.of(nom) .filter(n -> !n.trim().isEmpty()) .toEither("Le nom doit être renseigné avec des caractères alpha numériques") .flatMap(s -> { Option<String> errors = checkName(s); return errors.toLeft(s); }); } On utilise flatMap afin de pouvoir utiliser la valeur Right pour déduire la nouvelle valeur Left Comme précédemment, ce flatMap n’est appelé que si Either a la valeur Right. A défaut rien ne se passe et on demeure sur la valeur Left définie plus haut, à savoir « Le nom doit être renseigné avec des ... »
  • 36. Either<String, Integer> validateAge(String Birthdate) { return parse(birthdate) .onFailure(t -> log.debug("Parsing failure for birthdate " + birthdate, t)) .toEither("La date de naissance doit être au format " + DATE_PATTERN + ".") .map(d -> Period.between( d, LocalDate.now()).getYears()) .flatMap(age -> (age >= MIN_AGE) ? Either.right(age) : Either.left("Il faut avoir plus de " + MIN_AGE)); } Ici le flatMap se fait sur une either, vu qu’on a besoin de l’age pour déterminer s’il est correct.
  • 37. Either<String, Person> validatePerson( String name, String birthdate) { return validateNom(name) .flatMap(nomValide -> validateAge(birthdate) .map(age -> new Person(nomValide, age))); } Là on ne lève pas de nouveaux cas d’erreurs, du coup on se contente du flux normal. Joli non ? A noter qu’on peut aussi se servir d’Either pour une alternative (indépendamment de toute gestion d’erreur). Par exemple « Either<Cat,Dog> ». Autre usage souvent pratique :)
  • 38. Option, Try et Either Beaucoup de ressemblances Plusieurs fois le même principe: une ou des valeurs sur lesquelles on applique des opérations. Expliciter le comportement du code
  • 39. Option → Absence Try → Exception Either → Alternative Et tout cela à côté du cas normal Option, Try et Either explicitent des situations parfois cachées dans notre code Ainsi le code devient bien plus clair. Qui plus est, la façon d’encapsuler les éléments mis en avant, cad de n’appliquer les map ou flatMap que si la valeur est ok (cad non absente, sans exception et sans l’alternative) permet de faire un code très lisible, concis et sans if imbriqués. Du bonheur pour le lecteur :)
  • 40. Monade un état des opérations dans la monade des opérations de sortie de la monade La ressemblance est plus profonde que juste des similitudes de comportement, ces 3 notions sont trois déclinaisons d’un même concept : la monade. - un état (un élément ou plusieurs) - des opérations "dans" la monade (map, filter....) -- "happy path" dans les monades vues jusqu'ici, on ne décrit que le "chemin nominal", on ignore soit les autres soit ils sont un état à part embarqué en même temps -- permet de différer le traitement des erreurs quand on veut, une fois à la fin généralement - des opérations de sortie de la monade (get...)
  • 41. Monade Méthodes essentielles constructeur flatMap Avec le constructeur et flatMap on peut faire map, filter... En Haskell on appelle ces méthodes unit et bind
  • 42. IO Monad :) En programmation fonctionnelle, on utilise une monade pour décrire les opérations d'IO : description dans la monade des opérations, déclenchement à la demande en sortant de la monade
  • 43. Plus proche de nous, d’autre monades se cachent… Avez vous connaissance d’une autre Monade que vous utilisez au quotidien ? En Java..
  • 45. Outils de programmation fonctionnelle... en Java! Avec vavr, c’est java à l’envers. Littéralement : inverser le logo ! On reste toutefois à des niveaux « simples » de PF, du fait notamment des limites du langage Mais c’est déjà beaucoup et permet d’améliorer sensiblement notre code.
  • 46. Inspiré de Scala... en mieux! Découle de la lib standard Scala L'API de collection Scala se décline en version mutable ou immuable pour une même interface API à mi chemin, implémentation partiellement partagée source de bugs... Vavr ayant choisi de ne faire que de l'immuable, tout est grandement simplifié
  • 47. Précédemment javaslang... Actuellement en 0.9.2 Changement de nom car pb de copyright, Java étant la marque d’Oracle. Le nom Vavr a été trouvé en regardant le reflet d’un sticker « javaslang ». En profite pour un "reset", pas mal de modif non rétrocompatible prévues pour la 1.0.0
  • 48. Optional.map différent Transforme les retours null en empty()
  • 50. Transformer null en None toujours possible flatMap(x -> Option.of(x)) Déjà, retourner les nulls n’empêche pas d’atteindre le même résultat qu’Optional si on le désire
  • 51. map() retournant les nulls uniforme null != None associatif Mais au-delà, transformer les null en None serait étrange dans un Try ou un Either… Sans parler d’une List où null est une valeur valide ! D’ailleurs, dans une Map, avoir une clé avec une valeur nulle est différente de l’absence de valeur. Optional perd donc de l’information. Cette perte d’information est en fait dramatique : suivant l’ordre dans lequel on applique les map, Optional peut avoir différents résultats. En effet, nombreuses sont les méthodes pouvant retourner un résultat pour une valeur nulle. Or celles ci ne seront jamais appelée dans le cas d’Optional. Cet aspect, l’associativité, est une des lois des monades.
  • 52. map() retournant les nulls Risque de NullPointerException ? On risque toujours des NPE, même avec Optional. Au sein d’un map on met du code. Et là le risque de NPE persiste. Null est bel et bien une valeur possible en Java, Optional ne pourra rien y changer. A propos, qui a tenté de retourner null dans le flatMap d’Optional ? Du coup le gain de transformer les nulls en empty est très faible, alors que les pertes sont énormes.
  • 53. Retour au sujet Où en étions nous ? Pas simple de revenir sur un sujet après une distraction hein ? Vrai problème...
  • 54. Quid d’une doc pour avoir un récap ? Une petite doc dans l’idée devrait bien faire le job non ? On n’a pas 200 règles, on pourrait facilement avoir un aperçu…
  • 55. Une doc toujours à jour ? Seul hic, 99 % des docs ne sont pas à jour… Et le 1 % restant est souvent infâme de relents de procédures qualité ^^ Mais ce serait tellement bien...
  • 56. Une doc interactive ? Encore mieux qu’à jour, quid d’une doc qu’on pourrait manipuler ? Ajouter des cas, voir ce qu’ils retournent… A la volée, comme ça, parce qu’on le veut ?
  • 57. Informaticiens Protecteurs des Animaux On sait aussi teaser hein ? Vous pensez que c’est du rêve tout ça ? Vous voulez la réponse ?
  • 58. Elements validés en vert, éléments en erreur en rouge, avec résultats effectifs affichés et ceux erronés barrés.
  • 59. Spécifications par l’exemple L’idée des spécifications par l’exemple est qu’un exemple vaut beaucoup d’explications. C’est en discutant d’exemples concrets que l’on parvient à affiner. Il est donc important de matérialiser et mettre en conserver ces exemples.
  • 60. Affichage via html ou markdown Java ou .Net L’html (ou le markdown) donne une liberté de présentation infinie fortement appréciable, que ce soit en termes de contenu (choix de la langue) ou présentation.
  • 61. Communication possible de l’html ou du markdown Avec ou sans exécution du test On peut passer les fichiers bruts à n’importe qui. Là l’html a un avantage à mon sens : tout le monde a un navigateur a même de l’ouvrir, un outil pour visualiser du markdown est plus compliqué à avoir.
  • 62. Je me suis occupé de la mise en forme… Bien sûr qu’on en passe l’html d’origine sans exécution du test, on ne sait pas ce qui valide ou pas. Ceci dit, cela ne devrait casser que dans des feature branchs, pas en prod ou develop. Au développeur de s’assurer qu’il passe la bonne version. On peut aussi directement accéder à ces fichiers, en tant que non codeurs, via un outil de navigation de repo (github…).
  • 63. Instrumentation de l’affichage & Appels à la classe de test Évidemment ça ne marche pas tout seul ^^
  • 64. <table concordion:execute="#result = validate(#name,#birthdate)"> <tr> <th concordion:set="#name">Nom</th> <th concordion:set="#birthdate">Date de naissance <br/>(yyyy/MM/dd)</th> <th concordion:assert- equals="#result">Résultat</th> </tr> <tr> <td>Dupont</td> <td>1999/03/21</td> <td>Person(Dupont, 18)</td> </tr> </table> Coté htlm, on ajoute des éléments qui ne perturbent pas l’affichage, indiquant : - ce qu’on capture - ce qu’on exécute - ce qu’on vérifie. Dans le cas d’un tableau, on définit une fois le comportement au niveau de l’header. Cela s’applique alors sur chaque ligne.
  • 65. @RunWith(ConcordionRunner.class) public class PersonValidatorTest { public String validate( String name, String birthdate) { Either<String, String> strings = new PersonValidator() .validatePerson(name, birthdate) .map(Person::toString); return strings .getOrElse(() -> strings.getLeft()); } } La classe de test fait le lien entre les méthodes appelées depuis l’affichage et le code testé. Ici on « aplatit » les sorties pour les passer toutes en String.
  • 66. Exécution avec les tests unitaires Embarqué avec le code Le coût d’exécution d’un concordion est minime. On peut les exécuter en même temps que les tests unitaires (ce qui est le comportement par défaut). Le fait que tout soit avec le code est génial. En effet, si on utilise un gestionnaire de sources alors on a l’historique des changements et on peut adapter les concordion dans une feature branch quand cela s’impose, sans impacter les autres branches.
  • 67. Exemples hors tables possibles Également intéressant, mais plus limité en portée et plus coûteux en instrumentation.
  • 68. Nombreuses extensions Import d’xls Captures d’écran ... Non content de décrire les spécifications, on peut aussi faire une « vraie doc ».
  • 69. Documentation vivante ! Une documentation vivante : - s’adapte aux changements (captures actualisées) - dit si elle est périmée (tests qui plantent) - s’adapte aux différentes versions (est stockée par branche, avec le code)
  • 70. Du (good) code Attention toutefois, la facilité d’instrumentation, via du code, peut parfois conduire à des usines à gaz, comme tout code. Toujours penser à limiter le scope, à refactorer, à découper si cela devient trop complexe. On est vite embarqué dans un cycle perfectionniste de l’affichage, mais ce n’est pas une fin en soi !
  • 71. Jouons un peu avec les spécifications Souvent en ajoutant des exemples on découvre des choses...
  • 72. Arrêt à la première erreur de validation...
  • 73. Validation<E, T> <E> value type in the case of invalid <T> value type in the case of valid Très utile pour toutes les validations, notamment d’une IHM.
  • 74. Validation<String, String> nomValidation( String nom) { return Option.of(nom) .filter(n -> !n.trim().isEmpty()) .toValidation("Le nom doit être renseigné avec des caractères alpha numériques") .flatMap(s -> checkName(s).toInvalid(s)); } On indique le type d’erreur à remonter, là on a pris String, pour l’exemple, mais dans la vraie vie ça peut être bien plus évolué. On transforme l’état stocké dans la monade, ici le nom validé.. On décide alors s l’état de la monade est valide ou non puis on fournit l’autre information pour faire la validation.
  • 75. Validation<Seq<String>, Person> personValidation( String name, String birthdate) { return Validation .combine( nomValidation(name), ageValidation(birthdate)) .ap(Person::new); } A plus haut niveau, soit tout est valide et on construit une instance de ce qui est validé, soit on retourne les erreurs.
  • 77. Conversion en validation possible pour toutes les monades <E> Validation<E, T> toValid(E error) <U> Validation<T, U> toInvalid(U value) Du moins en vavr ;)
  • 78. Validation via du code Si besoin de différentes validations suivant le contexte, easy :)
  • 81. Et la mutabilité alors ?
  • 82. Partage de Stream de Java 8 possible ? - Oui, mais le premier qui collecte plante le Stream pour tous les autres consommateurs! - Oh, ce partage est il thread safe? Plein de choses à avoir à l’esprit !
  • 83. Vivre avec java.util.ArrayList<E>... Risque de changement dans la liste sans qu'on le sache... Que ce soit dans le code écrit dès à présent ou ... dans tout code écrit dans le futur!! Retour à la définition de code propre : Est ce le cas avec une list muable? Si oui, pouvez vous en être sûrs? Bien sûr, des palliatifs sont possibles: copies défensives, Collections.immutableList... Mettez vous toujours en oeuvre ces palliatifs ? A quel coût ? A propos, les éléments d'une immutableList peuvent ils être modifiés ?
  • 84. Immuabilité == tranquillité Quand tout est immuable, pas d’inquiétude. On peut passer son objet à tout le monde, ils ne pourront pas faire de mal. Contexte multi threadé ou non. Plus besoin de programmation défensive, plus de bug de folies dus à un changement non prévu. Que du bonheur :)
  • 85. Et les perfs alors ?
  • 86. Persistent data structure Ensemble d’algorithmes pour rendre efficace les structures immuables, que ce soit en coût de garbage collection ou rapidité de modification/consultation.
  • 87. List<Integer> list1 = List.of(1, 2, 3); // List(1, 2, 3) List<Integer> list2 = list1.tail().prepend(0); // List(0, 2, 3) On parle là de « structural sharing » : partage de données. On évite ainsi de créer de nouveaux tableaux et de faire pleins de nouvelles références à chaque action, voir même de dupliquer les objets référencés « par précaution »..
  • 88. Très peu de garbage collection
  • 89. SortedSet<Integer> xs = TreeSet.of(6, 1, 3, 2, 4, 7, 8); // TreeSet(1, 2, 3, 4, 6, 7, 8) SortedSet<Integer> ys = xs.add(5); // TreeSet(1, 2, 3, 4, 5, 6, 7, 8) La structure en arbre permet toujours le « structural sharing » mais en plus des mises à jour et parcours rapides. Lors des changements on change juste les « branches » affectées, le reste peut être réutilisé :)
  • 90. Utilisation de Tree Accès en temps constant Performances ! Au delà du partage de structure, des algoritmes tels que le Red/Black Tree permet de s’assurer de l’accès à chaque donnée en 4 sauts maximum, soit un temps d’accès constant quelque soit la taille du Set… D’ailleurs ces techniques ont été repris dans pour les collections de java.util.concurrent depuis java 7. Il y a même des discussions pour passer la taille d’une ConcurrentHashMap à long : désormais int et ses 4 milliards peuvent être atteints sans problème.
  • 91. Y a pas que les perfs ! Et l’IPA ? Au delà du partage de structure, des algoritmes tels que le Red/Black Tree permet de s’assurer de l’accès à chaque donnée en 4 sauts maximum, soit un temps d’accès constant quelque soit la taille du Set… D’ailleurs ces techniques ont été repris dans pour les collections de java.util.concurrent depuis java 7. Il y a même des discussions pour passer la taille d’une ConcurrentHashMap à long : désormais int et ses 4 milliards peuvent être atteints sans problème.
  • 92. List<Integer> ints = List.of(1, 22); // List(1, 22) ints.head(); // 1 ints.headOption(); // Some(1) tail(); // List(22) ints.tailOption(); // Some(List(22)) En termes d’accesseurs, on peut aisément avoir le premier élément, head, et les suivants, tail.
  • 93. ints; // List(1, 22) ints.map(i -> "Mon int est " + i); // List(Mon int est 1, Mon int est 22) ints.flatMap(i -> List.of(i)); // List(1, 22) Bien sûr, map et flatMap sont toujours là :)
  • 94. ints; // toujours List(1, 22)! ints.fold(0, (acc, val) -> acc + val); // 23 ints.foldLeft("acc", (acc, val) -> acc + val); // acc122 ints.foldRight("acc", (val, acc) -> acc + val); // acc221 Fold permet de parcourir tous les éléments de la liste et d’accumuler le résultat au fur et à mesure FoldLeft parcours la liste en commençant par la gauche FoldRight parcours la liste en commençant par la droite Ces deux derniers folds permettent d’avoir un accumulateur de type différent que les éléments.
  • 95. ints; // toujours List(1, 22)! ints.zipWithIndex(); // List((1, 0), (22, 1)) ints.intersperse(5); // List(1, 5, 22) ints.distinct(); // List(1, 22) ints.find(i -> i > 10); // Some(22) Avec les folds et les méthodes précédentes, on devine vite qu’on peut faire plein de choses, mais vavr a la gentillesse de nous proposer les plus courantes et/ou les plus pratiques ! De façon générale, rares sont les cas où vavr n’a pas de quoi grandement simplifier notre quotidien :)
  • 96. Pas de .stream() et autres .collect()
  • 97. Offre en fait bien plus ! Pattern matching, stream (collection infinies)... Vivement la 1.0 :)
  • 98. That’s all folks… until next time !