12 mai 2021

Choix du langage de programmation pour un projet: vraiment important ?

Source infinie de trolls, les débats sur les langages de programmation sont vieux comme le monde. Mais est ce que le choix du langage est vraiment important ? D’ailleurs, comment choisir ?

Il existe probablement des centaines de langage de programmations, certains très populaires, d’autres non. Il peut être difficile de faire le tri (et donc de faire des choix) à cause de cela. Dans cet article, je décrirai comment je classe les langages, des critères importants selon moi, de comment je fais mes choix.

En effet, c’est d’abord le type de projet qui va vous permettre de faire un premier filtre sur les langages à utiliser.

Catégogies de langages

Langages spécialisés

La première catégorie de langage que je souhaite aborder est celle des langages spécialisés. Dans certains domaines, vous n’avez finalement pas ou peu le choix du langage. Ce sont souvent des langages utilisés dans des domaines bien précis.

VHDL, que j’ai eu l’occasion de pratiquer pendant mes études, rentre par exemple dans cette catégorie. Il fait partie de ces quelques langages de niches permettant de décrire du hardware. C’est d’ailleurs un langage super fun, j’ai parfois presque envie d’acheter un peu de matériel pour rejouer avec.

Bref, ces langages one une niche où la question du choix se pose moins qu’ailleurs.

Gargage collector ou non ?

Si on veut comparer les langages "grands publics", un premier critère important est la présence ou non d’un garbage collector. Rappelez vous, le gargage collector (GC) est un composant vous permettant de ne pas gérer l’allocation et la libération de la mémoire de vos programmes. Cela simplifie grandement les programmes (on y reviendra), mais a un coût plus ou moins important en consommation mémoire, et en performance (notamment sur la latence de votre programme par exemple).

Certains programmes très spécifiques (kernel, temps réel…​) ne peuvent pas se permettre un garbage collector. On compare généralement trois langages lorsqu’on parle de langages sans GC: C, C++, et Rust.

C est le langage historique. Présent partout (comme dans le kernel Linux par exemple), on est pas prêt de pouvoir se passer complètement de lui. De même, C++ est un langage populaire qu’on retrouve dans de nombreux programmes.

Le soucis de ces deux derniers langages est qu’ils demandent une grande rigueur pour le développeur: en effet, gérer la mémoire manuellement est difficile, et des bugs peuvent rapidement apparaître. Cette catégorie de bug est assez dangereuse car elle peut permettre à un attaquant de prendre le contrôle de la machine par exemple.

Rust a été inventé pour résoudre ce problème: avoir un langage sans garbage collector mais qui évite à la compilation les bugs de gestion de mémoire. Rust n’apporte pas que cela (on parlera de typage et d’écosystème plus tard), mais c’est déjà un bon point pour lui.

C’est un peu hors du scope de cet article mais j’ai toujours trouvé la programmation système très difficilement accessible. C’est plein de subtilités et de bonnes pratiques très difficiles à assimiler (et si les gens d’OpenSSL arrivent à introduire des bugs en lien avec la mémoire, on sait direct que ce sera le cas pour nous aussi), plein d’incantations obscures à base de préprocesseurs, de processus de build incompréhensibles à moins d’avoir un doctorat en Makefile et en installation de dépendances…​ Bref, quoi qu’on pense de Rust, il permet au moins de rendre le truc accessible pour le commun des mortels que nous sommes.

Donc, GC ou pas ?

Je pense que 97 % des programmes (chiffre sorti de mon chapeau) peuvent tolérer un garbage collector. L’intêret du garbage collector est multiple:

  • Facilité de développement: Rust apporte par exemple une lourdeur énorme (mais justifiée vu ses objectifs) pour se passer de GC ET être safe. J’aime le code simple et concis, et j’aime mon confort de développement.

  • La majorité des applications peuvent tolérer un GC: les garbage collectors d’aujourd’hui sont très performants. On a récemment beaucoup parlé de ZGC sur la machine virtuelle Java (JVM) récemment par exemple. Demandez vous si c’est grave si parfois, quelques requêtes mettent quelques dizaines de millisecondes de plus à s’exécuter parce que vous avez le GC qui tourne. Pour la majorité des applications, c’est négligeable.

