IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

  1. #1
    Chroniqueur Actualités

    Homme Profil pro
    Administrateur de base de données
    Inscrit en
    Mars 2013
    Messages
    9 092
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Canada

    Informations professionnelles :
    Activité : Administrateur de base de données

    Informations forums :
    Inscription : Mars 2013
    Messages : 9 092
    Points : 209 783
    Points
    209 783
    Par défaut Java 14 est disponible en version définitive avec de nouvelles fonction de productivité des développeurs
    La version stable du Java Developpement Kit 14 est prévue pour février 2020,
    petit aperçu de fonctionnalités en préversion

    Le cycle de sortie de Java a changé de façon assez profonde récemment, ce qui signifie que nous obtenons de nouvelles fonctionnalités à un rythme plus rapide qu'auparavant. Aujourd'hui nous parlerons de deux fonctionnalités intéressantes du langage.

    Améliorations au niveau du switch

    Selon certains professionnels, l'instruction switch classique en Java n'est pas géniale. Contrairement à de nombreuses autres parties de Java, elle n'a pas été correctement repensée lors du retrait des fonctionnalités de C il y a toutes ces années. Le défaut clé est la "chute par défaut". Cela signifie que si vous oubliez de mettre une clause break dans chaque cas, le traitement se poursuivra jusqu'à la clause case suivante.

    Un autre défaut est que les variables sont étendues à l'intégralité du switch, vous ne pouvez donc pas réutiliser un nom de variable dans deux clauses case différentes. De plus, une clause par défaut n'est pas requise, ce qui laisse les lecteurs du code dans l'incertitude quant à savoir si une clause a été ou non oubliée.

    Et bien sûr, il y a aussi la principale limitation - le type à activer ne peut être qu'un entier, une énumération ou une chaîne.

    Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
     String instruction;
     switch (trafficLight) {
       case RED:
         instruction = "Stop";
       case YELLOW:
         instruction = "Prepare";
         break;
       case GREEN:
         instruction = "Go";
         break;
     }
     System.out.println(instruction);

    Le code ci-dessus ne se compile pas, car il n'y a pas de clause default par défaut, laissant instruction indéfinie. Mais même s'il compilait, il n'imprimerait jamais "Stop" en raison du break manquant.

    Dans le cadre du Project Amber, switch a bénéficié de quelques changements ! Ils ont été lancés en préversion dans JDK 12 et à nouveau dans 13 et sont au moment de la rédaction destinés à être publiés dans JDK 14. Les développeurs vont disposer d'une nouvelle fonctionnalité lorsque switch sera utilisé comme instruction. En outre, il pourra également être utilisé comme une expression à l'avenir.

    Bienvenue case X ->

    Dans la syntaxe de switch, il est désormais possible de se servir de case X -> en plus du traditionnel case X:. Dans ces cas d'utilisations, seule l'expression ou l'instruction sur le côté droit de la flèche sera exécutée. Cela signifie que vous n'aurez plus besoin de vous rappeler d'utiliser break pour sortir du switch dès lors qu'une languette va correspondre.

    Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    String quantityString;
    switch (n) {
        case 1 -> quantityString = "one";
        case 2 -> quantityString = "two";
        default -> quantityString = "many";
    }

    Expressions switch

    switch peut désormais être utilisé comme expressions, c'est-à-dire qu'il peut retourner une valeur. Cela signifie que nous n'aurons pas à déclarer d'abord une variable, puis à affecter une valeur dans chaque branche. Combiné avec des étiquettes de flèche, il nous permet d'exprimer notre intention en beaucoup moins de lignes de code:

    Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    String quantityString = switch (k) {
        case 1 -> "one";
        case 2 -> "two";
        default -> "many";
    };

    Parfois, vous pourriez être obligé d'exécuter un bloc de code dans le cadre d'une expression de cas. Afin de combiner cela avec la syntaxe d'étiquette de flèche, vous devez yield une valeur à la fin du bloc:

    Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    DayType type = switch (day) {
        case 1, 2, 3, 4, 5 -> WEEKDAY;
        case 6, 7          -> WEEKEND;
        default            -> {
            logger.warn(day + " is not a valid day. Legal values are [1..7]");
            yield UNKNOWN;
        }
    };

    Nom : jdk.png
Affichages : 27934
Taille : 82,7 Ko

    Vous pouvez également transformer les switch d'instruction en expression à l'aide des anciennes étiquettes de style en vous servant de yield.

    Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    Type type = switch (day) {
        case 1, 2, 3, 4, 5:
            yield WEEKDAY;
        case 6, 7:
            yield WEEKEND;
        default:
            logger.warn(day + " is not a valid day.");
            yield UNKNOWN;
    };

    N'oubliez pas que c'est la syntaxe des libellés fléchés qui empêche les chutes. Cela signifie que si vous oubliez de yield, l'expression de cas suivante sera évaluée et vous pourriez vous retrouver avec le mauvais résultat.

    Qu'en pensent les professionnels ?

    Le développeur Stephen Colebourne s'est laissé aller à un billet où il soulève quelques questions importantes concernant l'UX de ces fonctionnalités.

    « Je ne suis pas convaincu des mérites du nouveau design. Pour être clair, il y a de bons aspects, mais dans l'ensemble, je pense que la solution est trop complexe et avec des choix de syntaxe désagréables.

    « L'objectif principal est d'ajouter un formulaire d'expression, où vous pouvez affecter le résultat du switch à une variable. C'est un peu comme l'opérateur ternaire (par exemple. x != Null ? x : ""), qui est l'équivalent d'expression d'une instruction if. Une forme d'expression réduirait les problèmes comme la variable non définie ci-dessus, car elle rend plus évident que chaque branche doit entraîner une variable.

    « Le plan actuel consiste à ajouter non pas une, mais trois nouvelles formes de commutation. Oui, trois.
    • Type 1: déclaration avec syntaxe classique. Comme aujourd'hui. Avec fall-through-by-default. Pas exhaustive.
      • Déclaration
      • Fall-through-by-default
      • retour autorisé, également continue / break une boucle
      • Portée unique pour les variables
      • La logique pour chaque cas est une séquence d'instructions pouvant se terminer par yield
      • Non exhaustif - une clause par défaut n'est pas requise
      • yieldle rendement n'est pas autorisé
    • Type 2: Expression avec une syntaxe classique. NOUVEAU! Avec fall-through-by-default. Doit être exhaustive.
      • Expression
      • Fall-through-by-default
      • retour non autorisé, impossible de continue / break une boucle
      • Portée unique pour les variables
      • La logique pour chaque cas peut être une expression de rendement ou une séquence d'instructions se terminant potentiellement par yield
      • Exhaustive - une clause par défaut est requise
      • Doit utiliser yield pour renvoyer des valeurs
    • Type 3: instruction avec nouvelle syntaxe. NOUVEAU! Pas de fall-through. Pas exhaustive.
      • Déclaration
      • L'interruption n'est pas autorisée
      • retour autorisé, ainsi que continue / break sur une boucle
      • aucun problème de portée variable, la logique pour chaque cas doit être une instruction ou un bloc
      • Non exhaustif - une clause par défaut n'est pas requise
      • yield n'est pas autorisé
    • Type 4: Expression avec une nouvelle syntaxe. NOUVEAU! Pas de fall-through. Doit être exhaustive.
      • Expression
      • L'interruption n'est pas autorisée
      • retour non autorisé, impossible de continue / break une boucle
      • Aucun problème de portée variable, la logique pour chaque cas doit être une expression ou un bloc se terminant par yield
      • Exhaustive - une clause par défaut est requise
      • Doit utiliser yield pour renvoyer des valeurs, mais uniquement à partir de blocs (c'est implicite quand ce n'est pas un bloc)

    Voici un exemple de type 4

    Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     // type 4
     var instruction = switch (trafficLight) {
       case RED -> "Stop";
       case YELLOW -> "Prepare";
       case GREEN -> "Go";
     };
     System.out.println(instruction);

    « Comme on peut le voir, la nouvelle syntaxe de type 3 et 4 utilise une flèche au lieu d'un deux-points. Et il n'est pas nécessaire d'utiliser break si le code se compose d'une seule expression. Il n'y a pas non plus besoin d'une clause par défaut lors de l'utilisation d'une énumération, car le compilateur peut l'insérer pour vous à condition que vous ayez inclus toutes les valeurs énumérées connues. Donc, si vous avez raté GREEN, vous obtiendrez une erreur de compilation.

    « Le diable est bien sûr dans le détail.

    «  Premièrement, un point clairement positif. Au lieu de passer à travers en listant plusieurs étiquettes, elles peuvent être séparées par des virgules :

    Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     // type 4
     var instruction = switch (trafficLight) {
       case RED, YELLOW -> "Stop";
       case GREEN -> "Go";
     };
     System.out.println(instruction);

    « Simple et évident. En plus d'éviter de nombreux cas d'utilisation simples de sortie. Et si le code à exécuter est plus complexe qu'une expression ?

    Ce qu'il dit de tout cela ?

    « Les expressions switchde commutateur de type 4 sont correctes (bien que j'ai de réels problèmes avec l'extension de la syntaxe des flèches à partir de lambdas). Mon problème est avec les types 2 et 3. En réalité, ces deux types de switch seront très rares, et donc la plupart des développeurs ne les verront jamais. Compte tenu de cela, je pense qu'il serait préférable de ne pas les inclure du tout. Une fois que cela est accepté, il ne sert à rien de traiter les formes d'expressions comme un switch, car il n'aura en réalité pas beaucoup de connexions avec l'ancienne forme de déclaration. Je laisserais tomber les types 2 et 3 et autoriserais les expressions switch de type 4 à devenir ce qu'on appelle des expressions de déclaration.

    Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
     // Stephen's expression switch
     var instruction = match (trafficLight) {
       case RED: "Stop";
       case YELLOW: "Prepare";
       case GO: "Go";
     }
     // Stephen's expression switch used as a statement (technically a statement expression)
     match (instruction) {
       case "Stop": doStop();
       case "Go": doGo();
       default: ;
     }

    « Si vous ignorez la complexité et utilisez simplement des expressions switch de type 4, la nouvelle fonctionnalité est tout à fait raisonnable. Cependant, afin d'ajouter la seule forme de switch nécessaire à Java, nous devons également prendre en considération les deux autres ratés - type 2 et 3. À mon avis, la fonctionnalité doit revenir à la planche de travail, mais malheureusement je soupçonne que c'est maintenant trop tard pour ça ».

    Nom : 14.png
