Développement Durable – La vraie vie de développeur – 1 ‐ Less Is More



Développement Durable – La vraie vie de développeur – 1 ‐ Less Is More

1 0


maintainability-slides

Une présentation sur la maintenabilité des applications

On Github lcottereau / maintainability-slides

Développement Durable

Maintenabilité des applications

La vraie vie de développeur

  • Prise en charge d'un projet suite à démission. Livraison imminente
  • Au moins, pas de documentation, pas de fausse croyances
  • Correction de code dupliqué, mais en fait pas vraiment mais presque
  • Régression fonctionnelle (le plus frustant pour les utilisateurs)

Le drame du patch

  • Ne résoud en partie que la partie émergée d'un problème
  • Il est parfois nécessaire
  • Mais il faut réfléchir

Qu'est-ce que la maintenabilité ?

  • Après les définitions et l'exemple, qu'en pensez-vous ?
  • 4 types : corrective, évolutive, adaptative, préventive

Proposition de définition

Capacité à pouvoir maintenir une application sur le long terme et pour tout type de maintenance
  • au travers des changements d'équipe, des migrations techniques, des grosses évolutions

Pourquoi cette présentation ?

Programmers are constantly in maintenance mode  — The Pragmatic Programmer: From Journeyman to Master

  • Beaucoup de boulot : toujours après projet
  • site web Sénat début années 90, système de paie 25 ans
  • une des nombreuses compétences du dev moderne
  • montée en puissance (outils)
  • vu le marché, excellente perspective de valorisation

Ce que l'on verra

4 Bonnes pratiques Gestion des environnements Usine de développement
  • particulièrement pour l'informatique de gestion
  • indicateur de maintenabilité avec usine de dev

Ce que l'on ne verra pas

  • Organisationnel
Si vous êtes un jour responsables d'équipes de dev
  • You build it, you run it chez Amazon : dev plus responsables car contact utilisateurs et env production
  • connaissance technique en interne et capacité à évaluer les travaux externes
  • pas de perte de temps à abuser les indicateurs de suivi (uniquement une impression de contrôle)

1 ‐ Less Is More

Moins de fonctionnalité

  • YAGNI
  • Agilité aide à définir les fonctions au plus juste

Comprenez ce que vous faites !

private static final long serialVersionUID = 1L;
Sessions web et cookies

Convention over configuration

  • valeurs par défaut
  • code spécifique / paramétrages
  • maven, noms par défaut des tables/colonnes JPA
  • une des améliorations de mvn/gradle sur ant
  • on peut choisir des conventions mais les documenter
  • choix de bibliothèques exactement adaptées
  • idéal de rester dans les conventions

DRY – don't WET

Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.
  • Risques : corriger à un seul endroit, savoir lequel utiliser
  • Aide à une bonne conception (responsabilité unique)
  • Pas que copier/coller de code (ex : annuaire inversé)
  • Attention si presque-pareil : pourquoi ? comment gérer ?
  • Attention à la surgénéralisation (intra et inter projets)
  • Pas de généré dans dépôt (que sources)

Réfléchissez à ce que vous commitez

A supprimer

/**
 *
 */
