Привлекательность языка Ada ── типы выражают проект, язык, который держит ПО, работающее десятилетиями

· Обновлено: · · Ada, Язык программирования, Сильная типизация, SPARK, GNAT, Alire, HighIntegrity, Встраиваемые системы, Высокая надёжность

История изменений (1 обновлений, последнее 30 Aug 2026)

Журнал изменений этой статьи. Там, где версия до правки была заархивирована, она остаётся доступной для чтения по постоянной ссылке с DOI.

Русский текст переписан как полноценный технический перевод, а не калька с японского. Утверждения статьи не менялись.
Первая публикация
Цитирование статьи(DOI (зарегистрированный архив): 10.5281/zenodo.21619907)

Приведённые ниже DOI относятся к ранее зарегистрированным архивным версиям, которые могут отличаться от текущего текста. Для ссылки на текущий текст используйте URL этой страницы.

Го Комура (2026). Привлекательность языка Ada ── типы выражают проект, язык, который держит ПО, работающее десятилетиями. KomuraSoft LLC. https://comcomponent.com/ru/blog/ada-language-appeal/

DOI (зарегистрированный архив)
10.5281/zenodo.21619907
DOI (последняя зарегистрированная версия)
10.5281/zenodo.21619908

1. Что стоит зафиксировать сразу

Приходилось ли вам слышать название языка Ada?

У многих складывается впечатление: «старый язык», «военный язык», «название мелькало на паре».

Но Ada — действующий язык и сегодня.

В мире программ, где остановка стоит человеческих жизней — управление полётом самолётов, железнодорожная сигнализация, ракеты, управление воздушным движением, спутники, медицинское оборудование — Ada применяют уже несколько десятилетий подряд.

При знакомстве с Ada важно держать такую рамку.

Ada — язык, который направил все усилия на то, чтобы гасить ошибки до выполнения
Типы — не контейнеры для данных, а инструмент, которым выражают замысел проекта
Разделение спецификации и реализации, контракты и параллелизм встроены в язык
Модой Ada не стала, но её философия проектирования перешла в современные языки

В статье разберём историю Ada, синтаксис, сильную типизацию, ограничения диапазона, пакеты, контрактное проектирование, задачи, SPARK, среду разработки и слабые стороны — то, из чего складывается привлекательность Ada.

Цель — чтобы те, кто обычно пишет на C#, C++ или Java, унесли ощущение «говорить о проекте через типы».

Всего 21 глава, текст длинный, поэтому сначала карта.

Структура статьи:
  гл. 1–3    что такое Ada, история, где её используют
  гл. 4–5    первый шаг (Hello, World) и синтаксис, где важнее читаемость
  гл. 6–11   система типов: сильная типизация, ограничения диапазона, массивы, пакеты, записи, дженерики
  гл. 12–13  исключения и контрактное проектирование (Pre/Post)
  гл. 14–15  параллелизм: задачи и защищённые объекты
  гл. 16–17  формальная верификация SPARK и взаимодействие с C/C++
  гл. 18     среда разработки (GNAT и Alire)
  гл. 19–21  слабые стороны и оговорки, ценность с точки зрения долгоживущего ПО, итог

Текст написан так, чтобы выборочное чтение тоже складывалось: кто хочет сразу запустить код — главы 4 и 18, кто хочет только философию проектирования — главы 1, 6, 7 и 13.

Фрагменты кода из статьи выложены на GitHub как справочный набор, разложенный по файлам согласно главам.

ada-language-appeal - komurasoft-blog-samples (GitHub)

На схеме сплошная линия обозначает отношение, которое выполняется всегда, а пунктирная — условное отношение (условия указаны в пояснении к каждому отношению на странице сведений). Полный список отношений (всего 18, с доказательствами и степенью уверенности) и определения основных понятий собраны на странице сведений карты знаний (на японском). Данные: JSON-LD / Turtle

2. Что такое Ada ── происхождение названия и история

Ada — язык программирования общего назначения. Он появился в конце 1970-х по инициативе Министерства обороны США (DoD).