Affichages : 5003
Taille : 178,6 Ko

    Correspondance de modèle pour instanceof

    En tant que développeur Java, vous avez probablement été dans une situation où vous devez vérifier si un objet est d'un certain type, et si c'est le cas - le caster dans ce type. Ce modèle est largement utilisé par exemple dans les implémentations equals.

    Introduit en tant que fonctionnalité en préversion dans JDK 14, instanceof est étendu pour prendre ce qu'on appelle un modèle de test de type au lieu d'un simple type. Un modèle de test de type se compose d'un prédicat et d'une variable de liaison.

    Prenons cet exemple :

    Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    @Override
    public boolean equals(Object obj) {
        if (obj instanceof Person) {
            Person other = (Person) obj;
            return this.name == other.name;
        }
        return false;
    }

    En utilisant cette nouvelle fonctionnalité, vous pouvez réécrire le code comme suit:

    Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    @Override
    public boolean equals(Object obj) {
        if (obj instanceof Person other) {
            return this.name == other.name;
        }
        return false;
    }

    Dans l'exemple ci-dessus, Person other est le modèle de test de type.

    Dans le bloc if, vous pouvez utiliser other et il est garanti qu'il s'agit d'une Person. En revanche, other n'est pas accessible en dehors du bloc if.

    Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    if (!(obj instanceof String s)) {
        System.out.println(s); // 1
    } else {
        System.out.println(s); // 2
    }

    Comment activer les fonctionnalités en préversion ?

    Les nouveaux changements switch ont actuellement le drapeau préversion dans la version JDK la plus récente (13). Cela signifie que vous ne devez probablement pas les utiliser dans une large mesure dans la base de code qui constitue votre gagne-pain. Les API peuvent changer et il y a toujours une chance que la fonctionnalité ne soit pas promue en fonction stable dans sa forme actuelle.

    Il est cependant utile de les expérimenter. Essayer de nouvelles fonctionnalités est un bon moyen d'élargir votre ensemble de compétences, et s'il y a quelque chose que vous n'aimez pas du tout quant à la convivialité d'une fonctionnalité, vous pouvez même fournir des commentaires aux développeurs JDK.

    Pour commencer à expérimenter les préversions de ces fonctionnalités sur l'outil dont vous vous servez, il vous suffit de suivre les instructions ci-dessous:

    Maven

    Pour activer pendant la compilation, ajoutez la configuration suivante à maven-compiler-plugin:

    Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    <configuration>
        <release>13</release>
        <compilerArgs>
            --enable-preview
        </compilerArgs>
    </configuration>

    Pour l'exécution du test, ajoutez la configuration suivante à maven-surefire-plugin et / ou maven-fail-safe-plugin:

    Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    <configuration>
        <argLine>--enable-preview</argLine>
    </configuration>

    Gradle

    Activez l'indicateur du compilateur pour la compilation et l'exécution des tests comme suit:

    Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    compileJava {
        options.compilerArgs += ["--enable-preview"]
    }
    test {
        jvmArgs '--enable-preview'
    }

    IntelliJ

    Accédez à Paramètres du projet> Projet et recherchez la liste déroulante Niveau de langage du projet. Si vous avez précédemment ciblé la version 13 et que vous souhaitez activer les fonctionnalités en préversion, choisissez 13 (Preview).

    Nom : preview.png
Affichages : 4912
Taille : 109,3 Ko

    Sources : billet Stephen Colebourne, OpenJDK (switch, instanceOf)

    Voir aussi :

    Vaudra-t-il encore la peine de consacrer du temps à l'étude du langage Java en 2020 ? Voici 10 raisons de répondre à l'affirmative, selon l'éditeur du blog Javarevisited
    L'extrait de code Java le plus copié sur Stack Overflow contient un bogue et son auteur, Andreas Lundblad, développeur Java chez Palantir, propose un correctif
    Entre Java et Kotlin, lequel de ces langages est le meilleur pour le développement d'applications Android ?
    SQL et Java sont encore les compétences techniques les plus demandées en 2019, selon un rapport d'étude

  2. #2
    Modérateur
    Avatar de joel.drigo
    Homme Profil pro
    Ingénieur R&D - Développeur Java
    Inscrit en
    Septembre 2009
    Messages
    12 430
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 55
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Ingénieur R&D - Développeur Java
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2009
    Messages : 12 430
    Points : 29 131
    Points
    29 131
    Billets dans le blog
    2
    Par défaut
    Citation Envoyé par Stéphane le calme Voir le message
    Comment activer les fonctionnalités en préversion ?
    Complément pour les utilisateurs d'Eclipse :
    Nom : Capture.PNG
Affichages : 4415
Taille : 61,5 Ko

  3. #3
    Chroniqueur Actualités

    Homme Profil pro
    Administrateur de base de données
    Inscrit en
    Mars 2013
    Messages
    9 092
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Canada

    Informations professionnelles :
    Activité : Administrateur de base de données

    Informations forums :
    Inscription : Mars 2013
    Messages : 9 092
    Points : 209 783
    Points
    209 783
    Par défaut JDK 14 va apporter plus de fonctionnalités que les deux versions précédentes combinées
    JDK 14 va apporter plus de fonctionnalités que les deux versions précédentes combinées,
    petit tour d'horizon de celles qui sont susceptibles d'intéresser le plus les développeurs

    La sortie de JDK 14 est prévue pour le 17 mars. La version 14 comprend plus de JEP (Java Enhancement Proposals) que JDK 12 et 13 combinés. Qu'est-ce qui pourrait être le plus intéressant pour les développeurs Java qui écrivent et maintiennent du code au quotidien ? Dans un billet de blog, Raoul-Gabriel Urma, PDG et cofondateur de Cambridge Spark, a abordé quelques points  :
    • des améliorations au niveau des expressions switch, qui sont apparues pour la première fois dans Java 12 et Java 13 en tant que préversion et font désormais partie intégrante de Java 14 ;
    • le pattern matching pour instanceof (une fonction de langage)
    • Des NullPointerExceptions utiles (une fonctionnalité JVM)

    Selon certains professionnels, l'instruction switch classique en Java n'est pas géniale. Contrairement à de nombreuses autres parties de Java, elle n'a pas été correctement repensée lors du retrait des fonctionnalités de C il y a toutes ces années. Le défaut clé est la « chute par défaut ». Cela signifie que si vous oubliez de mettre une clause break dans chaque cas, le traitement se poursuivra jusqu'à la clause case suivante.

    Un autre défaut est que les variables sont étendues à l'intégralité du switch, vous ne pouvez donc pas réutiliser un nom de variable dans deux clauses case différentes. De plus, une clause par défaut n'est pas requise, ce qui laisse les lecteurs du code dans l'incertitude quant à savoir si une clause a été ou non oubliée.

    Et bien sûr, il y a aussi la principale limitation : le type à activer ne peut être qu'un entier, une énumération ou une chaîne.

    Dans le cadre du Project Amber, switch a bénéficié de quelques changements ! Ils ont été lancés en préversion dans JDK 12 et à nouveau dans 13 et sont destinés à être publiés dans JDK 14. Les développeurs vont disposer d'une nouvelle fonctionnalité lorsque switch sera utilisé comme instruction. En outre, il pourra également être utilisé comme une expression à l'avenir.

    Les avantages des nouvelles expressions switch incluent une portée réduite pour les bogues en raison de l'absence de comportement de transition, l'exhaustivité et la facilité d'écriture grâce à l'expression et à la forme composée. À titre d'exemple de mise à jour, une expression switch peut désormais tirer parti de la syntaxe ->, comme dans cet exemple:

    Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    var log = switch (event) {
        case PLAY -> "User has triggered the play button";
        case STOP, PAUSE -> "User needs a break";
        default -> {
            String message = event.toString();
            LocalDateTime now = LocalDateTime.now();
            yield "Unknown event " + message + 
                  " logged on " + now;
        }
    };

    Il faut noter également que switch peut désormais être utilisé comme expressions, c'est-à-dire qu'il peut retourner une valeur. Cela signifie que nous n'aurons pas à déclarer d'abord une variable, puis à affecter une valeur dans chaque branche. Combiné avec des étiquettes de flèche, il nous permet d'exprimer notre intention en beaucoup moins de lignes de code:

    Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    String quantityString = switch (k) {
        case 1 -> "one";
        case 2 -> "two";
        default -> "many";
    };

    Parfois, vous pourriez être obligé d'exécuter un bloc de code dans le cadre d'une expression de cas. Afin de combiner cela avec la syntaxe d'étiquette de flèche, vous devez yield une valeur à la fin du bloc:

    Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    DayType type = switch (day) {
        case 1, 2, 3, 4, 5 -> WEEKDAY;
        case 6, 7          -> WEEKEND;
        default            -> {
            logger.warn(day + " is not a valid day. Legal values are [1..7]");
            yield UNKNOWN;
        }
    };

    Vous pouvez également transformer les instructions switch en expressions à l'aide des anciennes étiquettes de style en vous servant de yield.

    Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    Type type = switch (day) {
        case 1, 2, 3, 4, 5:
            yield WEEKDAY;
        case 6, 7:
            yield WEEKEND;
        default:
            logger.warn(day + " is not a valid day.");
            yield UNKNOWN;
    };

    N'oubliez pas que c'est la syntaxe des libellés fléchés qui empêche les chutes. Cela signifie que si vous oubliez de yield, l'expression de cas suivante sera évaluée et vous pourriez vous retrouver avec le mauvais résultat.

    Nom : ja.png