private static final long serialVersionUID = 1L;
private void setResultCode(int resultOk) {
    // TODO Auto-generated method stub
}
//il affecte au parametre passé dans le query la valeur passée
q.setParameter("username", username);
/**
* $Id:$
* @author toto
*/
public int doSomething() {
	FinalUser user = new FinalUser();
	user = em.find(FinalUser.class, userName);
	/*return newUser.getUserName();*/
	return userNName;
}
  • exemples tirés des projets d'élèves
  • aucun code binaire (cf projet)
  • infos pas tenues à jour (@author déjà ailleurs)
  • pas de commentaires inutiles
  • classique : vieux code de test : git est là pour ça
  • données auto-remplies à réfléchir (OK car à jour)
  • se méfier du code généré (risque d'incohérence, de lier à un environnement)
  • suites de tests vides (2h perdues !)

A garder

/*
 * (c) Copyright 2013 Laurent Cottereau
 *
 * This file is part of Foobar.
 * Foobar is free software: you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation.
 * You should have received a copy of the GNU General Public
 * License along with Foobar.
 * If not, see <http://www.gnu.org/licenses/>.
 */
/**
 * commentaire décrivant le comportement pour qui
 * ne veut pas lire le code.
 * TODO penser à implémenter autre chose.
            
  • LICENCE, Commentaires, TODO
  • Le commit est un moment solennel et presque définitif
  • La revue doit être complète à chaque fois
  • Préférer les petits commits souvent (plus facile détecter erreurs et améliorations possibles)

Bibliothèques, API et Frameworks

Différences ?

  • Bibliothèques (pas library en français) : collections de nouvelles fonctions à utiliser
  • API : description de ces fonctions (javadoc)
  • Framework : IoC. un programme sur lequel on injecte un comportement. Inclut organisation, paradigms, conventions en plus de bibliothèques
  • raccord sur la curiosité permanente

Se concentrer sur la Valeur ajoutée

Attention au choix framework/bibliothèque/API

  • Coder uniquement spécificité/élément différenciateur
  • Code externe généralement mieux testé
  • gestion : code "métier". ergonomie ? affichage des pages (Play, Spring MVC,...)
  • blog : gestion articles. commentaires ? infrastructure, connexion
  • jeu vidéo : graphiques et IA.
  • OS, communauté, docu, prix, couteau suisse
  • Si vous n'avez qu'un marteau, tout aura l'air d'un clou

Un cas typique

/**
 * Copyright Sénat.
 */
package fr.senat.model.dosleg;

import fr.senat.model.dosleg.validator.ActiviteValide;
import fr.senat.model.senateurs.GroupePolitique;
import fr.senat.util.Constants;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.OrderBy;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
import org.hibernate.annotations.Type;
import static org.joda.time.DateTimeConstants.OCTOBER;
import org.joda.time.LocalDate;
import static org.joda.time.LocalDate.now;

/**
 * Représente une mission de contrôle et d'évaluation du Sénat. Peut être une Mission Commune d'Information, une
 * commission d'enquête, un contrôle d'application d'une loi, un contrôle de commission permanente, etc... Par défaut,
 * le contrôle n'est à l'initiative des groupes, ni avec l'aide de la cour des comptes ou clôturé, mais il est considéré
 * comme avec rapport. Par ailleurs, les organismes, lois, livrables, acteurs et thèmes sont des ensembles vides.
 */
@Entity
@ActiviteValide
public class Controle implements Serializable {
    private static final String FOREIGN_KEY_ID = "CON_ID";
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Integer id;
    @Size(max = 256, message = "trop long.")
    @Column(name = "LIB", length = 256)
    @NotNull
    private String libelle;
    @Size(max = 2048, message = "trop long.")
    private String objet;
    @Pattern(regexp = Constants.URL_PATTERN, message = "pas au bon format (par exemple : http://www.senat.fr/commission/XX/missions_d_information/mission_dinformation_sur_YY.html)")
    private String url;
    @NotNull
    @Column(name = "INITIATIVE_GROUPES")
    @Type(type = "fr.senat.util.hibernate.usertype.OuiNonSmallType")
    private boolean initiativeDesGroupes = false;
    @NotNull
    @Column(name = "COUR_COMPTES")
    @Type(type = "fr.senat.util.hibernate.usertype.OuiNonSmallType")
    private boolean courDesComptes = false;
    @NotNull
    @Column(name = "PROGRAMME_CREATION")
    private int programmeCreation = Controle.getSession(now());
    @Column(name = "PROGRAMME_REPORT")
    private Integer programmeReport;
    @Column(name = "DATE_CREATION")
    @Type(type = "org.jadira.usertype.dateandtime.joda.PersistentLocalDate")
    private LocalDate dateCreation;
    @NotNull
    @Type(type = "fr.senat.util.hibernate.usertype.OuiNonSmallType")
    private boolean cloture = false;
    @NotNull
    @Column(name = "AVEC_RAPPORT")
    @Type(type = "fr.senat.util.hibernate.usertype.OuiNonSmallType")
    private boolean avecRapport = true;
    @Embedded
    @NotNull
    private EcheanceControle echeance = new EcheanceControle();
    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "GRPPOL")
    private GroupePolitique groupePolitique;
    @ManyToMany
    @JoinTable(name = "CONTROLE_ORG", joinColumns = @JoinColumn(name = FOREIGN_KEY_ID), inverseJoinColumns = @JoinColumn(name = "ORGCOD"))
    private Set<Organisme> organismes = new HashSet<Organisme>();
    @ManyToMany
    @JoinTable(name = "CONTROLE_LOI", joinColumns = @JoinColumn(name = FOREIGN_KEY_ID), inverseJoinColumns = @JoinColumn(name = "LOICOD"))
    private Set<Loi> lois = new HashSet<Loi>();
    @OneToMany(orphanRemoval = true, cascade = CascadeType.ALL)
    @JoinColumn(name = FOREIGN_KEY_ID, nullable = false)
    private Set<LivrableControle> livrables = new HashSet<LivrableControle>();
    @OneToMany(orphanRemoval = true, cascade = CascadeType.ALL)
    @JoinColumn(name = FOREIGN_KEY_ID, nullable = false)
    @OrderBy("id asc")
    private Set<ActeurControle> acteurs = new HashSet<ActeurControle>();
    private final java.util.concurrent.AtomicReference<java.lang.Object> themes = new java.util.concurrent.AtomicReference<java.lang.Object>();


    public Controle() {
    }

    public Integer getId() {
        return this.id;
    }

    public String getLibelle() {
        return this.libelle;
    }

    public String getObjet() {
        return this.objet;
    }

    public String getUrl() {
        return this.url;
    }

    public boolean isInitiativeDesGroupes() {
        return this.initiativeDesGroupes;
    }

    public boolean isCourDesComptes() {
        return this.courDesComptes;
    }

    public int getProgrammeCreation() {
        return this.programmeCreation;
    }

    public Integer getProgrammeReport() {
        return this.programmeReport;
    }

    public LocalDate getDateCreation() {
        return this.dateCreation;
    }

    public boolean isCloture() {
        return this.cloture;
    }

    public boolean isAvecRapport() {
        return this.avecRapport;
    }

    public EcheanceControle getEcheance() {
        return this.echeance;
    }

    public GroupePolitique getGroupePolitique() {
        return this.groupePolitique;
    }

    public Set<Organisme> getOrganismes() {
        return this.organismes;
    }

    public Set<Loi> getLois() {
        return this.lois;
    }

    public Set<LivrableControle> getLivrables() {
        return this.livrables;
    }

    public Set<ActeurControle> getActeurs() {
        return this.acteurs;
    }

    public Set<Theme> getThemes() {
        java.lang.Object value = this.themes.get();
        if (value == null) {
            synchronized(this.themes) {
                value = this.themes.get();
                if (value == null) {
                    final Set<Theme> actualValue = Cache.getInstance().calculateThemes();
                    value = actualValue == null ? this.themes : actualValue;
                    this.themes.set(value);
                }
            }
        }
        return (Set<Theme>)(value == this.themes ? null : value);
     }

    public void setId(final Integer id) {
        this.id = id;
    }

    public void setLibelle(final String libelle) {
        this.libelle = libelle;
    }

    public void setObjet(final String objet) {
        this.objet = objet;
    }

    public void setUrl(final String url) {
        this.url = url;
    }

    public void setInitiativeDesGroupes(final boolean initiativeDesGroupes) {
        this.initiativeDesGroupes = initiativeDesGroupes;
    }

    public void setCourDesComptes(final boolean courDesComptes) {
        this.courDesComptes = courDesComptes;
    }

    public void setProgrammeCreation(final int programmeCreation) {
        this.programmeCreation = programmeCreation;
    }

    public void setProgrammeReport(final Integer programmeReport) {
        this.programmeReport = programmeReport;
    }

    public void setDateCreation(final LocalDate dateCreation) {
        this.dateCreation = dateCreation;
    }

    public void setCloture(final boolean cloture) {
        this.cloture = cloture;
    }

    public void setAvecRapport(final boolean avecRapport) {
        this.avecRapport = avecRapport;
    }

    public void setEcheance(final EcheanceControle echeance) {
        this.echeance = echeance;
    }

    public void setGroupePolitique(final GroupePolitique groupePolitique) {
        this.groupePolitique = groupePolitique;
    }

    public void setOrganismes(final Set<Organisme> organismes) {
        this.organismes = organismes;
    }

    public void setLois(final Set<Loi> lois) {
        this.lois = lois;
    }

    public void setLivrables(final Set<LivrableControle> livrables) {
        this.livrables = livrables;
    }

    public void setActeurs(final Set<ActeurControle> acteurs) {
        this.acteurs = acteurs;
    }

    public void setThemes(final Set<Theme> themes) {
        this.themes = themes;
    }

    /**
     * Renvoie la date à prendre en compte pour le nommage du groupe politique lié à ce contrôle. TODO vérifier cette
     * règle de gestion avec S. Dubourg.
     *
     * @return la date à prendre en compte.
     */
    private LocalDate getDateNommageGroupePolitique() {
        if (isClosed()) {
            return echeance.getDate();
        } else {
            return LocalDate.now();
        }
    }

    /**
     * Renvoie le libellé de groupe politique à la date adéquate pour ce contrôle, préfixé de l'article adéquat. Ainsi,
     * un contrôle en cours prendra le nom à la date du jour alors qu'un contrôle fini prendra le nom à la date
     * d'échéance.
     *
     * @return le libellé préfixé de l'article. <code>null</code> si le groupe politique n'est pas défini.
     */
    public String getLibelleLongEtArticleGroupePolitique() {
        if (groupePolitique != null) {
            LocalDate d = getDateNommageGroupePolitique();
            StringBuilder sb = new StringBuilder(groupePolitique.getArticle(d));
            sb.append(" ");
            sb.append(groupePolitique.getLibelleLong(d));
            return sb.toString();
        }
        return null;
    }

    /**
     * Détermine si le contrôle est fini.
     *
     * @return <code>true</code> si le contrôle est fini.
     */
    public boolean isClosed() {
        return echeance != null && echeance.getDate().isBefore(LocalDate.now());
    }

    /**
     * Reporte d'un an le contrôle. La date de début d'activité est inchangée mais la date de report est décalée d'un an
     * (année suivant le démarrage si c'est le premier report)
     */
    public void reporter() {
        if (canReporteControle()) {
            if (programmeReport == null) {
                programmeReport = programmeCreation + 1;
            } else {
                programmeReport++;
            }
        }
    }

    /**
     * Renvoie le dernier programme d'activité du contrôle.
     *
     * @return le dernier programme d'activité du contrôle.
     */
    public int getDernierProgramme() {
        if (programmeReport == null) {
            return programmeCreation;
        } else {
            return programmeReport;
        }
    }

    /**
     * Indique si un contrôle peut être reporté. C'est le cas si l'échéance correspond à une session strictement
     * postérieure supérieure à la session de report actuelle.
     *
     * @return <code>true</code> si le contrôle peut être reporté. <code>false</code> sinon.
     */
    public boolean canReporteControle() {
        if (echeance.getDate() != null) {
            return (getDernierProgramme() < echeance.getSession());
        } else {
            return false;
        }
    }

    /**
     * Renvoie la session correspondant à une date.
     *
     * @param date la date dont on cherche la session.
     * @return la session, représentée par l'année de démarrage de la session (2013 pour la session 2013-2014 par
     * exemple). <code>null</code> si la date en paramètre est nulle.
     */
    public static Integer getSession(LocalDate date) {
        if (date != null) {
            if (date.getMonthOfYear() < OCTOBER) {
                return date.getYear() - 1;
            } else {
                return date.getYear();
            }
        } else {
            return null;
        }
    }

    /**
     * Renvoie l'année de fin d'activité du contrôle.
     *
     * @return l'année de fin d'activité.
     */
    public int getFinActivite() {
        return getDernierProgramme() + 1;
    }

    /**
     * Définit la fin d'activité. Calcule le programme correspondant et définit éventuellement un report sur le
     * contrôle.
     *
     * @param fin l'année de fin d'activité spécifiée dans le formulaire.
     */
    public void setFinActivite(int fin) {
        if (fin > getProgrammeCreation() + 1) {
            setProgrammeReport(fin - 1);
        } else {
            setProgrammeReport(null);
        }
    }

    @java.lang.Override
    public boolean equals(final java.lang.Object o) {
        if (o == this) return true;
        if (!(o instanceof Controle)) return false;
        final Controle other = (Controle)o;
        if (!other.canEqual((java.lang.Object)this)) return false;
        final java.lang.Object this$id = this.getId();
        final java.lang.Object other$id = other.getId();
        if (this$id == null ? other$id != null : !this$id.equals(other$id)) return false;
        final java.lang.Object this$libelle = this.getLibelle();
        final java.lang.Object other$libelle = other.getLibelle();
        if (this$libelle == null ? other$libelle != null : !this$libelle.equals(other$libelle)) return false;
        final java.lang.Object this$objet = this.getObjet();
        final java.lang.Object other$objet = other.getObjet();
        if (this$objet == null ? other$objet != null : !this$objet.equals(other$objet)) return false;
        final java.lang.Object this$url = this.getUrl();
        final java.lang.Object other$url = other.getUrl();
        if (this$url == null ? other$url != null : !this$url.equals(other$url)) return false;
        if (this.isInitiativeDesGroupes() != other.isInitiativeDesGroupes()) return false;
        if (this.isCourDesComptes() != other.isCourDesComptes()) return false;
        if (this.getProgrammeCreation() != other.getProgrammeCreation()) return false;
        final java.lang.Object this$programmeReport = this.getProgrammeReport();
        final java.lang.Object other$programmeReport = other.getProgrammeReport();
        if (this$programmeReport == null ? other$programmeReport != null : !this$programmeReport.equals(other$programmeReport)) return false;
        final java.lang.Object this$dateCreation = this.getDateCreation();
        final java.lang.Object other$dateCreation = other.getDateCreation();
        if (this$dateCreation == null ? other$dateCreation != null : !this$dateCreation.equals(other$dateCreation)) return false;
        if (this.isCloture() != other.isCloture()) return false;
        if (this.isAvecRapport() != other.isAvecRapport()) return false;
        final java.lang.Object this$echeance = this.getEcheance();
        final java.lang.Object other$echeance = other.getEcheance();
        if (this$echeance == null ? other$echeance != null : !this$echeance.equals(other$echeance)) return false;
        final java.lang.Object this$groupePolitique = this.getGroupePolitique();
        final java.lang.Object other$groupePolitique = other.getGroupePolitique();
        if (this$groupePolitique == null ? other$groupePolitique != null : !this$groupePolitique.equals(other$groupePolitique)) return false;
        final java.lang.Object this$organismes = this.getOrganismes();
        final java.lang.Object other$organismes = other.getOrganismes();
        if (this$organismes == null ? other$organismes != null : !this$organismes.equals(other$organismes)) return false;
        final java.lang.Object this$lois = this.getLois();
        final java.lang.Object other$lois = other.getLois();
        if (this$lois == null ? other$lois != null : !this$lois.equals(other$lois)) return false;
        final java.lang.Object this$livrables = this.getLivrables();
        final java.lang.Object other$livrables = other.getLivrables();
        if (this$livrables == null ? other$livrables != null : !this$livrables.equals(other$livrables)) return false;
        final java.lang.Object this$acteurs = this.getActeurs();
        final java.lang.Object other$acteurs = other.getActeurs();
        if (this$acteurs == null ? other$acteurs != null : !this$acteurs.equals(other$acteurs)) return false;
        final java.lang.Object this$themes = this.getThemes();
        final java.lang.Object other$themes = other.getThemes();
        if (this$themes == null ? other$themes != null : !this$themes.equals(other$themes)) return false;
        return true;
    }

    public boolean canEqual(final java.lang.Object other) {
        return other instanceof Controle;
    }

    @java.lang.Override
    public int hashCode() {
        final int PRIME = 59;
        int result = 1;
        final java.lang.Object $id = this.getId();
        result = result * PRIME + ($id == null ? 0 : $id.hashCode());
        final java.lang.Object $libelle = this.getLibelle();
        result = result * PRIME + ($libelle == null ? 0 : $libelle.hashCode());
        final java.lang.Object $objet = this.getObjet();
        result = result * PRIME + ($objet == null ? 0 : $objet.hashCode());
        final java.lang.Object $url = this.getUrl();
        result = result * PRIME + ($url == null ? 0 : $url.hashCode());
        result = result * PRIME + (this.isInitiativeDesGroupes() ? 79 : 97);
        result = result * PRIME + (this.isCourDesComptes() ? 79 : 97);
        result = result * PRIME + this.getProgrammeCreation();
        final java.lang.Object $programmeReport = this.getProgrammeReport();
        result = result * PRIME + ($programmeReport == null ? 0 : $programmeReport.hashCode());
        final java.lang.Object $dateCreation = this.getDateCreation();
        result = result * PRIME + ($dateCreation == null ? 0 : $dateCreation.hashCode());
        result = result * PRIME + (this.isCloture() ? 79 : 97);
        result = result * PRIME + (this.isAvecRapport() ? 79 : 97);
        final java.lang.Object $echeance = this.getEcheance();
        result = result * PRIME + ($echeance == null ? 0 : $echeance.hashCode());
        final java.lang.Object $groupePolitique = this.getGroupePolitique();
        result = result * PRIME + ($groupePolitique == null ? 0 : $groupePolitique.hashCode());
        final java.lang.Object $organismes = this.getOrganismes();
        result = result * PRIME + ($organismes == null ? 0 : $organismes.hashCode());
        final java.lang.Object $lois = this.getLois();
        result = result * PRIME + ($lois == null ? 0 : $lois.hashCode());
        final java.lang.Object $livrables = this.getLivrables();
        result = result * PRIME + ($livrables == null ? 0 : $livrables.hashCode());
        final java.lang.Object $acteurs = this.getActeurs();
        result = result * PRIME + ($acteurs == null ? 0 : $acteurs.hashCode());
        final java.lang.Object $themes = this.getThemes();
        result = result * PRIME + ($themes == null ? 0 : $themes.hashCode());
        return result;
    }

    @java.lang.Override
    public java.lang.String toString() {
        return "Controle(id=" + this.getId() + ", libelle=" + this.getLibelle() + ", objet=" + this.getObjet() + ", url=" + this.getUrl() + ", initiativeDesGroupes=" + this.isInitiativeDesGroupes() + ", courDesComptes=" + this.isCourDesComptes() + ", programmeCreation=" + this.getProgrammeCreation() + ", programmeReport=" + this.getProgrammeReport() + ", dateCreation=" + this.getDateCreation() + ", cloture=" + this.isCloture() + ", avecRapport=" + this.isAvecRapport() + ", echeance=" + this.getEcheance() + ")";
    }
}
					
  • Une classe JPA assez touffue avec
  • getters (lazy), constructeur mini, utils et métier (noyée)
  • Consensus du monde professionel, standards (beans)