Тогда у министерства была проблема: в каждом проекте свой язык, и стоимость сопровождения ПО росла.

Поэтому стандартный язык, пригодный и для встраиваемых систем, и для систем реального времени, выбирали международным конкурсом проектов.

Победил проект команды Жана Ишбиа (Jean Ichbiah).

Имя языка Ada восходит к Аде Лавлейс (Августа Ада Кинг, графиня Лавлейс), которую называют первым программистом в мире.

Историю Ada коротко можно выстроить так.

1980  принята первая спецификация MIL-STD-1815
1983  Ada 83 (стандарт ANSI)
1987  становится стандартом ISO
1995  Ada 95 (объектная ориентация и защищённые объекты)
2005  Ada 2005 (интерфейсы, расширение библиотеки контейнеров)
2012  Ada 2012 (контрактное проектирование как возможность языка)
2022  Ada 2022 (актуальный стандарт)

Ada 95 — один из самых ранних объектно-ориентированных языков, стандартизированных ISO.

А в Ada 2012 в спецификацию языка вошло контрактное проектирование (Design by Contract): предусловия, постусловия и инварианты типа.

Ada — не «устаревший язык», а язык, который пересматривают уже больше 40 лет.

3. Где используют Ada

Ada продолжают применять там, где нужна высокая надёжность (High Integrity).

управление полётом и авионика гражданских самолётов
системы управления воздушным движением
железнодорожная сигнализация и системы безопасности
ракеты и искусственные спутники
оборонные системы
медицинское оборудование
часть критически важных систем в финансах и промышленности

У этих областей общие черты.

ошибка напрямую стоит жизней или огромных потерь
сертификация и аудит требуют оснований правильности
после развёртывания система живёт десятилетиями
стоимость позднего исправления чрезвычайно высока

Мир, где не работает «выпустим, потом поправим».

Проектирование Ada отвечает именно на эти требования.

Через весь язык проходит одна идея: ошибки, которые можно поймать при компиляции, ловят при компиляции; то, что видно только во время выполнения, — проверками времени выполнения; а то, что можно доказать математически, гасят доказательством.

Этой философии есть что взять и тем, кто пишет обычные веб- и десктопные приложения.

4. Сначала Hello, World

Посмотрим на код Ada.

with Ada.Text_IO;

procedure Hello is
begin
   Ada.Text_IO.Put_Line ("Hello, Ada!");
end Hello;

Этот код можно сохранить и сразу запустить. Основная реализация Ada — GNAT (бесплатный компилятор Ada в составе GCC; опции компиляции в примерах этой статьи тоже относятся к GNAT). Сборка и запуск — две строки.

gnatmake hello.adb   -> создаётся исполняемый файл hello (в Windows — hello.exe)
./hello              -> Hello, Ada!

Имя файла принято брать так: «имя единицы в нижнем регистре + .adb» (для procedure Hello это hello.adb). Как получить сам GNAT и более удобный путь через менеджер пакетов Alire — в главе 18. Если хотите сначала собрать среду и потрогать код руками, можно сразу перейти к главе 18.

Скорее всего, сразу бросится в глаза следующее.

with подключает библиотечную единицу
тело программы — процедура (procedure)
begin / end ограничивают блок
после end имя повторяется
операторы заканчиваются точкой с запятой

Повтор имени в конце, как в end Hello;, — характерная черта Ada.

Даже при глубокой вложенности сразу видно, какому блоку принадлежит этот end.

Компилятор тоже проверяет соответствие имён, поэтому неправильно закрытый блок становится ошибкой компиляции.

Мелочь, но хорошо показывает философию Ada: язык подставляет плечо там, где человек легко ошибается при чтении.

5. Синтаксис, где важнее читаемость

Синтаксис Ada спроектирован так, что читаемость важнее удобства написания.

Исходная посылка: программу читают гораздо чаще, чем пишут.

Циклы и ветвления, например, выглядят так.

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;

У оператора case есть характерная для Ada черта.

case Today is
   when Mon .. Fri =>
      Put_Line ("Weekday");
   when Sat | Sun =>
      Put_Line ("Weekend");
