L'attrait du langage Ada ── Exprimer la conception par les types, faire fonctionner des logiciels pendant des décennies
· Mis à jour le: · Go Komura · Ada, Langage de Programmation, Typage Fort, SPARK, GNAT, Alire, Haute Intégrité, Embarqué, Haute Fiabilité
1. Ce qu’il faut comprendre en premier
Avez-vous déjà entendu parler d’un langage appelé Ada ?
Beaucoup de gens en ont l’image d’un « vieux langage », d’un « langage militaire », ou d’un nom simplement entendu en cours.
Pourtant, Ada est encore aujourd’hui un langage bien vivant.
Dans le monde des logiciels dont l’arrêt met des vies humaines en jeu — contrôle de vol des avions, systèmes de signalisation ferroviaire, fusées, contrôle du trafic aérien, satellites, dispositifs médicaux — Ada est utilisé sans interruption depuis des décennies.
Pour comprendre Ada, il est important de garder à l’esprit les points de vue suivants.
Ada est un langage qui consacre toute son énergie à « éliminer les bogues avant l'exécution »
Les types ne sont pas des conteneurs de données, mais des outils pour exprimer l'intention de conception
La séparation de la spécification et de l'implémentation, les contrats et le traitement concurrent sont intégrés au langage
Ada n'est jamais devenu à la mode, mais sa philosophie de conception a été héritée par les langages modernes
Cet article passe en revue l’attrait d’Ada : son histoire, sa syntaxe, son typage fort, ses contraintes de plage, ses packages, sa conception par contrat, ses tâches, SPARK, son environnement de développement, et jusqu’à ses faiblesses.
L’objectif est que les personnes qui écrivent habituellement du C#, du C++ ou du Java repartent avec le sentiment de ce que signifie « exprimer la conception par les types ».
Les extraits de code présentés dans cet article sont publiés sur GitHub, organisés par chapitre dans un dépôt de référence.
ada-language-appeal - komurasoft-blog-samples (GitHub)
2. Qu’est-ce qu’Ada ── L’origine du nom et son histoire
Ada est un langage de programmation généraliste né à la fin des années 1970, sous l’impulsion du département de la Défense des États-Unis (DoD).
À l’époque, le DoD était confronté à un problème : chaque projet utilisait un langage différent, et les coûts de maintenance des logiciels explosaient.
Le DoD a donc organisé un concours de conception international pour sélectionner un langage standard, également utilisable pour les systèmes embarqués et temps réel.
C’est le projet conçu par l’équipe dirigée par Jean Ichbiah qui a été retenu.
Le nom du langage, Ada, vient d’Ada Lovelace (Augusta Ada King, comtesse de Lovelace), souvent considérée comme la première programmeuse au monde.
Voici, en résumé, l’histoire d’Ada.
1980 Première spécification établie sous le nom MIL-STD-1815
1983 Ada 83 (standard ANSI)
1987 Devient un standard ISO
1995 Ada 95 (introduction de l'orientation objet et des objets protégés)
2005 Ada 2005 (interfaces, bibliothèque de conteneurs étendue)
2012 Ada 2012 (la conception par contrat introduite comme fonctionnalité du langage)
2022 Ada 2022 (le standard le plus récent)
Ada 95 fait partie des tout premiers langages orientés objet à avoir été normalisés par l’ISO.
Et avec Ada 2012, la conception par contrat (Design by Contract) — préconditions, postconditions et invariants de type — a été intégrée à la spécification même du langage.
Ada n’est pas un « vieux langage » : c’est un langage révisé en continu depuis plus de 40 ans.
3. Où Ada est-il utilisé ?
Le domaine emblématique où Ada continue d’être utilisé est celui des systèmes exigeant une haute intégrité (High Integrity).
Contrôle de vol et avionique des avions civils
Systèmes de contrôle du trafic aérien
Systèmes de signalisation et de sécurité ferroviaires
Fusées et satellites
Systèmes de défense
Dispositifs médicaux
Certains systèmes centraux de la finance et de l'industrie
Ces domaines partagent des caractéristiques communes.
Un bogue se traduit directement par une perte de vies humaines ou des pertes financières considérables
La certification et les audits exigent des preuves de correction
Une fois déployé, le système fonctionne pendant des décennies
Le coût d'une correction ultérieure est extrêmement élevé
C’est un monde où l’idée de « livrer maintenant et corriger plus tard » n’a tout simplement pas cours.
La conception du langage Ada existe précisément pour répondre à ces exigences.
Les bogues détectables à la compilation sont détectés à la compilation ; ceux qui ne peuvent être détectés qu’à l’exécution le sont par des vérifications à l’exécution ; et, allant plus loin, ce qui peut être prouvé mathématiquement est éliminé par la preuve — cette philosophie traverse l’ensemble du langage.
Cette philosophie mérite d’être apprise, même par les développeurs qui écrivent des applications métier web ou de bureau.
4. Pour commencer, Hello, World
Regardons du code Ada.
with Ada.Text_IO;
procedure Hello is
begin
Ada.Text_IO.Put_Line ("Hello, Ada!");
end Hello;
Voici probablement les premières choses que vous remarquerez.
with importe une unité de bibliothèque
Le corps du programme est une procédure (procedure)
begin / end délimitent un bloc
Le nom est répété après end
Les instructions se terminent par un point-virgule
Réécrire le nom à la fin, comme dans end Hello;, est une caractéristique typiquement Ada.
Même lorsque les blocs sont profondément imbriqués, on voit d’un coup d’œil « de quel bloc est-ce le end ».
Le compilateur vérifie également la correspondance des noms, si bien qu’une mauvaise fermeture de bloc devient une erreur de compilation.
Cela paraît un détail, mais cela illustre bien la philosophie d’Ada : le langage soutient l’humain précisément là où les erreurs de lecture sont les plus probables.
5. Une syntaxe pensée pour la lisibilité
La syntaxe d’Ada a été conçue en donnant la priorité à la lisibilité plutôt qu’à la facilité d’écriture.
Elle repose sur le principe qu’un logiciel est lu bien plus souvent qu’il n’est écrit.
Par exemple, les boucles et les branchements conditionnels s’écrivent ainsi.
for I in 1 .. 5 loop
Ada.Text_IO.Put_Line (Integer'Image (I * I));
end loop;
if Temperature > 80.0 then
Start_Cooling;
elsif Temperature < 20.0 then
Start_Heating;
else
Keep_Current_State;
end if;
L’instruction case possède une caractéristique bien Ada.
case Today is
when Mon .. Fri =>
Put_Line ("Weekday");
when Sat | Sun =>
Put_Line ("Weekend");
end case;
Voici les points clés.
Un case doit couvrir toutes les valeurs, sinon c'est une erreur de compilation
Il n'existe pas de fallthrough implicite comme dans les langages de la famille C
Les conditions peuvent être regroupées par plage (Mon .. Fri) ou par liste de choix (Sat | Sun)
Ajoutez une valeur à un type énuméré, et tout case non exhaustif devient une erreur de compilation.
L’expérience où « le compilateur énumère lui-même tous les endroits affectés par un changement de spécification » est de celles qu’on ne peut plus abandonner une fois qu’on l’a goûtée.
Par ailleurs, les arguments peuvent utiliser une association nommée.
Draw_Rectangle (Left => 10, Top => 20, Width => 100, Height => 50);
Cela évite les confusions d’arguments, et le code appelant devient lui-même sa propre documentation.
L’affectation s’écrit := et la comparaison =, si bien que la confusion classique des langages de la famille C avec if (a = b) ne peut tout simplement pas se produire au niveau syntaxique.
6. Typage fort ── Transformer les confusions d’unités en erreurs de compilation
Le plus grand attrait d’Ada est son typage fort.
De nombreux langages emploient l’expression « typage fort », mais celui d’Ada va un cran plus loin.
En Ada, des types déclarés sous des noms différents sont des types distincts, même si leur structure est exactement identique.
type Meters is new Float;
type Seconds is new Float;
Distance : Meters := 100.0;
Time : Seconds := 9.58;
Ces deux types reposent tous deux sur un nombre à virgule flottante, mais ils ne peuvent pas être mélangés.
Distance := Time; -- erreur de compilation
Distance := Distance + Time; -- erreur de compilation
Seule une conversion délibérée est écrite explicitement.
Speed : constant Float := Float (Distance) / Float (Time);
Pourquoi une telle rigueur ?
Un nombre non négligeable d’accidents logiciels réels ont pour origine une « confusion d’unités ».
Exemple célèbre : la sonde martienne Mars Climate Orbiter a été perdue en 1999 à cause d’un mélange entre le système impérial et le système métrique.
La réponse d’Ada est simple.
Faire des mètres et des pieds des types différents
Faire du mélange des deux une erreur de compilation
Forcer l'écriture explicite des conversions
Non pas « faire attention », « le repérer en relecture » ou « le capturer par des tests », mais empêcher purement et simplement la compilation de réussir.
C’est la posture fondamentale d’Ada.
7. Contraintes de plage ── Empêcher les valeurs invalides au niveau du type de données
En Ada, un type peut porter une plage de valeurs.
subtype Percentage is Integer range 0 .. 100;
Progress : Percentage := 50;
Tenter d’affecter une valeur hors plage à une variable de type Percentage déclenche à l’exécution une exception Constraint_Error.
Progress := 120; -- Constraint_Error à l'exécution
Les violations détectables à la compilation le sont dès la compilation.
Les hypothèses implicites — « cette valeur devrait être comprise entre 0 et 100 », « cette valeur devrait être au moins 1 » — peuvent être exprimées dans le type, et non dans un commentaire.
D’ailleurs, la bibliothèque standard d’Ada prédéfinit déjà des types contraints d’usage courant.
Natural = Integer range 0 .. Integer'Last
Positive = Integer range 1 .. Integer'Last
De plus, depuis Ada 2012, on peut attacher des conditions arbitraires sous forme de prédicats.
subtype Even is Integer
with Dynamic_Predicate => Even mod 2 = 0;
Des types orientés vers le contrôle matériel, comme les types à virgule fixe, sont eux aussi intégrés au langage.
type Temperature is delta 0.1 range -50.0 .. 150.0;
Dans la plupart des langages, la vérification des valeurs invalides prend souvent la forme suivante.
Validation par instruction if en début de fonction
Les oublis de vérification reposent sur la relecture de code
On ne sait pas clairement quelles fonctions reçoivent des valeurs déjà validées
En Ada, on peut affirmer : « dès l’instant où une valeur est de ce type, la plage est déjà garantie ».
En transférant la responsabilité de la validation au type de données, la logique des fonctions peut se concentrer sur son véritable rôle.
8. Tableaux et indices ── Vérification des bornes et indices de type énuméré
Les tableaux Ada permettent de choisir librement le type de l’indice.
type Day is (Mon, Tue, Wed, Thu, Fri, Sat, Sun);
type Hours_Array is array (Day) of Natural;
Work_Hours : Hours_Array := (Mon .. Fri => 8, others => 0);
Voici un tableau indexé par le type énuméré Day.
On y accède par Work_Hours (Wed), sans avoir besoin de se souvenir de « ce que signifie cet indice numérique ».
Les boucles peuvent également suivre le type de l’indice.
for D in Work_Hours'Range loop
Put_Line (Day'Image (D) & ":" & Natural'Image (Work_Hours (D)));
end loop;
Des attributs comme 'Range, 'First, 'Last et 'Length donnent à tout moment les informations sur les bornes du tableau.
Comme les bornes ne sont jamais codées en dur, redimensionner un tableau ne se répercute pas sur les boucles.
Et surtout, l’accès aux tableaux fait toujours l’objet d’une vérification des bornes.
Buffer : String (1 .. 10);
Index : Integer := 11;
Buffer (Index) := 'x'; -- Constraint_Error à l'exécution
Les débordements de tampon en C/C++ restent depuis des décennies l’une des principales causes de failles de sécurité.
En Ada, un accès hors plage n’est pas un comportement indéfini, mais une exception définie.
Plutôt que de corrompre silencieusement la mémoire et de provoquer un plantage mystérieux ailleurs, le programme s’arrête bruyamment et immédiatement, à l’endroit précis où le problème s’est produit.
Compte tenu du coût d’investigation dans les systèmes exploités sur le long terme, cette différence est considérable.
9. Packages ── Séparer la spécification de l’implémentation
Le mécanisme de modularité d’Ada est le package.
Un package se divise en deux fichiers : la spécification (spec) et le corps (body).
counters.ads spec : l'interface exposée vers l'extérieur
counters.adb body : les détails de l'implémentation
La spécification s’écrit ainsi.
package Counters is
type Counter is private;
procedure Increment (C : in out Counter);
function Value (C : Counter) return Natural;
private
type Counter is record
Count : Natural := 0;
end record;
end Counters;
Le corps s’écrit ainsi.
package body Counters is
procedure Increment (C : in out Counter) is
begin
C.Count := C.Count + 1;
end Increment;
function Value (C : Counter) return Natural is
begin
return C.Count;
end Value;
end Counters;
Voici les points à noter.
Déclarer un type private empêche le code appelant de toucher à sa structure interne
Lire uniquement la spec (.ads) suffit à tout comprendre de son utilisation
Modifier le body (.adb) n'implique qu'une recompilation minimale du code appelant, tant que la spec reste identique
Cela ressemble aux fichiers d’en-tête C/C++, mais au lieu d’une expansion textuelle comme #include, la cohérence est vérifiée par la spécification même du langage.
Un décalage entre la spec et le body est une erreur de compilation.
De plus, chaque paramètre doit obligatoirement déclarer un mode : in, out ou in out.
procedure Increment (C : in out Counter);
On voit dès la signature si un paramètre est en lecture seule, en écriture seule, ou en lecture-écriture.
Même sans aucune connaissance des pointeurs ou des références, on peut lire directement le sens du flux de données.
10. Enregistrements et discriminants
L’équivalent Ada d’une structure (struct) est l’enregistrement (record).
type Point is record
X : Float := 0.0;
Y : Float := 0.0;
end record;
P : Point := (X => 1.0, Y => 2.0);
Les champs peuvent porter des valeurs par défaut, et les agrégats permettent une initialisation nommée.
Une fonctionnalité typiquement Ada est le discriminant.
type Buffer (Size : Positive) is record
Data : String (1 .. Size);
Length : Natural := 0;
end record;
Small : Buffer (Size => 16);
Large : Buffer (Size => 4096);
Un discriminant est un paramètre qui détermine la « forme » d’un enregistrement.
Buffer (16) et Buffer (4096) sont du même type, mais la taille du tableau interne est fixée à la déclaration et ne change plus ensuite.
C’est comme si le langage gérait, de manière sûre, sous forme de type, ce qui en C serait « une structure à membre de longueur variable, plus un champ de taille ».
L’incohérence entre la taille et les données réelles — une porte d’entrée classique aux bogues en C — n’existe tout simplement pas dès le départ.
11. Génériques
Ada possède des génériques (generic) depuis son tout premier standard, en 1983.
C’est bien plus tôt que les templates C++ (années 1990) ou les génériques Java (2004).
generic
type Element is private;
procedure Swap (Left, Right : in out Element);
procedure Swap (Left, Right : in out Element) is
Temp : constant Element := Left;
begin
Left := Right;
Right := Temp;
end Swap;
Le code appelant l’instancie avec des types concrets.
procedure Swap_Integers is new Swap (Element => Integer);
procedure Swap_Floats is new Swap (Element => Float);
La caractéristique déterminante des génériques Ada est que les opérations requises sont explicitement énoncées.
generic
type Element is private;
with function "<" (Left, Right : Element) return Boolean is <>;
function Max (Left, Right : Element) return Element;
La spécification indique : « cette fonction générique exige un opérateur de comparaison sur le type Element ».
Le problème qui a longtemps affligé les templates C++ — les erreurs qui n’apparaissent qu’à l’instanciation — ne se produit jamais avec Ada.
Le problème que les concepts de C++20 et les bornes de traits de Rust ont cherché à résoudre, Ada y avait déjà répondu 40 ans plus tôt.
12. Gestion des exceptions
Ada dispose d’une gestion des exceptions.
with Ada.Text_IO;
with Ada.Exceptions;
procedure Read_Config is
begin
Load_File ("config.txt");
exception
when Ada.Text_IO.Name_Error =>
Ada.Text_IO.Put_Line ("Fichier de configuration introuvable");
when E : others =>
Ada.Text_IO.Put_Line (Ada.Exceptions.Exception_Information (E));
raise;
end Read_Config;
On écrit une partie exception à la fin du bloc, et on y aligne les gestionnaires pour chaque type d’exception.
Voici les exceptions définies par le langage les plus représentatives.
Constraint_Error violation de contrainte de plage, violation des bornes d'un tableau, division par zéro, etc.
Program_Error violation d'une règle du langage (par exemple atteindre un point qui ne devrait jamais l'être)
Storage_Error mémoire insuffisante
Tasking_Error échec de communication entre tâches
Ce qui mérite l’attention, c’est que les violations de contraintes de plage et de vérification des bornes sont toutes intégrées à ce même mécanisme d’exceptions.
Quand une « contrainte écrite dans le type » est violée, on obtient un Constraint_Error.
Autrement dit, les contraintes de plage vues au chapitre 7 fonctionnent comme des assertions d’exécution générées automatiquement.
Il n’est pas nécessaire d’écrire soi-même du code de vérification par instructions if un peu partout.
13. Conception par contrat ── Écrire les conditions Pre/Post comme fonctionnalité du langage
La fonctionnalité phare d’Ada 2012 est le support langage de la conception par contrat (Design by Contract).
On peut écrire directement une précondition (Pre) et une postcondition (Post) sur un sous-programme.
package Stacks is
type Stack is private;
function Is_Full (S : Stack) return Boolean;
function Is_Empty (S : Stack) return Boolean;
function Count (S : Stack) return Natural;
procedure Push (S : in out Stack; Item : Integer)
with Pre => not Is_Full (S),
Post => Count (S) = Count (S)'Old + 1;
procedure Pop (S : in out Stack; Item : out Integer)
with Pre => not Is_Empty (S),
Post => Count (S) = Count (S)'Old - 1;
private
-- Détails de l'implémentation
end Stacks;
Pre est « la promesse que l’appelant doit respecter », Post est « la promesse que l’implémentation garantit ».
L’attribut 'Old permet de référencer la valeur d’avant l’appel.
Ces contrats peuvent être activés comme vérifications à l’exécution via une option de compilation (-gnata sous GNAT).
En cas de violation d’un contrat, une exception Assertion_Error est levée, et l’on sait clairement quel côté a rompu la promesse.
Violation de Pre -> bogue du côté de l'appelant
Violation de Post -> bogue du côté de l'implémentation
Quelle est la différence avec le fait d’écrire, dans un commentaire de documentation, « cette fonction ne doit pas être appelée sur une pile vide » ?
Un commentaire peut diverger de l'implémentation sans que personne ne s'en aperçoive
Un contrat voit sa syntaxe et ses types vérifiés par le compilateur
Un contrat peut être vérifié automatiquement à l'exécution
Un contrat devient l'entrée d'une preuve statique via SPARK (chapitre 16)
La spécification existe dans le code, sous une forme vérifiable.
C’est le monde d’Ada depuis 2012.
Avec les invariants de type (Type_Invariant), on peut également écrire des contraintes du type « les valeurs de ce type satisfont toujours cette propriété ».
14. Tâches ── La concurrence intégrée au langage
L’autre grand attrait d’Ada est que la concurrence fait partie de la spécification du langage.
Là où C/C++ s’appuient sur des API du système d’exploitation ou des bibliothèques (pthread, std::thread) pour les threads, Ada avait déjà intégré les tâches au langage dès 1983.
with Ada.Text_IO;
procedure Task_Demo is
task Worker;
task body Worker is
begin
for I in 1 .. 3 loop
Ada.Text_IO.Put_Line ("worker:" & Integer'Image (I));
delay 0.5;
end loop;
end Worker;
begin
for I in 1 .. 3 loop
Ada.Text_IO.Put_Line ("main :" & Integer'Image (I));
delay 0.5;
end loop;
end Task_Demo;
Déclarez une task, et l’exécution concurrente démarre dès que le bloc englobant commence.
Et surtout, le bloc ne se termine pas tant que toutes les tâches qu’il contient ne sont pas terminées.
Toute une catégorie de bogues du type « on a oublié le join d’un thread, et des choses étranges se produisent à la fin du processus » devient structurellement impossible.
Pour la synchronisation entre tâches, le langage fournit le rendez-vous (rendezvous).
task Logger is
entry Write (Message : String);
end Logger;
task body Logger is
begin
loop
select
accept Write (Message : String) do
Ada.Text_IO.Put_Line (Message);
end Write;
or
terminate;
end select;
end loop;
end Logger;
Le code appelant écrit Logger.Write ("hello");, exactement sous la forme d’un appel de procédure.
La communication entre tâches par passage de messages peut ainsi s’écrire sans aucune connaissance des verrous.
Pour les systèmes temps réel, la politique d’ordonnancement, le contrôle des priorités, et même — pour faciliter la vérification — le profil Ravenscar, qui restreint les fonctionnalités de tâches, sont tous normalisés.
15. Objets protégés ── Écrire l’exclusion mutuelle comme un type
Pour l’exclusion mutuelle sur des données partagées, on utilise les objets protégés (protected object), introduits avec Ada 95.
protected Shared_Counter is
procedure Increment;
function Value return Natural;
private
Count : Natural := 0;
end Shared_Counter;
protected body Shared_Counter is
procedure Increment is
begin
Count := Count + 1;
end Increment;
function Value return Natural is
begin
return Count;
end Value;
end Shared_Counter;
Les données d’un objet protégé ne sont accessibles que par les opérations que l’on a définies.
Et l’exclusion mutuelle est garantie par le langage lui-même.
procedure lecture-écriture autorisée, exécution exclusive
function lecture seule, autorise l'exécution simultanée de plusieurs tâches
entry peut faire attendre l'appelant jusqu'à ce qu'une condition (barrière) soit satisfaite
Dans la plupart des langages, l’exclusion mutuelle repose souvent sur la discipline du développeur.
Prendre tel mutex avant de toucher à ces données
Ne pas oublier de relâcher le verrou
Respecter l'ordre des verrous
Avec les objets protégés d’Ada, il est tout simplement impossible d’écrire du « code ayant oublié de prendre le verrou ».
Les données et l’exclusion mutuelle qui les protège sont déclarées ensemble, comme un seul et même type.
Grâce aux conditions de barrière d’entry, une synchronisation conditionnelle comme « attendre que des données arrivent dans la file » peut aussi s’écrire sans gérer manuellement de drapeaux ou de variables de condition.
16. SPARK ── La voie vers la vérification formelle
Le monde d’Ada compte un allié puissant : SPARK.
SPARK est un sous-ensemble d’Ada, conçu pour que les propriétés d’un programme puissent être prouvées mathématiquement.
procedure Increment (X : in out Integer)
with SPARK_Mode,
Pre => X < Integer'Last,
Post => X = X'Old + 1;
L’outillage SPARK (GNATprove) prouve, pour ce code, des propriétés comme les suivantes — sans jamais l’exécuter.
Aucun dépassement (overflow) ne se produit
Aucune violation de contrainte de plage ne se produit
Aucune division par zéro ne se produit
Aucune lecture de variable non initialisée
Cohérence entre Pre et Post
La différence avec les tests est décisive.
Test vérifie que le programme se comporte correctement pour les entrées choisies
Preuve démontre qu'une propriété est vraie pour toutes les entrées possibles
Les contrats (Pre/Post) vus au chapitre 13 deviennent, avec SPARK, directement l’objet de la preuve.
Un contrat écrit comme vérification à l’exécution peut ensuite être promu au statut de « prouvé ».
SPARK a fait ses preuves dans le monde de l’aérospatiale et de la défense, mais ces dernières années son usage industriel s’étend — NVIDIA, par exemple, l’a adopté pour la sécurité de ses firmwares.
L’idée reçue selon laquelle « les méthodes formelles sont trop académiques pour un usage professionnel » continue d’être discrètement, mais constamment, démentie par l’écosystème Ada/SPARK.
17. Interopérabilité avec C et C++
Ada n’est pas un langage isolé.
L’interopérabilité avec C est normalisée dans la spécification du langage (Annexe B).
Par exemple, pour appeler la fonction Sleep de l’API Windows depuis Ada, on écrit ceci.
with Interfaces.C;
procedure Sleep_Demo is
procedure Sleep (Milliseconds : Interfaces.C.unsigned)
with Import,
Convention => Stdcall,
External_Name => "Sleep";
begin
Sleep (1000);
end Sleep_Demo;
Voici les points clés.
Import importe une implémentation externe
Convention spécifie la convention d'appel (C, Stdcall, etc.)
External_Name spécifie le nom du symbole au moment de l'édition de liens
Interfaces.C fournit des types correspondant aux types C (int, unsigned, char*, etc.)
Le sens inverse est également possible.
Avec Export, une procédure écrite en Ada peut être exposée comme une fonction appelable depuis C.
Cela permet une utilisation progressive, par étapes, comme celle-ci.
Utiliser une bibliothèque C existante depuis Ada
N'écrire que le cœur du système en Ada/SPARK, en laissant la périphérie en C/C++
Compiler le code Ada en DLL pour l'appeler depuis d'autres langages
Ada n’est pas un langage dont la seule voie serait « tout réécrire » : il peut coexister avec les actifs existants tout en renforçant la fiabilité en commençant par les parties les plus critiques.
18. Environnement de développement ── GNAT et Alire (fonctionne aussi sous Windows)
On pourrait penser qu’« essayer Ada nécessite des outils coûteux ».
Aujourd’hui, un environnement de développement complet et gratuit est disponible.
GNAT le compilateur Ada inclus dans GCC (gratuit)
Alire le gestionnaire de packages et outil de build d'Ada
GNAT Studio l'IDE d'AdaCore
VS Code complétion et navigation vers les définitions via l'extension Ada Language Server
L’arrivée d’Alire en particulier (dont la commande est alr) a rendu les débuts avec Ada radicalement plus simples.
L’expérience est proche de celle de cargo pour Rust.
alr init --bin hello_ada
cd hello_ada
alr build
alr run
alr init crée le projet, alr build le compile, alr run l’exécute.
Alire récupère lui-même la chaîne d’outils (GNAT), si bien qu’il n’est même pas nécessaire d’installer le compilateur manuellement.
Cela fonctionne aussi bien sous Windows, Linux que macOS.
Pour développer sous Windows, voici le chemin le plus court.
1. Récupérer l'installateur Windows depuis le site officiel d'Alire
2. Créer un squelette avec alr init --bin
3. Installer l'extension Ada (par AdaCore) dans VS Code
4. Compiler et exécuter avec alr build
Les bibliothèques peuvent aussi être ajoutées avec alr with <nom_de_la_bibliothèque>.
L’époque où l’on renonçait dès la mise en place de l’environnement est révolue.
19. Faiblesses et points de vigilance d’Ada
Jusqu’ici, nous avons présenté les atouts d’Ada, mais il a aussi des faiblesses.
Faisons-en un état des lieux équilibré.
Un écosystème restreint
Peu de choix pour les frameworks web, les interfaces graphiques (GUI), les SDK cloud, etc.
Le nombre de packages d'Alire n'est pas du même ordre de grandeur que les langages dominants
Peu de talents et d'informations
L'information en japonais est particulièrement rare
Adopter Ada en équipe implique de prévoir un coût de formation
Une syntaxe qui peut sembler verbeuse
Les déclarations de types et la séparation spec/body sont lourdes pour de petits scripts
Peu adapté à un usage du type « faire tourner quelque chose rapidement »
Un marché de l'emploi limité
Concentré sur des secteurs comme l'aérospatiale, la défense, le ferroviaire
L’histoire nous enseigne aussi que l’idée « utiliser Ada, c’est être en sécurité » n’est pas si simple.
L’explosion du premier vol de la fusée Ariane 5, en 1996, avait pour cause, entre autres, un logiciel écrit en Ada.
Un code écrit pour Ariane 4 avait été réutilisé sur Ariane 5, dont les caractéristiques de vol diffèrent ; une valeur inattendue, trop grande, a déclenché un Constraint_Error lors d’une conversion, qui n’a pas été traité correctement, provoquant l’arrêt du système.
Voici ce que cet accident nous montre.
La vérification à l'exécution du langage a bien détecté le problème (il n'y a pas eu de rupture silencieuse)
Mais les hypothèses d'exploitation avaient changé, et cela n'avait pas été revérifié
La conception après le déclenchement de l'exception (le mode de repli sûr) était insuffisante
Ni le système de types ni les contrats ne peuvent remplacer le processus de remise en question des hypothèses.
Le langage fait partie de l’ingénierie de la sécurité, il n’en constitue pas la totalité.
C’est, je crois, l’avertissement le plus honnête à garder à l’esprit en apprenant Ada.
20. Logiciels à longue durée de vie et Ada ── Du point de vue de la maintenance
Sur ce site, nous traitons souvent de la maintenance et du prolongement de la durée de vie des actifs Windows existants.
Sous cet angle, Ada présente un autre type d’attrait.
Il n’est pas rare que des systèmes écrits en Ada continuent de fonctionner à l’échelle de plusieurs décennies.
Et la conception même du langage Ada part du principe d’une maintenance sur le long terme.
Séparation de la spec (.ads) et de l'implémentation (.adb)
-> Un mainteneur, 20 ans plus tard, peut comprendre l'interface en ne lisant que la spec
Types forts et contraintes de plage
-> Les hypothèses implicites restent dans le code, non dans la tradition orale ou les commentaires
Contrats (Pre/Post)
-> « Les promesses de cette fonction » restent sous une forme vérifiable
Vérification d'exhaustivité de case
-> Le compilateur énumère lui-même les endroits affectés par un changement de spécification
Rétrocompatibilité privilégiée même à travers les révisions du standard
-> Une grande partie du code Ada 83 compile encore avec les compilateurs modernes
Tout cela peut être importé tel quel comme principes de conception, y compris pour la maintenance à long terme en C# ou en C++.
Définir des types porteurs de sens (types représentant un identifiant, types porteurs d'une unité) plutôt que d'utiliser int
Concevoir des types dont les valeurs invalides ne peuvent pas être construites (validation dans le constructeur)
Séparer consciemment l'interface publique de l'implémentation
Exprimer les préconditions et postconditions par des assertions et des tests
Écrire les switch sur des types énumérés de façon exhaustive, et traiter les avertissements comme des erreurs
Même sans jamais utiliser Ada professionnellement, apprendre sa philosophie de conception a une valeur certaine.
En tant que support pédagogique pour ressentir ce que signifie « exprimer la conception par les types », Ada reste aujourd’hui de tout premier ordre.
21. Conclusion
Nous avons passé en revue l’attrait d’Ada.
Récapitulons les points essentiels.
Ada est un langage bien vivant, utilisé sans interruption depuis plus de 40 ans dans les systèmes à haute fiabilité
Son nom vient d'Ada Lovelace, et le dernier standard en date est Ada 2022
Des types de même structure mais de noms différents sont des types différents : confondre des unités devient une erreur de compilation
Les contraintes de plage empêchent les valeurs invalides au niveau même du type
Les tableaux sont vérifiés sur leurs bornes, et les débordements de tampon ne deviennent pas un comportement indéfini
Les packages séparent la spec de l'implémentation, et les modes de paramètres rendent le flux de données explicite
Les génériques énoncent dans la spécification les opérations requises, ce qui rend les erreurs d'utilisation limpides
Les contrats d'Ada 2012 (Pre/Post) laissent la spécification dans le code, sous une forme vérifiable
Les tâches et les objets protégés permettent d'écrire la concurrence de façon sûre, comme une fonctionnalité du langage
Avec SPARK, un contrat peut être promu d'une vérification à l'exécution à une preuve mathématique
Avec GNAT et Alire, on peut l'essayer immédiatement et gratuitement, même sous Windows
Ses faiblesses sont la petite taille de l'écosystème et la rareté des talents
Les mécanismes de sécurité du langage ne remplacent pas le processus de remise en question des hypothèses
En termes de popularité, Ada est un langage qui n’est jamais devenu dominant.
Pourtant, la sécurité des types nuls, la vérification d’exhaustivité, les contrats, une rigueur proche de la notion de propriété (ownership) — autant de choses que les langages modernes introduisent comme des « nouveautés » —, Ada les possédait déjà depuis des décennies.
L’essence d’Ada peut se résumer en une phrase.
Un bogue n’est pas quelque chose qu’on trouve, mais quelque chose que les types et les contrats rendent impossible à écrire.
Un week-end, créez un projet avec Alire, et écrivez un petit programme en vous laissant gronder par le compilateur.
Le jour où vous réaliserez que chacune de ces erreurs de compilation est « un bogue attrapé avant de devenir un incident en production », l’attrait d’Ada vous paraîtra évident.
Références
- Collection de code de référence organisée par chapitre pour cet article - komurasoft-blog-samples (GitHub)
- Ada Programming Language - AdaCore
- Learn Ada - AdaCore (learn.adacore.com)
- Introduction to Ada - learn.adacore.com
- Ada Reference Manual (Ada 2022)
- Alire - Ada Library Repository
- GNAT User’s Guide - GCC
- SPARK - AdaCore
- Introduction to SPARK - learn.adacore.com
- Ada Conformity Assessment Authority
- Ariane 501 Inquiry Board Report (ESA)
Articles associés
Articles récents partageant les mêmes étiquettes, pour approfondir des sujets proches.
La programmation générique en Ada ── Écrire des contrats par les types, réaliser une réutilisation à coût nul
Une présentation systématique de la programmation générique en Ada : sous-programmes génériques, paquets génériques, paramètres formels d...
Introduction à la vérification formelle avec SPARK ── Des contrats Ada à la preuve mathématique
Une introduction pratique à la vérification formelle avec SPARK, le sous-ensemble d'Ada. L'article couvre le passage des contrats (Pre/Po...
Concurrence sécurisée avec Ada ── Guide pratique des tâches et objets protégés
Un article d'introduction aux tâches et objets protégés, la concurrence intégrée au langage Ada. Il couvre le rendez-vous (entry/accept),...
Programmation de systèmes temps réel avec Ada ── Priorités, exécution périodique et contrôle du temps d'exécution en pratique
Découvrez l'Annexe D d'Ada (systèmes temps réel) à travers 8 exemples de code pratiques. L'article couvre progressivement les priorités d...
Gestion des erreurs et stratégie de nouvelle tentative dans Power Automate — éviter qu'un flux qui fonctionnait ne s'arrête sans que personne ne le remarque
Un ensemble de modèles de conception pour éviter qu'un flux Power Automate ne s'arrête sans que personne ne s'en aperçoive. Nous détaillo...
Sujets associés
Ces pages replacent le sujet dans un contexte plus large de services et de décisions.
Thèmes techniques Windows
Portail des sujets sur le développement Windows, l'analyse des incidents et la valorisation des actifs existants.
Questions fréquentes
Questions souvent posées lors d’une consultation sur le sujet de cet article.
- Le langage Ada est-il encore utilisé aujourd'hui ?
- Oui, il l'est. Ada continue d'être utilisé depuis des décennies dans des systèmes à haute fiabilité où un bogue se traduit directement par une perte de vies humaines ou des pertes financières considérables : contrôle de vol des avions civils, contrôle du trafic aérien, systèmes de signalisation et de sécurité ferroviaires, fusées et satellites, systèmes de défense, dispositifs médicaux, et une partie des systèmes centraux de la finance et de l'industrie. Ada est un langage révisé en continu depuis plus de 40 ans : après Ada 83 en 1983, il est passé par Ada 95, Ada 2005 et Ada 2012, et le dernier standard en date est Ada 2022.
- En quoi le typage fort d'Ada diffère-t-il des autres langages ?
- En Ada, deux types déclarés sous des noms différents sont traités comme des types distincts, même si leur structure est strictement identique. Par exemple, si l'on crée un type Meters et un type Seconds à partir de Float, les mélanger provoque une erreur de compilation, et toute conversion doit être écrite explicitement. De plus, un subtype peut faire porter au type une contrainte de plage de valeurs (par exemple de 0 à 100), et toute violation déclenche à l'exécution une exception Constraint_Error. C'est une conception qui ne repose pas sur la « vigilance », la « relecture » ou les « tests » pour repérer des bogues comme la confusion d'unités, mais qui empêche purement et simplement la compilation de réussir.
- Comment essayer gratuitement le langage Ada ?
- Avec GNAT (le compilateur Ada gratuit inclus dans GCC) et Alire (le gestionnaire de packages et outil de build d'Ada, dont la commande est alr), un environnement de développement complet et gratuit est disponible sous Windows, Linux et macOS. Récupérez l'installateur depuis le site officiel d'Alire, créez un squelette de projet avec alr init --bin, compilez avec alr build et exécutez avec alr run — une expérience proche de cargo pour Rust. Alire récupère lui-même la chaîne d'outils (le compilateur), donc aucune installation manuelle du compilateur n'est nécessaire. VS Code dispose d'une extension Ada développée par AdaCore.
- Quelles sont les faiblesses du langage Ada ?
- On peut citer un écosystème restreint, avec moins de choix que les langages dominants pour les frameworks web, les interfaces graphiques (GUI) ou les SDK cloud ; une pénurie de talents et d'information (particulièrement en japonais), ce qui impose de prévoir un coût de formation ; des déclarations de types et une séparation spécification/corps qui peuvent sembler lourdes pour de petits scripts ; et un marché de l'emploi concentré sur l'aérospatiale, la défense et le ferroviaire. Il faut aussi garder à l'esprit, comme le montre l'accident d'Ariane 5 en 1996, que les mécanismes de sécurité du langage ne remplacent pas le processus de remise en question des hypothèses de départ.
Profil de l’auteur
Page de présentation de l’auteur de l’article.
Go Komura
Représentant de KomuraSoft LLC
Spécialisé dans le développement de logiciels Windows, le conseil technique et l’analyse de pannes, notamment pour les systèmes existants et les incidents difficiles à reproduire.
Liens publics