L’élégance du code

Aussi longtemps que je fouille dans ma mémoire, la majorité des développeurs que j’ai côtoyés avaient pour objectifs de produire du code élégant.

Dans cet article, je vais définir du code élégant comme celui qui valide au moins une des conditions ci-dessous :

  • Résout une famille de problème au lieu d’un
  • Emploie des patterns reconnus
  • Emploie des abstractions fréquemment

Lorsque que nous créons du code qui respectent ces propriétés, le sentiment de satisfaction nous vient, ainsi que la conviction d’être professionnel.

Cet article a pour objectif de nuancer la valeur ajouté d’un code élégant. Les 3 propriétés ci-dessus ont bien sûr des avantages indéniables. Mais ils ne devraient pas être systématiquement un objectif à atteindre.

Vision d’un développeur

J’observe chez mes collègues la tendance à vouloir rendre l’écriture du code agréable au détriment de la lisibilité. Écrire du code élégant a quelque chose de grisant, rassurant. Je pense que c’est grâce à ces sentiments que la communauté de développeurs s’accorde à dire qu’écrire du code élégant est un but de tous les jours. Toutefois, notre but est de satisfaire nos clients (le business et nos utilisateurs). Souvent et heureusement, fournir un code élégant est la bonne solution. Cependant, ça n’est pas toujours le cas. Je vous propose cher lecteur de réfléchir à d’autres propriétés que notre code doit satisfaire que j’apporte sous le nom de code maintenable :

  • Lisible
  • Extensible
  • Facilement refactorable
  • Ennuyeux

Je pense que la majorité de mes collègues comprennent déjà pourquoi la lisibilité et extensible sont importants. Mais les quelques fois dont je partageais cet avis avec mon entourage, les interrogations se portent sur le code facilement refactorable ainsi qu’ennuyeux.

D’aprés le livre Refactoring Improving the Design of Existing Code, refactorer le code est un processus fréquent, qui s’applique en début de l’écriture d’une fonctionnalité. Cette pratique s’applique lorsque le design du code ne permet pas une implémentation facile de la nouvelle fonctionnalité. Il peut s’agir d’un manque d’injection de code, remplacer un primitif par un objet, etc…

Souvent, le mot refactoring est employé pour résoudre de la dette technique présent depuis plusieurs mois et nécessite plusieurs jours de travail. L’auteur Martin Fowler appelle cette pratique la restructuration. Cette étape implique que le code ne sera pas délivrable ou compilable pendant plusieurs heures/jours. Autrement dit, une restructuration corrige une dette technique lourde. A l’inverse, le refactoring résout des dettes techniques légères, probablement apparues durant les semaines précédentes.

Maintenabilité du code

Alors que le développeur trouve de la satisfaction dans un code élégant, un business gagne de la performance avec des délivrables fréquents et qui puisse s’adapter facilement et rapidement. Trouver une solution élégante et bien plus grisant qu’une solution naïve.

// Via une portée "internal", cette classe peut être refactorer à tout moment sans avoir peur d'introduire des breaking change sur l'api publique.
internal stqtic class IEnumUtils
{
    // Moins élégant, ennuyeux, mais refactorable facilement
    internal static IEnumerable<int> GetInRangeNaive(int min, int max, IEnumerable<int> values)
    {
        return values.Where(v => v >= min && v <= max);
    }

    // Plus élégant car l'objet Range est extensible. Ajouter une propriété n'introduira pas de breaking change pour le code consommateur. Toutefois, la portée "internal" n'introduit pas de besoin d'avoir une api stable.
    internal record Range(int Min, int Max);
    internal static IEnumerable<int> GetInRangeElegant(Range range, IEnumerable<int> values)
    {
        return values.Where(v => v >= range.Min && v <= range.Max);
    }
}

Moi-même je trouve plus de satisfaction avec la deuxième version de la fonction. Toutefois je vais préférer la première car la solution naive est ici plus simple. Grâce à la portée “internal”, l’équipe de développement peut retravailler ce code à tout moment et au besoin. Dans un contexte équivalent, la solution ennuyeuse est supérieure.

Confusion entre élégance et valeur ajoutée

L’être humain peut parfois faire des raisonnements erronés. Celui que je vois le plus souvent est d’associer élégance et valeur ajoutée. Moi-même ainsi que des excellents collègues que j’ai rencontrés ont pu avoir cette association mentale.

Malheureusement, un code gratifiant n’implique pas nécessairement un code de qualité pour le business. Il est dans notre responsabilité d’appliquer les propriétés d’un code élégant quand ceci apporte une plus-value.

Est-ce que l’application a but d’apporter une couche d’anticorruption ? Écrire un moteur de mapping capable d’accepter beaucoup de cas différent via une solution élégante vaut le coup. Toutefois, une application CRUD ne devrait pas avoir de moteur de mapping aussi complexe que puissant.

Relation avec l’agilité