lombok

<dependency>
  <groupId>org.projectlombok</groupId>
  <artifactId>lombok</artifactId>
  <version>1.16.6</version>
  <scope>provided</scope>
</dependency>
						
  • plutôt que IDE (fragile) ou guava/JDK (intrusif)
  • plus mûr que Google Auto
  • le jar dans le CLASSPATH lors de la compilation est suffisant
  • rien au runtime

Les plus évidentes

  • @ToString
  • @EqualsAndHashCode
  • @Getter(lazy=true)
  • @Setter
  • @RequiredArgsConstructor
  • @Data
  • toString classiquement utilisé pour le debug
  • ces annotations sont documentées en ligne

Gain

  • diviser par 2 les lignes de code
  • se concentrer sur la valeur ajoutée (le différenciateur)

D'autres exemples

  • @Delegate
  • @Value
  • @Log
  • @Builder
SuperHero.builder().name("Superman").from("Krypton").alias("Clark").build();
  • déléguer l'exécution d'une partie des tâches. classique en objet. plomberie lourde
  • classe immutable sans risque d'oubli, et bien documentée
  • pas besoin de se rappeler de la ligne à écrire, et cohérence des clés
  • interface fluide d'instanciation (cf expressivité du code)
  • 16 stables, 6 expérimentaux (officiels), 30 playground (externes) ou perso

