Ada言語の魅力 ── 型で設計を語り、数十年動き続けるソフトウェアを支える言語
· 小村 豪 · Ada, ProgrammingLanguage, StrongTyping, SPARK, GNAT, Alire, HighIntegrity, Embedded, 高信頼性
1. 最初に押さえるべきこと
Ada という言語の名前を聞いたことはあるでしょうか。
「昔の言語」「軍用の言語」「授業で名前だけ出てきた」という印象を持っている方が多いかもしれません。
しかし、Ada は今も現役の言語です。
航空機のフライトコントロール、鉄道の信号システム、ロケット、航空管制、人工衛星、医療機器など、止まると人命に関わるソフトウェアの世界では、Ada は数十年にわたって使われ続けています。
Ada を理解するときに大事なのは、次の視点です。
Adaは「バグを実行前に潰す」ことに全力を注いだ言語である
型はデータの入れ物ではなく、設計の意図を表現する道具である
仕様と実装の分離、契約、並行処理が言語に組み込まれている
流行はしなかったが、設計思想は現代の言語に受け継がれている
この記事では、Ada の歴史、構文、強い型付け、範囲制約、パッケージ、契約による設計、タスク、SPARK、開発環境、そして弱みまで、Ada の魅力を整理します。
普段 C#、C++、Java などを書いている方が、「型で設計を語る」という感覚を持ち帰れることを目指します。
なお、この記事に登場するコード断片は、章ごとにファイルへ整理した参照用コード集として GitHub で公開しています。
ada-language-appeal - komurasoft-blog-samples (GitHub)
2. Adaとは何か ── 名前の由来と歴史
Ada は、1970 年代後半にアメリカ国防総省(DoD)の主導で生まれた汎用プログラミング言語です。
当時の国防総省は、プロジェクトごとにバラバラの言語が使われ、ソフトウェアの保守コストが膨れ上がるという問題を抱えていました。
そこで、組み込みシステムやリアルタイムシステムにも使える標準言語を、国際的な設計コンペで選定しました。
採用されたのは、Jean Ichbiah 率いるチームの設計案です。
言語名の Ada は、世界初のプログラマーと呼ばれる Ada Lovelace(オーガスタ・エイダ・キング、ラブレス伯爵夫人)に由来します。
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 の言語設計は、まさにこの要求に応えるためのものです。
コンパイル時に検出できるバグはコンパイル時に、実行時にしか検出できないものは実行時チェックで、さらに進んで数学的に証明できるものは証明で潰す、という思想が言語全体を貫いています。
この思想は、Web やデスクトップの業務アプリを書く開発者にとっても学ぶ価値があります。
4. まずはHello, World
Ada のコードを見てみます。
with Ada.Text_IO;
procedure Hello is
begin
Ada.Text_IO.Put_Line ("Hello, Ada!");
end Hello;
最初に気づくのは、次の点だと思います。
withでライブラリユニットを取り込む
プログラムの本体は手続き(procedure)
begin / endでブロックを囲む
endの後に名前を繰り返す
文はセミコロンで終わる
end Hello; のように、終端に名前を書き直すのが Ada らしいところです。
ブロックが深くなっても「この end は何の 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はすべての値を網羅しないとコンパイルエラーになる
C系言語のような暗黙のフォールスルーは存在しない
範囲(Mon .. Fri)や選択肢(Sat | Sun)で条件をまとめられる
列挙型に値を追加すると、網羅していない case 文がすべてコンパイルエラーになります。
「仕様変更の影響箇所をコンパイラが列挙してくれる」という体験は、一度味わうと手放せなくなります。
また、引数には名前付き関連付けが使えます。
Draw_Rectangle (Left => 10, Top => 20, Width => 100, Height => 50);
引数の取り違えを防ぎ、呼び出し側のコードがそのままドキュメントになります。
代入は :=、比較は = で、C 系言語の if (a = b) のような取り違えは構文レベルで起きません。
6. 強い型付け ── 単位の取り違えをコンパイルエラーにする
Ada の最大の魅力は、強い型付けです。
多くの言語で「強い型付け」という言葉が使われますが、Ada のそれは一段深いものです。
Ada では、構造がまったく同じでも、別の名前で宣言した型は別の型です。
type Meters is new Float;
type Seconds is new Float;
Distance : Meters := 100.0;
Time : Seconds := 9.58;
この 2 つはどちらも実体は浮動小数点数ですが、混ぜて使うことはできません。
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 のモジュール機構はパッケージです。
パッケージは、仕様(spec)と本体(body)の 2 つのファイルに分かれます。
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;
注目してほしいのは、次の点です。
typeをprivateと宣言すると、利用側は内部構造に触れない
仕様(.ads)だけ読めば、使い方がすべて分かる
本体(.adb)を変更しても、仕様が同じなら利用側の再コンパイルは最小限
C/C++ のヘッダーファイルと似ていますが、#include のようなテキスト展開ではなく、言語仕様として整合性がチェックされます。
仕様と本体の食い違いはコンパイルエラーです。
また、引数には in、out、in out というモードを必ず書きます。
procedure Increment (C : in out Counter);
「この引数は読むだけか、書くだけか、読み書きするのか」が、シグネチャを見るだけで分かります。
ポインタや参照の知識がなくても、データの流れる方向が読み取れるのです。
10. レコードと判別子
Ada の構造体に相当するのがレコードです。
type Point is record
X : Float := 0.0;
Y : Float := 0.0;
end record;
P : Point := (X => 1.0, Y => 2.0);
フィールドにデフォルト値を持たせられ、集成体(aggregate)で名前付き初期化ができます。
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 は、1983 年の最初の標準からジェネリクス(generic)を備えていました。
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 では最初から起きません。
C++20 の concepts や 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 タスク間通信の失敗
注目したいのは、範囲制約や境界チェックの違反が、すべてこの例外機構に統合されていることです。
「型に書いた制約」が破られたら Constraint_Error になる。
つまり、7 章で見た範囲制約は、自動生成される実行時アサーションとして機能します。
自分で 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 属性で、呼び出し前の値を参照できます。
この契約は、コンパイルオプション(GNAT では -gnata)で実行時チェックとして有効化できます。
契約に違反すると Assertion_Error 例外が発生し、どちらが約束を破ったのかが明確になります。
Pre違反 -> 呼び出し側のバグ
Post違反 -> 実装側のバグ
ドキュメントコメントに「この関数は空のスタックに対して呼んではいけない」と書くのと、何が違うのでしょうか。
コメントは実装とずれても誰も気づかない
契約はコンパイラが構文と型を検査する
契約は実行時に自動検証できる
契約はSPARKによる静的証明の入力になる(16章)
仕様が、検証可能な形でコードの中に存在する。
これが Ada 2012 以降の世界です。
型不変条件(Type_Invariant)を使えば、「この型の値は常にこの性質を満たす」という制約も書けます。
14. タスク ── 並行処理が言語に組み込まれている
Ada のもう 1 つの大きな魅力は、並行処理が言語仕様の一部であることです。
C/C++ がスレッドを OS の API やライブラリ(pthread、std::thread)に頼るのに対し、Ada は 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 の保護オブジェクトでは、「ロックを取り忘れたコード」はそもそも書けません。
データと、それを守る排他制御が、1 つの型として宣言されるからです。
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の整合性
テストとの違いは決定的です。
テスト 選んだ入力に対して正しく動くことを確認する
証明 すべての入力に対して性質が成り立つことを示す
13 章で見た契約(Pre/Post)は、SPARK ではそのまま証明の対象になります。
実行時チェックとして書いた契約を、あとから「証明済み」に格上げできるのです。
SPARK は航空・防衛の世界で実績を積んできましたが、近年では NVIDIA がファームウェアのセキュリティ向けに採用するなど、産業界での利用が広がっています。
「形式手法は学術的すぎて実務では使えない」という常識を、Ada/SPARK のエコシステムは静かに覆し続けています。
17. CやC++との相互運用
Ada は孤立した言語ではありません。
C との相互運用が言語仕様(Annex B)で標準化されています。
たとえば、Windows API の Sleep を Ada から呼ぶには、次のように書きます。
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 GCCに含まれるAdaコンパイラ(フリー)
Alire Adaのパッケージマネージャー兼ビルドツール
GNAT Studio AdaCore製のIDE
VS Code Ada Language Server拡張で補完・定義ジャンプが使える
特に Alire(コマンド名は alr)の登場で、Ada の入門は劇的に簡単になりました。
Rust の cargo に近い体験です。
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. Alireの公式サイトからWindows用インストーラーを取得する
2. alr init --bin で雛形を作る
3. VS CodeにAda拡張(AdaCore製)を入れる
4. alr buildでビルドして動かす
ライブラリも alr with ライブラリ名 で追加できます。
「環境構築で挫折する」時代は終わっています。
19. Adaの弱みと注意点
ここまで魅力を紹介してきましたが、Ada にも弱みがあります。
公平に整理しておきます。
エコシステムが小さい
Webフレームワーク、GUI、クラウドSDKなどの選択肢が少ない
Alireのパッケージ数は主流言語と桁が違う
人材と情報が少ない
日本語の情報は特に少ない
チーム開発で採用するには教育コストを見込む必要がある
構文が冗長に感じられる
型宣言や仕様/本体の分離は、小さなスクリプトには重い
「とりあえず動かす」用途には向かない
求人市場が限定的
航空宇宙・防衛・鉄道などの分野に偏っている
また、「Ada を使えば安全」という単純な話ではないことも、歴史が教えてくれます。
1996 年の Ariane 5 ロケット初号機の爆発事故は、Ada で書かれたソフトウェアが原因の 1 つでした。
Ariane 4 向けに書かれたコードを、飛行特性の違う Ariane 5 に再利用した結果、想定外の大きな値が変換時に Constraint_Error を引き起こし、適切に処理されずにシステムが停止したのです。
この事故が示すのは、次のことです。
言語の実行時チェックは問題を検出した(黙って壊れはしなかった)
しかし、運用前提が変わったのに検証されなかった
例外発生後の設計(フェイルセーフ)が不十分だった
型システムも契約も、前提を見直すプロセスの代わりにはなりません。
言語は安全工学の一部であって、全部ではない。
これは Ada を学ぶうえで、最も誠実な注意書きだと思います。
20. 長寿命ソフトウェアとAda ── 保守の視点から
当サイトでは、Windows の既存資産の保守や延命をよく扱っています。
その視点から見ると、Ada には別の魅力があります。
Ada で書かれたシステムは、数十年単位で動き続けているものが珍しくありません。
そして、Ada の言語設計そのものが、長期保守を前提にしています。
仕様(.ads)と実装(.adb)の分離
-> 20年後の保守者が、仕様だけ読めばインターフェースを把握できる
強い型と範囲制約
-> 暗黙の前提がコードに残る。口伝やコメントに頼らない
契約(Pre/Post)
-> 「この関数の約束」が検証可能な形で残る
caseの網羅性チェック
-> 仕様変更時の影響箇所をコンパイラが列挙する
標準の改訂でも後方互換性を重視
-> Ada 83のコードの多くが現代のコンパイラでも通る
これらは、C# や C++ で長期保守をするときにも、そのまま設計指針として輸入できます。
intではなく意味のある型(IDを表す型、単位を持つ型)を定義する
不正な値を作れない型を設計する(コンストラクターでの検証)
公開インターフェースと実装を意識的に分離する
事前条件・事後条件をアサーションやテストで表現する
列挙型のswitchを網羅的に書き、警告をエラー扱いにする
Ada を業務で使う機会がなくても、Ada の設計思想を学ぶことには十分な価値があります。
「型で設計を語る」感覚を学ぶ教材として、Ada は今でも一級品です。
21. まとめ
Ada の魅力を整理してきました。
ポイントを振り返ります。
Adaは高信頼システムで40年以上使われ続けている現役の言語
名前はAda Lovelaceに由来し、最新標準はAda 2022
構造が同じでも別名の型は別の型。単位の混同がコンパイルエラーになる
範囲制約により、不正な値を型のレベルで防げる
配列は境界チェックされ、バッファオーバーランが未定義動作にならない
パッケージで仕様と実装を分離し、引数モードでデータの流れを明示する
ジェネリクスは要求する操作を仕様に書くため、利用時のエラーが明確
Ada 2012の契約(Pre/Post)で、仕様を検証可能な形でコードに残せる
タスクと保護オブジェクトにより、並行処理を言語機能として安全に書ける
SPARKを使えば、契約を実行時チェックから数学的証明に格上げできる
GNATとAlireにより、無償でWindowsでもすぐに試せる
エコシステムの小ささと人材の少なさが弱み
言語の安全機構は、前提を見直すプロセスの代わりにはならない
Ada は、流行という意味では主流になれなかった言語です。
しかし、null 安全、網羅性チェック、契約、所有権に近い厳格さといった、現代の言語が「新機能」として導入しているものの多くを、Ada は何十年も前から備えていました。
Ada の本質は、次の一言にまとめられます。
バグは見つけるものではなく、型と契約で「書けなくする」もの。
週末に Alire でプロジェクトを 1 つ作り、コンパイラに怒られながら小さなプログラムを書いてみてください。
そのコンパイルエラーの 1 つひとつが、「本番障害になる前に捕まえられたバグ」だと気づいたとき、Ada の魅力が腑に落ちるはずです。
参考
- この記事のコード断片を章ごとに整理した参照用コード集 - 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)
関連する記事
同じタグを共有する最新の記事です。さらに近い話題で知識を深められます。
WindowsのMFCとは何か ── 既存資産を保守するための基礎知識
Microsoft Foundation Classes(MFC)の概要、Win32との関係、アプリケーション構造、メッセージマップ、Document/View、DDX/DDV、保守時の注意点を整理します。
Windows PCを廃棄する前にやっておきたいこと ── データ消去・アカウント解除・バックアップの実務チェックリスト
Windows PCを廃棄・譲渡・売却・リース返却する前にやっておきたいことを、バックアップ、データ消去、BitLocker、Microsoftアカウント、OneDrive、仕事用アカウント、開発者PC特有の秘密情報、廃棄証跡の観点から整理します。
PDB(プログラムデータベース)とは何か ── デバッグ情報・シンボル・Source Linkを理解する
PDB(Program Database / プログラムデータベース)とは何か、何が入っていて何が入っていないのか、Debug / Release、Portable PDB、Source Link、シンボルサーバー、ダンプ解析との関係まで実務向けに整理します。
Roslynとは何か ── C#コードをコンパイラの視点で読む・直す・生成する
Roslyn(.NET Compiler Platform)の概要、Syntax Tree、SemanticModel、Workspace、Analyzer、Source Generator、実務での使いどころと注意点を整理します。
Windowsアプリ 外注・受託開発を依頼する前に整理したいこと
Windowsアプリの外注・受託開発を依頼する前に、既存ソフト改修、装置連携、COM/ActiveX、配布・更新、保守の整理ポイントを解説します。
関連トピック
このテーマと近いトピックページです。記事を起点に、関連するサービスや他の記事へ進めます。
Windows技術トピック
Windows 開発、不具合調査、既存資産活用の技術トピックをまとめた入口です。
著者プロフィール
記事の著者プロフィールページです。
小村 豪
合同会社小村ソフト 代表
Windows ソフト開発、技術相談、不具合調査を中心に、既存資産が残る案件や原因が見えにくい障害調査に強みがあります。
公開リンク