end case;

Ключевые моменты такие.

case, который не покрывает все значения, — ошибка компиляции
неявного проваливания (fall-through), как в языках семейства C, нет
условия можно группировать диапазоном (Mon .. Fri) и перечислением вариантов (Sat | Sun)

Если в перечисление добавить значение, все case, которые его не покрывают, становятся ошибками компиляции.

Ощущение, что «компилятор сам перечисляет места, затронутые сменой спецификации», один раз попробовав, уже не хочется терять.

Аргументы можно передавать именованным сопоставлением.

Draw_Rectangle (Left => 10, Top => 20, Width => 100, Height => 50);

Так труднее перепутать аргументы, а код вызова сам становится документацией.

Присваивание — :=, сравнение — =. Путаница вроде if (a = b) из языков семейства C на уровне синтаксиса не возникает.

6. Сильная типизация ── путаницу единиц делаем ошибкой компиляции

Главная привлекательность Ada — сильная типизация.

Слова «сильная типизация» употребляют во многих языках, но в Ada это понятие идёт на ступень глубже.

В Ada типы, объявленные под разными именами, — разные типы, даже если структура полностью совпадает.

type Meters  is new Float;
type Seconds is new Float;

Distance : Meters  := 100.0;
Time     : Seconds := 9.58;

По сути оба — числа с плавающей точкой, но смешивать их нельзя.

Distance := Time;            -- ошибка компиляции
Distance := Distance + Time; -- ошибка компиляции

Преобразование пишут явно, только когда оно действительно нужно.

Speed : constant Float := Float (Distance) / Float (Time);

Зачем такая строгость?

В реальных авариях ПО нередко виновата именно «путаница единиц измерения».

Известный пример: в 1999 году марсианский аппарат Mars Climate Orbiter потеряли из-за смешения английской и метрической систем.

Ответ Ada простой.

сделать метры и футы разными типами
смешение сделать ошибкой компиляции
преобразование заставить писать явно

Не «быть внимательным», не «найти на ревью», не «поймать тестом», а сделать так, чтобы сборка просто не проходила.

В этом базовая позиция Ada.

7. Ограничения диапазона ── недопустимые значения режем на уровне типа

В Ada типу можно задать диапазон значений.

subtype Percentage is Integer range 0 .. 100;

Progress : Percentage := 50;

Попытка записать в переменную типа Percentage значение вне диапазона во время выполнения даёт исключение Constraint_Error.

Progress := 120;  -- Constraint_Error во время выполнения

Нарушения, видимые при компиляции, ловят при компиляции.

Неявные допущения вроде «значение от 0 до 100» или «не меньше 1» можно выразить не комментарием, а типом.

В стандартной библиотеке Ada заранее определены часто нужные типы с ограничениями.

Natural  = Integer range 0 .. Integer'Last
Positive = Integer range 1 .. Integer'Last

Начиная с Ada 2012 к типу можно прикрепить произвольное условие-предикат.

subtype Even is Integer
  with Dynamic_Predicate => Even mod 2 = 0;

В язык встроены и типы для управления аппаратурой, например с фиксированной точкой.

type Temperature is delta 0.1 range -50.0 .. 150.0;

Во многих языках проверка недопустимых значений часто выглядит так.

валидация через if в начале функции
пропущенные проверки остаются на совести ревью
неясно, какие функции уже получают проверенные значения

В Ada можно сказать: «раз значение этого типа, диапазон уже гарантирован».

Когда ответственность за валидацию переходит к типу данных, логика функции может заняться своей настоящей работой.

8. Массивы и индексы ── проверка границ и индексация перечислениями

В массивах Ada можно свободно выбирать тип индекса.

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);

Это массив, индексированный перечислением Day.

К нему обращаются как Work_Hours (Wed), и не нужно помнить, «что означает этот числовой индекс».

Циклы тоже пишут вдоль типа индекса.

for D in Work_Hours'Range loop
   Put_Line (Day'Image (D) & ":" & Natural'Image (Work_Hours (D)));
end loop;