Points d'attention

  • prise en main
  • code non maîtrisé
  • intégration aux outils
  • debug
  • mise en place échelonnée pour comprendre les impacts (delombok si problème)
  • StackOverflow avec @toString par ex.
  • penser au exclude dans ce cas-là
  • si pas Netbeans : installation (via lombok.jar ou plugin)
  • debug difficile : rester sur les classiques
  • conclusion : permet d'exprimer l'intention pas l'implémentation

Travaux Pratiques : nettoyez vos projets

Lister le maximum de tâches permettant de diminuer la quantité de code sur votre projet

fonctionnalités inutiles code contournant une convention code mal maîtrisé code dupliqué code inutile code à externaliser code "automatique"
  • N'hésitez pas à poser vos questions
Dans quelque domaine que ce soit, la perfection est enfin atteinte non pas lorsqu'il n'y a plus rien à ajouter mais lorsqu'il n'y a plus rien à enlever.  — Antoine de Saint-Exupéry

Et les optimisations de performances ?

D'autres s'en préoccupent mieux que vous !

La modération doit être le premier soin de l'homme.  — De la Voie et de la Vertu, Lao-tseu
  function(a,b,c,d){d+=c;return[d<0|a&b<<
  d?a=parseInt((a|b<<c).toString(d=32,b=new Date&2|1)
  .replace('v',''),d):a|b<<d,a,b,d]}
              
 —140byt.es
  • Pour tétris, doc énorme
  • Il faut garder l'expressivité
  • Le plus important est de réfléchir à ce qui est fait. Savoir pourquoi on fait des choix
  • Pas possible de faire tous les bons choix, mais y réfléchir vous empêche de les refaire ⇒ meilleurs plus vite

2 ‐ #WTF

  • Principle of least astonishment (via Java Hall Of Shame)
  • Incompréhension par un autre dev (ou soi-même après 6 mois)
  • Toujours un peu mais doit être limité

KISS

Keep It Simple, Stupid

La simplicité est la sophistication suprême  — Léonard de Vinci

Code expressif / lisible

  Calendar c = new GregorianCalendar();
  Calendar b = j.bd();
  if (b != null &&
    c.get(Calendar.DAY_OF_YEAR) ==
    b.get(Calendar.DAY_OF_YEAR)) {
      return new BD();
  }
            
  if (joe.hasBirthday()) {
      BandeDessinee gift = new BandeDessinee("Lincoln");
      return gift;
  }
            
  when(joe.hasBirthday()).thenSend(gift);
            
The ratio of time spent reading (code) versus writing is well over 10 to 1 ... (therefore) making it easy to read makes it easier to write.  — Robert C. Martin in Clean Code

3 ‐ Votre code est votre documentation

Dans le code

  • javadoc
  • TODO
  • contournements / hacks

Gestionnaire de tickets

  • documentation automatique
  • historique des problèmes, incidents, évolutions fonctionnelles, choix d'architecture
  • Pas automatique : créer et bien remplir (penser à soi dans 2 ans). Trouver la maille.
  • Automatisme permet des vérif. : obligation d'un ticket pour accepter commit