Affichages : 71888
Taille : 82,7 Ko

    Blocs de texte

    Java 13 a introduit des blocs de texte comme fonction en préversion. Les blocs de texte facilitent le travail avec les littéraux de chaînes multilignes. Cette fonctionnalité est de nouveau en préversion sur Java 14 et intègre quelques ajustements. En guise de rappel, il est assez courant d'écrire du code avec de nombreuses concaténations de chaînes et séquences d'échappement afin de fournir une mise en forme de texte multiligne adéquate. Le code ci-dessous montre un exemple de mise en forme HTML:

    Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    String html = "<HTML>" +
    "\n\t" + "<BODY>" +
    "\n\t\t" + "<H1>\"Java 14 is here!\"</H1>" +
    "\n\t" + "</BODY>" +
    "\n" + "</HTML>";

    Avec les blocs de texte, vous pouvez simplifier ce processus et écrire du code plus élégant à l'aide des trois guillemets qui délimitent le début et la fin d'un bloc de texte:

    Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    String html = """
    <HTML>
      <BODY>
        <H1>"Java 14 is here!"</H1>
      </BODY>
    </HTML>""";

    Les blocs de texte offrent également une plus grande expressivité par rapport aux littéraux de chaîne normaux.

    Deux nouvelles séquences d'échappement ont été ajoutées dans Java 14. Premièrement, vous pouvez utiliser la nouvelle séquence d'échappement \s pour signifier un seul espace. Deuxièmement, vous pouvez utiliser une barre oblique inverse, \, comme moyen de supprimer l'insertion d'un nouveau caractère de ligne à la fin d'une ligne. Ceci est utile lorsque vous avez une très longue ligne que vous souhaitez diviser pour plus de lisibilité à l'intérieur d'un bloc de texte.

    Par exemple, la façon actuelle de faire des chaînes multilignes est :

    Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    String literal = 
             "Lorem ipsum dolor sit amet, consectetur adipiscing " +
             "elit, sed do eiusmod tempor incididunt ut labore " +
             "et dolore magna aliqua.";

    Avec la séquence d'échappement \ dans des blocs de texte, cela peut s'exprimer comme suit:

    Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    String text = """
                    Lorem ipsum dolor sit amet, consectetur adipiscing \
                    elit, sed do eiusmod tempor incididunt ut labore \
                    et dolore magna aliqua.\
                    """;

    Pattern Matching pour instanceof

    En tant que développeur Java, vous avez probablement été dans une situation où vous devez vérifier si un objet est d'un certain type, et si c'est le cas le transtyper. Ce modèle est largement utilisé par exemple dans les implémentations equals. Java 14 introduit une fonctionnalité en préversion qui permet d'éliminer le besoin de transtypages explicites en passant par des vérifications instanceof. Par exemple, considérez le code suivant:

    Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    if (obj instanceof Group) {
      Group group = (Group) obj;
     
      // use group specific methods
      var entries = group.getEntries();
    }

    Cet extrait peut être réécrit en utilisant la fonction en préversion comme ceci :

    Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    if (obj instanceof Group group) {
      var entries = group.getEntries();
    }

    Étant donné que la vérification des conditions affirme que obj est de type Group, pourquoi devez-vous indiquer à nouveau que obj est de type Group avec le bloc de condition dans le premier extrait ? Ce besoin augmente potentiellement la possibilité d'erreurs.

    La syntaxe plus courte supprimera de nombreux transtypages de programmes Java typiques.

    « Il s'agit d'une fonctionnalité en préversion intéressante à expérimenter, car elle ouvre la porte à un pattern matching plus général. L'idée du pattern matching est de fournir une fonction de langage avec une syntaxe pratique pour extraire des composants d'objets en fonction de certaines conditions. C'est le cas avec l'opérateur instanceof, car la condition est une vérification de type et l'extraction appelle une méthode appropriée ou accède à un champ spécifique.

    « En d'autres termes, cette fonctionnalité en préversion n'est que le début et vous pouvez vous attendre à une fonctionnalité de langage qui peut aider à réduire davantage la verbosité et ainsi réduire la probabilité de bogues ».

    Nom : va.png