Bien sûr, gérer des programmes consommant d’énormes quantités de RAM (centaines de GB, voir TB) est un challenge, mais on est ici sur des besoins très particuliers et on voit que des solutions comme ZGC peuvent convenir pour ces besoins. Et de toute façon, GC ou pas, je pense que coder ce genre d’applications est toujours difficile. Peut être que la productivité amenée par l’utilisation d’un GC est plus intéressante ?

TL:DR: je ne vois aucun intêret à utiliser par exemple Rust pour faire des applications web standards. Je préfère un langage de plus haut niveau qui me permettra d’être beaucoup plus productif et avec selon moi un code plus clair.

On aura toujours quelques trolls, connus ou non, qui affirmeront que le VRAI code et les VRAIS langages sont des langages sans GC, je préfère ignorer ce genre de remarques.

Typage

Un autre grand débat est le typage statique vs le typage dynamique. Comparons le même code écrit en Python et en Rust:

Les variables dans un programme ont généralement un type (string, int, double, ou des types plus complexes). Ecrivons par exemple en Python et en Rust la même fonction permettant d’additionner deux entiers.

Python

#!/usr/bin/env python

def sum(a, b):
    return a + b

print(sum(1, 1))
print(sum(1, "hello"))

Ce code Python peut être exécuté. Cela donnera:

python main.py
2
Traceback (most recent call last):
  File "main.py", line 7, in <module>
    print(sum(1, "hello"))
  File "main.py", line 4, in sum
    return a + b
TypeError: unsupported operand type(s) for +: 'int' and 'str'

Rust

Ecrivons la même fonction en Rust

pub fn sum(a: i32, b: i32) -> i32 {
    a + b
}

fn main() {
    println!("{}", sum(1, 1));
    println!("{}", sum(1, "hello"));
}

On remarque déjà que contrairement à Python, nous devons déclarer les types de nos variables pour notre function sum. On spécifie ici que la fonction prend deux paramètres de type i32 (un entier), et returne également un i32.

En Rust, ce code ne compile même pas:

error[E0308]: mismatched types
 --> src/main.rs:8:27
  |