Gestionnaire de versions

$ git log --pretty=oneline --abbrev-commit

5b5be75 getIdClient
458ce6b corrections
59ba74e getIdClient
5292c95 corrections
851b58e merge
4020e4f Supprimer et Ajouter avec corrections
c89ad42 Supprimer et Ajouter avec corrections
231195a crebas.sql représente maintenant l'état de la
        dernière version de la base
31c15dd #1996 - léger espace autour d'une image dans un
        datatable-ui
e0b5cf9 suppression de l'onglet contexte (élément mis
        dans l'onglet détails)

Pour le reste

Simple – Court – Facile

  • Wiki
  • fichier README
  • Wiki
    • entreprise ou forge logicielle (github)
    • Ouvert à tous (idéal) – imédiat – historique
  • Readme
    • github, logiciels unix, paris-web
    • Lu car court et adapté : viser le lecteur cible
    • livré avec le code et lisible (markdown)
  • Si nécessaire, doublon avec word

Qu'y trouve-t-on ?

Point d'entrée unique

  • Description haut niveau
  • Contacts
  • Installation et Environnements
  • Changelog
  • Catchall
  • Adapté à cible (dev, utilisateur, manager)
  • Manuel, Prise en main, ...
  • Lien vers systèmes et docs
  • Fonctionnel et Technique
  • Plutôt contacts permanents (mail d'équipe)
  • Changelog et historique des choix techniques si reformulation pour utilisateurs
  • Roadmap, Métaphore, FAQ, Plan de reprise

Travaux Pratiques : Etat des lieux

On change les mainteneurs sur les projets !

Installez le projet en local Faites une modification mineure Testez, Packagez, Diffusez Notez vos conseils d'amélioration
  • Tester en automatique ou non
  • Idée de modification : ajouter NEW au début du sujet d'un nouveau message
  • Bilan : 1) Où est le source ? 2) Comment lancer ?