Affichages : 5359
Taille : 178,6 Ko

    Les enregistrements

    Il existe une autre fonctionnalité de langage en préversion : les enregistrements. Comme d'autres idées lancées jusqu'à présent, cette fonctionnalité suit la tendance de réduire la verbosité en Java et d'aider les développeurs à écrire du code plus concis. Les enregistrements se concentrent sur certaines classes de domaine dont le but est uniquement de stocker des données dans des champs et qui ne déclarent aucun comportement personnalisé.

    Pour passer directement au problème, prenez une classe de domaine simple, BankTransaction, qui modélise une transaction avec trois champs: une date, un montant et une description. Vous devez vous soucier de plusieurs composants lorsque vous déclarez la classe :
    • Le constructeur
    • Les méthodes Getter
    • toString()
    • hashCode() et equals()

    Le code de ces composants est souvent généré automatiquement par l'EDI et prend beaucoup de place. Voici une implémentation entièrement générée pour la classe BankTransaction:

    Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    public class BankTransaction {
        private final LocalDate date;
        private final double amount;
        private final String description;
     
     
        public BankTransaction(final LocalDate date, 
                               final double amount, 
                               final String description) {
            this.date = date;
            this.amount = amount;
            this.description = description;
        }
     
        public LocalDate date() {
            return date;
        }
     
        public double amount() {
            return amount;
        }
     
        public String description() {
            return description;
        }
     
        @Override
        public String toString() {
            return "BankTransaction{" +
                    "date=" + date +
                    ", amount=" + amount +
                    ", description='" + description + '\'' +
                    '}';
        }
     
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            BankTransaction that = (BankTransaction) o;
            return Double.compare(that.amount, amount) == 0 &&
                    date.equals(that.date) &&
                    description.equals(that.description);
        }
     
        @Override
        public int hashCode() {
            return Objects.hash(date, amount, description);
        }
    }

    Java 14 fournit un moyen de supprimer la verbosité et d'indiquer clairement que tout ce que vous voulez est une classe qui agrège uniquement les données avec les implémentations des méthodes equals, hashCode et toString. Vous pouvez réécrire BankTransaction comme suit:

    Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    public record BankTransaction(Date date,
                                  double amount,
                                  String description) {}

    Avec un enregistrement, vous obtenez « automatiquement » les implémentations de equals, hashCode et toString en plus du constructeur et des Getters.

    Les champs d'un enregistrement sont implicitement final. Cela signifie que vous ne pouvez pas les réaffecter. Notez, cependant, que cela ne signifie pas que l'ensemble de l'enregistrement est immuable; les objets eux-mêmes qui sont stockés dans les champs peuvent être mutables.

    NullPointerExceptions

    Considérez le code suivant :

    Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
    var name = user.getLocation().getCity().getName();

    Avant Java 14, vous pouvez obtenir l'erreur suivante:

    Citation Envoyé par Message d'erreur
    Exception in thread "main" java.lang.NullPointerException
    at NullPointerExample.main(NullPointerExample.java:5)
    Malheureusement, si à la ligne 5, il y a une affectation avec plusieurs invocations de méthode - getLocation() et getCity() - l'une ou l'autre pourrait retourner null. En fait, la variable user pourrait également être null. Par conséquent, la cause de <strong> NullPointerException </strong> n'est pas claire.

    Maintenant, avec Java 14, il existe une nouvelle fonctionnalité JVM à travers laquelle vous pouvez recevoir des diagnostics plus informatifs:

    Citation Envoyé par Message d'erreur
    Exception in thread "main" java.lang.NullPointerException: Cannot invoke "Location.getCity()" because the return value of "User.getLocation()" is null
    at NullPointerExample.main(NullPointerExample.java:5)
    Le message comporte désormais deux éléments clairs:
    • La conséquence: Location.getCity() ne peut pas être invoquée.
    • La raison: la valeur de retour de User.getLocation() est nulle.

    Source : Oracle

  4. #4
    Membre actif
    Profil pro
    Concepteur/Développeur
    Inscrit en
    Mai 2007
    Messages
    98
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations professionnelles :
    Activité : Concepteur/Développeur

    Informations forums :
    Inscription : Mai 2007
    Messages : 98
    Points : 273
    Points
    273
    Par défaut
    En regardant les nouvelles fonctionnalités Java, j'ai toujours l'impression d'être 5 ans en arrière.
    Ils essaient toujours de rattraper leur "retard" comparé au .Net mais en sont encore plutôt loin...

  5. #5
    Membre émérite
    Inscrit en
    Janvier 2006
    Messages
    739
    Détails du profil
    Informations forums :
    Inscription : Janvier 2006
    Messages : 739
    Points : 2 809
    Points
    2 809
    Par défaut Du bon, du moins bon et des questions...
    Blocs de texte

    Citation Envoyé par Stéphane le calme Voir le message
    Deux nouvelles séquences d'échappement ont été ajoutées dans Java 14. Premièrement, vous pouvez utiliser la nouvelle séquence d'échappement \s pour signifier un seul espace.
    Sans un exemple, je ne vois pas trop l'intérêt par rapport à un espace tapé à la main

    Citation Envoyé par Stéphane le calme Voir le message
    Deuxièmement, vous pouvez utiliser une barre oblique inverse, \, comme moyen de supprimer l'insertion d'un nouveau caractère de ligne à la fin d'une ligne. Ceci est utile lorsque vous avez une très longue ligne que vous souhaitez diviser pour plus de lisibilité à l'intérieur d'un bloc de texte.
    [...]
    Avec la séquence d'échappement \ dans des blocs de texte, cela peut s'exprimer comme suit:

    Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    String text = """
                    Lorem ipsum dolor sit amet, consectetur adipiscing \
                    elit, sed do eiusmod tempor incididunt ut labore \
                    et dolore magna aliqua.\
                    """;
    Questions:
    • Le retour à la ligne au début de la chaîne est-il obligatoire ou comptabilisé?
    • Le \ enlève la fin de ligne mais les espaces au début ne sont-ils pas comptabilisés?
    • Le choix de \ est discutable - certes, il existe dans d'autres langages mais ces derniers ont-ils d'autres séquences d'échappement commençant par \ ?
      Que se passe-t-il si l'éditeur de texte rajoute un espace juste après?
    • Une meilleure syntaxe pour les chaînes très longues c'est très bien; mais quand aura-t-on droit à un soupçon d'interpolation? Parce que s'il faut écrire """très long""" + variable + """très long", les situations où on peut utiliser une chaîne très longue vont vite se réduire, au point qu'il sera plus efficace de mettre la chaîne dans un fichier séparé et de la lire comme une ressource!


    Les enregistrements

    Citation Envoyé par Stéphane le calme Voir le message
    Avec un enregistrement, vous obtenez « automatiquement » les implémentations de equals, hashCode et toString en plus du constructeur et des Getters.
    Oui mes les getters s'appellent-ils getDate(), date() ou simplement date (après tout comme la variable est finale, l'intérêt de la protéger est moindre)

    Les enregistrements sont-ils des classes ou des structures sans héritage?

    Citation Envoyé par Stéphane le calme Voir le message
    Il existe une autre fonctionnalité de langage en préversion : les enregistrements. Comme d'autres idées lancées jusqu'à présent, cette fonctionnalité suit la tendance de réduire la verbosité en Java et d'aider les développeurs à écrire du code plus concis. Les enregistrements se concentrent sur certaines classes de domaine dont le but est uniquement de stocker des données dans des champs et qui ne déclarent aucun comportement personnalisé.
    Cet article étant une traduction, il est un peu dommage que vous n'ayez pas inclus la traduction de cette partie:

    Stay tuned. Records also raise interesting questions from an educational standpoint for the next generation of Java developers. For example, if you mentor junior developers, when should records be introduced in the curriculum: before introducing OOP and classes or after?
    Franchement, autant les autres fonctions sont utiles, autant celle-là, j'aimerais la voir disparaître. Elle ne va pas seulement "réduire la verbosité", elle va surtout conforter ceux qui travaillent toujours de la même façon à base soit de copier-coller soit de génération automatique (genre avec Eclipse, créer les champs et demander la génération automatique des accesseurs). Au final, les gens vont lire la spécification Java Beans et générer des POJO, c'est à dire des objets sans aucune intelligence où on se demande bien quelle est la valeur ajoutée des getters et des setters. Et toutes les méthodes un tant soit peu utiles vont se retrouver dans des "objets" séparés qui ne contiennent aucune donnée ou presque, genre les "data access objects". Soit exactement le contraire de ce qu'essaie de faire la programmation objet

  6. #6
    Expert éminent
    Avatar de Pyramidev
    Homme Profil pro
    Tech Lead
    Inscrit en
    Avril 2016
    Messages
    1 494
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Tech Lead

    Informations forums :
    Inscription : Avril 2016
    Messages : 1 494
    Points : 6 208
    Points
    6 208
    Par défaut
    Citation Envoyé par esperanto Voir le message
    Stay tuned. Records also raise interesting questions from an educational standpoint for the next generation of Java developers. For example, if you mentor junior developers, when should records be introduced in the curriculum: before introducing OOP and classes or after?
    Franchement, autant les autres fonctions sont utiles, autant celle-là, j'aimerais la voir disparaître.
    Personnellement, au contraire, je trouve cette fonctionnalité très utile.
    Quand je structure mon code, je découple le code qui parse les données en entrée (arguments en ligne de commande, URL, fichier, etc.) du code qui les traite et, comme dans l'article Parse, don’t validate de Alexis King, j'utilise la puissance du typage statique pour propager statiquement les garanties sur les données en sortie du code qui parse.
    En entrée du code qui traite les données, il m'arrive d'avoir un amas de champs dont chacun a sa propre contrainte (ex : entier entre certaines bornes, chaîne qui doit respecter un certain format, etc.), mais pas de contrainte qui fait intervenir plusieurs champs à la fois. Dans ce cas, définir une data class (ce que Java 14 appelle une record) est approprié. Dans cette data class, chaque champ a un type qui garantit la contrainte spécifique au champ (ex : une classe qui encapsule un entier avec comme invariant de classe que cet entier est entre certaines bornes, une classe qui encapsule une chaîne avec comme invariant de classe que cette chaîne respecte un certain format, etc.)
    Du coup, le code qui parse est bien découplé du code qui traite. Quand on met à jour le code, si on modifie le format des données en entrée, cela minimise la quantité de code que l'on doit analyser : on ne dépend pas de classes qui font des choses sophistiquées qui n'ont rien à voir avec le parsing des données en entrée. Ce découplage facilite aussi la mise en place de tests unitaires spécifiques au code qui parse.
    De même, le code qui traite les données déjà parsées est découplé du code qui parse : il dépend de l'objet en sortie du code qui parse, mais il ne sait pas si le code qui parse a pour entrée des arguments en ligne de commande, une URL, un fichier, etc. On peut aussi faire des tests unitaires dessus qui ne dépendent pas du code qui parse.

  7. #7
    Membre émérite
    Inscrit en
    Janvier 2006
    Messages
    739
    Détails du profil
    Informations forums :
    Inscription : Janvier 2006
    Messages : 739
    Points : 2 809
    Points
    2 809
    Par défaut Découplage
    Citation Envoyé par Pyramidev Voir le message
    Quand je structure mon code, je découple le code qui parse les données en entrée (arguments en ligne de commande, URL, fichier, etc.) du code qui les traite et, comme dans l'article Parse, don’t validate de Alexis King, j'utilise la puissance du typage statique pour propager statiquement les garanties sur les données en sortie du code qui parse.
    Je ne vois pas comment tu utilises le typage statique, du moins en Java: dans les exemples que tu donnes (entier borné, chaîne respectant une expression régulière...), les types String et int étant finaux, je ne vois pas comment tu peux propager la contrainte grâce à un type.
    A moins de créer un type qui contient une String et délègue toutes les méthodes... pas très pratique.
    Au contraire j'ai déjà fait ça... en Ada, où il est possible de définir des types juste pour ajouter ce genre de contrainte, le système de types permettant alors de vérifier que tu ne mélanges jamais des objets de type différent même si ces types sont dérivés de la même base.
    Et Ada, ce n'est pas de la programmation objet (du moins avant la version 1995). De même, l'article que tu cites utilise un langage à typage statique (Haskell) assez proche de celui d'Ada (enfin, avec l'inférence en plus parce qu'il est plus récent)... et qui n'est pas vraiment orienté objet. D'où la question: pourquoi utiliser un langage objet - où les méthodes sont supposées intelligentes - si c'est pour au final n'avoir que des POJO qui sont des structures sans méthodes intelligentes?

    Citation Envoyé par Pyramidev Voir le message
    Quand on met à jour le code, si on modifie le format des données en entrée, cela minimise la quantité de code que l'on doit analyser : on ne dépend pas de classes qui font des choses sophistiquées qui n'ont rien à voir avec le parsing des données en entrée.
    A moins d'avoir un moyen de propager la contrainte au niveau des POJO (voir point précédent), le problème ici c'est qu'on oublie un détail important: une certaine tendance à ré-utiliser le code. Si tu découples le parsing et les POJO, comment garantir que d'autres programmes ne créeront pas des objets de la même classe mais sans passer par le même parsing en entrée? Comme c'est la même classe, des objets ne respectant pas la contrainte peuvent se retrouver injectés dans ton programme initial!
    Ce n'est pas qu'une vue de l'esprit, c'est un problème que j'ai au quotidien sur un programme pour lequel je maintiens des plugins: quand l'API du programme me sort un POJO, je suis certain qu'il respecte les contraintes; mais faute de documentation, je ne sais pas quelles contraintes je dois respecter quand c'est moi qui dois fournir un objet à l'API: tout ce que la doc dit, c'est que ça doit être une String...

    Du coup je n'ai pas la même position sur le découplage. Placer une fonction de parsing en dehors de la classe elle-même, oui, sans problème - en plus ça évite à la classe de base d'avoir certaines dépendances extérieures (par exemple elle n'aura pas besoin de faire référence au parseur xml). Mais la validation par contre, si les contraintes sont inhérentes à l'objet créé, c'est dans sa classe qu'il faut valider! Donc soit le système de types le permet - et là encore, j'attends un exemple parce que pour le moment je ne vois pas comment le faire en Java - soit il faut le faire dans le constructeur, ce que ne permet pas un record ou un POJO!

  8. #8
    Expert éminent
    Avatar de Pyramidev
    Homme Profil pro
    Tech Lead
    Inscrit en
    Avril 2016
    Messages
    1 494
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Tech Lead

    Informations forums :
    Inscription : Avril 2016
    Messages : 1 494
    Points : 6 208
    Points
    6 208
    Par défaut
    Citation Envoyé par esperanto Voir le message
    Je ne vois pas comment tu utilises le typage statique, du moins en Java: dans les exemples que tu donnes (entier borné, chaîne respectant une expression régulière...), les types String et int étant finaux, je ne vois pas comment tu peux propager la contrainte grâce à un type.
    A moins de créer un type qui contient une String et délègue toutes les méthodes... pas très pratique.
    Par exemple, si un identifiant d'utilisateur a pour contrainte que c'est une chaîne non vide qui n'a que des caractères alphanumériques ASCII, alors je crée un type UserId qui encapsule une String et qui a pour invariant de classe que cette chaîne respecte le format.
    La classe UserId doit empêcher de violer l'invariant. Par exemple, si UserId a un constructeur public qui prend en paramètre une String alors, si le paramètre ne respecte pas le format, le constructeur doit lancer une exception.
    La classe UserId a un getter qui permet de récupérer la String. String est immuable, donc il n'y a pas de danger que l'utilisateur ne casse la chaîne qui est dans UserId. Avec ce getter, il n'y a pas besoin de réimplémenter dans UserId toutes les méthodes de String.
    Cette classe UserId ne sera pas une record Java, mais un des champs d'une record Java pourrait être de type UserId.

    Citation Envoyé par esperanto Voir le message
    D'où la question: pourquoi utiliser un langage objet - où les méthodes sont supposées intelligentes - si c'est pour au final n'avoir que des POJO qui sont des structures sans méthodes intelligentes?
    Utiliser des POJO à certains endroits ne veut pas dire qu'il faut en mettre partout.

    Citation Envoyé par esperanto Voir le message
    A moins d'avoir un moyen de propager la contrainte au niveau des POJO (voir point précédent), le problème ici c'est qu'on oublie un détail important: une certaine tendance à ré-utiliser le code. Si tu découples le parsing et les POJO, comment garantir que d'autres programmes ne créeront pas des objets de la même classe mais sans passer par le même parsing en entrée? Comme c'est la même classe, des objets ne respectant pas la contrainte peuvent se retrouver injectés dans ton programme initial!
    Non car la classe doit empêcher de violer son invariant (voir ci-avant).
    D'ailleurs, d'autres programmes doivent pouvoir passer par la même classe (ex : UserId ) sans passer par le même parseur. Par exemple, si un identifiant d'utilisateur peut être en argument de ligne de commande ou dans un fichier, le code qui parse la ligne de commande et le code qui parse le fichier, s'ils sont codés dans le même langage, devraient tous les deux utiliser la même classe UserId. Le code de la classe UserId ne connaît pas le code des parseurs : ce sont les parseurs qui connaissent la classe UserId. Cette réutilisation du code (la classe UserId) permet de garantir une certaine cohérence. Au quotidien, je vois trop souvent des codes incohérents qui n'imposent pas les mêmes contraintes sur les données métiers.

    Citation Envoyé par esperanto Voir le message
    Mais la validation par contre, si les contraintes sont inhérentes à l'objet créé, c'est dans sa classe qu'il faut valider! Donc soit le système de types le permet - et là encore, j'attends un exemple parce que pour le moment je ne vois pas comment le faire en Java - soit il faut le faire dans le constructeur, ce que ne permet pas un record ou un POJO!
    Si l'un des champs de la record est de type UserId, alors la contrainte de type est bien propagée pour ce champ. Par contre, ce que la record ne peut pas faire, c'est imposer des contraintes qui font intervenir plusieurs champs à la fois. C'est dans ce sens que j'ai écrit :
    Citation Envoyé par Pyramidev Voir le message
    En entrée du code qui traite les données, il m'arrive d'avoir un amas de champs dont chacun a sa propre contrainte (ex : entier entre certaines bornes, chaîne qui doit respecter un certain format, etc.), mais pas de contrainte qui fait intervenir plusieurs champs à la fois. Dans ce cas, définir une data class (ce que Java 14 appelle une record) est approprié. Dans cette data class, chaque champ a un type qui garantit la contrainte spécifique au champ (ex : une classe qui encapsule un entier avec comme invariant de classe que cet entier est entre certaines bornes, une classe qui encapsule une chaîne avec comme invariant de classe que cette chaîne respecte un certain format, etc.)

  9. #9
    Expert éminent sénior
    Homme Profil pro
    Analyste/ Programmeur
    Inscrit en
    Juillet 2013
    Messages
    4 694
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Analyste/ Programmeur

    Informations forums :
    Inscription : Juillet 2013
    Messages : 4 694
    Points : 10 738
    Points
    10 738
    Par défaut
    Citation Envoyé par Pyramidev Voir le message
    Le code de la classe UserId ne connaît pas le code des parseurs : ce sont les parseurs qui connaissent la classe UserId.
    c'est la définition d'un Data Transfert Object (DTO) (<- lien wiki en anglais) : un "sac de données" entre 2 classes, entre 2 domaines/ couches d'abstraction.


    Citation Envoyé par Pyramidev Voir le message
    Non car la classe doit empêcher de violer son invariant (voir ci-avant).
    D'ailleurs, d'autres programmes doivent pouvoir passer par la même classe (ex : UserId ) sans passer par le même parseur.
    Justement , ce n'est pas aux DTO de vérifier les règles de gestion ni les règles de validation données :
    • La classe qui va remplir le DTO (lecteur fichier, requêtes SQL, saisie formulaire, dump réseau, enregistrement du gyroscope, ...) doit vérifier les données si nécessaire
    • La classe qui va enregistrer le DTO (dans une base de données, un fichier, ...), je pense, doit valider les données.
    • La classe qui va utiliser les données doit respecter les règles de gestion. C'est ton exemple UserId - dans le cahier de charges , cela doit être indiqué que l'identifiant ne doit pas être vide, avoir ...

    Le DTO peut avoir des champs avec une représentation précise. Par exemple, une adresse IPv4 : soit 4 chars (1 octet), soit un entier (4 octets), soit ...


    Citation Envoyé par Pyramidev Voir le message
    Cette réutilisation du code (la classe UserId) permet de garantir une certaine cohérence.
    Peut-être qu'1 façon de coder , c'est d'utiliser les "traits" (<- ceux du PHP pas ceux du C++ ). Et ainsi, tu peux faire par exemple, un "trait" "validation d'identifiant", un autre "chiffrement de données sensibles", ...

  10. #10
    Expert éminent
    Avatar de Pyramidev
    Homme Profil pro
    Tech Lead
    Inscrit en
    Avril 2016
    Messages
    1 494
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Tech Lead

    Informations forums :
    Inscription : Avril 2016
    Messages : 1 494
    Points : 6 208
    Points
    6 208
    Par défaut
    Citation Envoyé par foetus Voir le message
    Citation Envoyé par Pyramidev Voir le message
    Le code de la classe UserId ne connaît pas le code des parseurs : ce sont les parseurs qui connaissent la classe UserId.
    c'est la définition d'un Data Transfert Object (DTO) (<- lien wiki en anglais) : un "sac de données" entre 2 classes, entre 2 domaines/ couches d'abstraction.
    Ça, c'est encore autre chose. Dans l'article Data Transfer Object de Martin Fowler, je vois un exemple de DTO : une classe AlbumDTO qui a deux attributs title et artist et deux méthodes toXmlElement et readXml. À côté, il y a aussi deux classes métier Album et Artist. Pour convertir les classes métiers en DTO ou les DTO en classes métiers, il y a une quatrième classe AlbumAssembler.

    Dans mon exemple, UserId n'est pas un DTO. Conceptuellement, UserId est presque comme une String avec une étiquette collée dessus qui indique que la String respecte le format de l'identifiant d'utilisateur. Et, pour construire un objet de type UserId à partir d'un objet de type String, on est obligé de passer par une opération qui sépare le flot d'exécution en deux : une branche dans laquelle le format est correct, ce qui a permis de construire un objet de type UserId et une branche dans laquelle le format est incorrect, où aucun objet de type UserId n'a été créé.
    Dans les langages où la manière idiomatique de gérer les erreurs est de lancer une exception, l'opération qui sépare le flot d'exécution en deux sera typiquement un constructeur public qui prend en paramètre une String et qui lance une exception en cas de format incorrect. Ou alors, pour ceux qui n'aiment pas les constructeurs qui lancent des exceptions, on peut rendre privé le constructeur et obliger de passer par une méthode statique qui retourne un UserId, qui prend en paramètre une String et qui lance une exception en cas de format incorrect.
    Ainsi, on a la garantie qu'un objet de type UserId contient une String qui respecte le format. Cela évite les erreurs d'étourderie. C'est un peu comme des assert vérifiés à la compilation, en moins verbeux.

  11. #11
    Expert éminent sénior
    Homme Profil pro
    Analyste/ Programmeur
    Inscrit en
    Juillet 2013
    Messages
    4 694
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Analyste/ Programmeur

    Informations forums :
    Inscription : Juillet 2013
    Messages : 4 694
    Points : 10 738
    Points
    10 738
    Par défaut
    Citation Envoyé par Pyramidev Voir le message
    Dans mon exemple, UserId n'est pas un DTO. Conceptuellement, UserId est presque comme une String avec une étiquette collée dessus qui indique que la String respecte le format de l'identifiant d'utilisateur. Et, pour construire un objet de type UserId à partir d'un objet de type String, on est obligé de passer par une opération qui sépare le flot d'exécution en deux : une branche dans laquelle le format est correct, ce qui a permis de construire un objet de type UserId et une branche dans laquelle le format est incorrect, où aucun objet de type UserId n'a été créé.
    Dans les langages où la manière idiomatique de gérer les erreurs est de lancer une exception, l'opération qui sépare le flot d'exécution en deux sera typiquement un constructeur public qui prend en paramètre une String et qui lance une exception en cas de format incorrect. Ou alors, pour ceux qui n'aiment pas les constructeurs qui lancent des exceptions, on peut rendre privé le constructeur et obliger de passer par une méthode statique qui retourne un UserId, qui prend en paramètre une String et qui lance une exception en cas de format incorrect.
    Ainsi, on a la garantie qu'un objet de type UserId contient une String qui respecte le format. Cela évite les erreurs d'étourderie. C'est un peu comme des assert vérifiés à la compilation, en moins verbeux.
    Ouais , en gros tu codes le patron de conception fabrique/ "factory" (<- lien wiki en anglais) ... mais seulement avec 2 types : le types effectif et le type NULL (ou une exception ("unchecked exception" il me semble ))



    Citation Envoyé par esperanto Voir le message
    le problème ici c'est qu'on oublie un détail important: une certaine tendance à ré-utiliser le code. Si tu découples le parsing et les POJO, comment garantir que d'autres programmes ne créeront pas des objets de la même classe mais sans passer par le même parsing en entrée?
    Je pense que le but des enregistrements (DTO) c'est de garantir la cohérence des données. Toutes les classes utilisent les mêmes données et ainsi en cas de modification (ajout et/ ou suppression de champs), l'entièreté du code est impacté.
    Et l'autre truc, c'est la solution pour lier quelque chose "non objet" (une base de données relationnelle, un formulaire, ...) à un objet.

  12. #12
    Modérateur
    Avatar de wax78
    Homme Profil pro
    Chef programmeur
    Inscrit en
    Août 2006
    Messages
    4 086
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : Belgique

    Informations professionnelles :
    Activité : Chef programmeur
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2006
    Messages : 4 086
    Points : 8 005
    Points
    8 005
    Par défaut
    Citation Envoyé par esperanto Voir le message
    [*]Une meilleure syntaxe pour les chaînes très longues c'est très bien; mais quand aura-t-on droit à un soupçon d'interpolation? Parce que s'il faut écrire """très long""" + variable + """très long", les situations où on peut utiliser une chaîne très longue vont vite se réduire, au point qu'il sera plus efficace de mettre la chaîne dans un fichier séparé et de la lire comme une ressource!
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    String str = String.format("""
    très long %s très long
    """, "le truc à mettre entre les très longs");
    ou

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     
    String str = String.format(
    """
    <HTML>
      <BODY>
        <H1>"Java %d is here!"</H1>
      </BODY>
    </HTML>"""
    ,14
    );
    Tu parles d'un truc de ce genre ?

  13. #13
    Membre émérite
    Inscrit en
    Janvier 2006
    Messages
    739
    Détails du profil
    Informations forums :
    Inscription : Janvier 2006
    Messages : 739
    Points : 2 809
    Points
    2 809
    Par défaut Formats et interpolation
    Citation Envoyé par wax78 Voir le message
    Tu parles d'un truc de ce genre ?
    Pas tout à fait. Moi je pensais plutôt à quelque chose comme

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    String str = """
    <HTML>
      <BODY>
        <H1>Hello, ${user.getName()}</H1>
      </BODY>
    </HTML>""";
    qui serait traduit à la compilation vers

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    String str = """
    <HTML>
      <BODY>
        <H1>Hello, " + user.getName()+ """</H1>
      </BODY>
    </HTML>""";
    Ce genre de mécanisme existe depuis longtemps dans les langages de script, et plus récemment les dernières versions de C# le supportent.

    Quand on commence à avoir des chaînes très longues, ce que permet la nouvelle syntaxe, on risque d'avoir aussi beaucoup de variables à l'intérieur, donc si tu dois les mettre à la fin comme avec String.format, tu risques de les mettre dans le désordre.
    De plus, les chaînes formatées sont évaluées à l'exécution (String.format étant une fonction), alors que la substitution que j'ai décrite plus haut peut se faire dès la compilation (sans même parler des optimisations que le compilateur pourrait encore faire dessus).

    Cela dit, les chaînes formatées restent utiles quand tu ne connais pas le format à l'avance. C'est par exemple le cas quand tu utilises des tables de traduction (et de fait, on trouve fréquemment des chaînes formatées dans les Ressource Bundles en Java). A l'inverse, les chaînes longues et l'interpolation sont souvent utilisées pour introduire un bout de code dans un autre langage, comme une requête SQL ou, comme dans le cas de l'exemple, du code HTML. Et on peut alors se retrouver à combiner les deux techniques:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    String str = $"""
    <HTML>
      <BODY>
        <H1>${String.format(bundle.getString("HELLO_USER"), user.getName())}</H1>
      </BODY>
    </HTML>""";

  14. #14
    Expert éminent
    Avatar de Pyramidev
    Homme Profil pro
    Tech Lead
    Inscrit en
    Avril 2016
    Messages
    1 494
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Tech Lead

    Informations forums :
    Inscription : Avril 2016
    Messages : 1 494
    Points : 6 208
    Points
    6 208
    Par défaut
    Citation Envoyé par esperanto Voir le message
    A l'inverse, les chaînes longues et l'interpolation sont souvent utilisées pour introduire un bout de code dans un autre langage, comme une requête SQL ou, comme dans le cas de l'exemple, du code HTML. Et on peut alors se retrouver à combiner les deux techniques:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    String str = $"""
    <HTML>
      <BODY>
        <H1>${String.format(bundle.getString("HELLO_USER"), user.getName())}</H1>
      </BODY>
    </HTML>""";
    Ouh là, malheureux. Ça, c'est la porte ouverte aux injections SQL et à de bêtes bogues sur le format du HTML.

    Dans le cas des requêtes SQL, au lieu de s'aventurer à concaténer des bouts de chaînes (par exemple via le sucre syntaxique de l'interpolation, pour les langages qui le permettent), il vaut mieux utiliser la notion de requête préparée. Par exemple, en Java, c'est avec PreparedStatement. Extrait de la doc :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
       PreparedStatement pstmt = con.prepareStatement("UPDATE EMPLOYEES
                                         SET SALARY = ? WHERE ID = ?");
       pstmt.setBigDecimal(1, 153833.00)
       pstmt.setInt(2, 110592)
    Et concernant HTML, si tu concatènes directement des chaînes, il ne faut pas oublier d'encoder, par exemple pour remplacer < par &lt;, > par &gt;, etc. Dans le cas de Java, après une recherche sur internet, je suis tombé sur StringEscapeUtils.escapeHtml4.
    Au lieu de concaténer des chaînes, il est aussi possible, à partir d'un objet de type HTMLDocument, de construire les éléments du HTML un par un, mais c'est verbeux.

    En ce moment, je fais du Python et l'endroit où j'utilise le plus la fonctionnalité de l'interpolation, c'est dans la construction des messages de log et des messages d'erreur.

  15. #15
    Membre émérite
    Inscrit en
    Janvier 2006
    Messages
    739
    Détails du profil
    Informations forums :
    Inscription : Janvier 2006
    Messages : 739
    Points : 2 809
    Points
    2 809
    Par défaut
    Citation Envoyé par Pyramidev Voir le message
    Ouh là, malheureux. Ça, c'est la porte ouverte aux injections SQL et à de bêtes bogues sur le format du HTML.

    Dans le cas des requêtes SQL, au lieu de s'aventurer à concaténer des bouts de chaînes (par exemple via le sucre syntaxique de l'interpolation, pour les langages qui le permettent), il vaut mieux utiliser la notion de requête préparée. .
    Les requêtes préparées, ça ne marche que si le nom des tables et des champs est statique. Si tu écris par exemple

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
       PreparedStatement pstmt = con.prepareStatement("UPDATE " + ((some condition) ? "TABLE1" : "TABLE2") 
                                      + "  SET SALARY = ? WHERE ID = ?");*
    et de réutiliser la requête préparée si la condition a changé.

    L'injection de code, c'est vrai qu'il faut faire attention dès lors que l'une des variables peut contenir quelque chose saisi par l'utilisateur. Quand ce n'est pas le cas, on reste relativement tranquille.

    Quant au code HTML, oui en effet si j'ai des doutes sur le contenu potentiel des variables je peux écrire

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    String str = $"""
    <HTML>
      <BODY>
        <H1>${StringEscapeUtils.escapeHtml4(String.format(bundle.getString("HELLO_USER"), user.getName()))}</H1>
      </BODY>
    </HTML>""";
    Maintenant, c'est bien d'avertir sur les dangers de la mauvaise utilisation d'une technique, mais on ne va pas interdire l'informatique parce que certains l'utilisent de façon incorrecte.

  16. #16
    Membre émérite
    Inscrit en
    Janvier 2006
    Messages
    739
    Détails du profil
    Informations forums :
    Inscription : Janvier 2006
    Messages : 739
    Points : 2 809
    Points
    2 809
    Par défaut POJO pas très bean!
    Citation Envoyé par Pyramidev Voir le message
    Par exemple, si un identifiant d'utilisateur a pour contrainte que c'est une chaîne non vide qui n'a que des caractères alphanumériques ASCII, alors je crée un type UserId qui encapsule une String et qui a pour invariant de classe que cette chaîne respecte le format.
    Bon, quand j'ai parlé avant de "type qui contient une String et délègue toutes les méthodes", je m'attendais en effet à ce que tu proposes quelque chose dans ce genre.

    Questions:
    • 1. as-tu déjà utilisé cette technique sur un projet réel?


    A priori ça m'a l'air bien lourd: non seulement tu dois déclarer plein de classes avec une seule méthode, mais en plus tu vas traîner des getValue() partout (qui en plus ne sont pas neutres en performances) alors même que le test qui justifie l'usage de cette classe n'est appelé que dans le constructeur!

    • 2. connais-tu un projet (idéalement open source pour qu'on puisse vérifier) qui utilise cette technique?


    Je demande ça parce que quand tu écris:

    Citation Envoyé par Pyramidev Voir le message
    Non car la classe doit empêcher de violer son invariant (voir ci-avant).
    je suis tout à fait d'accord... quand l'auteur de la classe record a choisi d'utiliser cette technique! Quand ce n'est pas le cas, quand je dois utiliser l'API écrite par un tiers, il y a peu de chances qu'il ait pensé à blinder ses records comme tu le fais!

    Globalement je reste d'accord avec ce que dit l'article que tu cites... quand tu utilises un langage qui a un système de types approprié. Avec cet exemple on voit bien que ce n'est pas réellement le cas de Java. Même si tu peux utiliser cette technique, elle est loin d'être idiomatique en Java ce qui réduit son intérêt. D'ailleurs, si tel était le cas, ceux qui ont proposé le mot-clé record pour simplifier les POJO auraient probablement proposé quelque chose aussi pour simplifier la création de ce genre de types!

    Puisqu'on parle d'idiomatismes, qu'est-ce qui interdit dans un POJO de faire ceci:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    public void setUserId(String userId) {
        assert(! userId.contains(" "));
        this.userId = userId;
    }
    Alors, qu'est-ce qui l'empêche? Techniquement, absolument rien. Même la spécification Java Beans ne l'interdit pas, elle dit même explicitement le contraire (paragraphe 7.1 de la spécification):

    So properties need not just be simple data fields, they can actually be computed values. Updatesmay have various programmatic side effects. For example, changing a bean’s background colorproperty might also cause the bean to be repainted with the new color
    Sauf que la plupart des gens ne lisent jamais les specs, ils lisent des exemples, en oubliant au passage qu'un exemple sert à illustrer un argument et non à être recopié au pied de la lettre dans tous les projets! Résultat, les Java Beans sont peu à peu remplacés par les POJO, qui eux interdisent ce genre d'écriture. Encore que... les POJO, contrairement à Java Beans, ce n'est pas une spécification, seulement des règles écrites nulle part mais admises par tous!

    Citation Envoyé par Pyramidev Voir le message
    Utiliser des POJO à certains endroits ne veut pas dire qu'il faut en mettre partout.
    Non, mais le fait d'en simplifier la syntaxe entérine leur utilisation comme une bonne pratique, et tout le monde ne fait pas la part des choses.

    A la limite, les record m'embêteraient moins s'il était possible d'écrire:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    public record BankTransaction(
                                  @Check(id -> ! (id.contains(" "))) String userId,
                                  Date date,
                                  double amount,
                                  String description) {}
    A noter dans cette syntaxe que:
    • le contenu de l'annotation n'est pas une expression statique mais une fonction booléenne: ici c'est un bout de code dont je charge le compilateur de l'injecter au bon endroit (ici dans le constructeur)
    • j'utilise une annotation: l'idéal serait alors qu'on puisse déclarer ses propres annotations avec pour chacune, la possibilité de déclarer dans le code de l'annotation l'effet qu'elle aura sur le code généré quand elle est utilisée dans un record (puisqu si j'ai bien compris un record est transformé en POJO à la compilation...)


    De cette manière, les record me permettraient de simplifier sans sacrifier toute possibilité d'évolution. Tant que ce n'est pas le cas, non merci.

  17. #17
    Expert éminent
    Avatar de Pyramidev
    Homme Profil pro
    Tech Lead
    Inscrit en
    Avril 2016
    Messages
    1 494
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Tech Lead

    Informations forums :
    Inscription : Avril 2016
    Messages : 1 494
    Points : 6 208
    Points
    6 208
    Par défaut
    Citation Envoyé par esperanto Voir le message
    1. as-tu déjà utilisé cette technique sur un projet réel?
    Oui. Au boulot, j'utilise cette technique dans un projet Python (avec le typage statique optionnel de Python 3) et je l'ai aussi utilisée dans un projet C++.

    Citation Envoyé par esperanto Voir le message
    A priori ça m'a l'air bien lourd: non seulement tu dois déclarer plein de classes avec une seule méthode, mais en plus tu vas traîner des getValue() partout (qui en plus ne sont pas neutres en performances) alors même que le test qui justifie l'usage de cette classe n'est appelé que dans le constructeur!
    Dans les programmes où j'ai utilisé cette technique, la quantité d'appels de getValue() reste modérée, car je n'ai pas besoin d'appeler cette méthode pour passer l'objet de paramètre en paramètre, pour le ranger dans un attribut de classe, pour m'en servir de clef d'un tableau associatif, etc. (du moins, tant que le type correspond).

    Concernant le coût des appels à getValue(), dans un projet Java, si la méthode getValue() est final, je pense que le compilateur est assez intelligent pour faire de l'inlining, donc ça m'étonnerait que cela impacte les performances.
    Idem en C++ : le coût de getValue() sera nul si on fait de l'inlining et si on évite les copies coûteuses. Par exemple, si une classe encapsule une std::string, on peut définir une méthode dans le ".h" qui retourne une std::string_view qui donne accès en lecture seule à la chaîne contenue dans la std::string, sans la copier. Je n'avais pas fait ça dans le projet C++ dont j'ai parlé, mais on peut le faire.
    Dans un projet Python, à la place d'une méthode getValue(), on aura plutôt une propriété value (c'est plus idiomatique). Là, par contre, le coût sera non-nul, mais... voir le paragraphe ci-dessous.

    En fait, selon la manière qu'on a d'aborder la gestion d'erreurs, l'utilisation de classes comme UserId peut réduire le volume de code et augmenter sensiblement les performances.
    Pourquoi ? Admettons que je n'ai pas le droit de définir un type comme UserId et que je passe de paramètre en paramètre une variable userId de type String. Alors, au début de chaque fonction, je vais avoir envie de faire un contrôle au runtime pour vérifier si userId respecte le format. Comme ça, le jour où il y aura un oubli de vérification de format, l'erreur sera détectée le plus proche possible de la source de l'erreur. Mais, syntaxiquement, c'est bien plus lourd que de simplement remplacer le type du paramètre userId par UserId. En plus, c'est moins performant de vérifier au runtime la précondition de userId à chaque entrée de fonction que de la vérifier une fois, lors de la construction de l'objet de type UserId.

    La seule étape syntaxiquement lourde, c'est la définition de la classe UserId.

    Citation Envoyé par esperanto Voir le message
    2. connais-tu un projet (idéalement open source pour qu'on puisse vérifier) qui utilise cette technique?
    La bibliothèque standard de Rust utilise cette technique, quoique, à la place des exceptions, la gestion d'erreurs se fait via des types sommes.
    Le type String encapsule un Vec[u8] (un vecteur d'octets) avec un invariant : cette suite d'octets doit être une chaîne UTF-8 valide. La conversion de Vec[u8] en String se fait via l'appel à String::from_utf8 dont la signature est :
    Code Rust : Sélectionner tout - Visualiser dans une fenêtre à part
    pub fn from_utf8(vec: Vec<u8>) -> Result<String, FromUtf8Error>
    Si les octets du paramètre de type Vec[u8] forment une chaîne UTF-8 valide, alors la valeur retournée par String::from_utf8 contient bien un objet de type String. Sinon, il contient un objet de type FromUtf8Error, ce qui est un peu l'équivalent d'une exception.

    Par contre, dans les langages où la manière idiomatique de gérer les erreurs est de lancer une exception (C++, Java, Python, etc.), il n'y a pas d'exemple qui me vient en tête.

    Citation Envoyé par esperanto Voir le message
    Globalement je reste d'accord avec ce que dit l'article que tu cites... quand tu utilises un langage qui a un système de types approprié. Avec cet exemple on voit bien que ce n'est pas réellement le cas de Java. Même si tu peux utiliser cette technique, elle est loin d'être idiomatique en Java ce qui réduit son intérêt. D'ailleurs, si tel était le cas, ceux qui ont proposé le mot-clé record pour simplifier les POJO auraient probablement proposé quelque chose aussi pour simplifier la création de ce genre de types!
    Pour créer un type comme UserId, c'est vrai que c'est plus lourd dans des langages comme Java, Python et C++ qu'en Rust. En Rust, pour générer en une ligne de code les opérations de comparaison, de hashage et de conversion en chaîne pour le débogage, il suffit d'écrire #[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] au dessus de la structure. Par contre, par exemple en Python, je dois me retaper à la main les définitions de __eq__, __lt__, __hash__ et __repr__.

    Cela dit, même dans un langage qui n'a pas de sucre syntaxique qui facilite la création de classes comme UserId, je reste favorable à cette technique.

  18. #18
    Membre émérite
    Inscrit en
    Janvier 2006
    Messages
    739
    Détails du profil
    Informations forums :
    Inscription : Janvier 2006
    Messages : 739
    Points : 2 809
    Points
    2 809
    Par défaut EN JAVA!
    Citation Envoyé par Pyramidev Voir le message
    Citation Envoyé par esperanto Voir le message
    [*]1. as-tu déjà utilisé cette technique sur un projet réel?
    Oui. Au boulot, j'utilise cette technique dans un projet Python (avec le typage statique optionnel de Python 3) et je l'ai aussi utilisée dans un projet C++.
    Compte tenu de ce que tu écris ensuite je me rends compte que j'aurais dû écrire en Java. Vu que l'essentiel du reste du message tendait à montrer que ce n'est pas adapté à Java (mais à d'autres langages) je pensais que c'était une évidence, visiblement pas. Mea culpa.

    Citation Envoyé par Pyramidev Voir le message
    Concernant le coût des appels à getValue(), dans un projet Java, si la méthode getValue() est final, je pense que le compilateur est assez intelligent pour faire de l'inlining, donc ça m'étonnerait que cela impacte les performances.
    Même avec de l'inlining, faut pas oublier qu'en Java tout sauf les types primitifs (nombres et booléens) passe par des références. L'inlining supprime l'appel de fonction mais pas le déréférencement qu'il contient.

    Citation Envoyé par Pyramidev Voir le message
    Idem en C++ : le coût de getValue() sera nul si on fait de l'inlining et si on évite les copies coûteuses.
    En C++ les classes contiennent directement les objets, sauf si tu demandes explicitement une référence. Et les objets aussi sont sur la pile. Ce n'est pas comparable.
    Et puis je crois même que tu pourrais faire un cast qui ne coûte rien car il serait fait à la compilation et non à l'exécution.

    Citation Envoyé par Pyramidev Voir le message
    La bibliothèque standard de Rust utilise cette technique, quoique, à la place des exceptions, la gestion d'erreurs se fait via des types sommes.
    Rust, comme C++, est un langage qui compile en natif et contrairement à Java, il n'utilise pas des références quand ce n'est pas explicitement demandé.

    Citation Envoyé par Pyramidev Voir le message
    Cela dit, même dans un langage qui n'a pas de sucre syntaxique qui facilite la création de classes comme UserId, je reste favorable à cette technique.
    Donc au final nous disons la même chose: je n'ai pas dit que cette technique était mauvaise indépendamment du langage, mais que sa transposition à Java est peu efficace. Ce qui rend d'autant plus regrettable le fait que Java ne propose rien pour y remédier, et qu'au contraire, ils semblent privilégier les POJO qui ne font aucun contrôle, même pas dans le constructeur.

  19. #19
    Expert éminent
    Avatar de Pyramidev
    Homme Profil pro
    Tech Lead
    Inscrit en
    Avril 2016
    Messages
    1 494
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Tech Lead

    Informations forums :
    Inscription : Avril 2016
    Messages : 1 494
    Points : 6 208
    Points
    6 208
    Par défaut
    Citation Envoyé par esperanto Voir le message
    Citation Envoyé par Pyramidev Voir le message
    Citation Envoyé par esperanto Voir le message
    1. as-tu déjà utilisé cette technique sur un projet réel?
    Oui. Au boulot, j'utilise cette technique dans un projet Python (avec le typage statique optionnel de Python 3) et je l'ai aussi utilisée dans un projet C++.
    Compte tenu de ce que tu écris ensuite je me rends compte que j'aurais dû écrire en Java. Vu que l'essentiel du reste du message tendait à montrer que ce n'est pas adapté à Java (mais à d'autres langages) je pensais que c'était une évidence, visiblement pas. Mea culpa.
    Citation Envoyé par esperanto Voir le message
    mais que sa transposition à Java est peu efficace
    Je ne vois pas en quoi ce serait moins adapté à Java que, par exemple, à Python.

    Citation Envoyé par esperanto Voir le message
    Ce qui rend d'autant plus regrettable le fait que Java ne propose rien pour y remédier, et qu'au contraire, ils semblent privilégier les POJO qui ne font aucun contrôle, même pas dans le constructeur.
    Dans l'exemple :
    Citation Envoyé par esperanto Voir le message
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    public record BankTransaction(
                                  @Check(id -> ! (id.contains(" "))) String userId,
                                  Date date,
                                  double amount,
                                  String description) {}
    si on définit la classe UserId, on peut redéfinir BankTransaction ainsi :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    public record BankTransaction(
                                  UserId userId,
                                  Date date,
                                  double amount,
                                  String description) {}
    et il n'y a plus besoin de contrôle sur le format de l'identifiant de l'utilisateur dans le constructeur de BankTransaction, car ce contrôle a déjà été fait avant et la garantie sur le format a été propagée statiquement via le type UserId.

    Par contre, ça aurait été bien de pouvoir définir le type UserId de manière concise aussi.

  20. #20
    Membre émérite
    Inscrit en
    Janvier 2006
    Messages
    739
    Détails du profil
    Informations forums :
    Inscription : Janvier 2006
    Messages : 739
    Points : 2 809
    Points
    2 809
    Par défaut
    Citation Envoyé par Pyramidev Voir le message
    La bibliothèque standard de Rust utilise cette technique, quoique, à la place des exceptions, la gestion d'erreurs se fait via des types sommes.
    Le type String encapsule un Vec[u8] (un vecteur d'octets) avec un invariant : cette suite d'octets doit être une chaîne UTF-8 valide.
    La classe string est un mauvais exemple: elle contient plein de méthodes intéressantes, ce qui fait que quand je manipule un objet de type string, il reste relativement rare d'avoir à récupérer le vecteur interne. Au contraire avec ta classe UserId, tout sauf l'affectation et le passage de paramètres nécessite un appel à getValue() !!!

    Citation Envoyé par Pyramidev Voir le message
    si on définit la classe UserId, on peut redéfinir BankTransaction ainsi : [...]
    Par contre, ça aurait été bien de pouvoir définir le type UserId de manière concise aussi.
    Oui ça j'avais compris. Mais comme justement la définition de la classe UserId est loin d'être courte, et qu'en plus il faut se trimballer des getValue() partout, je doute que cette technique se généralise. Et comme je disais, si je suis seulement utilisateur et non auteur du type BankTransaction, et que l'auteur n'a pas utilisé le type UserId, je ne peux rien faire.

    en y réfléchissant, le mécanisme @Check que je propose s'apparente à de la programmation par contrat. Et si Java ne l'implémente pas, des librairies permettent de le faire, ce qui prouve bien qu'il y a une demande.

    Citation Envoyé par Pyramidev Voir le message
    En fait, selon la manière qu'on a d'aborder la gestion d'erreurs, l'utilisation de classes comme UserId peut réduire le volume de code et augmenter sensiblement les performances.
    Pourquoi ? Admettons que je n'ai pas le droit de définir un type comme UserId et que je passe de paramètre en paramètre une variable userId de type String. Alors, au début de chaque fonction, je vais avoir envie de faire un contrôle au runtime pour vérifier si userId respecte le format. Comme ça, le jour où il y aura un oubli de vérification de format, l'erreur sera détectée le plus proche possible de la source de l'erreur. Mais, syntaxiquement, c'est bien plus lourd que de simplement remplacer le type du paramètre userId par UserId. En plus, c'est moins performant de vérifier au runtime la précondition de userId à chaque entrée de fonction que de la vérifier une fois, lors de la construction de l'objet de type UserId.
    Quand on fait de la programmation par contrat, on se retrouve en effet à avoir des @Check partout et on peut craindre pour les performances. Mais en pratique il y a deux moyens d'optimiser:
    1. si une méthode requiert un certain contrat et qu'elle est appelée par une méthode exigeant le même, alors le compilateur peut être assez intelligent pour sauter le second check;
    2. comme ils sont tous définis par des contrats, les check peuvent être désactivés tous en même temps à la compilation, au moment du passage en production

    (après faut faire attention, l'échec de la première Ariane 5 était dû justement à un check désactivé - alors que les paramètres étaient toujours bons sur Ariane 4 mais il aurait fallu les réactiver juste une fois)

    Au final, si la librairie permet de définir une classe UserId comme identique à la classe String mais avec juste un contrat en plus (ce qui revient à donner un nom aux contrats les plus fréquents), on retrouve la syntaxe que tu préfères et là je suis preneur. Mais définir plein de classes juste pour des check, et surtout me trimballer des getValue partout, surtout si je suis le seul à le faire, ça ne me tente pas trop.

Discussions similaires

  1. Réponses: 9
    Dernier message: 06/05/2019, 12h10
  2. Réponses: 0
    Dernier message: 04/11/2017, 23h12
  3. Réponses: 27
    Dernier message: 15/04/2012, 12h42
  4. Réponses: 0
    Dernier message: 18/08/2010, 21h01
  5. Réponses: 0
    Dernier message: 27/10/2009, 17h13

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo