Arsitektur proyek interlocking digambar lebih dahulu sebelum perilaku lengkap diisi. Topik ini menetapkan komponen proyek, yaitu package domain yang memuat kontrak, objek terlindungi untuk state bersama, dan task pengendali. Ketiganya kemudian dikunci dalam kerangka berkas proyek yang dikompilasi nyata. Setiap keluaran pada topik ini merupakan keluaran asli kerangka, dan perilaku penuh proyek mengisi kerangka tersebut tanpa mengubah kontrak yang sudah digambar.
Komponen arsitektur#
Kerangka proyek terbagi menjadi tiga komponen dengan tanggung jawab yang tidak tumpang tindih. Package Domain_Interlock menyimpan tipe domain, kontrak transisi, dan jadwal durasi. Package State_Interlock memuat satu objek terlindungi bernama Keadaan yang menyimpan fase berjalan. Prosedur Utama berisi task Pengendali yang memutar siklus melalui kedua package tersebut.
Pembagian ini mengikuti satu prinsip, yaitu kontrak harus terbaca sebelum badan. Spesifikasi Domain_Interlock memuat seluruh aturan domain dalam bentuk yang dapat diperiksa, sedangkan badannya hanya menerjemahkan aturan itu menjadi kasus sederhana. Task Pengendali tidak pernah mengakses variabel fase secara langsung karena satu-satunya jalur menuju state adalah operasi terlindungi. Diagram berikut merangkum ketiga komponen beserta aliran panggilannya.
Aliran data pada kerangka ini berurutan dan tunggal. Task Pengendali mengambil fase dari Keadaan, membaca durasinya dari jadwal domain, menunggu selama durasi tersebut, lalu meminta pemajuan fase kepada Keadaan. Tidak ada task kedua pada kerangka ini, sehingga keluaran yang tercetak pasti berurutan dan sama persis pada setiap eksekusi.
Kontrak domain digambar di muka#
Spesifikasi package domain adalah tempat seluruh kontrak proyek ditetapkan. Tipe Fase memuat empat nilai fase, sedangkan tipe Detik dengan subtipe Durasi_Hijau dan Durasi_Kuning mengunci rentang durasi setiap jenis fase. Fungsi Sah menetapkan tabel transisi. Melalui aspek Post, fungsi ini menjanjikan bahwa hasil kembaliannya sama dengan ekspresi tabel itu, sedangkan prosedur Maju menjanjikan bahwa setiap hasil pemajuan sah. Predikat dinamis pada Baris_Jadwal menolak pasangan fase dan durasi yang tidak sah, dan konstanta Jadwal menuliskan jadwal penuh pada spesifikasi yang terbaca tanpa membuka badan.
package Domain_Interlock is
-- Empat fase siklus. UTS menunjuk pasangan arah utara-selatan dan
-- BRT pasangan arah barat-timur.
type Fase is (Hijau_UTS, Kuning_UTS, Hijau_BRT, Kuning_BRT);
-- Satuan waktu domain dalam detik.
type Detik is range 0 .. 3_600;
subtype Durasi_Hijau is Detik range 10 .. 60;
subtype Durasi_Kuning is Detik range 2 .. 5;
-- Tabel transisi: hanya empat perpindahan yang sah.
function Sah (Dari, Ke : Fase) return Boolean with
Post => Sah'Result =
((Dari = Hijau_UTS and then Ke = Kuning_UTS)
or else (Dari = Kuning_UTS and then Ke = Hijau_BRT)
or else (Dari = Hijau_BRT and then Ke = Kuning_BRT)
or else (Dari = Kuning_BRT and then Ke = Hijau_UTS));
-- Memajukan fase sesuai tabel transisi.
procedure Maju (F : in out Fase) with
Post => Sah (F'Old, F);
-- Satu baris jadwal memasangkan fase dengan durasinya. Predikat
-- dinamis menolak kombinasi yang tidak sah, misalnya hijau tiga
-- detik.
type Baris_Jadwal is record
Fase_Kini : Fase;
Lama : Detik;
end record with
Dynamic_Predicate =>
(if Baris_Jadwal.Fase_Kini in Hijau_UTS | Hijau_BRT
then Baris_Jadwal.Lama in Durasi_Hijau
else Baris_Jadwal.Lama in Durasi_Kuning);
type Jadwal_Siklus is array (Fase) of Baris_Jadwal;
-- Jadwal yang digambar di muka dan terbaca tanpa membuka badan.
Jadwal : constant Jadwal_Siklus :=
(Hijau_UTS => Baris_Jadwal'(Fase_Kini => Hijau_UTS, Lama => 12),
Kuning_UTS => Baris_Jadwal'(Fase_Kini => Kuning_UTS, Lama => 3),
Hijau_BRT => Baris_Jadwal'(Fase_Kini => Hijau_BRT, Lama => 12),
Kuning_BRT => Baris_Jadwal'(Fase_Kini => Kuning_BRT, Lama => 3));
end Domain_Interlock;Aspek Post pada Sah menyatakan nilai kembalian sama dengan ekspresi tabel transisi, sehingga badan yang menyimpang dari tabel tertangkap saat run time dengan flag -gnata. Aspek Post pada Maju menegaskan bahwa fase baru selalu sah menurut tabel yang sama. Predikat pada Baris_Jadwal dievaluasi pada setiap inisialisasi objek bertipe itu, termasuk saat konstanta Jadwal disusun, sehingga program dengan jadwal yang salah ditolak sebelum task pertama berjalan.
package body Domain_Interlock is
function Sah (Dari, Ke : Fase) return Boolean is
begin
return (Dari = Hijau_UTS and then Ke = Kuning_UTS)
or else (Dari = Kuning_UTS and then Ke = Hijau_BRT)
or else (Dari = Hijau_BRT and then Ke = Kuning_BRT)
or else (Dari = Kuning_BRT and then Ke = Hijau_UTS);
end Sah;
procedure Maju (F : in out Fase) is
begin
case F is
when Hijau_UTS => F := Kuning_UTS;
when Kuning_UTS => F := Hijau_BRT;
when Hijau_BRT => F := Kuning_BRT;
when Kuning_BRT => F := Hijau_UTS;
end case;
end Maju;
end Domain_Interlock;Badan package pendek karena seluruh kebijakan sudah ditetapkan pada spesifikasi. Prosedur Maju hanya menerjemahkan tabel transisi menjadi pernyataan case dengan empat cabang, sedangkan fungsi Sah mengulang ekspresi tabel yang dinyatakan pada aspek Post-nya. Kompilator menolak case yang kehilangan satu nilai enumerasi, sehingga pertumbuhan tipe Fase memaksa pemutakhiran badan ini secara eksplisit.
State terlindungi untuk fase bersama#
State bersama proyek ini hanya satu, yaitu fase berjalan, dan state itu disimpan di bagian private objek terlindungi Keadaan. Dua operasi terlindungi yang diekspor adalah Ambil untuk membaca fase dan Maju untuk memajukannya. Tidak ada entry dengan barrier pada kerangka ini karena siklus dengan durasi tetap tidak menunggu sinyal dari luar, dan barrier disediakan ketika permintaan dari luar siklus ditambahkan.
with Domain_Interlock;
package State_Interlock is
-- State bersama seluruh task, yaitu fase berjalan. Seluruh akses
-- melalui operasi terlindungi, sehingga tidak ada task yang
-- menyentuh variabel fase secara langsung.
protected Keadaan is
procedure Ambil (F : out Domain_Interlock.Fase);
procedure Maju;
private
Fase_Berjalan : Domain_Interlock.Fase := Domain_Interlock.Hijau_UTS;
end Keadaan;
end State_Interlock;package body State_Interlock is
protected body Keadaan is
procedure Ambil (F : out Domain_Interlock.Fase) is
begin
F := Fase_Berjalan;
end Ambil;
procedure Maju is
begin
Domain_Interlock.Maju (Fase_Berjalan);
end Maju;
end Keadaan;
end State_Interlock;Operasi Maju mendelegasikan pemajuan kepada Domain_Interlock.Maju, sehingga aspek Post tabel transisi berlaku pada setiap pemajuan tanpa penulisan ulang. Kedua operasi pendek dan tidak memblokir, sesuai batasan aksi terlindungi. Penugasan fase hanya terjadi di dalam badan objek terlindungi, sehingga tidak ada task yang membaca fase di tengah penugasannya.
Pemisahan antara state dan task ini juga menyiapkan pengujian. Seluruh perilaku fase dapat diuji dengan memanggil operasi terlindungi dari program uji tanpa menyertakan task mana pun, sebab state berdiri sendiri sebagai unit. Apabila fase disimpan pada variabel biasa, pengujian semacam itu tidak mungkin dilakukan dengan aman.
Task pengendali dan berkas proyek#
Task Pengendali dinyatakan di dalam prosedur Utama dan berjalan selama delapan fase, yaitu dua siklus penuh. Setiap putaran mengambil fase dari Keadaan, membaca durasi dari Jadwal, mencetak satu baris keluaran, menunggu, lalu meminta pemajuan fase. Durasi yang ditunggu dibagi 100 agar kerangka selesai cepat, dan pembagian itu dihilangkan pada perilaku penuh proyek.
with Ada.Text_IO;
with Domain_Interlock;
with State_Interlock;
-- Kerangka utama proyek interlocking. Task Pengendali memutar siklus
-- melalui state terlindungi dan mengambil durasi dari jadwal domain.
procedure Utama is
Jumlah_Fase : constant := 8; -- dua siklus penuh
task Pengendali;
task body Pengendali is
F : Domain_Interlock.Fase;
Lama : Domain_Interlock.Detik;
begin
for Urut in 1 .. Jumlah_Fase loop
State_Interlock.Keadaan.Ambil (F);
Lama := Domain_Interlock.Jadwal (F).Lama;
Ada.Text_IO.Put_Line
("fase" & Integer'Image (Urut) & ": "
& Domain_Interlock.Fase'Image (F)
& " selama" & Domain_Interlock.Detik'Image (Lama) & " detik");
delay Duration (Lama) / 100.0; -- dibagi 100 agar kerangka cepat
State_Interlock.Keadaan.Maju;
end loop;
Ada.Text_IO.Put_Line ("kerangka menyelesaikan dua siklus penuh");
end Pengendali;
begin
null;
end Utama;Berkas proyek GPRbuild mengunci susunan kerangka. Source_Dirs menunjuk direktori tempat berkas proyek berada, objek dipisahkan ke folder obj, dan Exec_Dir menempatkan hasil build di direktori proyek. Flag -gnata dipasang pada package Builder sehingga pemeriksaan kontrak aktif untuk seluruh unit. Dengan susunan itu, pembaca berkas proyek mengetahui titik masuk dan pilihan pemeriksaan tanpa membuka satu pun berkas sumber.
project Interlock is
-- Kerangka proyek: satu direktori sumber, objek terpisah, dan
-- pemeriksaan kontrak aktif melalui flag -gnata.
for Source_Dirs use (".");
for Object_Dir use "obj";
for Exec_Dir use ".";
for Main use ("utama.adb");
package Builder is
for Default_Switches ("ada") use ("-gnata");
end Builder;
end Interlock;Susunan berkas kerangka terdiri atas enam berkas berikut.
interlock/
├── interlock.gpr
├── domain_interlock.ads
├── domain_interlock.adb
├── state_interlock.ads
├── state_interlock.adb
└── utama.adb# build kerangka proyek melalui berkas proyek
gprbuild -q -P interlock.gpr
# jalankan kerangka utama
./utamafase 1: HIJAU_UTS selama 12 detik
fase 2: KUNING_UTS selama 3 detik
fase 3: HIJAU_BRT selama 12 detik
fase 4: KUNING_BRT selama 3 detik
fase 5: HIJAU_UTS selama 12 detik
fase 6: KUNING_UTS selama 3 detik
fase 7: HIJAU_BRT selama 12 detik
fase 8: KUNING_BRT selama 3 detik
kerangka menyelesaikan dua siklus penuhKeluaran tercetak berurutan dari fase 1 sampai fase 8 dan berakhir dengan pesan penyelesaian karena satu-satunya task yang mencetak adalah Pengendali. Setiap pemajuan melewati operasi terlindungi dan setiap durasi berasal dari jadwal yang diperiksa predikatnya, sehingga tidak ada satu angka pun yang ditulis dua kali. Kerangka ini menjadi tempat perilaku penuh proyek diisi.
Kontrak yang menolak pelanggaran#
Kontrak yang ditetapkan lebih dahulu berlaku sejak baris pertama program. Probe berikut, yaitu program kecil yang sengaja memicu pelanggaran kontrak, menyusun satu Baris_Jadwal dengan durasi hijau tiga detik, yakni kombinasi yang ditolak predikatnya. Kompilasi menggunakan flag -gnata agar pemeriksaan predikat benar-benar berjalan, dan program diharapkan berhenti sebelum baris apa pun tercetak. Predikat jadwal dipilih sebagai contoh karena penolakannya paling mudah dibaca, sedangkan aspek Post tabel transisi diperiksa dengan pola yang sama.
with Ada.Text_IO;
with Domain_Interlock;
-- Probe pelanggaran kontrak: durasi hijau tiga detik melanggar
-- predikat Baris_Jadwal karena tiga detik di luar Durasi_Hijau.
procedure Langgar_Predikat is
Baris : Domain_Interlock.Baris_Jadwal :=
Domain_Interlock.Baris_Jadwal'(Fase_Kini => Domain_Interlock.Hijau_UTS,
Lama => 3);
begin
Ada.Text_IO.Put_Line ("baris jadwal diterima");
end Langgar_Predikat;# kompilasi probe pelanggaran predikat
gnatmake -q -gnata langgar_predikat.adb
# jalankan probe, exception memang diharapkan muncul
./langgar_predikatraised ADA.ASSERTIONS.ASSERTION_ERROR : Dynamic_Predicate failed at langgar_predikat.adb:8Pesan exception menyebut Dynamic_Predicate failed beserta berkas dan baris penyusunan objek yang melanggar, sedangkan teks baris jadwal diterima tidak pernah tercetak. Penolakan terjadi pada batas penyusunan data, bukan jauh di dalam logika, dan itulah kegunaan kontrak yang ditetapkan lebih dahulu. Jadwal yang tidak sah tidak pernah sempat dilihat oleh task mana pun. Pola yang sama berlaku ketika konstanta Jadwal disunting di kemudian hari, karena jadwal baru diperiksa predikatnya pada saat program dijalankan pertama kali. Pesan yang menunjuk berkas dan baris juga memangkas pencarian penyebab karena lokasi penolakan disebut langsung.
Kesalahan umum#
Empat kekeliruan berikut sering muncul ketika arsitektur digambar lebih dahulu, dan semuanya berkaitan dengan tempat keputusan ditulis.
- Menulis badan sebelum spesifikasi selesai. Badan yang ditulis lebih dulu menempatkan keputusan domain di tempat yang tidak diperiksa kontrak. Perbaikannya, selesaikan spesifikasi beserta aspek dan predikatnya, lalu tulis badan yang mematuhinya.
- Menyimpan fase pada variabel global biasa. Task yang membaca variabel di tengah perubahan menghasilkan keputusan yang salah tanpa pesan error apa pun. Perbaikannya, pindahkan state ke objek terlindungi dan akses hanya melalui operasinya.
- Menempatkan
delaydi dalam aksi terlindungi. Aksi yang memblokir menahan seluruh task lain yang menunggu objek yang sama. Perbaikannya, lakukan penundaan di dalam task dan biarkan aksi terlindungi hanya membaca serta menulis state. - Menghitung durasi di dalam task. Angka durasi yang tersebar menyalin jadwal dan cepat kedaluwarsa. Perbaikannya, ambil durasi dari
Jadwalyang diperiksa predikatnya sebagaimana dilakukan Pengendali.
Ringkasan#
- Arsitektur kerangka terbagi menjadi package domain berisi kontrak, objek terlindungi untuk state bersama, dan task pengendali yang memutar siklus.
- Kontrak ditetapkan pada spesifikasi berupa tabel transisi dengan aspek
Post, rentang durasi bertipe, predikat jadwal, dan jadwal berupa konstanta. - Kerangka dikompilasi nyata melalui berkas proyek GPRbuild dengan flag
-gnata, dan pelanggaran predikat tertangkap dengan pesan exception yang menunjuk baris penyusunan objek yang melanggar.
Referensi#
- 3.2.4 Subtype Predicates — Ada Reference Manual 2022 — Menetapkan semantik predikat subtipe dinamis beserta saat pemeriksaannya dilakukan.
- 9.4 Protected Units and Protected Objects — Ada Reference Manual 2022 — Menetapkan semantik aksi terlindungi yang dipakai objek Keadaan.
- GPRbuild User’s Guide — AdaCore — Dokumentasi berkas proyek GPRbuild yang membangun kerangka ini.