Exile on Keyboard St. - Blog sur Linux et Debian

Aller au contenu | Aller au menu | Aller à la recherche

lundi 6 août 2018

Java: Bien utiliser le Builder pattern

Quand on maintient du code Java, on tombe malheureusement assez souvent sur des méthodes aux signatures complexes comme celle-ci:

public int myMethod(String id, String name, Boolean secure, Object obj, Object backupObj, boolean restart)

Et il n'est pas rare d'avoir encore plus de paramètres !

La situation se dégrade encore quand pour "simplifier", on surcharge la méthode précédente en:

public int myMethod(String id, String name, Boolean secure, Object obj, Object backupObj)

quand restart vaut false puis en:

public int myMethod(String id, String name, Boolean secure, Object obj)

quand en plus backupObj est null.

On peut avoir ainsi 3 à 5 signatures différentes pour la même méthode. Et cela devient vite unsupportable quand on veut refactorer ces méthodes, parce qu'elles sont référencées n fois dans le code de production et également dans les tests.

Si le développeur qui a créé la première méthode avait créé un object et l'avait passé en paramètre de sa méthode, on n'en serait pas là !

Imaginons maintenant la méthode suivante qui permet de créer un utilisateur:

public long create(final String firstName, final String name, final int age)

Elle retourne l'identifiant de l'utilisateur créé. Supposons que l'age n'est pas obligatoire et que l'on souhaite anticiper l'ajout de nouveaux champs pour créer un utilisateur.

On va alors plutôt procéder comme suit:

public long create(final User user)

La classe User commencera comme ceci:

public class User
{
    private long id;
    private String firstName;
    private String lastName;
    private int age;
}

Mais pour créer un utilisateur à passer en paramètre de la méthode create, on va créer un Builder pour construire l'objet User.

Pour cela, on peut installer le Plugin Builder Generator. Un autre plugin est également disponible: Inner Builder.

Après redémarrage d'IDEA, un clic-droit puis Generate propose maintenant l'option "Builder" et on a alors la boite de dialogue suivante:

Capture-CreateBuilder.png

Ici on a choisi un "innerBuilder" afin que le Builder soit une classe interne de l'objet lui même.

On choisit ensuite la liste des champs à inclure, on ne prends pas l'id puisqu'il ne sera pas calculé par l'appelant de la méthode create mais bien par celle-ci:

Capture-Select_Fields_to_Be_Available_in_Builder.png

Et on obtient:


    public static final class UserBuilder
    {
        private String firstName;
        private String lastName;
        private int age;

        private UserBuilder()
        {
        }

        public static UserBuilder anUser()
        {
            return new UserBuilder();
        }

        public UserBuilder withFirstName(String firstName)
        {
            this.firstName = firstName;
            return this;
        }

        public UserBuilder withLastName(String lastName)
        {
            this.lastName = lastName;
            return this;
        }

        public UserBuilder withAge(int age)
        {
            this.age = age;
            return this;
        }

        public User build()
        {
            User user = new User();
            user.lastName = this.lastName;
            user.age = this.age;
            user.firstName = this.firstName;
            return user;
        }
    }

On crée ensuite notre User comme suit:

        final User user = UserBuilder
                    .anUser()
                    .withFirstName("Brian")
                    .withLastName("Kernighan")
                    .build();

Cette façon de faire permet:

  • de faire évoluer l'objet User en changeant un minimum de code
  • de valider les différents champs dans la méthode build

Et les signatures des méthodes ne changent pas.

On peut même éviter de dupliquer les champs de la classe User dans la classe UserBuilder en procédant comme ceci:

public static final class UserBuilder
    {
        private User user;

        private UserBuilder()
        {
            user = new User();
        }

        public static UserBuilder anUser()
        {
            return new UserBuilder();
        }

        public UserBuilder withFirstName(String firstName)
        {
            this.user.firstName = firstName;
            return this;
        }

        public UserBuilder withLastName(String lastName)
        {
            this.user.lastName = lastName;
            return this;
        }

        public UserBuilder withAge(int age)
        {
            this.user.age = age;
            return this;
        }

        public User build()
        {
            return user;
        }
    }

