Êtes-vous sûrs de vos bonnes pratiques ?

En écrivant cet article, je voulais aborder les « bonnes pratiques » sous un angle un peu différent de l’habitude.

Les bonnes pratiques sont bonnes par essence, non ? Oui, évidemment… Sauf que tout le monde ne s’accorde pas sur la définition de ce qu’est ou non une bonne pratique. Et malheureusement, comme souvent, chacun pense que sa définition est la bonne.

Il faut donc nuancer les choses. Comme toujours, je pense qu’il faut être pragmatique ; la réalité de notre travail ne peut pas s’exprimer dans les extrêmes. J’ai trop vu des gens qui assènent leurs vérités de manière absolue. Eux savent comment vous devez travailler. Ils édictent les codes de conduite qui vont déterminer si vous travaillez “comme il faut” ou non.

Et pourtant… Après plus de 20 ans de développement informatique professionnel, j’ai pu voir que :

  • Les bonnes pratiques suivent des modes. Ce qui était bien vu hier ne l’est plus aujourd’hui… mais le sera peut-être de nouveau demain.
  • Chaque langage de programmation, chaque plate-forme, chaque framework, chaque « courant de pensée », édicte des bonnes pratiques qui ne vont pas toutes dans le même sens.
  • Les fanatiques de telle ou telle pratique sont comme les fanatiques de tel ou tel langage de programmation (ou autre techno). Cela devient leur nouvelle religion et ils essayent d’y convertir tout le monde, au lieu d’y voir juste un outil de plus à mettre dans leur boîte à outils.

Mais alors, où est-ce que je veux en venir ? Avant d’apporter mes conclusions, je vais prendre quelques exemples qui illustrent mon propos.

Au programme :

L’indentation dans le code source

Vaste sujet, sur lequel les informaticiens ont la capacité de se déchirer depuis l’époque où l’on a arrêté de programmer sur des fiches perforées.

La série télé Silicon Valley a même consacré un épisode sur ce sujet :
https://www.youtube.com/watch?v=V7PLxL8jIl8

Pendant longtemps, il existait deux principaux ensembles de normes : la norme BSD (aussi utilisée dans le kernel Linux) et la norme GNU. Pour faire simple, la norme GNU utilise des espaces (habituellement 4) pour indenter les blocs, avec les accolades sur leur propre ligne, et les blocs sont séparés visuellement les uns des autres de manière verticale. De l’autre côté, la norme BSD utilise des tabulations, avec les accolades sur la même ligne que l’instruction ; les tabulations faisant 8 espaces de large par défaut, les blocs sont visuellement séparés de manière horizontale.

Exemple de norme GNU (c’est pas tout à fait ça, mais je schématise) :

int i = some_function();
for (int j = 0; j < 10; ++j)
{
    int k = other_function(i, j);
    if (k < last_function(j))
    {
        k *= i + j;
        printf("some text: %d\n", k);
    }
    else
    {
        printf("some other text: %d\n", j);
    }
}

Exemple de norme BSD :

int i = some_function();
for (int j = 0; j < 10; ++j) {
        int k = other_function(i, j);
        if (k < last_function(j)) {
                k *= i + j;
                printf("some text: %d\n", k);
        } else {
                printf("some other text: %d\n", j);
        }
}

(Regardez ce que Linus Torvalds dit de la norme GNU. Oui, c’est dans le code source de Linux depuis au moins 20 ans…)

Et pendant très longtemps, dans tous les langages impératifs avec une syntaxe plus ou moins semblable à celle du C, on devait choisir entre les deux. Et ça semble plutôt logique de vouloir bien séparer les blocs, soit verticalement, soit horizontalement. Chaque entreprise édictait sa norme de codage ; certaines écoles d’ingénieurs sont aussi connues pour leurs normes rigides.

Mais depuis quelques années, on a vu apparaître une nouvelle norme dans le monde Javascript ; on indente avec seulement deux espaces, tout en gardant les accolades sur la même ligne que l’instruction :

i = some_function();
for (j = 0; j < 10; ++j) {
  k = other_function(i, j);
  if (k < last_function(j)) {
    k *= i + j;
    console.log("some text: " + k);
  } else {
    console.log("some other text: " + j);
  }
}

Quoi ? Seulement deux espaces ? Mais c’est illisible !
Manifestement, il y a beaucoup de gens que ça ne dérange absolument pas. Comme quoi, tout n’est que question de modes et d’habitudes.

L’instruction GOTO

On est bien d’accord, l’instruction goto ne devrait jamais être utilisée. Jamais ! Dans aucun langage de programmation ! C’est la marque d’un mauvais développement. On le sait depuis que Dijkstra a écrit son fameux “A Case against the GO TO Statement” en 1968.

Sauf que… Dijkstra avait écrit ça à une époque où la programmation structurée n’existait pas encore, ou était encore bancale (cf. cette étude de Donald Knuth). C’était l’époque des premières versions des langages BASIC et FORTRAN. Les goto étaient utilisés pour aller d’un endroit à l’autre du programme, en l’absence de fonctions. Clairement, ça ne pouvait conduire qu’à du code-spaghetti.

Mais est-ce que ça veut dire que le goto ne doit jamais être utilisé ? Eh bien tout dépend. Quand il est bien utilisé, dans des langages où c’est la solution la plus adaptée au besoin, le goto reste très efficace et lisible.
Il est notamment utilisé en C, pour aller directement à la fin d’une fonction en cas d’erreur. Il est massivement utilisé de cette manière dans le code du noyau Linux.

