Qui suis-je ?
2
Sébastien Laoût
Chez Ubik Ingénierie depuis 10 ans
Développeur full-stack depuis 14 ans
Java / TypeScript
Principalement sur de l'e-commerce
Actuellement Tech Leach chez Adeo Services
Chargé de la maintenabilité du projet
Décollage d'Apollo : 4 pièces mobiles
3
[...]which left only four moving parts to go wrong in the entire ascent engine[...]
Plan
4
Problèmes
Solution
Implémentation
Refactoring
Et après ?
Des questions ?
Prenez-note ✏ :
Les problèmes
5
Avant : (ré-)initialisation en 13 ou 18 étapes
Truc createTruc(int x) {� Truc truc = new Truc(); // Nulls uniquement transitoires ?� truc.setStatus(VALID);� initBidule(truc, x, false);� validate(truc);� if (someCondition()) {� initBidule(truc, x, true); // Réinitialisation !� truc.setStatus(INVALID); // Un objet à demi-initialisé est valide !� }�}
6
Après : initialisation en une étape
Truc createTruc(int x) {� Truc truc = new Truc(� someCondition() ? INVALID : VALID, // Décision à un endroit� createBidule(x, someCondition())� ); // Truc() se valide lui-même�}
7
Avant : appel de setter ou builder oublié
Truc truc1 = new Truc();�truc1.setOjdsfg(51);�truc1.setGjdkfh(15);�truc1.setPbsugi(59);
Truc truc2 = Truc.builder()� .pbsugi(36);� .ojdsfg(14);� .ybchjo(68);� .build();
8
Avant : appel de setter ou builder oublié
Truc truc1 = new Truc();�truc1.setOjdsfg(51);�truc1.setGjdkfh(15); // Manquant dans truc2�truc1.setPbsugi(59);
Truc truc2 = Truc.builder()� .pbsugi(36);� .ojdsfg(14);� .ybchjo(68); // Manquant dans truc1� .build();
9
Après : erreur de compilation si paramètre oublié
Truc truc1 = new Truc(51, 59, 15, -1);
Truc truc2 = new Truc(14, 36, -2, 68);
10
Avant : dommages collatéraux
Bidule bidule = getBiduleById(id);�trucs.get(0).setBidule(bidule);�trucs.get(1).setBidule(bidule);��// Quelques méthodes plus tard...�trucs.get(1).getBidule().setHeight(42);
11
Après : aucun souci
Bidule bidule = getBiduleById(id);�trucs.get(0).setBidule(bidule);�trucs.get(1).setBidule(bidule);��// Quelques méthodes plus tard...�Truc truc = trucs.get(1);�truc.setBidule(truc.getBidule().withHeight(42));
12
Avant : multi-threading
public synchronized boolean wouldBeImportantWith(Truc truc) {� setTruc(truc);� recomputeState();� return getState() == State.IMPORTANT;�}
13
Après : multi-threading
public boolean wouldBeImportantWith(Truc truc) {� return withTruc(truc).getState() == State.IMPORTANT;�}
14
Une solution
15
Qu’est-ce qu’un Value-Object immutable ?
Objet non-modifiable après sa construction�Créé en une seule opération atomique
Aucun setter�Ses champs sont finaux
Objet à copier pour en obtenir des variantes�Repasser par un constructeur, directement ou indirectement
Objet toujours dans un état cohérent�Pas de transitions d’états pendant l’initialisation�Le constructeur s’assure de cette cohérence
16
Exemples de classes immutables en Java
String
BigDecimal
Instant, LocalDate, ZonedDateTime, etc.
17
Exemples de classes immutables métier
Une adresse postale�Changer un champ revient à déménager
Argent�Montant + devise
Nom complet�Prénom + nom
Couleurs�Mixer deux couleurs produit une troisième couleur
Numéros de téléphone, adresses IP…
18
C’est quand même contraignant !
Oui et non…
Chaque évolution ajoute des contraintes pour se faciliter la vie�Tests+boucles au lieu de GOTOs�Pas d’accès direct à la mémoire�Programmation orientée objet�Programmation fonctionnelle : immutabilité
19
Implémentation
20
Implémentation
Java :�public final class Color {� private final String hex;�� public Color(String hex) {� this.hex = hex;� }�� public String getHex() { return hex; }� // equals� // hashCode� // [toString] [compareTo]�}
Lombok :�@Value�public class Price {� BigDecimal amount;� Currency currency;�}�
Java 16 :�(preview en 14 et 15)�public record FullName(� String firstName,� String lastName) {}
21
Attention : copier les objets mutables
List<String> names = new ArrayList<>(List.of(“red”, “green”));�Colors immutable = new Colors(names);�names.add(“blue”);�immutable.getNames().add(“purple”);
22
Attention : héritage fortement déconseillé
@Value class UserId { long id; }
class MutableUserId extends UserId {� private long mutableId;� public MutableUserId(long id) { super(id); mutableId = id; };� public long getId() { return mutableId; }� public void setId(long id) { mutableId = id; }�}
23
Autorisé : mutabilité en interne
class Lower {� private /*final*/ String value;� private /*final*/ boolean lazyLowered;� public Lower(String value) { this.value = value; }� public get() {� if (!lazyLowered) {� value = value.toLowerCase();� lazyLowered = true;� }� return lazyLowered;� }�}
24
Refactoring
Refactoring
25
L’application d’exemple
26
Édité :�par Sébastien L.�le 26/04/2022�via import
Les gestes métier
������Chargement
Insérer une ligne
Changer un montant
Changer la devise
Importer un fichier Excel
27
Éditions à historiser�Qui ? Quand ? Pourquoi ?
Les classes
28
BigDeci.�amount
Currency�currency
Price
PriceReport
Édité :�par Sébastien L.�le 26/04/2022�via import
Edit
Action
User
1/4 : préférez la composition à l'héritage
29
2a/4 : transformer en Value Objects immutables
30
2a/4 : transformer en Value Objects immutables
31
2a/4 : transformer en Value Objects immutables
32
2a/4 : transformer en Value Objects immutables
33
2b/4 : construire les objets par leurs constructeurs
GetPriceReportsUseCase
34
2b/4 : construire les objets par leurs constructeurs
InsertPriceReportRowUseCase
Tous les bugs étaient dans le vrai projet ayant servi d’inspiration
35
2b/4 : construire les objets par leurs constructeurs
UpdateAmountUseCase
36
2b/4 : construire les objets par leurs constructeurs
UpdateCurrencyUseCase
37
2b/4 : construire les objets par leurs constructeurs
XlsxImportUseCase
38
3a/4 : créer des with*() & static-factories orientées métier
GetPriceReportsUseCase
39
3a/4 : créer des with*() & static-factories orientées métier
InsertPriceReportRowUseCase
40
3a/4 : créer des with*() & static-factories orientées métier
UpdateAmountUseCase
41
3a/4 : créer des with*() & static-factories orientées métier
UpdateCurrencyUseCase
42
3b/4 : créer des with*() & static-factories orientées métier
43
44
4/4 : centraliser les règles métier structurantes
45
Gloire au compilateur !
Les bugs initiaux sont devenus des erreurs de compilation !
On a créé des méthodes réutilisables�décrivant les gestes métier
Le code est plus simple et descriptif�malgré les setX(getX().withY(y))
On vérifie l’intégrité dans le constructeur, passage obligé�même si on n’appelle quasiment jamais le constructeur directement
46
Mission accomplie
4 parties mobiles
47
Retrouver l’exercice
https://github.com/slaout/immutability-super-power-kata
Le kata�Branche “main”
La solution�Branche “java/exercise1/solution”
Une solution qui va plus loin�Branche “java/exercise1/solution-bonus”
48
Et après ?
49
Et Hibernate, dans tout ça ?
@Embeddable +�@Embedded +�@AttributeOverrides
EmbeddableInstantiator�Dans Hibernate 6�Sortie le 31 mars 2022
50
Niveaux suivants
Autres principes�Tell Don't Ask�Feature Envy�Loi de Déméter�Encapsulation
The Checker Framework : @NotNull / @Nullable
Domain Driven Design
Programmation fonctionnelle
Vavr
Kotlin ?
51
Liens
52
Vidéos
Tell Don't Ask Principle Kata�Le kata : https://kata-log.rocks/tell-dont-ask-kata�Explication : https://www.youtube.com/watch?v=36ILTQb_JpI�Solution partie 1 : https://www.youtube.com/watch?v=WKTdM6uObQQ�Solution partie 2 : https://www.youtube.com/watch?v=6cB0qUrTvQs
Decluttering Java - Project Lombok Best Practices�https://www.youtube.com/watch?v=DaOmyyRA8VU
53
Aides mémoire de refactoring
(par Victor Rentea)�https://drive.google.com/drive/folders/1hvLAWeAgT753Mkb8zaHxNyBYwCewuWbb
54
Questions ?
55
Crédits des images
Voir les commentaires présentateur des diapositives concernées sur�https://docs.google.com/presentation/d/1T_q3zIphJUg_Vy9SHhShFILbqupDH3_8cYYvPaf4l5k
56