Mais notre générateur ne sait pas le faire :-(

dimanche 15 avril 2018

Rechercher rapidement dans le code source avec ack

Rechercher dans du code source est une des activités principales du développeur.

Dans les logiciels aux architectures incertaines et maintenant avec la "méthode" Agile, il est souvent diffcile de savoir rapidement ou est gérée telle ou telle fonctionnalité dans le code.

Pendant longtemps, rechecher dans le code ressemblait à ça:

find . -name "*.java" -exec grep HashMap {} \; -print

Certains me diront:

Bahhhhh, Qu'est-ce que c'est compliqué ?

Où encore l'excuse qui n'en est pas une:

Mais moi je suis sous Windows ...

Heureusement, il existe maintenant des outils plus rapides à l'usage que les commandes find et grep combinées.

J'ai découvert il y a quelques années la commande ack-grep ou ack qui est précisément faites pour notre besoin: chercher des motifs dans une base de code.

Je vous passe ici l'installation d'ack: ack est un script Perl qui s'installe très simplement ; évitez le package de votre distribution afin d'avoir la version la plus récente.

Maintenant, notre commande précédente s'écrit comme suit:

ack --java HashMap

C'est quand même plus court !

J'ai précisé à la commande que je voulais chercher dans des sources Java. Dans types de fichiers sont prédéfinis dans ack:

 ack --help-types
Usage: ack [OPTION]... PATTERN [FILES OR DIRECTORIES]

The following is the list of filetypes supported by ack.  You can
specify a file type with the --type=TYPE format, or the --TYPE
format.  For example, both --type=perl and --perl work.

Note that some extensions may appear in multiple types.  For example,
.pod files are both Perl and Parrot.

    --[no]actionscript .as .mxml
    --[no]ada          .ada .adb .ads
    --[no]asm          .asm .s
    --[no]asp          .asp
    --[no]aspx         .master .ascx .asmx .aspx .svc
    --[no]batch        .bat .cmd
    --[no]cc           .c .h .xs
    --[no]cfmx         .cfc .cfm .cfml
    --[no]clojure      .clj
    --[no]cmake        CMakeLists.txt; .cmake

Si vous utilisez un type de fichier non connu d'ack, vous pouvez le définir.

Ensuite, de nombreuses options de la commandes ack sont reprises de celles de grep:

       -i, --ignore-case
           Ignore case distinctions in PATTERN
       -v, --invert-match
           Invert match: select non-matching lines

ou les classiques:

       -l, --files-with-matches
           Only print the filenames of matching files, instead of the matching text.

       -L, --files-without-matches
           Only print the filenames of files that do NOT match.

ack permet comme grep d'afficher les lignes de contexte autour du motif trouvé:

       -A NUM, --after-context=NUM
           Print NUM lines of trailing context after matching lines.

       -B NUM, --before-context=NUM
           Print NUM lines of leading context before matching lines.

ce qui facilite la lisibilité de la sortie.

Par défaut, ack va parcourir tous les sous-répertoires du répertoire courant. Ce comportement peut-être changé par:

       -r, -R, --recurse
           Recurse into sub-directories. This is the default and just here for compatibility with grep. You can also use it for
           turning --no-recurse off.

Enfin, ack peut utiliser un fichier de configuration ~/.ackrc.

Pour créer celui utilisant les options par défaut:

ack --create-ackrc > ~/.ackrc

dimanche 29 octobre 2017

IntelliJ IDEA: Quel formatage pour votre code Java

Beaucoup de développeurs, même expérimentés, ne prêtent pas (ou pas assez) attention au formatage du code.

Pourtant, l'utilisation d'un template de formatage de code présente les entre autres les avantages suivants:

  • le code est plus facile à lire
  • Les lignes blanches successives et inutiles sont évitées
  • En Java, le pavé des imports sera toujours cohérent entre les développeurs
  • Il permet de voir facilement les blocs, même ceux ne contenant qu'une instruction
  • Une fois un template de formatage choisi dans une équipe, les conflits à l'update sont évités et les merges facilités

Malheureusement, la plupart des projets de développement n'imposent pas un template de formatage et les développeurs se contentent trop souvent du template par défaut de leur environnement de développement. Et comme tous les développeurs n'utilisent pas le même IDE ...

Eclipse propose par exemple trois templates par défaut, qui tous utilisent des tabulations pour indenter le code ... Pire, le profile "Java Conventions" utilise à la fois des tabulations et des espaces. C'est bien dommage qu'Eclipse propose de formater le code avec des tabulations, ce que très peu de gens font encore. Parait t'il qu'historiquement, ce choix avait pour but de "sauver" de l'espace disque !!! Cette époque là est bien révolue. Enfin, pour modifier la façon de formater le code par défaut d'Eclipse, il faut dupliquer le profile avant de pouvoir le modifier, ce qui n'est pas très commode. IntelliJ IDEA n'impose pas cette restriction.

Dans ce billet, on va quand même dupliquer le profile par défaut d'IntelliJ, pour pouvoir exporter nos paramètres et aussi revenir facilement au profile par défaut.

Interdire la détection de l'indentation existante

Première chose à faire dans IntelliJ IDEA, décocher la case "Detect and use existing file indents for editing". En effet, avec cette option, si un fichier est indenté avec des tabulations, IntelliJ considèrera qu'il faut continuer d'indenter de cette façon !!!

Utiliser EditorConfig ou pas ?

EditorConfig est un format de fichier texte permettant de définir des profiles de formatage de code, via quelques propriétés. Ce format est censé être supporté par de nombreux éditeurs, ce qui est très séduisant. Malheureusement, trop peu de propriétés sont implémentées par exemple par le plugin EditorConfig pour IntelliJ, et on est donc amené à définir le reste du paramétrage dans la configuration standard de l'éditeur. J'y reviendrai peut être dans un billet séparé. Pour l'instant, on va cocher qu'on n'utilise pas EditorConfig.

Onglet 'Tabs and Indents'

On ne change rien dans cette section puisqu'IntelliJ utilise par défaut les espaces pour l'indentation et 4 de plus à chaque bloc. Ce formatage nous convient.

Onglet 'Spaces'

Dans cette section, on va cocher dans la sous-section 'Within' la case 'Array initializer braces', ce qui permet de mieux voir les paramètres dans les appels à log4j ou java.util.logging.

Onglet 'Wrapping and braces'

C'est dans cette section que l'on va modifier le plus de choses.

Concernant la position des accolades, on va adopter le style Allman, c'est à dire positionner les accolades sur la ligne suivante.

Toutes les valeurs de la sous section 'Braces placement' seront donc positionnées à 'Next line'.

Une fois qu'on a fait cela, il est logique:

  • dans 'if() statement' de cocher 'else on new line'
  • dans 'try statement' de cocher 'catch on new line' et 'finally on new line'
  • dans 'do while statement' de cocher 'while on new line'

Enfin, afin que les blocs soient bien visibles, même ceux ne contenant qu'une instruction, on va position à 'always' les propriétés 'Force braces' des sous-sections suivantes:

  • 'if() statement'
  • 'for() statement'
  • 'while() statement'
  • 'do while() statement'

Onglet 'Blank lines'

Dans la sous-section 'Keep maximum blank lines', on positionnera les 3 propriétés suivantes à 1 au lieu de 2:

  • In declarations:
  • In code:
  • Before '{':

En effet, je trouve que trop de lignes blanches n'aide pas à la lisibilité mais nous oblige à scroller.

Onglets 'JavaDoc', 'Imports' et 'Arrangement'

Je ne modifie rien pour l'instant.

Onglet 'Code generation'

Dans la section 'Final modifier' on coche les deux cases:

  • Make generated local variables final
  • Make generated parameters final

Vous pouvez utiliser le Formateur de code Java pour IDEA défini ci-dessous en le téléchargeant avec le lien précédent.

- page 1 de 4