Licence CC BY

La « configuration » par langage dédié (DSL), une invention de Satan

Pire que XML ou JSON ? Oui, c'est possible !

Avez-vous remarqué comme les systèmes de configurations complexes, en particulier des systèmes de build et de dépendances, sont un musée de choix douteux et pénibles à l’usage ?

Je ne parle pas de la pénibilité intrinsèque à la configuration de choses complexe : celle-ci est « normale » si le système à configurer est lui-même complexe. Non, je parle de cette surcouche de complexité apportée par l’outil, qui fait que des opérations fonctionnellement simples se retrouvent à être inutilement compliquées quand on tente de les réaliser.

Les problèmes avec les langages déclaratifs

Souvent, on trouve des systèmes de configuration complexes (de frameworks entiers par exemple), de build ou de gestion des dépendances qui utilisent des langages déclaratifs. En vrac :

  • Des properties, qui sont (très) verbeuses et historiquement ne géraient pas Unicode ;
  • Du JSON, qui est plutôt lisible mais ne gère pas les commentaires, ce qui est vite un problème majeur dans ce genre de contexte ;
  • Du YAML, qui est lisible mais pénible à écrire avec ses détails de syntaxe et sa tendance à exploser en vol à la moindre type ;
  • Du XML, qui est très carré mais ultraverbeux et très vite illisible.

Un remède pire que le mal : la configuration par langage dédié

Face à ces problématiques, certains ont imaginé que l’on pourrait écrire un langage dédié (ou DSL, abréviation de Domain-Specific Language) pour gérer ces configurations. En première approche, ça a deux énormes avantages :

  1. Ça donne des fichiers qui sont faciles à lire1 ;
  2. On peut y mettre du code arbitraire, donc faire des tâches spécifiques complexes sans avoir à « développer un plugin » ou quelque chose de ce genre.

Je pense en particulier à deux d’entre eux : Gradle, et les fichiers de configuration de Jenkins (appelés Jenkinsfiles). Les deux sont basés sur le langage Groovy.

Alors, oui, les deux points ci-dessus sont bien remplis. Constatez à quel point il est plus lisible de déclarer une dépendance comme ceci :

implementation 'fr.spacefox:confusable-homoglyphs:1.1.1'

Que comme ceci :

<dependency>
    <groupId>fr.spacefox</groupId>
    <artifactId>confusable-homoglyphs</artifactId>
    <version>1.1.1</version>
</dependency>

Surtout qu’en général, on a pas une seule dépendance.

Sauf que tout ça, c’est très bien sur de touts petits exemples pour de touts petits projets, sans particularités un peu complexes.

En pratique, ces DSL ne sont qu’un écran de fumée qui tente de faire croire que des processus complexes sont simples. Ce qui, par conception, ne peut pas fonctionner.

Les points qui posent le plus problème sont les suivants :

  • Toute la complexité est masquée derrière une syntaxe et des « outils » qui se veulent simples.
  • La partie masquée de la complexité est rendue artificiellement inaccessible, parce que les API mises à disposition sont trop partielles et n’ont pas de points d’extension :
    • Opérations possibles sur des éléments uniques mais pas sur des collections ;
    • Opérations possibles sur seulement un sous-type des possibilités ;
    • Opérations tout simplement impossibles parce que le DSL n’a pas jugé bon de les gérer ;
    • etc.
  • Même configurés à fond de leurs capacités, les meilleurs IDE n’ont pas les moyens de donner des conseils pertinents.
  • On doit donc se reposer entièrement sur la documentation, qui est souvent mauvaise.
  • La notion même de DSL rends complexe le lien entre une déclaration et le code exécuté – ce qui empêche l’exploration pour savoir ce qui va être fait exactement.
  • On peut mettre du code arbitraire ou presque, mais le debug est impossible (notamment les points d’arrêt).
  • Rien n’est typé et la syntaxe est trop libérale, on découvre toutes les typo au runtime.
  • Comme le DSL masque la complexité, plus personne ne comprends ce qui se passe réellement2. Conséquence : les plugins sont d’une qualité catastrophique et conçus pour ne gérer que le cas précis de la personne qui les a développés – or ces plugins sont indispensables.
  • Pire : les documentations pour développer (ou aider à la maintenance) des plugins sont elles-mêmes mauvaises, ce qui empire le phénomène.
  • Et j’en passe.

Bref, la configuration par langage dédié, c’est l’archétype de la fausse bonne idée : ça a l’air bien de loin, c’est bien quand on fait juste un test (ce qui permet de choisir cette technologie, si tant est qu’on ait le choix)… et en pratique c’est l’enfer dès qu’on a le malheur de sortir des petites cases prévues pour gérer une forme de « standard ».

Et c’est comme ça qu’on se retrouve à passer des heures à essayer de réaliser des choses pourtant aussi simples fonctionnellement que « s’identifier sur deux dépôts Docker différents » ou « déclencher une intégration continue au push d’un tag Git »… Avec une dédicace particulière au développeur officiel du produit qui refuse l’intégration d’une fonctionnalité ultra-demandée (avec des gens prêts à la développer) avec pour seule réponse « allez voir cette doc de 2 lignes et utilisez ce plugin pas maintenu depuis 5 ans » : Satan lui-même n’aurait pas agi autrement.


  1. J’ai bien écrit « lire » et pas « comprendre », et ce sont deux choses différentes.
  2. Jeu : demandez à un développeur (qui utilise ces outils) de vous explique le fonctionnement de base de Gradle, ou de webpack (et de ses « versions simplifiées » comme webpack-chain ou webpack-merge). Un conseil, munissez-vous d’un paquet de popcorn.


Est-ce que j’ai écrit ce billet juste pour râler parce que j’ai eu les problèmes sus-cités (entre autres) avec les DSL sus-cités ? Vous n’avez aucune preuve.

N’hésitez pas à ne pas venir expliquer en commentaire que votre langage préféré a fait un meilleur choix que les autres ce qui rends plus beau plus agréable et plus parfait que tous les autres langages, ça n’aurait aucun intérêt. Donnez plutôt vos trucs et astuces pour moins galérer avec ces outils pétés avec lesquels on est coincés, ça, ce serait utile.


Icône : Le génie du mal par Guillaume Geefs, photographie CC BY-SA 3.0 Luc Viatour retaillée.

4 commentaires

Et encore, tu parles des DSL qui ont une certaine adoption du public (bon gré mal gré) de par leur intégration à des outils communs (Gradle, Jenkins), qui sont donc à peu près documentés et qui restent, je suppose, « google-ables » en cas de pépin.

Là où les portes de l’enfer se referment pour de bon derrière soi, c’est avec les DSL « faits maison » et internes à une boîte, boîte dans laquelle l’idée d’implémenter un DSL en premier lieu a pu éclore. :diable:

Bref, la configuration par langage dédié, c’est l’archétype de la fausse bonne idée : ça a l’air bien de loin, c’est bien quand on fait juste un test (ce qui permet de choisir cette technologie, si tant est qu’on ait le choix)… et en pratique c’est l’enfer dès qu’on a le malheur de sortir des petites cases prévues pour gérer une forme de « standard ».

Autre souci auquel je pense, tu n’as pas des soucis de compatibilité (ascendante ou descendante) entre les versions ?

Avec un DSL présentant une syntaxe aussi dense que implementation 'fr.spacefox:confusable-homoglyphs:1.1.1', que se passe-t-il si une prochaine version veut supporter une nouvelle information ? Avec XML (ou autre) on ajoute les balises adéquates et libre aux versions précédentes (ou à venir) de les ignorer.

Mais je vois mal comment bénéficier d’une telle souplesse avec cet exemple :

implementation 'fr.spacefox:confusable-homoglyphs:1.1.1'

Si les informations sont dépendantes de leur emplacement par rapport aux : (je ne connais pas du tout Gradle), ça doit être pénible à faire évoluer sans tout casser au niveau de la compatibilité.

Bien-sûr, même avec XML/JSON/YAML, il y a des limites à cette souplesse.

+1 -0

Autre souci auquel je pense, tu n’as pas des soucis de compatibilité (ascendante ou descendante) entre les versions ?

Avec un DSL présentant une syntaxe aussi dense que implementation 'fr.spacefox:confusable-homoglyphs:1.1.1', que se passe-t-il si une prochaine version veut supporter une nouvelle information ? Avec XML (ou autre) on ajoute les balises adéquates et libre aux versions précédentes (ou à venir) de les ignorer.

Mais je vois mal comment bénéficier d’une telle souplesse avec cet exemple :

implementation 'fr.spacefox:confusable-homoglyphs:1.1.1'

Si les informations sont dépendantes de leur emplacement par rapport aux : (je ne connais pas du tout Gradle), ça doit être pénible à faire évoluer sans tout casser au niveau de la compatibilité.

Bien-sûr, même avec XML/JSON/YAML, il y a des limites à cette souplesse.

sgble

Dans ce cas précis, la syntaxe que tu cites est en fait un raccourci pour :

implementation group: 'fr.spacefox', name: 'confusable-homoglyphs', version: '1.1.1'

Et tu peux mettre les autres valeurs attendues, séparées par :, tant que tu les mets dans le bon ordre.

Dans le cas général, les principaux problèmes de compatibilité ne sont pas tellement dans la syntaxe mais dans les API et la matrice de compatibilité entre les outils.

Vous savez ce qui est encore pire qu’un DSL :

Un DSL qui te fait croire que tu peux utiliser des méthodes en provenance d’un vrai langage, mais en fait non. Par exemple, Jenkins te dit que tu peux utiliser des scripts Groovy, mais utilise un moteur d’exécution avec tellement de limitations que tout ce qui fait l’intérêt d’un script Groovy y devient inutilisable.

Ah, et comme si c’était pas assez, il n’y a jamais aucune erreur claire qui indique le problème, tout ce qu’on va avoir c’est des erreurs du type « Cette méthode n’existe pas » (alors qu’elle existe, a fonctionné et n’a pas été modifiée). Et seulement à l’exécution, parce que Groovy – qui lui-même est une purge sans nom, mais ça c’est l’objet d’un autre billet.

Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte