1 of 56

2 of 56

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

3 of 56

Décollage d'Apollo : 4 pièces mobiles

3

[...]which left only four moving parts to go wrong in the entire ascent engine[...]

APOLLO 11 Owner's Workshop Manual - Haynes

4 of 56

Plan

4

Problèmes

Solution

Implémentation

Refactoring

Et après ?

Des questions ?

Prenez-note ✏ :

5 of 56

Les problèmes

5

6 of 56

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

7 of 56

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

8 of 56

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

9 of 56

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

10 of 56

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

11 of 56

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

12 of 56

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

13 of 56

Avant : multi-threading

public synchronized boolean wouldBeImportantWith(Truc truc) {� setTruc(truc);� recomputeState();� return getState() == State.IMPORTANT;�}

13

14 of 56

Après : multi-threading

public boolean wouldBeImportantWith(Truc truc) {� return withTruc(truc).getState() == State.IMPORTANT;�}

14

15 of 56

Une solution

15

16 of 56

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

17 of 56

Exemples de classes immutables en Java

String

BigDecimal

Instant, LocalDate, ZonedDateTime, etc.

17

18 of 56

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

19 of 56

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

20 of 56

Implémentation

20

21 of 56

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

22 of 56

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

23 of 56

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

24 of 56

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

25 of 56

Refactoring

Refactoring

25

26 of 56

L’application d’exemple

26

Édité :�par Sébastien L.�le 26/04/2022�via import

27 of 56

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 ?

28 of 56

Les classes

28

BigDeci.�amount

Currency�currency

Price

PriceReport

Édité :�par Sébastien L.�le 26/04/2022�via import

Edit

Action

User

29 of 56

1/4 : préférez la composition à l'héritage

29

30 of 56

2a/4 : transformer en Value Objects immutables

30

31 of 56

2a/4 : transformer en Value Objects immutables

31

32 of 56

2a/4 : transformer en Value Objects immutables

32

33 of 56

2a/4 : transformer en Value Objects immutables

33

34 of 56

2b/4 : construire les objets par leurs constructeurs

GetPriceReportsUseCase

34

35 of 56

2b/4 : construire les objets par leurs constructeurs

InsertPriceReportRowUseCase

Tous les bugs étaient dans le vrai projet ayant servi d’inspiration

35

36 of 56

2b/4 : construire les objets par leurs constructeurs

UpdateAmountUseCase

36

37 of 56

2b/4 : construire les objets par leurs constructeurs

UpdateCurrencyUseCase

37

38 of 56

2b/4 : construire les objets par leurs constructeurs

XlsxImportUseCase

38

39 of 56

3a/4 : créer des with*() & static-factories orientées métier

GetPriceReportsUseCase

39

40 of 56

3a/4 : créer des with*() & static-factories orientées métier

InsertPriceReportRowUseCase

40

41 of 56

3a/4 : créer des with*() & static-factories orientées métier

UpdateAmountUseCase

41

42 of 56

3a/4 : créer des with*() & static-factories orientées métier

UpdateCurrencyUseCase

42

43 of 56

3b/4 : créer des with*() & static-factories orientées métier

43

44 of 56

44

45 of 56

4/4 : centraliser les règles métier structurantes

45

46 of 56

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

47 of 56

Mission accomplie

4 parties mobiles

47

48 of 56

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

49 of 56

Et après ?

49

50 of 56

Et Hibernate, dans tout ça ?

@Embeddable +�@Embedded +�@AttributeOverrides

EmbeddableInstantiator�Dans Hibernate 6�Sortie le 31 mars 2022

50

51 of 56

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

52 of 56

Liens

52

53 of 56

Vidéos

Tell Don't Ask Principle Kata�Le kata : https://kata-log.rocks/tell-dont-ask-kataExplication : https://www.youtube.com/watch?v=36ILTQb_JpI�Solution partie 1 : https://www.youtube.com/watch?v=WKTdM6uObQQSolution partie 2 : https://www.youtube.com/watch?v=6cB0qUrTvQs

Decluttering Java - Project Lombok Best Practices�https://www.youtube.com/watch?v=DaOmyyRA8VU

53

54 of 56

Aides mémoire de refactoring

54

55 of 56

Questions ?

55

56 of 56

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