Атрибуты 'Range, 'First, 'Last, 'Length в любой момент дают сведения о границах массива.

Границы нигде не зашиты жёстко, поэтому смена размера массива не расползается по циклам.

И важно: доступ к массиву всегда проверяется на выход за границы.

Buffer : String (1 .. 10);
Index  : Integer := 11;

Buffer (Index) := 'x';  -- Constraint_Error во время выполнения

Переполнение буфера в C/C++ десятилетиями остаётся одной из главных причин уязвимостей.

В Ada выход за границы — не неопределённое поведение, а определённое исключение.

Память не портят молча, чтобы потом загадочно упасть в другом месте. Программа сразу останавливается в точке возникновения проблемы, явно сигнализируя об ошибке.

Если учесть стоимость расследования в системах с долгим сроком жизни, разница огромная.

9. Пакеты ── разделение спецификации и реализации

Модульный механизм Ada — пакет (package).

Пакет делится на два файла: спецификацию (spec) и тело (body).

counters.ads  спецификация: интерфейс, открытый вовне
counters.adb  тело: детали реализации

Спецификация пишется так.

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;

Тело пишется так.

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;

Обратите внимание на следующее.

если объявить тип как private, вызывающая сторона не доберётся до внутренней структуры
прочитав только спецификацию (.ads), можно полностью понять, как пользоваться пакетом
изменение тела (.adb) при той же спецификации даёт минимальную перекомпиляцию у пользователей пакета

Это похоже на заголовочные файлы C/C++, но это не текстовое включение через #include: согласованность проверяется на уровне спецификации языка.

Расхождение спецификации и тела — ошибка компиляции.

У каждого аргумента обязательно указывают режим: in, out или in out.

procedure Increment (C : in out Counter);

Достаточно взглянуть на сигнатуру, чтобы понять: аргумент только читают, только пишут или читают и пишут.

Направление потока данных читается без знаний об указателях и ссылках.

10. Записи и дискриминанты

Аналог структуры в Ada — запись (record).

type Point is record
   X : Float := 0.0;
   Y : Float := 0.0;
end record;

P : Point := (X => 1.0, Y => 2.0);

У полей могут быть значения по умолчанию, а инициализация — именованным агрегатом.

Характерная для Ada возможность — дискриминант (discriminant).

type Buffer (Size : Positive) is record
   Data   : String (1 .. Size);
   Length : Natural := 0;
end record;

Small : Buffer (Size => 16);
Large : Buffer (Size => 4096);

Дискриминант — параметр, который задаёт «форму» записи.

Buffer (16) и Buffer (4096) — один и тот же тип, но размер внутреннего массива фиксируется в момент объявления и дальше не меняется.

Это похоже на «структуру с членом переменной длины плюс поле размера» в C, только язык сам безопасно ведёт это как тип.

Рассогласование размера и фактических данных — частая точка входа для ошибок в C — здесь с самого начала не возникает.

11. Дженерики

У Ada дженерики (generic) были уже в первом стандарте 1983 года.

Это намного раньше шаблонов C++ (1990-е) и дженериков 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;

Пользователь инстанцирует дженерик конкретным типом.

procedure Swap_Integers is new Swap (Element => Integer);
procedure Swap_Floats   is new Swap (Element => Float);

Особенность дженериков Ada в том, что требуемые операции указывают явно.

generic
   type Element is private;
   with function "<" (Left, Right : Element) return Boolean is <>;
function Max (Left, Right : Element) return Element;

В спецификации прямо написано: «эта обобщённая функция требует от типа Element операцию сравнения».

Проблема, которая долго мучила шаблоны C++ — «ошибка видна только при инстанцировании», — в Ada с самого начала не возникает.

То, что пытались закрыть concepts в C++20 и границы трейтов (trait bounds) в Rust, у Ada было ответом уже 40 лет назад.

12. Обработка исключений

В Ada есть обработка исключений.

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 ("Конфигурационный файл не найден");
   when E : others =>
      Ada.Text_IO.Put_Line (Ada.Exceptions.Exception_Information (E));
      raise;