4 ‐ Prenez vos responsabilités

Théorie des fenêtres brisées

 

  • Où va être fait le prochain tag ?
  • Ce n'est pas bien grave de casser une fenêtre de plus
  • New York 80s (graffitis dans métro) puis police
  • En projet, ne pas laisser petits pb pour éviter les gros : traiter au fur et à mesure
  • NY est votre code, les devs sont les flics
  • Si code legacy, choisir stratégie (utile+facile en 1er)

Amélioration Continue

Compilateur

Note : Foo() in interface FooBase has been deprecated.
Note : Foo.java uses unchecked or unsafe operations.
            
javac -nowarn
javac -Xlint -Werror
            

IDE

  • Reprend les infos du compilateur. En rajoute : imports
  • Possibilité de plugins en plus
  • Souvent code couleur (en haut à droite)

Logs

  • Nombreuses API
  • Maîtrise nécessaire
  • Suivez-les
  • Log4j classique mais préfère Slf4j et Logback
  • serveurs, frameworks, bibliothèques, applicatif
  • maîtriser les API de logs configurer chaque env
  • classiquement, en production, warnings ou error
  • en dev, parfois en info pour anticiper les pb
  • savoir comment passer en debug
  • en gros, pas géré dans vos code

Plateforme Qualité

  • Colonne de droite (avec LCOM4) puis duplications
  • En gris: commentaires (sauf API), Classes/Méthodes
  • Permet aussi la revue de code
  • feedback non automatisés
  • cher mais sans pareil pour maintenabilité
  • anticipe le risque de changement humain
  • comme de faire tomber les serveurs au hasard

Résolution d'un problème

Analyser (Lire, Chercher, En parler) Corriger OU repousser OU ne pas traiter Cacher le message Documenter
  • facile car toujours explication
  • Vraiment lire logs et recherches
  • Chercher ses notes, recherche, forums, code source ?
  • En parler canard plastique, collègue, expert
  • repousser avec TODO ou ticket
  • pas garder le message pour voir les nouveaux : @SuppressWarning ou config de la source
  • ne pas désactiver les futurs messages
  • doc critique si pas capable de cacher le msg

Honnêteté

  • Mettez-vous à la place d'un autre
  • 2 termes possibles mais pas juste fonction de l'auteur

Remaniement