D’ailleurs, en C comme en Lua, l’instruction goto ne peut être utilisée que pour se déplacer à l’intérieur d’une fonction. Elle ne permet pas d’aller directement à l’intérieur d’une autre fonction − ce qui serait évidemment extrêmement risqué.

Si on regarde ce faux exemple, on voit l’utilité du goto :

int do_something(const char *input) {
    int return_value = 0;
    char *str = NULL;

    // tentative d'allocation de mémoire
    if (!(str = strdup(input))
        goto error;
    // suite des traitements
    // ...
    if (do_something_else(str) != IT_IS_OK)
        goto error;
    // ...
    // fin des traitements de la fonction
    goto cleanup;

error:
    return_value = 1;
cleanup:
    if (str)
        free(str);
    return (return_value);
}

Ben oui, c’est simple et lisible. Il n’y a strictement aucun problème avec ce code.

On peut remarquer que les goto peuvent aussi servir à sortir de boucles imbriquées (pour les langages qui ne permettent pas de spécifier le nombre de boucles dont on veut sortir avec l’instruction break).

Enfin, les goto sont aussi utilisés pour faire des optimisations bas-niveau, qui permettent des gains de performance importants (15-20% d’amélioration de l’interpréteur Python grâce aux « computed goto », voir cet article). Mais là on est d’accord, ça sort un peu du développement applicatif classique.

Le pattern singleton

Ah, le singleton ! Si vous ne connaissez pas, c’est un design pattern qui sert − en POO − à faire des objets qui ne sont instanciables qu’une seule fois ; le constructeur est privé et il faut passer par une méthode statique pour créer une instance ; et chaque appel à cette méthode retournera toujours la même instance.

Le singleton fait partie du fameux Gang of Four. À une époque, c’était vu comme une manière moderne et propre de développer. Il y avait des livres et des sites web qui vous expliquaient à quel point vous étiez un mauvais développeur si vous n’utilisiez pas les super-pouvoirs de ce merveilleux pattern. J’ai même vu des projets être entièrement réécrits pour se baser sur le singleton.

Sauf que depuis, le singleton est tombé en disgrâce et est maintenant considéré comme un anti-pattern. La raison est simple : le singleton cache les dépendances dans le code, ce qui le rend plus difficile à comprendre et à faire évoluer, mais surtout ça rend le code beaucoup plus difficile à tester. Et comme les tests unitaires sont clairement devenus une bonne pratique, il n’est plus envisageable de créer de tels trous noirs impossibles à tester dans nos applications.

Surtout qu’avec une bonne injection de dépendances, on peut aisément se passer du singleton.

Le nommage des exceptions

Souvent, dans un projet ayant un peu d’envergure, on a besoin de lever des exceptions avec une granularité fine, donc on crée nos propres objets qui dérivent de l’exception de base. Et s’il y a bien une pratique répandue, c’est d’ajouter le suffixe “Exception” à la fin du nom de l’objet. Après tout, cela semble logique et naturel : une erreur de base de données lèvera une DatabaseException ; une erreur d’entrée-sortie lèvera une IOException ; une erreur de droits d’accès lèvera une UnauthorizedUserException. Vous voyez l’idée.

C’est tellement répandu qu’il semble inutile de remettre ça en question, n’est-ce pas ? Et pourtant, il y a deux arguments pour ne pas mettre ce suffixe.

Comme l’explique cet article du site Developer 2.0, certains langages comme le C++ permettent d’utiliser n’importe quelle valeur pour lever une exception ; il paraît alors nécessaire de dire clairement qu’un objet est prévu spécialement pour cet usage. Par contre, dans des langages de plus haut niveau, il n’est possible d’utiliser que des objets de type Exception. Donc le suffixe n’apporte aucune information pertinente, puisqu’on sait qu’un throw ou un catch ne manipulera qu’une exception et rien d’autre.

L’autre argument, c’est de voir que l’utilisation de namespaces rend le suffixe redondant.
Si on prend l’exemple du framework PHP Symfony, les exceptions sont proprement rangées dans des namespaces assez détaillés. La classe Symfony\Component\HttpFoundation\File\Exception\PartialFileException est assez explicite sur son utilité. Mais est-ce que ça changerait vraiment les choses si elle s’appelait Symfony\Component\HttpFoundation\File\Exception\PartialFile (sans le suffixe) ? De toute façon, on l’utilisera la plupart du temps avec un alias, et tout reste très lisible :

use Symfony\Component\HttpFoundation\File\Exception\PartialFile
    as PartialFileException;
// ...
throw new PartialFileException();

Donc soyons clairs, si ce genre de convention vous paraît absolument évidente, réfléchissez-y à deux fois avant d’en faire une bonne pratique.

Les ORM, les query builders et le pattern Active Record

Il y a beaucoup à dire sur la manière dont on accède aux bases de données.

Concernant les ORM, leur but est de manipuler les données directement dans le code comme s’il s’agissait d’objets, sans avoir besoin d’écrire du SQL. Au passage, cela permet une plus grande abstraction vis-à-vis de la base de données utilisée, permettant de développer sur une base différente de celle qui tourne en production, par exemple.

Sauf qu’il faut savoir que, par défaut, les ORM sont lents. Et pour qu’ils fonctionnent correctement, il faut leur enseigner comment est défini le schéma de la base, à grand renfort de fichiers de configuration ou de code qui décrit les tables et leurs relations.

Ce que j’ai du mal à comprendre, c’est le raisonnement. On m’a déjà dit qu’un développeur doit être un expert en développement orienté objet, pas en SQL ou en modélisation de bases de données.
Mais à côté de ça, il est courant de dire que les développeurs modernes doivent connaître un certain nombre de langages différents.

Est-ce que le SQL serait particulier au point de mériter d’être ignoré ? Au contraire, j’ai tendance à penser qu’un développeur web (ou quelqu’un qui travaille dans le domaine de la « data ») doit obligatoirement avoir des connaissances solides en SQL, et être capable de faire des jointures (en connaissant les différences entre INNER/OUTER et LEFT/RIGHT). Impossible de bien faire son travail si on ne comprend rien au SQL.

Il est intéressant de voir que l’un des créateurs de Propel (un ORM majeur en PHP) dit qu’il n’utilise plus d’ORM aujourd’hui.

Les « query builders » sont une manière de créer des requêtes via du code uniquement. Ils sont censés cacher la complexité d’écriture des requêtes SQL, et peuvent − pour certains − être utilisés avec ou sans ORM.

Mais est-ce que le code suivant (query builder du framework Laravel) :

DB::table('Users')
    ->select('Groups.id')
    ->join('Groups', 'Groups.userId', 'Users.id')
    ->where('Users.created_at', '>', $date1)
    ->where('Groups.created_at' '<', $date2)
    ->get();

Est vraiment plus simple que la requête SQL correspondante ?

DB::select("
    SELECT Groups.id
    FROM Users
        INNER JOIN Groups ON (Groups.userId = Users.id)
    WHERE Users.created_at > :date1
      AND Groups.created_at < :date2
", ['date1' => $date1, 'date2' => $date2]);

Franchement, je ne trouve pas. Et ça empire si vous ajoutez des jointures et des critères de recherches supplémentaires.

Le pattern Active Record sert à faire un mapping simple depuis un enregistrement dans une table vers un objet. Il est intéressant de regarder ce qu’en dit le livre Clean Code (traduction de mon cru) :

Les objets Active Records sont une forme particulière de DTO1. Ce sont des structures de données avec des variables publiques ; mais elles ont habituellement des méthodes de navigation comme save et find. Typiquement, ces Active Records sont des traductions directes de tables dans une base de données.

Malheureusement, on trouve souvent des développeurs qui essayent d’utiliser ces structures de données comme des objets, en y ajoutant des méthodes métier. C’est bancal parce que cela crée un hybride entre une structure de données et un objet métier.

La solution, évidemment, consiste à traiter un Active Record comme une structure de données, et de créer des objets séparés qui contiennent les règles métier et qui cachent leurs données internes (qui sont probablement juste des instances de l’Active Record).

1 : Les DTO (Data Transfer Object) sont des objets qui ne comportent que des attributs − soit publics, soit privés et accompagnés de getters et de setters.

C’est intéressant, non ? Ce livre, qui est une référence du développement moderne, dit bien que la manière dont tout le monde utilise le pattern Active Record est erronée.

Qui faut-il croire ? Où est la bonne pratique ?

Les architectures à microservices

Les architectures à microservices sont arrivées en réponse à deux problématiques bien particulières : la montée en charge et le développement collaboratif à grande échelle.

Pour la montée en charge, quand un serveur atteignait vite ses limites, il devenait nécessaire de faire de la scalabilité horizontale, en repensant les applications de manière à ce qu’elles soient répartissables et redondables sur plusieurs serveurs.
Pour le développement collaboratif, il fallait faire en sorte que des équipes de plusieurs centaines de développeurs puisse faire évoluer une application sans se marcher sur les pieds les uns les autres, et sans qu’une modification à un endroit puisse causer des bugs de régression ailleurs.

La solution a donc été de découper finement les applications, en rendant leurs composants les plus autonomes possibles les uns des autres. Ainsi, des équipes peuvent prendre en charge le développement de chaque partie séparément ; et des ressources serveur peuvent être allouées à chaque brique sans impacter les autres.

Ça marche très bien, et en fait, dans certains cas, c’est absolument obligatoire.
Le problème, c’est que − comme d’habitude − certaines personnes considèrent que si cette pratique est bonne dans certaines conditions, elle l’est dans toutes les conditions. Et le développement monolithique est alors vu comme dépassé.

C’est évidemment une erreur. Découpler une architecture en plusieurs composants distincts n’a aucun sens pour une petite application :

  • Cela prend plus de temps à développer. Dans une application monolithique, les objets se connaissent et se parlent directement, sans avoir besoin de mettre en place des couches d’abstraction et d’interfaçage.
  • C’est beaucoup plus compliqué à tester et à déboguer. Chaque composant est facile à tester de manière unitaire, bien sûr. Mais lorsque vous voulez tester tous les composants ensemble, vous verrez apparaître des bugs aléatoires, soit parce qu’il y a des interactions qui n’ont pas été anticipées, soit parce que c’est la communication entre les composants qui entraîne des bugs.
  • C’est plus gourmand en ressources. Là où une application monolithique tournera efficacement sur un petit serveur, une architecture à microservices demandera plus de puissance et coûtera beaucoup plus cher.

Je parlais déjà de tout ça dans mon article sur les architectures distribuées.

Dans le monde des startups, où la hype est souvent très forte, il est amusant de voir des architectures complexes être développées, pour ensuite être déployées en nécessitant des moyens élevés. Alors que l’essence même d’une startup, c’est de devoir se réinventer et sûrement tout réécrire deux ans plus tard. Pourquoi dépenser plus d’argent et mettre plus de temps à développer son MVP ? Avec zéro client pendant les premiers mois, l’argument de la montée en charge ne tient pas ; avec une toute petite équipe, celui du développement collaboratif à grande échelle ne tient pas non plus.

Il est intéressant de voir que la société Basecamp, connue pour le framework Ruby on Rails et pour son influence quant à l’application des méthodes agiles, glorifie l’approche du monolithe : c’est le monolithe majestueux (voir l’article The Majestic Monolith).
Ils sont même allés plus loin avec le concept de citadelle (voir l’article The Citadel) : lorsqu’une brique nécessite de vivre sa vie séparément du reste, il ne faut pas transformer toute l’architecture pour passer sur du microservice ; il faut sortir cette brique et la faire communiquer efficacement avec le reste de l’application ; elle devient alors un « avant-poste », laissant la citadelle intacte et efficace.

Je vous suggère aussi de lire un article de Martin Fowler − personnalité majeure du génie logiciel − intitulé You must be this tall to use microservices.

Donc avant de vouloir tout coder en utilisant des micro-services, posez-vous quelques questions : Est-ce vraiment une bonne pratique absolue ? En avez-vous besoin dès maintenant ? Et enfin, avez-vous les ressources nécessaires pour vous lancer dans cette voie ?

REST vs RPC

Attention, je parle ici à un niveau plus philosophique que technique. La question n’est pas de savoir si vous devez faire du SOAP, du XML-RPC, du CORBA, ou du simple HTTP. Quelle que soit la solution technique utilisée, il est toujours possible d’avoir une approche REST, à partir du moment où vous en respectez les contraintes.

Ce dont je veux parler, c’est le fait que le REST implique que vous n’utilisiez que les méthodes GET, POST, PUT et DELETE (et PATCH éventuellement). Donc des opérations CRUD de base.

Entendons-nous bien : il y a plein de cas pour lesquels REST fonctionne merveilleusement bien. Le souci, encore une fois, c’est que c’est devenu une bonne pratique qui s’est tellement généralisée que certaines personnes pensent que si vous dérogez au credo REST, ça veut dire que vous ne savez pas faire une « vraie API ».

Prenons un exemple concret. Imaginons un système de discussion.
Si vous voulez récupérer la liste des salons de discussion, le REST fonctionnera très bien. Vous vous connecterez à l’URL :
GET /api/channels

Pour récupérer les messages d’un salon (dont l’identifiant est 123) :
GET /api/channels/123/messages

Pour effacer un message du salon (identifiant 456) :
DELETE /api/channels/123/messages/456

Maintenant, pour abonner un utilisateur (identifiant 789) au salon, on pourrait imaginer faire l’appel suivant :
POST /api/channels/123/users/789

Ça fonctionne… mais en voyant ça, je ne sais pas pour vous, mais j’ai l’impression que ça ne va faire que ça : abonner l’utilisateur au salon. Je n’imagine pas un seul instant que ça va changer des statuts, que ça va envoyer des notifications par email et/ou SMS, et que sais-je encore ?

Par contre, dans mon application, il y a de grandes chances que j’ai écrit le code suivant :

// PHP
$channelsManager->subscribeUser(123, 789);
// Lua, Javascript ou Python
channelsManager.subscribeUser(123, 789)

Et là, je ne me pose pas de questions. Je sais qu’en abonnant l’utilisateur, il va se passer un certain nombre de choses.

Donc je ne me poserais pas plus de questions si mon appel à l’API était :
/api/channels/subscribeUser/123/789

Et d’ailleurs, pourquoi les appels à des API devraient fondamentalement fonctionner différemment des appels que nous faisons à l’intérieur même de notre code ? Que l’on dise à un objet local de faire quelque chose, ou qu’on le dise à un objet distant, ça ne devrait pas changer grand-chose.

Le REST restreint le vocabulaire à notre disposition. C’est un peu comme vouloir faire des objets métiers en ayant à notre disposition uniquement des requêtes SQL INSERT / SELECT / UPDATE / DELETE. On se retrouve vite coincés, ou alors il faut faire des cabrioles pour exprimer les choses, qui deviennent très verbeuses, ou au contraire pas du tout explicites. Par contre, pour du CRUD, c’est top.

Pas besoin de le préciser, mais il y a des contraintes REST qui sont du simple bon sens, comme le fait que les requêtes soient sans état. Faire du stateful sur une API, c’est se tirer une balle dans le pied à moyen terme.

Les frameworks front-end Javascript

Depuis environ 10 ans, les frameworks front-end se sont considérablement développés. Angular, React et Vue en sont les principaux représentants, mais il en existe d’autres. Ces frameworks permettent de développer des interfaces graphiques d’une manière qui s’apparente à ce qu’on pouvait faire nativement, en s’affranchissant de la logique de « pages » des sites web.

Il y a tout un tas d’applications pour lesquelles c’est non seulement souhaitable, mais absolument nécessaire. Les véritables applications web complexes ne peuvent pas continuer à être pensées comme des suites de pages.

Mais là encore, ce qui est utile dans certains cas a tendance à être vu comme incontournable dans toutes les situations.
J’ai vu des sites de contenu, pour lesquels le référencement naturel est primordial, être (re)développés avec un framework JS ; l’information devenant moins facile à trouver pour les robots des moteurs de recherche, le référencement s’est évidemment écroulé.
J’ai aussi vu des outils très simples, qui ne nécessitaient aucune interactivité, être développés en utilisant un framework JS, juste parce qu’une personne voulait monter en compétence dessus. Évidemment, le jour où cette personne a quitté l’entreprise, personne n’avait les compétences nécessaires pour reprendre ce code.

Par leur approche très différente, ces frameworks demandent des compétences spécifiques. En fait, c’est normal d’avoir besoin de ressources différentes lorsqu’il s’agit de faire un métier somme toute différent. Mais pour beaucoup d’applications, il y a moyen de faire autrement.

Quand j’ai créé l’outil Skriv, je n’avais pas le temps de me former à Angular ou React. Pourtant, je voulais proposer une interface dynamique et (surtout) rapide. J’ai donc fait en sorte que chaque page soit accessible directement, ou bien puisse être chargée en AJAX. Les chargements AJAX n’envoient pas la page entière, mais juste le corps principal (sans les headers et footer, ni les directives de chargement JS et CSS). Un tout petit bout de code Javascript se positionne sur les liens pour prendre la main et passer par le chargement AJAX.

Cette méthode fonctionne vraiment bien. L’application est rapide et fluide. On m’a plusieurs fois demandé quel framework front-end j’utilisais, car les temps de chargement sont invisibles. Et pourtant, j’ai eu quelques remarques négatives ; ne pas utiliser de framework Javascript serait un signe de logiciel de mauvaise qualité…

Récemment, j’ai découvert que mon approche était la même que celle utilisée par Basecamp. Leur outil Turbo fait exactement la même chose, et pour les mêmes raisons : ils sont très efficaces dans le développement d’application back-end, et l’important est d’offrir la bonne ergonomie aux utilisateurs.
C’était aussi l’approche de pjax, par le créateur de GitHub.

Donc si on vous bassine en disant que les frameworks front-end sont une bonne pratique incontournable, sachez prendre du recul. Et n’oubliez pas que le javascript est un écosystème parfois risible.

Les underscores pour les attributs et méthodes privés

Dans la programmation orientée objet, il y a une convention qui est utilisée depuis des dizaines et des dizaines d’années : préfixer les noms des attributs et des méthodes privés par un underscore. On a pu voir cette habitude en C++, en Java, en C# et plein d’autres.

Ces dernières années, le monde PHP semble avoir abandonné cette pratique, suivant ce qui s’est passé dans la communauté Java.
Vraiment ? Eh bien la norme PSR la déconseille, influencée par des projets comme Symfony ou Drupal. Mais d’un autre côté, Zend Framework, Code Igniter ou encore FuelPHP continuent de la conseiller. Alors faut-il se sentir honteux de continuer à utiliser cette convention ?

Je disais plus haut que les bonnes pratiques suivent des effets de mode. Cette histoire d’underscore en est la parfaite illustration. Car si certains essayent de faire croire que c’est has been, je trouve intéressant de voir que c’est au contraire au cœur de deux langages, le Python et le Dart. Le Python est un langage qui a pris énormément d’ampleur ces dernières années, et le Dart est un langage inventé par Google pour remplacer le Javascript, inspiré par le Python, et qui est utilisé dans Flutter pour concevoir des applications multi-plateformes.
Dans ces deux langages, la visibilité des méthodes et attributs n’est pas définie explicitement. Par défaut, tout est public ; mais si on met un underscore devant un élément, il devient privé (librement accessible par tous les objets du même module, mais inaccessible depuis l’extérieur du module).

Du coup, est-ce qu’on dit que ces deux langages sont complètement nuls à cause de ça ? Évidemment que non.

Le XHTML et le HTML 5

Le HTML 4.01 est apparu en 1999 et a été utilisé pendant une bonne partie des années 2000. Le W3C a formulé le XHTML 1.0 en 2000, et il constituait globalement en un reformatage du HTML 4 avec une syntaxe plus stricte, respectueuse du formalisme du XML.

Comme toujours, ce type d’évolution prend du temps, et les navigateurs ont supporté les deux syntaxes pendant très longtemps.

Pendant toutes les années 2000, on a pu voir toute une littérature expliquer pourquoi il était très important d’arrêter d’utiliser le HTML 4 et de passer au XHTML. Ceux qui ne le faisaient pas étaient considérés comme des hérétiques, des informaticiens de pacotille qui n’avaient pas compris pourquoi c’était si important d’écrire <br/> au lieu de <br>.

À la fin des années 2000, le XHTML semblait avoir enfin pris le dessus. Mais on commençait à entendre parler du HMTL 5, dont le premier working draft a été publié en 2008, et qui n’a été finalisé qu’en 2014.
Là encore, la transition a pris plusieurs années, et les navigateurs étaient capables de gérer plusieurs versions en même temps.

Ce qui est magique avec le HTML 5, c’est que d’un seul coup on devait oublier les contraintes du XML. Toutes les raisons impérieuses, pour lesquelles on nous avait convaincus de suivre le dogme XHTML, étaient bonnes à mettre à la poubelle.
Finalement, écrire <br> est suffisant, pas besoin d’écrire <br/> (enfin, jusqu’au jour où vous voulez lire du HTML avec un parseur XML ; ça marchait avec le XHTML, ça ne fonctionne plus avec le HTML 5).

Sérieusement, le HTML est la brique la plus basique servant à construire le web. En schématisant, le web s’est développé de manière un peu anarchique durant sa première décennie ; on pouvait alors comprendre le choix d’évoluer vers un formalisme plus fort. Mais un changement de cap tous les 10 ans est difficile à comprendre.
Surtout que − encore une fois − chaque étape est à chaque fois présentée comme la bonne pratique, la seule et unique envisageable…

Le découpage du code

L’organisation du code source est une science à part entière. C’est un élément déterminant de la modularité et de l’évolutivité d’un logiciel. Mais en ce domaine, il y a deux grandes visions qui s’affrontent. Ce qui est intéressant, c’est de voir que certains langages de programmation se placent d’eux-mêmes dans une catégorie ou l’autre.

Il y a des langages où vous vous organisez comme vous le souhaitez. En C ou en C++, par exemple, mais aussi en Perl et d’autres, c’est à vous de vous débrouiller pour organiser votre code comme bon vous semble. Vous pouvez écrire tout votre programme dans un seul fichier, ou vous pouvez découper en ne mettant qu’une seule fonction (ou classe) par fichier ; c’est comme vous le sentez.

Lorsque le Java est arrivé en 1995, il a rendu obligatoire la création d’une arborescence correspondant à la hiérarchie des namespaces. Cette manière de faire n’était pas nouvelle, mais là elle était rendue absolument nécessaire ; faire autrement empêche le langage de trouver votre code.
Ainsi, un objet A.B.C est contenu dans le fichier C.java, placé dans le répertoire B, lui-même placé dans le répertoire A. C’est limpide.
On peut remarquer que le PHP a décidé de coller à ce fonctionnement (par le biais du choix d’implémentation des autoloaders).

Le corollaire de cela, c’est qu’il faut créer autant de fichiers que d’objets, et on peut vite se sentir submergé, avec des dizaines d’onglets ouverts dans un IDE, parfois pour des objets qui ne font que quelques lignes (par exemple pour des objets d’exception).

De l’autre côté, on peut voir des langages comme le Python, dont l’unité de base est le module ; en vrai, un module est simplement un fichier, qui peut contenir aussi bien des fonctions que des objets. Et là encore, la gestion des namespaces est calquée sur l’arborescence de fichiers.
De cette manière, l’objet A.B.C sera écrit dans le fichier B.py, placé dans le répertoire A.

On se rend bien compte que cela pousse à mettre un certain nombre d’objets à l’intérieur d’un fichier. La logique d’un seul objet par fichier n’est pas envisageable : si on veut mettre l’objet C tout seul dans un fichier C.py, on se retrouverait avec un namespace A.B.C.C (si on reste sur le même exemple de namespace).
Il n’est donc pas rare de voir des fichiers Python contenant plusieurs dizaines d’objets, et faisant plusieurs milliers voire dizaines de milliers de lignes. Au lieu de naviguer parmi des dizaines d’onglets, on en a moins mais on doit naviguer à l’intérieur de chacun d’eux pour s’y retrouver.

Évidemment, les IDE modernes facilitent grandement la navigation, dans un cas comme dans l’autre. Mais on peut tout de même remarquer que ce qui est vu comme une bonne pratique dans un cas est vu comme une mauvaise pratique dans l’autre cas, et inversement.

Le C++ qui n’est pas du « C with classes »

Le C est un langage simple, qui propose un nombre limité de briques de base : d’un côté les données (types scalaires et structures), de l’autre les fonctions qui servent à manipuler ces données, et au milieu les pointeurs. Bien utilisées, ces briques de base sont extrêmement puissantes, mais l’expérience des développeurs est primordiale pour avoir du code propre, bien organisé, et qui ne finisse pas en plat de spaghetti.

Le C++, de son côté, est un langage qui offre énormément de fonctionnalités et de paradigmes de programmation. Chacune de ses différentes évolutions a apporté des ajouts, qui en font un langage complexe à maîtriser, mais hyperpuissant quand on y arrive.

Dans la littérature (que ce soit dans les livres ou sur internet), il est répété à l’envi que pour bien développer en C++, il faut bien le connaître et l’utiliser « comme il doit être utilisé ». Notamment, les développeurs qui connaissent bien le C sont mis en garde de ne pas continuer à écrire du code ressemblant à du C dans sa philosophie générale, mais saupoudré de quelques facilités venant du C++.
En somme, bien que le C++ soit pleinement compatible avec le C, il faudrait se taper l’intégralité de la courbe d’apprentissage avant de vouloir écrire la moindre ligne de code en C++.

Ça me semble un peu élitiste comme approche. Un développeur C pourrait vouloir se simplifier la gestion des chaînes de caractère en utilisant le type String, sans pour autant réécrire 100% de son programme. Je ne vois pas ce qu’il y a de choquant là-dedans.

Et si c’est le fait de ne pas tout regrouper dans des objets qui peut sembler choquant pour les développeurs C++ expérimentés, je ferai un parallèle avec les développeurs Python, qui sont bien plus décomplexés vis-à-vis de cette question : leurs modules contiennent des fonctions aussi bien que des objets, et cela ne stresse personne − à la condition que le code soit bien pensé, évidemment ; mais on peut toujours faire du code sale, quels que soient les garde-fous qui auront été prévus par le langage.

Le « Hype Driven Development » de manière générale

Un grand nombre des sujets que j’ai abordés recoupent d’une manière ou d’une autre le hype-driven development. Il s’agit de la surévaluation qui est donnée aux technologies récentes, et l’erreur des développeurs (souvent jeunes, mais pas uniquement) de croire qu’il faut mettre en œuvre ces technos au fur et à mesure qu’elles apparaissent, sous réserve de perdre complètement pied avec l’informatique moderne.

Je comprends l’envie d’apprendre, d’expérimenter, de monter en compétence. C’est normal quand on est informaticien ; les chefs cuisiniers testent régulièrement de nouvelles recettes, de nouveaux produits, avant de les incorporer à leurs menus.

Mais justement, il ne faut pas confondre veille technique et développement. La R&D sert à se maintenir à l’affût des choses nouvelles qui pourraient se révéler utiles dans le futur. Mais lorsqu’il faut développer une application professionnelle, on n’est pas là pour expérimenter ; on doit apporter des solutions stables, performantes et évolutives.

Conclusion

Tous ces exemples servent à illustrer le fait que les bonnes pratiques sont à double tranchant. Elles sont nécessaires et nous aident à nous améliorer dans la pratique de notre métier. Les bonnes pratiques peuvent aussi s’enrichir les unes les autres, il ne faut pas hésiter à regarder ce qui se fait chez les autres (autres langages, autres frameworks, autres équipes, autres époques).

Par contre, il faut savoir faire le tri. Une bonne pratique, il faut se l’approprier. Mais surtout, il ne faut pas prêter attention aux ayatollahs qui veulent vous imposer leur manière de voir. Peut-être ont-ils raison dans certaines circonstances. Peut-être sont-ils juste de gros cons intolérants.

Dans tous les cas, je pense avoir donné suffisamment d’exemples qui montrent que les bonnes pratiques, ça se remet en cause.

8 commentaires pour “Êtes-vous sûrs de vos bonnes pratiques ?

  1. Bonjiur, Zend, Fuel, Code Igniter, ne sont plus vraiment des framework avec lesquels on démarre un nouveau projet en 2021.

  2. Merci

    Un grand Merci !!!

    C’est l’article le plus pertinent que j’ai eu la chance de lire depuis des années et je n’exagère pas.

    « Les bonnes pratiques suivent des modes. Ce qui était bien vu hier ne l’est plus aujourd’hui » »
    Tellement vrai, sans compter la notion de « bon développeur » ou encore de « stack idéal » et j’en passe. Que du marketing made in ESN ou GAFAM pour vendre…

    Goto
    Les « goto », c’est le truc plus efficace et magique que j’ai découvert en apprenant la programmation. J’ai jamais saisi pourquoi ce n’est pas bon. Donc merci, je vais en remettre.
    Les « goto » peuvent tellement alléger le code !

    ORM
    « Les ORM sont lents » Oui très, et aussi très coûteux en maintenance / migration.
    En ce moment on upgrade nos applications, il n’y’a pas photo.
    Les codes contenant des requêtes SQL brutes sont hypers simples à migrer.
    A l’inverse les requêtes via l’ORM sont toutes à recoder entièrement.
    Niveau performance et temps de chargement notamment des tests unitaires c’est juste hallucinant.
    Si on veut changer de framework on ne touche même pas les requêtes…
    On ne le répéteras jamais assez, SQL c’est puissant, et rapide.
    Malheureusement trop développeurs ne prenne pas le temps de l’apprendre. (moi le premier, mais je m’y suis mis) Dans notre boîte on essaie au maximum de descendre vers le plus bas : requềtes SQL brutes, fonctions natives de PHP, le moins de dépendances possibles etc … à contre courant mais tellement plus stable et facile à maintenir !

    Framework
    Pareil pour les Frameworks, à part quand il faut gérer des systèmes de Session compliqués c’est souvent inutile, trop lourd pour rien (écolo) et trop lourd à maintenir à jour.
    On oublie facilement que les framework etc … sont souvent OpenSource mais surtout des « vitrines » pour vendre des prestations ou faire un CV.
    Cela permet de dire au monde : « regardez comment je sais trop bien coder ». Et parfois on peut même en profiter pour inventer des BEST PRACTICE

    BEST PRACTICE
    Y’a pas de BEST PRACTICE, c’est juste pour essayer de sortir du lot. Cela permet de faire croire qu’il y a ceux qui savent coder et les autres.
    C’est du vent, il n’y a que du code qui fonctionne et qui répond au exigences des utilisateurs, point barre.
    Quasi personne va voir ton code ou le code ton équipe,
    Même si tu le publie, 90% des utilisateurs ne vont même pas le lire
    Le code doit être avant tout utile, le reste c’est gadget.
    Beaucoup trop de gens ne publient par leurs codes car ils le trouvent moche. On s’en fou qu’il soit moche, si il est vraiment trop moche, quelqu’un le recodera en mieux, mais au moins il sera utile.
    Donc codes, comme il te plaît, on a encore cette liberté de coder comme bon nous semble.
    Pareil, tu veux publier en OpenSource et t’as pas codé les tests unitaires ? C’est pas un problème, quelqu’un les feras pour toi. (ou pas)
    Décomplexez vous de votre Code, y’a pas de normes.
    Tout est utile, même si c’est parfois codé avec le cul, au moins on rigole !

    La seule BEST PRACTICE qui tienne c’est que si tu bosses avec quelqu’un il ne devrait pas (trop) s’y perdre dedans. Si le code tourne bien, sans faille et que personne ne s’arrache les cheveux en le maintenant : Hallelujah C’est comme la folie des objets et de la POO, dans bien des cas c’est bien plus lisible avec des fonctions bien rangées dans des libs. Ou alors utiliser juste les classes pour stocker et ranger des méthodes, mais ne pas stocker de propriété dans l’objet.

    L’Autoload de Composer et le Routing en PHP…
    Cela me fait tellement rire de voir des scripts de routing et d’autoload en PHP.
    Tu veux une route vers /ma_route sans script de routage ?
    /ma_route/index.php , Fini.
    Tu veux gérer des modules ? ‘include « toto.php »’ et les namespaces , pas besoin d’autoloader.
    PHP n’a pas forcément besoin de composer.

    Le Templating et PHP
    Pareil, ceux qui utilisent un langage de templating en codant avec PHP.
    PHP c’est un lagange de templating bordel, il se suffit à lui même :
    Utilisez La syntaxe alternative ou « courte » et le Short Tag
    C’est n’importe quoi, tout est déjà intégré dans PHP, il a été conçu pour ça….
    Contrairement à python, javascript etc …

    Ajax
    Ajax, merci.
    Idem, on a viré la plupart des trucs CRUD/Json et on fait du Ajax à l’ancienne avec des minis lignes de Jquery. Cela marche du tonnerre, c’est simple et facile à comprendre.

    HTML
    « Sérieusement, le HTML est la brique la plus basique servant à construire le web. »
    On ne le répétera jamais assez…
    Le nombre de site passionnant, fait à la main qui tourne en full HTML écrit à la main !
    Qui se charge instantanément et se sauvegarde en une seule page sans perte grâce au balises « style » dans le Header.
    Oui ça aussi, c’est une soit disant mauvaise pratique… mais ça évite de devoir se trimbaler des .css quand on sauvegarde la page.
    Quand j’entends parler de ce Gemini pour libérer le web, je rage …

    Conclusion
    Je vais m’arrêter là… j’aime tellement ton post…
    On devrait l’afficher dans toutes les écoles de Dev.
    Je viens de découvrir ton Blog, mais vraiment continue.
    On a besoin d’article pertinent comme celui-là pour remotiver les troupes.
    J’aurai bien ajouté ton site à l’annuaire mais le petit Google Analytic fais tâche.

    A la prochaine.
    David kole.

  3. @MaitrePylos : Bah à en écouter certains, le PHP n’est plus un langage avec lequel on démarre un nouveau projet en 2021… 🙄

    Quelque part, ça confirme en partie mon message : à une époque, coder proprement un site web en PHP, ça voulait dire choisir entre Symfony 1, Code Igniter (qui avait une vraie doc) ou Zend Framework (en version beta).
    Ceux qui ont choisi Symfony 1 ont dû tout réécrire lorsque Symfony 2 a cassé la rétro-compatibilité pas longtemps après.
    Ceux qui ont choisi Zend Framework ou Code Igniter… ben comme tu le dis, ils passent maintenant pour des dinosaures.

    Ça ressemble à de l’obsolescence programmée, tout ça.

  4. Excellent article !

    Bravo, ca rafraîchit de lire ceci quand on voit tous les ayatollahs ^_^

  5. « Ceux qui ont choisi Zend Framework ou Code Igniter… ben comme tu le dis, ils passent maintenant pour des dinosaures. »
    Peut-être, mais mon ZF de 2009 ronronne toujours, et je peux même mettre du composer dessus, car les conventions de l’époque était quand même excellente.
    Perso en PHP, depuis les PSR, c’est quand même vachement bien.

  6. @David : Merci pour ton enthousiasme 🙂

    Tout est une question de choisir où on place le curseur. Tu es un peu plus extrême que moi.
    Par exemple, je ne pense pas que les frameworks sont inutiles (mais ils ont tendance à devenir obèses, complexes à comprendre et à utiliser, je suis d’accord).

    Le plus important, c’est de faire ses propres choix (seul ou en équipe, tout dépend des projets), et de ne pas se laisser dicter quoi que ce soit par des donneurs de leçons. Sans oublier de quand même écouter ce qu’ils ont à dire, parce qu’il y aura des choses qu’on aura choisi de garder.

  7. Petite précision : Je ne publie pas les commentaires anonymes et provenant d’un adresse email jetable.

    La personne qui a écrit un commentaire très pertinent sur le fond (mais un peu vif sur la forme) est invitée à le poster de nouveau, mais avec une adresse email réelle.
    Merci.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Notifiez-moi des commentaires à venir via email. Vous pouvez aussi vous abonner sans commenter.