end Read_Config;

В конце блока пишут раздел exception и перечисляют обработчики по видам исключений.

Среди определённых языком исключений типичны такие.

Constraint_Error  нарушение ограничения диапазона, выход за границы массива, деление на ноль и т. п.
Program_Error     нарушение правил языка (например, попадание туда, куда выполнение не должно доходить)
Storage_Error     нехватка памяти
Tasking_Error     сбой во взаимодействии между задачами (task)

Важно, что нарушения ограничений диапазона и проверок границ полностью входят в этот механизм исключений.

Если нарушено «ограничение, записанное в типе», возникает Constraint_Error.

Иными словами, ограничения диапазона из главы 7 работают как автоматически порождённые утверждения времени выполнения (run-time assertions).

Не нужно самому разбрасывать по коду проверки через if.

13. Контрактное проектирование ── Pre и Post как возможность языка

Главная новинка Ada 2012 — поддержка контрактного проектирования (Design by Contract) на уровне языка.

Для подпрограмм можно писать предусловия (Pre) и постусловия (Post) прямо в коде.

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
   -- детали реализации
end Stacks;

Pre — «обещание, которое должна соблюдать вызывающая сторона», Post — «обещание, которое гарантирует реализация».

Атрибут 'Old позволяет обратиться к значению до вызова.

Эти контракты можно включить как проверки времени выполнения опцией компилятора (-gnata в GNAT).

При нарушении контракта возникает исключение Assertion_Error, и становится ясно, какая сторона нарушила обещание.

нарушение Pre  -> ошибка вызывающей стороны
нарушение Post -> ошибка реализации

Чем это отличается от комментария «эту функцию нельзя вызывать для пустого стека»?

комментарий может разойтись с реализацией, и никто этого не заметит
контракт проверяется компилятором по синтаксису и типам
контракт можно автоматически проверить во время выполнения
контракт становится входом для статического доказательства SPARK (глава 16)

Спецификация живёт внутри кода в проверяемой форме.

Так устроен мир Ada 2012 и последующих версий.

Через инварианты типа (Type_Invariant) можно записать и такое ограничение: «значение этого типа всегда удовлетворяет данному свойству».

14. Задачи ── параллелизм, встроенный в язык

Ещё одна сильная сторона Ada: параллелизм — часть спецификации языка.

C/C++ опираются на API ОС или библиотеки (pthread, std::thread). Ada встроила задачи (task) в язык ещё в 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;

Как только объявлен task, параллельное выполнение начинается вместе со стартом охватывающего блока.

И важно: блок не завершится, пока не завершатся все задачи внутри него.

Класс ошибок вроде «забыли join у потока, и при завершении процесса происходит что-то странное» здесь структурно невозможен.