8 |     println!("{}", sum(1, "hello"));
  |                           ^^^^^^^ expected `i32`, found `&str`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0308`.
error: could not compile `example`

To learn more, run the command again with --verbose.

Il est donc impossible d’exécuter ce code. Le compilateur a en effet détecté mon erreur (passer une chaine de caractère à ma fonction) et me l’indique immédiatement.

Le premier intérêt des types est donc de détecter à la compilation un certain nombre d’erreurs. Ils permettent aussi de structurer son programme. Bien sûr, il existe aussi des outils et techniques pour structurer un programme dans un langage dynamiquement typé (nous y reviendrons), mais il existe de nombreux mécanismes (par exemple en Rust les Enum, Option, Result, Generic, Trait…​) pour encoder certaines propriétés de son programmes qui seront ensuite vérifiées à la compilation.

Le typage statique permet également parfois de meilleures performances grâce au compilateur.

On pourrait donc se dire mais les langages de programmations fortement typés sont forcément supérieurs ? Je pense que ce n’est pas si simple. Déjà, il y a d’autres critères de choix (les autres catégories de cet article), mais les langages dynamiques sont aussi très intéressants.

Le typage dynamique a aussi des avantages

Mon langage de prédilection est Clojure, un langage fonctionnel et dynamique sur la JVM. Je l’utilise depuis fin 2014, et je travaille depuis 3 ans avec professionnellement chez Exoscale.

J’ai eu l’occasion de développer des programmes complexes avec: piloter des load balancers, orchestrer des control planes Kubernetes, gérer des groupes de machines virtuelles…​ Tout ça en Clojure.

Est ce que mes programmes sont moins fiables parce que codés en Clojure. Non.

Comme dit précédemment, le typage n’est pas le seul critère: la programmation fonctionnelle et l’immuabilité, l’expressivité, la simplicité, la productivité, la cohérence du langage, son écosystème…​ sont autant de sujets importants. Nous reviendrons sur ces sujets un peu plus loin.

Dans le cas de Clojure, le fait de pouvoir représenter le monde sous la forme de structures de données simples, immuables, et de pouvoir facilement ensuite intéragir sur ces données, fait sa force. Notre job c’est à 80 % d’écrire des programmes qui prennent des trucs en entrée, les traitent, les envoient à d’autres systèmes, les retournent…​ Et Clojure est parfait pour ça. De plus, des outils comme Clojure Spec peuvent aider pour valider ces données.

Il n’existe aujourd’hui aucune preuve votre projet marcherait mieux dans un langage fortement typé. Il existe également des langages comme Golang (retrouvez mon article sur le sujet ici) qui sont statiquement typés mais avec un système de typage peu expressif, ce qui cause plus de problèmes qu’autre chose.

Parlons maintenant d’expressivité.

Expressivité

Je pense que les langages dynamiquement typés (comme par exemple Clojure), comparés à des langages fortement typés, permettent:

  • De prototyper plus vite, d’arriver plus vite à un résultat

  • De produire des programmes plus courts. Ce point est important. A compréhension égale (donc, sans utiliser de fonctionnalités ésotériques pour volontairement réduire la taille d’un programme), je préfère maintenir un programme avec 1000 lignes de code que 10000. Il existe je dirai au moins un facteur de 5 à 10, voir plus par exemple entre le même programme en Java et en Clojure. C’est énorme, et ça se ressent sur la maintenance des programmes.

  • De se concentrer sur l’essentiel.

Le dernier point est également important. Un problème des langages fortement typés est de ne jamais s’arrêter à sortir de nouvelles fonctionnalités sur le système de typage. Vous aurez toujours quelques personnes ayant 3 doctorats en types qui voudront utiliser une fonctionnalité incompréhensible, et chaque nouvelle fonctionnalité rajoute une couche de complexité au langage.

Ces langages sont également sujets à l’explosion de types. Chaque petite variation de donnée doit avoir son propre types (même si les Enum, Option ou autre peuvent aider), ce qui alourdit la base de code, rajoute des fonctions de conversions entre types…​ Manipuler et étendre des types est toute une cérémonie.

J’ai ouvert dans le cadre de cet article un fichier aléatoire d’un projet en Rust par exemple:

impl<T> futures_io::AsyncBufRead for Compat<T>
where
    T: tokio::io::AsyncBufRead,
{
    fn poll_fill_buf<'a>(
        self: Pin<&'a mut Self>,
        cx: &mut Context<'_>,
    ) -> Poll<io::Result<&'a [u8]>> {
        tokio::io::AsyncBufRead::poll_fill_buf(self.project().inner, cx)
    }

    fn consume(self: Pin<&mut Self>, amt: usize) {
        tokio::io::AsyncBufRead::consume(self.project().inner, amt)
    }
}

Wtf ?

Certes, un expert Rust comprendrait cela (et en Rust les lifetimes n’arrangent rien), mais je vois cette lourdeur dans tellement de langages fortement typés: types complexes à rallonge (avec plusieurs niveaux d’imbrications: Result<Option<Foo<Bar<Baz>>>, fonctionnalités obscures ajoutées à chaque release, symboles incantatoires…​).

Dans le cas de Rust, cela peut se justifier au vu de ses objectifs. Mais si j’ai le choix, je veux un langage stable, simple, expressif. Je pense sérieusement que des programmes comme Riemann, ou bien mon fork Mirabelle sont des programmes qui n’auraient pas pu être construits simplement dans un langage fortement typé par exemple.

Performances

On a parlé de l’impact du garbage collector sur les performances. Mais même entre langages ayant un garbage collector par exemple, il existe une énorme disparité entre langages sur le sujet de la performance.

Les langages interprétés (comme Python pour sa version de base) sont généralement beaucoup plus lents que les langages compilés. Certaines plateformes, comme la JVM, sont connues pour leurs très bonnes performances (ce n’est pas un hasard si la plupart des outils Big Data tournent sur la JVM).

Mais là aussi, il faut se poser la question: est ce que la performance du langage est vraiment importante ?

La plupart des applications ont finalement peu de charges, peu de requêtes par seconde. La majorité du temps sera passée dans les I/O, et donc les performances du langage sont négligeables.

Pour un projet d’application web classique qui recevra quelques requêtes par seconde, je ne pense pas que le choix du langage ait un énorme impact. Bien sûr, sur des sites web conséquents, avec de nombreuses instances de l’application, cela aura un impact et le gain en serveur (et donc en argent) d’un langage plus performant peut se justifier. Et encore, si votre équipe est à l’aise avec le langage actuellement utilisé c’est sûrement ce qui est le plus important. A vous de gérer la balance.

Je rajouterai également que les performances brutes sont une chose, mais le fait de pouvoir facilement faire de la programmation concurrente et parallèle en est une autre. Selon les plateformes et langages c’est plus ou moins compliqué, donc à vous d’étudier la question avant de vous lancer.
Certains plateformes (comme Erlang/OTP) peuvent répondre de manière élégante à ces problématiques par exemple.

Programmation fonctionnelle, objet…​

On découpe également souvent les langages en familles: langages objets, langages fonctionnels…​ Il y en a d’autre. Ces catégories ne sont pas simples à définir car certains langages peuvent rentrer dans plusieurs catégories, ou rajoutent des fonctionnalités dans un sens ou un autre, donc je ne vais pas m’étendre sur le sujet.

Néammoins, la programmation fonctionnelle est très intéressante. Le fait de pouvoir décrire son programme comme des données immuables sur lesquelles on va appliquer des fonctions limite grandement les bugs.

En passant, programmation fonctionnelle != typage statique.

Ecosystème

L’écosystème est très important. Il y a plein de langages intéressants sur le papier mais qui ont au final un écosystème trop petit pour pouvoir être sérieusement utilisé.

Si je ne peux pas faire de mTLS, d’HTTP 2, que je ne peux pas intéragir avec des outils comme Kafka, Rabbit MQ, avec des outils cloud…​ car l’écosystème est absent, ce sera un problème.

C’est là la force de plateformes comme la JVM, Python, ou bien encore Golang. L’écosystème est énorme, il existe des librairies de qualité pour un grand nombre de besoins, et donc je sais que je ne me retrouverai pas bloqué à cause d’une manque de librairies.

Le packaging et la gestion des dépendances est aussi important. Avoir des outils comme cargo en Rust, maven en Java ou lein en Clojure pour gérer proprement ses dépendances et ses builds est pour moi obligatoire.
l’outillage externes (linter par exemple) est également un gros plus (sur ça Golang est très fort par exemple).

bref, l’écosystème est vraiment un point à ne pas négliger.

Préférences personnelles

Enfin, je pense qu’on a tous le cerveau branché différement, et donc que l’on va tomber sous le charme de certains langages pour des critères subjectifs.

Dans mon cas, j’espère par exemple ne jamais avoir à retravailler en Java "entreprise". Attention, je trouve le langage Java largement utilisable et très intéressant, mais je pense que la façon de développer en Java aujourd’hui au niveau des frameworks utilisés est une aberration. J’ai d’ailleurs quelques articles sur le sujet ici et ici.

Et généralement, utiliser le langage que l’on connait reste le plus intéressant.

Conclusion

Une chose importante à comprendre est que le choix du langage est d’abord en lien avec le type de projet. Comme expliqué avant, si vous ne pouvez pas tolérer un GC, vos choix sont limités. Si vous voulez vous inscrire dans un écosystème précis (Kubernetes par exemple), les choix sont aussi limités.

Mais souvent, plusieurs langages peuvent correspondre au besoin. Dans ce ca là, à vous de trancher. Mais je pense qu’au final il n’existe pas vraiment de mauvais langages dans les choix "classiques", plus des mauvaises utilisations.

Vous êtes maintenant libre de troller dans les commentaires.

Tags: programming

Add a comment








If you have a bug/issue with the commenting system, please send me an email (my email is in the "About" section).

Top of page