Je suis convaincu que notre rôle de développeur ne doit pas avoir recours à un code élégant à tout moment. Occasionnellement, selon la nature du business, du marché et de la concurrence, avoir recours à des raccourcis sont nécessaires. Ces raccourcis ne me dérangent pas tant qu’ils restent faciles à corriger à tout moment et que l’application ne croule pas sous la dette technique. De nombreux business ont prouvés leur viabilité en produisant à moindre coût une application, ou bien se sont démarqués de la concurrence avec un code répugnant. Si vous voulez en savoir plus sur l’usage de la dette technologique, je vous renvoie vers l’article Relation entre dette financière et dette technique

Les fondamentaux de l’agilité requièrent bien sûr une excellence technique, ce qui implique un niveau minimum d’élégance. Et il requiert aussi de délivrer continuellement les nouvelles versions de l’application afin de valider le développement. La validation peut se faire via un représentant du business interne à l’entreprise, ou bien avec de l’expérimentation auprès de nos utilisateurs. Je ne vois pas d’inconvénient d’employer de la dette technique légère pour valider un développement.

Sans avoir recours systématiquement à chaque sprint, voici comment appliquer un compromis entre élégance de code, livraison continue, yagni et boy scout :

flowchart TD
    A[Développement d'une nouvelle fonctionnalitée] --> E[Correction de dette technique du sprint précédent]
    E -->|Emploie une balance de code de qualité et de la dette technique| B(Délivre une nouvelle version de l'application)
    B --> C{Application valide ?}
    C -->|Oui| A
    C -->|Non, usage modérée de la dette technique| B

Jusqu’ici, je n’ai parlé que de développement sans rappeler que la rédaction de test unitaires classiques fait partie du développement. Ce que je propose ici ne fonctionne que si les tests font correctement employés, et permet un refactoring aisé en conservant la confiance aux tests. Voir Comment éviter les tests avec un couplage structurel

Les principes de l’agilité

Rappelons le premier principe de l’agilité

Notre plus haute priorité est de satisfaire le client en livrant rapidement et régulièrement des fonctionnalités à grande valeur ajoutée.

le 8ᵉ

Les processus Agiles encouragent un rythme de développement soutenable. Ensemble, les commanditaires, les développeurs et les utilisateurs devraient être capables de maintenir indéfiniment un rythme constant.

et le 10ᵉ

La simplicité – c’est-à-dire l’art de minimiser la quantité de travail inutile – est essentielle.

A la fois livrer continuellement, créer du code maintenable et reconnaitre quand du travail “simple et stupide” a la plus haute valeur ajoutée sont des compétences essentielles dans notre industrie. La livraison continue permet de valider le développement au plus vite et à moindre coût. La maintenabilité rend le développement soutenable. Enfin, une solution simple, mais avec des tests automatiques et refactorable est une voie pragmatique.

De tous les principes agile, il n’y a aucun point sur la satisfaction des développeurs. Certains développeurs trouvent leurs plaisirs de travail avec du code élégant, et d’autres avec des livraisons qui satisfait les clients (business et utilisateurs). Clairement, l’agilité récompense la seconde famille de développeur.

Relation avec l’attractivité des profils

Je n’ai malheureusement pas de sources sur la proportion des développeurs qui sont motivés par la qualité du code par rapport à ceux qui soutiennent avant tout le business. J’ai cependant le sentiment que notre industrie penche significativement vers la première catégorie. Le turnover conséquent a un cout pour les entreprises, le limiter est souvent un objectif important. Alors que j’apporte durant cet article un contraste sur ce à quoi doit ressembler le code, et parce que beaucoup de développeur sont sensibles à quoi ressemble le code, parfois apporter une élégance inutile peut être bénéfique pour l’entreprise. Garder les équipes motivées doit être un objectif du management.

Que ce soit sur le style du code, les préférences, la sensibilité sur ce qui est important diffère d’un individu à un autre, la source de motivation est diverse. Tout à fait en dehors de l’agilité, d’un code base froidement efficace, et d’un business sans communication humaine, accepter occasionnellement du code sous-performant (du point de vue business) a un impact positif sur l’engagement des développeurs.

Je me souviens de 2 développeurs sur-expérimentés pour leur travail demandé. Les 2 développeurs avaient toute la liberté de briller sur des projets plus ambitieux et plus complexe. Les 2 amis m’ont répondu que la confiance que le management ont en eux, et sur la liberté de choix des technologies les gardait motivés. L’organisation a probablement accepté des technologies qui sont stratégiquement risquées, car trop diversifiées. En contrepartie, ces 2 profils exceptionnels ont collaboré de nombreuses années avec l’organisation.

Conclusion

Je suis convaincu qu’être capable de créer du code élégant et un outil indispensable au bon développeur. Mais qu’il est une erreur de vouloir créer du code élégant à toutes les occasions. Il vaut mieux pour nos clients d’être avant tout capable de créer du code ennuyeux, explicite, lisible et refactorable. Comme tout outil, il doit être utilisé dans la bonne situation.