Demandez le programme
Un ordinateur qui se bloque, un écran bleu, un logiciel qui se ferme intempestivement… c’est un « bug » ! Ce terme, qui signifie « cafard » (l’insecte) en anglais, désigne une erreur dans la programmation d’un logiciel ou, plus rarement, dans son algorithme. Personne n’y échappe ! Ainsi, le premier vol d’Ariane 5 en 1996 a explosé et détruit pour 500 millions de dollars de satellites. La faute était due à un morceau de programme qui convertissait une valeur. Créé pour Ariane 4, il a été réutilisé tel quel. La valeur à convertir, beaucoup plus grande dans le cas d’Ariane 5, a donné un résultat aberrant et des ordres de braquage maximum… On peut également citer la sonde américaine Venus Mariner 1, perdue en 1962 à cause d’un trait d’union oublié dans un programme… Mais quelle est donc cette activité mystérieuse si sujette aux erreurs ? La programmation.
C’est elle qui spécifie, dans un langage très précis, ce que l’ordinateur doit exécuter. D’un algorithme décrit verbalement ou avec un dessin, la programmation fait une liste d’ordres que la machine va suivre pour obtenir la réponse désirée (valeur, image, application, etc.). Les programmes sont absolument nécessaires à notre interaction avec l’ordinateur, ils sont partout : un jeu est un programme au même titre qu’un navigateur (ou « butineur » comme Firefox, Internet Explorer…) ou qu’un système d’exploitation (Linux, Mac OS, Windows…), programme très long et compliqué, mais qui, comme pour un jeu, est un texte écrit dans un certain langage.
Ce texte est un petit programme écrit dans le langage C. Son objectif est de trouver qui, parmi les élèves d’une classe, porte le nom le plus long. La liste des noms est donnée par un tableau, appelé noms, dont le nombre de lignes, noté l, est égal à celui des élèves. Principe du programme : parcourir, à l’aide de l’instruction de boucle for, le tableau ligne par ligne, en commençant par la ligne de rang 1. Au début, le nom le plus long est le premier de la liste, celui de la ligne de rang max dans le tableau, avec max qui vaut 0. Dans la boucle, pour chaque ligne i, la longueur du nom courant, noms[i], est comparée à la longueur du nom le plus long rencontré, noms[max]. Si elle est supérieure, la valeur de la variable max est modifiée pour prendre la valeur i, rang du nom courant, désormais le plus long parmi ceux examinés. À la fin du parcours, max est le rang dans la liste où se trouve le nom d’élève le plus long.
Le système d’exploitation (« OS » ou « Operating System » en anglais) est un (ou plusieurs) programme qui régit tous les autres. Il fait le lien entre le matériel (processeur, carte réseau, clavier…) et les autres programmes (navigateur, traitement de texte, jeu vidéo…). Son rôle est déterminant, il attribue le temps processeur (ou comment ce dernier se « partage » entre plusieurs programmes) et la mémoire nécessaire aux autres applications en cours d’exécution. Par l’intermédiaire d’un système de fichiers, il gère les fichiers sur le disque dur : il faut connaître le nom de chacun, son emplacement physique dans la mémoire, sa taille… Véritable administrateur, le système d’exploitation répartit le travail parmi les différentes unités de la machine pour que celles-ci fonctionnent de la meilleure façon.
Le langage de la machine
Le seul langage compris par le processeur, l’unité centrale dans laquelle sont câblées toutes les opérations de base de l’ordinateur, est appelé « assembleur ». Il définit simplement les opérations électroniques que peut exécuter le processeur. Ce langage change d’un processeur à un autre. Il est donc très efficace puisque tout peut être ajusté « sur mesure », mais il est nettement plus compliqué à manipuler que des langages de plus haut niveau, sans faire d’erreur.
L’assembleur est une liste d’ordres, un fichier d’instructions, comme additionner deux valeurs, les comparer… L’une des instructions permet de sauter à un autre ordre de la liste. Cela permet par exemple de faire des boucles comme « Frapper un monstre jusqu’à ce qu’il disparaisse. » ou « Avancer jusqu’à rencontrer un mur. ».
Cette dernière boucle peut également s’écrire : « Avancer. S’il n’y a pas de mur en face, retourner à la ligne précédente. Tourner à gauche. ». Ainsi, on ne tournera à gauche que lorsqu’on aura rencontré un mur. Mais ce genre de programme pose une question : que se passe-t-il si l’on rencontre un obstacle autre qu’un mur ? En effet, si un humain peut penser à contourner cet obstacle, un tel comportement n’a pas été programmé et le programme va donc tenter désespérément de passer à travers. Dans ce cas absurde, le programme exécutera bêtement l’instruction jusqu’à ce qu’il soit arrêté de l’extérieur (par le système d’exploitation ou par l’utilisateur).
Le compilateur, le traducteur homme-machine
Les langages actuels sont de plus haut niveau, ils permettent de préciser plus facilement toutes les actions à exécuter. Cependant, ces langages doivent eux-mêmes être exécutés. Pour cela, le programme source, qui n’est qu’un texte, va être « compilé ». Cela signifie qu’un programme (un de plus !), appelé « compilateur », va transformer le texte du programme source en une suite d’instructions en langage assembleur exécutables par l’ordinateur. Le compilateur joue un rôle de traducteur entre ce qui est écrit par le programmeur et ce qui est compris par la machine. Le compilateur produit un fichier exécutable que nous lançons pour utiliser le programme. De même qu’il y a de nombreuses langues (français, arabe, russe, japonais…), il y a plusieurs types de programmation, c’est-à-dire plusieurs façons de donner des ordres à l’ordinateur. Ces modes de programmation sont appelés « paradigmes ». Le choix du langage et du paradigme de programmation dépend de l’application, de l’algorithme, mais aussi de l’interaction du programme avec d’autres programmes et enfin du choix et de l’expertise du programmeur.
Ces petits personnages sont définis pour la machine comme l’ensemble d’un très grand nombre de points. Une fois reliés, ces points forment de petits triangles représentant la surface du personnage. Ce qui est intéressant ici est le programme qui passe de l’image « fil de fer » (à droite) à l’image « lisse » (à gauche). Il aboutit à l’image finale à partir des différentes textures des triangles (bleu d’un vêtement, verre réfléchissant la lumière comme sur les boules) et de l’éclairage (ici la lumière vient de la gauche) qui lui sont donnés. Le programme calcule quelles sont les parties visibles des personnages, les parties de l’image réfléchies sur les boules, les ombres des personnages… tout ce qui contribue à donner du réalisme à l’image. Bien sûr, le programme doit être efficace et opérer très rapidement et sans erreur. En effet, si dans un jeu vidéo, le joueur doit attendre trop longtemps l’image d’une scène suivante par exemple, ou si une image n’est visiblement pas crédible (bug d’affichage, personnage « pixelisé » ou flou, sans ombre…), il ne s’impliquera pas dans l’univers et le jeu perdra de son attrait.
Les langages et les paradigmes
Le premier paradigme est le fait d’avoir, comme en langage assembleur, une suite d’instructions interprétées dans l’ordre. Ces instructions sont par exemple des ordres permettant de modifier une valeur dans la mémoire de l’ordinateur : si un personnage de jeu vidéo meurt, son nombre de vies restantes doit diminuer d’une unité. Ainsi, selon le programme et les valeurs de la mémoire de l’ordinateur, cette mémoire va être petit à petit modifiée. Ces changements de l’état mémoire sont les instructions de base du paradigme appelé « impératif ». À cela s’ajoutent des boucles de plus haut niveau : « tant que » et « pour ».
Dans le deuxième paradigme, toute donnée informatique est un objet composé de valeurs (appelées attributs) et de mini-programmes (appelés méthodes) qui vont modifier ou accéder aux attributs de l’objet ou communiquer avec d’autres objets. C’est une façon différente d’appréhender la programmation appelée « paradigme objet ». Au lieu d’une unique liste d’instructions, on a des objets avec leurs caractéristiques qui s’envoient des messages. On peut comparer cela à un jeu de stratégie où chaque unité a ses caractéristiques propres (santé, attaque, défense) et peut interagir avec d’autres (attaque, réparation). Cela simplifie l’utilisation de programmes déjà écrits et permet également de cacher des détails techniques aux autres programmeurs. Ainsi, on peut comparer cela à utiliser des gants en écaille de dragon dans un jeu de rôle. Quelle que soit la façon dont on a obtenu les gants (la façon de programmer la fonction) : achat, vol, combat contre un dragon, le joueur en profitera de la même manière (la fonction donne la même réponse).
Ces petits robots footballeurs permettent de faire une validation en situation réelle de systèmes multi-agents, c’est-à-dire de systèmes collaboratifs. Un comportement compliqué ne naît pas forcément de dizaines de milliers de lignes de code. On peut obtenir des résultats surprenants en associant des robots et en les faisant collaborer. Chaque robot a un programme relativement simple, mais leur association donne des résultats fascinants, en particulier des résultats qu’il aurait été difficile de programmer directement. La communication entre les robots est évidemment primordiale, ils doivent se mettre d’accord sur la tâche à effectuer et la façon de le faire, par exemple chaque robot va dans une direction pendant une minute, puis revient raconter ce qu’il a vu. Les décisions suivantes sont alors prises en fonction des données de chacun des robots (danger, nourriture…). Les systèmes multi-agents, peuvent être comparés à des fourmis œuvrant ensemble vers un résultat (survie de la fourmilière par exemple).
Dans le troisième paradigme, on voit le programme comme une évaluation mathématique de fonctions et les programmes ressemblent plus à l’intuition mathématique qu’on peut en avoir. Ce paradigme, dit « fonctionnel », ressemble à l’écriture d’une fonction mathématique qui prend une ou des valeurs en entrée et renvoie un résultat : on ne gère plus la mémoire ni certains détails de bas niveau. Ce paradigme repose sur la notion de « typage » : si un sort magique nécessite une plume de phénix, il est dangereux de la remplacer par de la bave de crapaud. En termes informatiques, cela signifie que lorsqu’une fonction prend en entrée un nombre entier (comme la multiplication par 2 par exemple), on ne peut pas lui donner un nom de fleur par exemple, sous peine de faire échouer le programme. En effet, comment multiplier par 2 un nom de fleur ? Heureusement, un tel échec est évité par le compilateur, qui signale le problème. Il est impossible d’obtenir un exécutable à partir d’un programme mal typé. Ce mécanisme permet d’éviter de nombreuses erreurs.
Un quatrième paradigme est la programmation logique, représentée par le langage « Prolog ». L’idée est de déclarer des faits, des implications et des règles de type logique. En réponse à une requête, le démonstrateur utilise ces règles et ces faits pour fournir une réponse. Par exemple, un problème d’emploi du temps peut être décrit de cette façon (nombre d’élèves, types de salles, nombre de professeurs, durée des cours…). C’est une programmation plus souple et plus déclarative qui est beaucoup utilisée en intelligence artificielle.
Un cinquième paradigme est utilisé dans les systèmes embarqués, comme les avions ou les voitures. Le paradigme « synchrone » permet de spécifier des événements, leurs durées, la façon dont ils se suivent et se déclenchent. Cela permet de bien formaliser un système qui interagit avec l’environnement, et fonctionne sans s’arrêter en fonction des entrées qu’il reçoit, par exemple de capteurs. Les langages de programmation modernes utilisent un ou plusieurs paradigmes. Par exemple, des langages comme « C » ou « Fortran » ne sont qu’impératifs, « Haskell » n’est que fonctionnel alors que « OCaml » est à la fois impératif, objet et fonctionnel.
L’erreur est humaine
Pour faciliter la programmation et réduire la possibilité d’erreur, il existe de nombreux outils : des programmes eux aussi ! Ces environnements de développement permettent d’aider le programmeur. Ils contiennent typiquement en plus du compilateur, un éditeur de texte coloré, pour mettre en relief les éléments du langage, et des débogueurs qui surveillent la façon dont un programme s’exécute et débusquent les bugs, invisibles au premier coup d’œil. Les outils peuvent également contenir une aide à la création d’interfaces graphiques (boutons, menus déroulants…). Ils peuvent faciliter la gestion des différentes versions des programmes et permettre de gérer de gros projets logiciels (nombreux fichiers, nombreux programmeurs). Certains sont capables de déceler des variables non utilisées ou de faire choisir les éléments à utiliser dans des menus.
Les programmeurs ne sont que des hommes et des femmes. L’erreur est possible, elle peut résulter d’une faute de frappe ou être plus subtile : une fonction qui présuppose qu’une valeur est positive ne peut pas donner un résultat satisfaisant avec une valeur négative. Enfin l’erreur peut découler d’une mauvaise communication entre fonctions ou entre programmeurs, par exemple si une distance est exprimée en miles alors que l’on pense qu’elle l’est en kilomètres, les résultats seront évidemment faussés.
Pour déceler ces bugs, les programmes sont testés. Ils sont utilisés dans des conditions normales, pour voir s’ils se comportent correctement. Mais comment tester tous les cas et tous les comportements ? C’est le plus souvent impossible et les programmes sont fréquemment victimes de bugs plus ou moins coûteux, même dans les domaines les plus sensibles.
Dans les années 1990, la marine américaine entame un plan de modernisation appelé « Smart Ship », « Navire intelligent ». Il vise à réduire les coûts de fonctionnement en utilisant davantage de produits informatiques grand public et donc en diminuant le nombre de marins nécessaire. Le premier bateau équipé de ces technologies, l’USS Yorktown, a rencontré de nombreux problèmes… En septembre 1998, l’un des opérateurs humains a entré par erreur 0 comme valeur pour une donnée. Cela a créé une division par zéro, un dépassement de capacité et des erreurs en chaîne qui ont conduit à un arrêt de la propulsion du bateau, qui est donc resté « planté » quelques heures sur l’eau et a dû être remorqué… Le programme aurait dû reconnaître cette valeur comme erronée et ne pas échouer ! Heureusement que ce n’était pas un avion…
Pour résoudre ces problèmes, diverses techniques de génie logiciel peuvent être utilisées. Il existe des formalismes de modélisation, visant à décrire de façon simple et compréhensible ce que doit faire chaque morceau du programme avant de le programmer. Il existe aussi des méthodes formelles plus sûres : ainsi, une telle méthode, la méthode B, a été utilisée pour concevoir la ligne 14 (Météor) du métro parisien. On peut aussi spécifier de façon précise et logique ce que doit faire chaque morceau de programme. Cette programmation par contrat nécessite une preuve mathématique que cette spécification est remplie.
Pour plus de garantie, cette preuve doit être expliquée et vérifiée par l’ordinateur : on est ainsi sûr de n’oublier aucun sous-cas et de ne pas faire d’erreur d’inattention lors de la démonstration. On a ainsi l’assurance que le programme n’échouera pas et qu’il ne fera pas échouer les éventuels programmes qui l’utilisent. Ces programmes formellement prouvés offrent un très haut niveau de garantie.
En conclusion, la programmation n’est pas seulement une simple traduction de l’algorithme vers la machine. La façon de faire, la gestion de la mémoire, les types de données peuvent rendre un même algorithme lent ou très efficace. La programmation introduit également des bugs aux conséquences parfois dramatiques. Les éviter reste un vrai défi, un défi scientifique.
Pour aller plus loin, nous vous proposons de consulter sur Wikipédia l’article Langage de programmation.
Cet article est paru dans la revue DocSciences n°5 Les clés de la révolution numérique, éditée par le CRDP de l’Académie de Versailles en partenariat avec Inria.
Newsletter
Le responsable de ce traitement est Inria. En saisissant votre adresse mail, vous consentez à recevoir chaque mois une sélection d'articles et à ce que vos données soient collectées et stockées comme décrit dans notre politique de confidentialité
Niveau de lecture
Aidez-nous à évaluer le niveau de lecture de ce document.
Votre choix a été pris en compte. Merci d'avoir estimé le niveau de ce document !