Changer la structure sans changer le fonctionnel

  • Objectif : maintenabilité améliorée, coût du changement plus faible
    • lisibilité
    • nettoyages (direct si suppr fonction)
    • architecture logicielle/OO
  • Vigilance pour ce qui peut être un code smell
  • Les tests sont quasi-nécessaires

Tests automatisés

  • tests manuels rajoutent environ 30% * livraison
  • bug est moins cher si détecté tôt (exponentiel)
  • Evite pire bug pdv user : régressions
  • pas de catégorisation officielle (jeunesse)
  • aussi Canary Tests : vérif env
  • parfois Acceptation quand test écrit par utilisateur
  • Doit être maintenable lui aussi

Test Driven Development

  • code applicatif doit résoudre un test en erreur
  • aide à ne coder que le nécessaire fonctionnel
  • naturellement 100% de couverture (non-régression)
  • peut aider à faire émerger design
  • très bonne documentation (ex d'utilisation)
  • attention aux tests non pertinents

Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live.  — John F. Woods

Environnements

  • peut-être pas trop vu en stages
  • local : tout devrait être testable (dev sous linux ?)
  • décrire en sens historique des développements
  • nombre d'utilisateurs. De + en + la vraie vie (hors projet)
  • risques et criticité des bugs avec adhérence au SI
  • criticité des données de test ou réelles
  • autonomie : communiquer avant maj, pas vendredi en prod, possibilité de debug

Adapter au contexte

  • Pré-production
  • Plusieurs qualifications
  • Intégration
  • préprod : debug sur données de prod, performances
  • tests de perf presque inutiles hors identique prod
  • qualif : plusieurs versions en parallèle
  • integ : pour compilation/packaging manuel
  • Sur ce slide, équipe de gestion d'env nécessaire (cher)

Portabilité du binaire

  • Unique .jar ou .war réutilisable
  • Configuration spécifique sur l'environnement
Limite les risques en prod (tests pertinent) Partage plus facile (déploiement auto ?) Sécurité (mots de passe que sur env, pas au dev)
  • Variables d'environnement (voire normes exploit. figées)
  • Pointent sur fichier de conf (ou catalina.properties)
  • Se méfier de context.xml
  • Temps pour trouver une méthode vaut le coup
  • Sinon filtres maven (automatisation)

Prévisibilité du binaire

[WARNING] File encoding has not been set,
 using platform encoding UTF-8, i.e. build is platform dependent!
<properties>
    <maven.compiler.source>1.6</maven.compiler.source>
    <maven.compiler.target>${maven.compiler.source}</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    (...)
</properties>
						

Travaux Pratiques : binaire portable

  • Lister les parties non portables dans votre code source
  • Séparer binaire et configuration

Aperçu des méthodes pour externaliser : variables d'environnements, fichiers à un endroit fixe, ...

tests nécessitant une BD ?

platform-dépendant

Usine de développement

  • Quel IDE utilisez-vous et pourquoi ?
  • Importance de maîtriser ses outils + remise en cause
  • Possible revue de code (avant commit ou après)
  • Google sur code critique : nb de revues minimum pour sortie de quarantaine
  • Evoquer le déploiement continu (plus de 10/j à Flickr, 35/j à Etsy, 1000/j à Netflix)

Indicateur de maintenabilité ?

  • 100% partout louche (très simple ou triche)
    • /* COMMENT. */ (vu en externalisation)
    • variable privée non utilisée (OK Lombok)
    • Extension de bibliothèques
  • Note moins utile qu'évolution (cf RATP maintenance)
  • A quoi pensez-vous qu'il correspond ?

Dette Technique

  • Intérêtsp doivent être payés
  • Couts exponentiels
  • Expression d'un choix

Travaux Pratiques : Stratégie

  • SQALE : par fichier, dashboard global, ...
  • Priorités doivent être en fonction de leur coûts et criticité
  • Ne pas oublier : couverture des tests, documentation, ...

Pour aller plus loin

  • participer à des projets Open Source
  • penser à s'abonner aux RSS/twitter/...
  • l'anglais "technique" est presqu'indispensable
  • aussi 97 things every software architect should know
  • qualités: curiosité, remise en cause, réaliste, polyvalent
  • peur OK (mieux de savoir). Diversité = intérêt

Software Craftmanship

apprentissage, mentoring, discipline, passion

Crédits

  • VOUS : c'est en discutant de ses erreurs que l'on progresse, en info comme ailleurs
  • je leur paie une bière non virtuelle
  • dans le même esprit de partage, partagé sur github
Any fool can write code that a computer can understand. Good programmers write code that humans can understand.  — Martin Fowler

dans Refactoring: Improving the Design of Existing Code

Merci

Développement Durable Maintenabilité des applications