Prenons l'exemple de Python, un langage connu pour sa faible barrière à l'entrée et, par conséquent, un choix favori comme langage de programmation d'entrée. Les écoles, les universités, les centres de recherche et un grand nombre d'entreprises à travers le monde ont choisi Python précisément parce qu'il est accessible à tous, quel que soit leur niveau d'éducation ou leur formation académique (ou leur absence totale). Il est rare que l'on ait besoin de beaucoup de théorie des types ou de comprendre comment et où les choses sont stockées dans la mémoire, sur quels threads un morceau de code s'exécute, etc. De plus, Python est la porte d'entrée vers certaines des bibliothèques scientifiques et de niveau système les plus profondes. Le fait de pouvoir contrôler une telle puissance avec une seule ligne de code est un argument de poids pour que Python devienne l'un des langages de programmation les plus populaires de la planète.
Et c'est là que le bât blesse : la facilité d'exprimer les choses en code Python a un coût. Sous le capot, l'interpréteur Python est massif, et de nombreuses opérations doivent être effectuées pour qu'une seule ligne de code soit exécutée. Lorsque vous entendez quelqu'un qualifier Python de langage "lent", une grande partie de la lenteur perçue provient du nombre de décisions prises par l'interpréteur au moment de l'exécution. Mais ce n'est même pas le plus gros problème, à mon avis. La complexité de l'écosystème d'exécution de Python, ainsi que certaines décisions de conception libérales concernant la gestion des paquets, constituent un environnement très fragile, et les mises à jour conduisent souvent à des incompatibilités et à des blocages de l'exécution. Il n'est pas rare de quitter une application Python pour y revenir quelques mois plus tard et se rendre compte que l'environnement hôte a suffisamment changé pour qu'il ne soit même plus possible de démarrer l'application.
Bien sûr, il s'agit d'une simplification grossière, et même les enfants de nos jours savent que les conteneurs existent pour résoudre des problèmes de ce type. En effet, grâce à Docker et à ses semblables, il est possible de "geler" les dépendances d'une base de code Python dans le temps afin qu'elle puisse pratiquement fonctionner éternellement. Cependant, cela se fait au prix d'un déplacement de la responsabilité et de la complexité vers l'infrastructure du système d'exploitation. Ce n'est pas la fin du monde, mais ce n'est pas non plus quelque chose à sous-estimer ou à négliger.
De la facilité à la simplicité
Si nous nous attaquions aux problèmes de Python, nous obtiendrions quelque chose comme Rust - extrêmement performant mais avec une barrière à l'entrée notoirement élevée. À mon avis, Rust n'est pas facile à utiliser et, qui plus est, il n'est pas simple. Bien qu'il fasse l'objet d'un engouement total ces jours-ci, malgré 20 ans de programmation et mes premiers pas en C et C++, je ne peux pas regarder un morceau de code Rust et dire avec certitude que je comprends ce qui s'y passe.
J'ai découvert Go il y a environ cinq ans alors que je travaillais sur un système basé sur Python. Bien qu'il m'ait fallu quelques essais pour me familiariser avec la syntaxe, j'ai immédiatement adhéré à l'idée de simplicité. Go est censé être simple à comprendre pour n'importe quel membre d'une organisation - du développeur junior fraîchement sorti de l'école au responsable technique de haut niveau qui ne regarde le code qu'occasionnellement. De plus, en tant que langage simple, Go reçoit très rarement des mises à jour syntaxiques - la dernière en date est l'ajout des génériques dans la version 1.18, qui n'est intervenu qu'après une décennie de discussions sérieuses. Dans l'ensemble, que vous regardiez du code Go écrit il y a cinq jours ou cinq ans, il est pratiquement identique et devrait fonctionner.
La simplicité exige cependant de la discipline. Elle peut sembler contraignante et même quelque peu rétrograde au début. Surtout si on la compare à une expression succincte, telle qu'une liste ou une compréhension de dictionnaire en Python :
Code : | Sélectionner tout |
1 2 3 4 5 | temperatures = [ {"city": "City1", "temp": 19}, {"city": "City2", "temp": 22}, {"city": "City3", "temp": 21}, ] filtered_temps = { entry["city"]: entry["temp"] for entry in temperatures if entry["temp"] > 20 } |
Le même code en Go nécessite quelques frappes supplémentaires mais devrait idéalement être plus proche de ce que l'interpréteur Python fait sous le capot :
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | type CityTemperature struct { City string Temp float64 } // ... temperatures := []CityTemperature{ {"City1", 19}, {"City2", 22}, {"City3", 21}, } filteredTemps := make(map[string]float64) for _, ct := range temperatures { if ct.Temp > 20 { filteredTemps[ct.City] = ct.Temp } } |
Bien qu'il soit possible d'écrire un code équivalent en Python, une règle non écrite en programmation veut que si le langage offre une option plus facile (c'est-à-dire plus concise, plus élégante), les programmeurs graviteront autour de cette option. Mais la facilité est subjective, et la simplicité devrait s'appliquer de la même manière à tout le monde. La disponibilité d'alternatives pour effectuer la même action conduit à différents styles de programmation, et on peut souvent trouver plusieurs styles au sein d'une même base de code.
Go étant verbeux et "ennuyeux", il remplit naturellement une autre fonction : le compilateur Go a beaucoup moins de travail à faire lors de la compilation d'un exécutable. La compilation et l'exécution d'une application Go sont souvent aussi rapides, voire plus rapides, que le chargement de l'interpréteur Python ou de la machine virtuelle Java avant même l'exécution de l'application proprement dite. Il n'est pas surprenant qu'un exécutable natif soit aussi rapide qu'un exécutable peut l'être. Il n'est pas aussi rapide que ses homologues C/C++ ou Rust, mais avec une fraction de la complexité du code. Je suis prêt à négliger cet inconvénient mineur de Go. Enfin, les binaires Go sont statiquement liés, ce qui signifie que vous pouvez en construire un n'importe où et l'exécuter sur l'hôte cible - sans aucune dépendance de runtime ou de bibliothèque. Pour des raisons de commodité, nous enveloppons toujours nos applications Go dans des conteneurs Docker. Toutefois, ces derniers sont nettement plus petits et ne consomment qu'une fraction de la mémoire et de l'unité centrale de leurs homologues Python ou Java.
Comment nous utilisons Python et Go à notre avantage
La solution la plus pragmatique que nous avons trouvée dans notre travail est de combiner les pouvoirs de la facilité de Python et de la simplicité de Go. Pour nous, Python est un formidable terrain de jeu pour le prototypage. C'est là que les idées naissent et que les hypothèses scientifiques sont acceptées et rejetées. Python est naturellement adapté à la science des données et à l'apprentissage automatique, et comme nous travaillons beaucoup dans ce domaine, il n'est pas logique d'essayer de réinventer la roue avec quelque chose d'autre. Python est également au cœur de Django, ce qui témoigne de sa devise qui est de permettre le développement rapide d'applications comme peu d'autres outils (bien sûr, Ruby on Rails et Phoenix d'Elixir méritent d'être mentionnés ici).
Supposons qu'un projet ait besoin d'un minimum de gestion des utilisateurs et d'administration des données internes (comme c'est le cas pour la plupart de nos projets). Dans ce cas, nous commencerons par un squelette de Django en raison de son administration intégrée, qui est fantastique. Une fois que le prototype Django commence à ressembler à un produit, nous déterminons dans quelle mesure il peut être réécrit en Go. Comme l'application Django a déjà défini la structure de la base de données et l'aspect des modèles de données, il est assez facile d'écrire le code Go qui vient s'y ajouter. Après quelques itérations, nous parvenons à une symbiose, où les deux parties coexistent pacifiquement au-dessus de la même base de données et utilisent une messagerie simple pour communiquer l'une avec l'autre. Finalement, le "shell" Django devient un orchestrateur - il sert nos objectifs d'administration et déclenche des tâches qui sont ensuite gérées par son homologue Go. La partie Go s'occupe de tout le reste, depuis les API et les points d'accès frontaux jusqu'à la logique d'entreprise et le traitement des tâches en arrière-plan.
C'est une symbiose qui a bien fonctionné jusqu'à présent, et j'espère qu'il en sera ainsi à l'avenir.
Source : "Python is Easy. Go is Simple. Simple != Easy." par Preslav Rachev, ingénieur en informatique
Et vous ?
Quel est votre avis sur le sujet ?
Voir aussi
Go signe son retour dans le top 10 de l'indice de popularité des langages TIOBE après une absence de près de six ans. Python, C et Java conservent la tête du classement
Le créateur de Python, Guido van Rossum, dit ce qu'il pense de Rust, Go, Julia et TypeScript et relève des similitudes entre ces langages et Python
Go 1.21 apporte des améliorations au niveau du langage, de nouveaux paquets de bibliothèque standard, PGO GA, et une compatibilité ascendante et descendante dans la chaîne d'outils
Python 3.12 : la dernière version apporte des modifications du langage et de la bibliothèque standard, elle comporte aussi une amélioration des messages d'erreur