Для синхронизации между задачами в языке есть рандеву (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;

Со стороны вызывающего кода это выглядит как обычный вызов процедуры: Logger.Write ("hello");.

Обмен сообщениями между задачами можно писать, не имея понятия о блокировках.

Для систем реального времени стандартизированы политика планирования и управление приоритетами, а для проверяемости — ещё и профиль Ravenscar, который ограничивает возможности задач.

15. Защищённые объекты ── взаимоисключение как тип

Для взаимоисключающего доступа к разделяемым данным в Ada 95 введены защищённые объекты (protected object).

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;

К данным защищённого объекта можно обратиться только через определённые для него операции.

Взаимоисключение гарантирует сам язык.

procedure  чтение и запись разрешены, выполняется монопольно
function   только чтение, допускает одновременное выполнение несколькими задачами
entry      может заставить вызывающую сторону ждать, пока не выполнится условие (барьер)

Во многих языках взаимоисключение часто держится на дисциплине.

перед обращением к этим данным нужно взять этот мьютекс
не забыть освободить блокировку
соблюдать порядок захвата блокировок

В защищённых объектах Ada «код, забывший взять блокировку», написать нельзя.

Данные и защищающее их взаимоисключение объявляются как один тип.

Через барьерные условия entry условную синхронизацию вроде «ждать, пока в очередь не поступят данные» пишут без ручного управления флагами и переменными условия.

16. SPARK ── путь к формальной верификации

В мире Ada есть сильный союзник — SPARK.

SPARK — подмножество Ada, спроектированное так, чтобы свойства программы можно было доказывать математически.

procedure Increment (X : in out Integer)
  with SPARK_Mode,
       Pre  => X < Integer'Last,
       Post => X = X'Old + 1;

Инструмент SPARK (GNATprove) для такого кода без выполнения доказывает следующее.

что не будет переполнения
что не будет нарушения ограничения диапазона
что не будет деления на ноль
что не будет чтения неинициализированной переменной
согласованность Pre и Post

Отличие от тестирования принципиальное.

тест         подтверждает корректную работу на выбранных входах
доказательство  показывает, что свойство выполняется для всех входов

Контракты (Pre/Post) из главы 13 в SPARK становятся прямым объектом доказательства.

Контракт, написанный как проверка времени выполнения, потом можно повысить до статуса доказанного.

SPARK накопил опыт в авиации и обороне. В последние годы применение шире: например, NVIDIA использует его для защиты прошивок.

Стереотип «формальные методы слишком академичны для практики» экосистема Ada/SPARK продолжает опровергать.

17. Взаимодействие с C и C++

Ada — не изолированный язык.

Взаимодействие с C стандартизировано в приложении B спецификации языка (Annex B ── раздел Ada Reference Manual, ARM, «Interface to Other Languages», который задаёт интерфейсы с другими языками, включая C и Fortran).

Например, чтобы вызвать из Ada функцию Sleep из Windows API, пишут так.

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;

Ключевые моменты такие.

Import           подключает внешнюю реализацию
Convention       задаёт соглашение о вызовах (C, Stdcall и т. п.)
External_Name    задаёт имя символа для компоновщика
Interfaces.C     даёт типы, соответствующие типам C (int, unsigned, char* и т. п.)

Возможно и обратное направление.

Через Export процедуру, написанную на Ada, можно открыть как функцию, вызываемую из C.

То есть можно идти поэтапно.

использовать существующую библиотеку C из Ada
написать на Ada/SPARK только ядро системы, оставив периферию на C/C++
собрать код Ada в DLL и вызывать его из других языков

Это не язык, где единственный путь — «переписать всё». Можно жить рядом с уже существующими наработками и повышать надёжность, начиная с самых важных частей.

18. Среда разработки ── GNAT и Alire (работает и в Windows)

Может показаться, что для знакомства с Ada нужны дорогие инструменты.

Сейчас полноценную бесплатную среду можно собрать.

GNAT        компилятор Ada в составе GCC (бесплатный)
Alire       менеджер пакетов и инструмент сборки для Ada
GNAT Studio IDE от AdaCore
VS Code     с расширением Ada Language Server — автодополнение и переход к определению

Именно появление Alire (команда alr) сильно упростило вход в Ada.

Опыт близкий к cargo в Rust.

alr init --bin hello_ada
cd hello_ada
alr build
alr run

alr init создаёт проект, alr build собирает, alr run запускает.

Сам тулчейн (компилятор GNAT) тоже подтягивает Alire, поэтому ставить компилятор вручную не нужно.

Работает и в Windows, и в Linux, и в macOS.

Если вы разрабатываете в Windows, кратчайший путь такой.

1. Скачать установщик для Windows с официального сайта Alire
2. Создать шаблон командой alr init --bin
3. Поставить в VS Code расширение Ada (от AdaCore)
4. Собрать и запустить командой alr build

Библиотеки добавляются командой alr with имя_библиотеки.

Время, когда всё заканчивалось на настройке окружения, прошло.

19. Слабые стороны Ada и на что смотреть

Мы разобрали привлекательные стороны Ada, но слабости тоже есть.

Разберём их честно.

маленькая экосистема
  мало вариантов веб-фреймворков, GUI, облачных SDK
  число пакетов в Alire на порядки меньше, чем у основных языков

мало специалистов и материалов
  материалов на японском особенно мало
  для внедрения в командную разработку нужно закладывать обучение

синтаксис может казаться избыточным
  объявление типов и разделение спецификации/тела тяжеловаты для мелких скриптов
  не подходит для задач «лишь бы заработало»

рынок труда ограничен
  смещён в авиакосмос, оборону, железные дороги и близкие области

История также показывает: «используешь Ada — значит, безопасно» в такой простой форме неверно.

Взрыв первой ракеты Ariane 5 в 1996 году произошёл в том числе из-за ПО, написанного на Ada.

Код, написанный для Ariane 4, повторно использовали на Ariane 5 с другими лётными характеристиками. Неожиданно большое значение при преобразовании вызвало Constraint_Error, его не обработали как следует, и система остановилась.

Конкретный ход событий такой. В инерциальном эталоне (SRI) было преобразование BH (Horizontal Bias, внутреннее значение, связанное с горизонтальной скоростью), которое считают для выставки до взлёта, из 64-битного числа с плавающей точкой в 16-битное знаковое целое. Траектория Ariane 5 даёт примерно в пять раз большую горизонтальную скорость, чем у Ariane 4, поэтому BH вышел за диапазон 16 бит, и преобразование не удалось (в отчёте расследования это «ошибка операнда»; как проверка диапазона Ada это преобразование вне диапазона). При этом преобразование не было защищено. У вычислителя SRI была цель «максимальная нагрузка 80 %», поэтому из семи рискованных переменных защитили четыре, а остальные три оставили без защиты, решив, что «они физически ограничены или запас достаточный». Для BH это суждение на Ariane 5 уже не держалось.

Эта авария показывает следующее.

проверка времени выполнения языка проблему обнаружила (система не сломалась молча)
но условия эксплуатации изменились, а повторной проверки не было
проектирование поведения после исключения (fail-safe) оказалось недостаточным

Ни система типов, ни контракты не заменяют процесс пересмотра исходных допущений.

Язык — часть инженерии безопасности, но не вся инженерия безопасности.

Пожалуй, это самое честное предостережение при изучении Ada.

20. Долгоживущее ПО и Ada ── взгляд со стороны сопровождения

На этом сайте мы часто разбираем сопровождение и продление жизни существующих Windows-решений.

С этой точки зрения у Ada есть и другая привлекательность.

Системы, написанные на Ada, нередко работают десятилетиями.

И само проектирование языка Ada рассчитано на долгосрочное сопровождение.

разделение спецификации (.ads) и реализации (.adb)
  -> сопровождающий через 20 лет поймёт интерфейс, прочитав только спецификацию

сильные типы и ограничения диапазона
  -> неявные допущения остаются в коде, а не в устной передаче и комментариях

контракты (Pre/Post)
  -> «обещание этой функции» остаётся в проверяемой форме

проверка полноты case
  -> компилятор сам перечисляет места, затронутые сменой спецификации

даже при пересмотре стандарта важна обратная совместимость
  -> значительная часть кода Ada 83 проходит на современных компиляторах

Всё это можно напрямую брать как принципы проектирования и при долгосрочном сопровождении на C# или C++.

определять осмысленные типы (тип для ID, тип с единицей измерения) вместо голого int
проектировать типы, для которых нельзя создать недопустимое значение (проверка в конструкторе)
осознанно разделять публичный интерфейс и реализацию
выражать предусловия и постусловия через утверждения и тесты
писать switch по перечислению исчерпывающе и относиться к предупреждениям как к ошибкам

Даже если Ada на работе не пригодится, изучать её философию проектирования стоит.

Как учебный материал, чтобы почувствовать, что значит «говорить о проекте через типы», Ada и сегодня первоклассный пример.

21. Итог

Мы разобрали привлекательность Ada.

Коротко по пунктам.

Ada — действующий язык, который больше 40 лет используют в высоконадёжных системах
название восходит к Ada Lovelace, актуальный стандарт — Ada 2022
типы с одинаковой структурой, но разными именами — разные типы; смешение единиц — ошибка компиляции
ограничения диапазона не дают недопустимым значениям появиться на уровне типа
массивы проверяют по границам, переполнение буфера не становится неопределённым поведением
пакеты разделяют спецификацию и реализацию, режимы параметров явно показывают поток данных
дженерики фиксируют требуемые операции в спецификации, поэтому ошибки использования видны сразу
контракты (Pre/Post) Ada 2012 оставляют спецификацию в коде в проверяемой форме
задачи (task) и защищённые объекты позволяют безопасно писать параллелизм как возможность языка
через SPARK контракты можно поднять с проверок времени выполнения до математического доказательства
благодаря GNAT и Alire язык можно сразу и бесплатно попробовать даже в Windows
слабые стороны — небольшая экосистема и нехватка специалистов
механизмы безопасности языка не заменяют процесс пересмотра исходных допущений

По моде Ada так и не стала массовым языком.

Но многое из того, что современные языки подают как «новые возможности» — null-безопасность, проверка полноты, контракты, строгость, близкая к владению (ownership), — у Ada было уже десятилетия назад.

Суть Ada можно свести к одной фразе.

Ошибки не ищут. Типы и контракты делают так, чтобы их нельзя было написать.

Попробуйте на выходных создать в Alire один проект и написать небольшую программу, пока компилятор вас поправляет.

Когда станет ясно, что каждая из этих ошибок компиляции — «баг, пойманный до сбоя в продакшене», привлекательность Ada сложится в цельную картину.

Справочные материалы

Недавние статьи с теми же тегами помогут подробнее изучить близкие темы.

Эти страницы показывают тему статьи в более широком контексте услуг и решений.

Частые вопросы

Вопросы, которые часто возникают при консультациях по теме статьи.

Язык Ada всё ещё используют?
Да. Его десятилетиями применяют в высоконадёжных системах, где ошибка напрямую угрожает жизням или оборачивается огромными потерями: в управлении полётом гражданских самолётов, в управлении воздушным движением, в железнодорожной сигнализации и системах безопасности, в ракетах и спутниках, в оборонных системах, в медицинском оборудовании. Ada пересматривают уже больше 40 лет: от Ada 83 в 1983 году через Ada 95, Ada 2005 и Ada 2012 до актуального стандарта Ada 2022.
Чем сильная типизация Ada отличается от других языков?
В Ada типы, объявленные под разными именами, считаются разными, даже если структура совпадает полностью. Если типы Meters и Seconds оба сделаны из Float, смешать их нельзя: это ошибка компиляции, а преобразование нужно писать явно. Через subtype типу можно задать ограничение диапазона (например, 0–100); нарушение во время выполнения даёт исключение Constraint_Error. Путаницу единиц не оставляют на «будьте внимательны»: сборка просто не проходит.
Как бесплатно попробовать Ada?
Полную бесплатную среду на Windows, Linux и macOS дают GNAT (бесплатный компилятор Ada в составе GCC) и Alire (менеджер пакетов и сборщик Ada, команда alr). Установщик берут с официального сайта Alire, затем alr init --bin создаёт шаблон, alr build собирает, alr run запускает — опыт близкий к cargo в Rust. Сам тулчейн тоже подтягивает Alire, вручную компилятор ставить не нужно. Для VS Code есть расширение Ada от AdaCore.
В чём слабые стороны Ada?
Экосистема невелика: веб-фреймворков, GUI и облачных SDK меньше, чем у основных языков. Специалистов и материалов мало, особенно на японском, поэтому при внедрении в команду нужно закладывать обучение. Объявление типов и разделение спецификации и тела тяжеловаты для мелких скриптов. Рынок труда смещён в авиакосмос, оборону, железные дороги. И, как показала авария Ariane 5 в 1996 году, механизмы безопасности языка не заменяют процесс пересмотра исходных допущений.

Об авторе

Страница с профилем автора статьи.

Го Комура

Представитель KomuraSoft LLC

Специализируется на разработке программного обеспечения для Windows, техническом консалтинге и расследовании сбоев, особенно в проектах с унаследованными системами и трудно воспроизводимыми ошибками.

Публичные ссылки

Вернуться в блог