Predikat menempel pada subtipe dan memeriksa setiap nilai yang dialihkan ke subtipe tersebut, berbeda dengan aspek Pre dan Post yang hanya menjaga satu subprogram. Topik ini membahas predikat statis dan dinamis, titik-titik pemeriksaannya, serta pemanfaatannya untuk menjaga nomor antrean dan bacaan sensor tetap valid. Contoh yang dapat dikompilasi dan dijalankan memperlihatkan nilai sah yang diterima dan nilai tidak sah yang ditolak saat run time.
Predikat pada subtipe#
Subtipe adalah nama untuk sebuah tipe dengan kendala tambahan, dan predikat menambahkan kendala berupa ekspresi boolean yang wajib benar untuk setiap nilai subtipe itu. Kendala rentang hanya mampu menyatakan satu interval, sedangkan predikat dapat menyatakan kondisi apa pun, misalnya gabungan dua rentang atau kelipatan tertentu. Predikat dinyatakan pada deklarasi subtipe dengan kata with, dan Ada membedakan dua bentuknya.
subtype Nomor_Antrean is Positive
with Static_Predicate => Nomor_Antrean in 1 .. 99 | 200 .. 250;
subtype Suhu_Sensor is Integer
with Dynamic_Predicate => Suhu_Sensor in -40 .. 85;Static_Predicate hanya menerima kondisi yang dapat dinilai saat kompilasi, yaitu gabungan rentang dan nilai diskrit, sehingga kompilator mengetahui anggota sah subtipe tersebut. Dynamic_Predicate menerima ekspresi boolean bebas yang dinilai saat run time, sehingga lebih luwes tetapi lebih mahal. Kedua bentuk ditulis pada deklarasi subtipe, bersama kendala lainnya, dan setiap objek yang dideklarasikan dengan subtipe itu otomatis tunduk pada pemeriksaannya.
Pemeriksaan pada setiap peralihan nilai#
Pemeriksaan predikat berjalan pada setiap peralihan nilai ke subtipe yang berkendala, termasuk penugasan ke variabel, pengikatan parameter, dan konversi. Program berikut memuat dua subtipe berkendala, yaitu Nomor_Antrean dengan predikat statis dan Suhu_Sensor dengan predikat dinamis. Tiga penugasan pertama memasukkan nilai yang sah, sedangkan penugasan terakhir memasukkan bacaan 120 yang diambil dari teks log sensor melalui atribut Integer'Value, dan nilainya melanggar predikat.
with Ada.Text_IO; use Ada.Text_IO;
procedure Predikat_Suhu is
-- Predikat statis: dua blok nomor antrean yang sah di loket.
subtype Nomor_Antrean is Positive
with Static_Predicate => Nomor_Antrean in 1 .. 99 | 200 .. 250;
-- Predikat dinamis: rentang bacaan sensor yang sah.
subtype Suhu_Sensor is Integer
with Dynamic_Predicate => Suhu_Sensor in -40 .. 85;
Nomor : Nomor_Antrean;
Bacaan : Suhu_Sensor;
Terukur : Integer;
begin
Nomor := 42;
Put_Line ("Nomor antrean" & Positive'Image (Nomor) & " diterima");
Nomor := 205;
Put_Line ("Nomor antrean" & Positive'Image (Nomor) & " diterima");
Bacaan := 24;
Put_Line ("Bacaan" & Integer'Image (Bacaan) & " diterima");
Terukur := Integer'Value ("120"); -- teks bacaan dari log sensor
Bacaan := Terukur; -- melanggar predikat -40 .. 85
Put_Line ("Bacaan" & Integer'Image (Bacaan) & " diterima");
end Predikat_Suhu;# kompilasi dengan flag -gnata agar pemeriksaan predikat aktif
gnatmake -q -gnata predikat_suhu.adb
# jalankan program hasil kompilasi
./predikat_suhuNomor antrean 42 diterima
Nomor antrean 205 diterima
Bacaan 24 diterima
raised ADA.ASSERTIONS.ASSERTION_ERROR : Dynamic_Predicate failed at predikat_suhu.adb:25Nilai 42 dan 205 sah karena masing-masing berada pada salah satu blok nomor yang diizinkan predikat statis, dan bacaan 24 sah karena berada di dalam rentang sensor. Peralihan nilai 120 ke Suhu_Sensor ditolak, dan pemeriksaan membangkitkan exception dengan pesan yang menunjuk baris penugasannya. Pada GNAT, pelanggaran predikat memunculkan ADA.ASSERTIONS.ASSERTION_ERROR, bukan CONSTRAINT_ERROR, dengan keterangan Dynamic_Predicate failed beserta berkas dan baris pemeriksaannya.
Perhatikan peran variabel perantara Terukur pada program di atas. Variabel itu bertipe Integer tanpa predikat, sehingga nilai 120 boleh tersimpan di dalamnya, dan pelanggaran baru terjadi ketika nilainya disalin ke Suhu_Sensor. Pola ini menjelaskan titik-titik pemeriksaan yang lebih lengkap: predikat juga diperiksa pada deklarasi objek dengan nilai awal, pada pengikatan parameter ke subprogram, pada pernyataan return fungsi yang hasilnya bertipe berkendala, serta pada konversi eksplisit antar tipe yang berkaitan. Karena pemeriksaan menempel pada peralihan nilai dan bukan pada variabel, membaca sebuah objek berkendala tidak memicu pemeriksaan apa pun.
Predikat statis atau dinamis#
Pilihan bentuk predikat menentukan apa yang diketahui kompilator. Dengan Static_Predicate, anggota sah subtipe diketahui saat kompilasi, sehingga nilai yang sudah pasti melanggar dapat diberi peringatan oleh kompilator sebelum program dijalankan. Dengan Dynamic_Predicate, kondisinya boleh merujuk variabel lain dan memanggil fungsi, sehingga cocok untuk aturan yang bergantung pada keadaan program saat berjalan. Aturan praktisnya, gunakan predikat statis jika kondisinya dapat dinyatakan sebagai gabungan rentang, dan pindahkan sisanya ke predikat dinamis hanya jika benar-benar diperlukan. Pelanggaran kedua bentuk memunculkan exception yang sama pada GNAT, sehingga perbedaannya terletak pada pengetahuan kompilator dan biaya pemeriksaan, bukan pada pesannya.
Predikat dinamis tidak terbatas pada skalar. Predikat dapat ditempel pada subtipe record, dan ekspresinya boleh mengaitkan antarkomponen, misalnya menuntut bahwa batas akhir jadwal tidak lebih kecil daripada batas awalnya atau bahwa komponen status dan komponen keterangan saling cocok. Kendala semacam ini sulit dinyatakan sebagai rentang karena melibatkan dua komponen sekaligus, sehingga predikat menjadi alat yang tepat untuk menyatakannya. Pola yang sama inilah yang digunakan pada proyek akhir jalur ini, yaitu predikat dinamis yang menjaga jadwal fase lampu lalu lintas tetap konsisten.
Kesalahan umum#
- Menggunakan
Dynamic_Predicateuntuk kondisi yang sebenarnya berupa gabungan rentang. Bentuk dinamis lebih mahal dan menyembunyikan anggota sah subtipe dari kompilator. Perbaikannya, pindahkan kondisi seperti itu keStatic_Predicate, dan sisakan bentuk dinamis untuk kondisi yang bergantung pada keadaan program saat berjalan. - Membuat ekspresi predikat yang berat atau berefek samping. Predikat dieksekusi pada setiap peralihan nilai, dan ekspresi dengan efek samping membuat perilaku program sulit diprediksi. Perbaikannya, jaga ekspresi predikat tetap sederhana dan murni, lalu pindahkan logika yang berat ke badan subprogram.
- Menyangka pemeriksaan predikat aktif tanpa
-gnata. Seperti kontrak lain, pemeriksaan predikat dilewati apabila flag itu tidak digunakan, dan nilai tidak sah masuk tanpa ditolak. Perbaikannya, aktifkan-gnatapada build dan uji subtipe berkendala dengan nilai di luar batasnya.
Ringkasan#
- Predikat menempel pada subtipe dan diperiksa pada setiap peralihan nilai, termasuk penugasan, pengikatan parameter, dan konversi.
Static_Predicatemenyatakan gabungan rentang yang diketahui saat kompilasi, sedangkanDynamic_Predicatemenerima ekspresi boolean bebas yang dinilai saat run time.- Pada GNAT, pelanggaran predikat memunculkan
ADA.ASSERTIONS.ASSERTION_ERRORdengan pesan yang menunjuk berkas dan baris pemeriksaannya.
Referensi#
- Introduction to Ada: Contracts — AdaCore — Bab kursus resmi yang membahas predikat statis dan dinamis pada bagian Predicates.
- Introduction to Ada: Strongly Typed Language — AdaCore — Bab kursus resmi yang membahas subtipe sebagai dasar kendala dan predikat tipe.