Tutorial

C++26 Contracts: Cara Menulis Precondition dan Postcondition

C++26 Contracts: Cara Menulis Precondition dan Postcondition

Proposal P2900 Contracts for C++ sudah diadopsi untuk C++26, dan sejak itu kontrak jadi bagian dari bahasa, bukan sekadar makro. Pengantar di C++ Stories menunjukkan alurnya secara bertahap: mulai dari cassert sederhana, pindahkan syarat ke dalam deklarasi fungsi, lalu periksa hasilnya. Ada tiga bentuk yang perlu dikenal, yaitu pre untuk precondition, post untuk postcondition, dan contract_assert untuk pemeriksaan di tengah fungsi. Artikel ini merangkum cara pakainya, plus kapan pendekatan ini lebih baik daripada assert dan komentar dokumentasi biasa.

TL;DR

  • C++26 memasukkan contracts sebagai fitur bahasa lewat P2900, dengan tiga bentuk: pre, post, dan contract_assert.
  • pre dan post ditulis langsung di deklarasi fungsi, sehingga syarat pemanggilan dan jaminan hasil ikut terlihat oleh pembaca dan oleh alat analisis.
  • contract_assert menggantikan assert untuk pemeriksaan di tengah badan fungsi, dan tetap bisa dimatikan lewat NDEBUG.
  • Pemeriksaan bawaan seperti operator[] pada container standar juga dinyatakan sebagai kontrak, jadi konsepnya sama dengan yang dipakai di kode sendiri.
  • Dukungan compiler untuk contracts masih bertahap per Oktober 2026, jadi cek status compiler masing-masing sebelum memakainya di produksi.

Apa Itu Contracts di C++26?

Contracts adalah cara menuliskan syarat dan jaminan sebuah fungsi di dalam kode, bukan di komentar. Precondition menyatakan apa yang harus benar sebelum fungsi dipanggil, postcondition menyatakan apa yang dijamin benar setelah fungsi selesai, dan contract_assert menyatakan pemeriksaan yang harus lolos di titik tertentu di dalam fungsi. Sebelum C++26, semua itu ditulis sebagai komentar, dokumentasi, atau assert yang tersebar, sehingga tidak ada satu pun yang bisa dibaca compiler secara resmi.

Alasan contracts dianggap penting sederhana: banyak bug mahal lahir dari pelanggaran asumsi antar fungsi, bukan dari logika aritmetika yang rumit. Ketika asumsi itu ditulis sebagai kontrak, pelanggarannya bisa dideteksi lebih awal dan lokasinya jelas, bukan muncul sebagai crash acak beberapa lapis di atas titik kesalahan asli.

Konsep ini sebenarnya sudah lama dipakai di lapisan lain. Pemeriksaan bawaan pada container standar, misalnya akses elemen lewat operator[], juga dinyatakan sebagai kontrak: ada syarat yang harus dipenuhi pemanggil, dan ada perilaku yang dijanjikan penyedia library. Sebelumnya syarat itu hidup di teks standar dan di dokumentasi, bukan di kode. Perubahan besarnya di C++26 adalah kontrak yang sama sekarang bisa ditulis untuk fungsi milik sendiri, dengan aturan yang dipahami compiler.

Yang membuat pendekatan ini berbeda dari sekadar menambah pemeriksaan manual adalah posisinya. Pemeriksaan manual di dalam badan fungsi tidak pernah terlihat oleh pemanggil, tidak muncul di header, dan tidak bisa dianalisis alat bantu. Kontrak menempel di deklarasi, jadi informasi itu jadi bagian dari antarmuka publik fungsi, bukan rahasia implementasi.

Apa Bedanya pre, post, dan contract_assert?

pre dipakai untuk syarat masukan, post untuk jaminan keluaran, dan contract_assert untuk pemeriksaan di tengah alur fungsi. Ketiganya menempel pada deklarasi atau badan fungsi yang sama, tapi perannya berbeda: pre melindungi fungsi dari pemanggilan yang tidak sah, post memastikan fungsi benar-benar memenuhi janjinya, dan contract_assert menjaga invarian internal selama proses berjalan.

Urutan pemeriksaannya juga berbeda secara logis. pre dievaluasi saat fungsi dipanggil, post dievaluasi saat fungsi keluar, dan contract_assert dievaluasi ketika eksekusi melewati baris tempat ia ditulis. Karena itu, menaruh syarat di tempat yang salah membuat pesan errornya menyesatkan: pelanggaran yang sebenarnya berasal dari pemanggil bisa terlihat seperti bug di dalam fungsi.

Contoh paling jelas ada pada fungsi yang memproses rentang data. Precondition-nya adalah rentang yang tidak kosong dan indeks yang masih dalam batas. Postcondition-nya adalah keluaran yang panjangnya sesuai aturan tertentu. Pemeriksaan yang harus lolos di tengah, misalnya nilai akumulasi tidak pernah melewati batas aman, lebih cocok jadi contract_assert karena ia bukan syarat pemanggilan maupun jaminan keluaran, melainkan invarian proses.

Membedakan ketiganya bukan soal kerapian. Ketika sebuah kegagalan terjadi di produksi, jenis kontrak yang dilanggar langsung memberi tahu siapa yang salah. Pelanggaran pre berarti pemanggil, pelanggaran post berarti implementasi fungsi, dan pelanggaran contract_assert berarti ada asumsi internal yang tidak lagi benar. Informasi itu jauh lebih berguna daripada sekadar tahu bahwa program berhenti di baris sekian.

Kapan Harus Pakai Contracts Dibanding assert Biasa?

Pakai contracts ketika syaratnya adalah bagian dari kesepakatan antar modul dan ingin terlihat di antarmuka fungsi. assert lebih cocok untuk pemeriksaan internal sementara saat debugging, karena ia hilang begitu NDEBUG aktif dan tidak memberi tahu pemanggil apa pun tentang apa yang diharapkan.

Perbedaan paling praktisnya ada di posisi informasi. assert tinggal di dalam badan fungsi, jadi hanya pembaca isi fungsi yang menemukannya. pre dan post tinggal di deklarasi, jadi mereka muncul di header dan ikut terbaca siapa pun yang memakai fungsi itu. Untuk kode yang dipakai lintas tim atau lintas repositori, perbedaan ini menentukan apakah syaratnya benar-benar dipatuhi atau hanya jadi legenda lisan.

Ada juga pertimbangan biaya. assert selalu ikut dikompilasi bersama kode di build debug, dan menghilang total di build rilis. Kontrak punya tingkat evaluasi yang lebih terperinci, sehingga pemeriksaan yang mahal bisa dimatikan sementara pemeriksaan murah tetap jalan. Untuk fungsi yang dipanggil jutaan kali, perbedaan ini bisa terasa, terutama kalau pemeriksaannya melibatkan iterasi atau alokasi.

AspekassertContracts C++26
Lokasi penulisanDi dalam badan fungsiDi deklarasi (pre, post) atau badan (contract_assert)
Terlihat pemanggilTidak, tersembunyi di implementasiYa, karena menempel di antarmuka fungsi
PeranPemeriksaan debug sementaraKesepakatan resmi antar modul
Matikan saat build rilisLewat NDEBUGLewat level evaluasi yang ditentukan saat build
Reaksi saat dilanggarAbortDiproses lewat handler pelanggaran kontrak

Bagaimana Cara Mulai Memakainya di Kode Sendiri?

Mulai dari satu fungsi yang paling sering dipanggil dengan argumen salah, tulis pre untuk syaratnya, lalu post untuk jaminan hasilnya. Setelah itu baru ganti assert di tengah fungsi dengan contract_assert, satu per satu, supaya setiap perubahan mudah ditelusuri kalau ada perilaku yang berubah.

Contoh paling sering dipakai adalah fungsi pembagian. Precondition-nya jelas: pembagi tidak boleh nol. Sebelum ada contracts, syarat itu biasanya ditulis sebagai komentar di atas fungsi, lalu diulang sebagai pemeriksaan manual di badan fungsi, dan akhirnya juga muncul di dokumentasi. Tiga tempat, satu aturan, dan ketiganya bisa berbeda diam-diam ketika kode berubah. Dengan pre, aturannya hanya ditulis sekali di tempat yang dibaca compiler.

Untuk pemeriksaan hasil, post memberi tempat resmi bagi jaminan seperti ukuran keluaran yang tidak pernah negatif atau urutan data yang selalu terjaga. Jaminan semacam ini biasanya hanya ada di kepala penulis aslinya, dan hilang begitu orang itu pindah proyek. Menuliskannya sebagai post membuat jaminan itu bertahan dan bisa diuji.

Urutan migrasi yang masuk akal ada tiga langkah. Pertama, tambahkan pre pada fungsi yang paling sering jadi sumber bug. Kedua, tambahkan post pada fungsi yang keluarannya diandalkan fungsi lain. Ketiga, ganti assert yang ada dengan contract_assert sambil menghapus komentar syarat yang jadi duplikat. Setelah tiga langkah itu, setiap aturan punya satu rumah saja.

Satu hal yang mudah terlewat, kontrak yang salah lebih berbahaya daripada tidak ada kontrak. Precondition yang terlalu ketat akan menolak pemanggilan yang sebenarnya sah, dan itu baru ketahuan di produksi. Karena itu, uji dulu kontrak barunya dengan data nyata sebelum dianggap selesai.

Apakah Kontrak Bisa Dimatikan Saat Build Rilis?

Ya, evaluasi kontrak bisa diatur pada waktu build, sehingga pemeriksaan yang mahal bisa dimatikan di rilis tanpa mengubah kode sumber. Ini yang membedakannya dari komentar: aturannya tetap ada di kode, tapi biaya menjalankannya bisa disesuaikan per konfigurasi build. Karena tingkat evaluasinya ditentukan lewat opsi build, penting memastikan konfigurasi rilis benar-benar mematikan pemeriksaan berat, bukan hanya mengandalkan kebiasaan.

Satu hal yang perlu diingat, aturan yang dimatikan bukan berarti aturannya hilang. Pelanggaran yang tidak lagi diperiksa tetap bisa terjadi di produksi, hanya saja tidak lagi terdeteksi lebih awal. Karena itu, pemeriksaan yang murah sebaiknya dibiarkan aktif, dan hanya pemeriksaan yang benar-benar mahal yang dimatikan.

Praktik yang lazim di proyek besar adalah menjalankan dua konfigurasi build: satu dengan semua pemeriksaan aktif untuk pengujian, dan satu dengan pemeriksaan berat dimatikan untuk rilis. Cara ini menjaga biaya produksi tetap rendah tanpa kehilangan deteksi di tahap pengujian.

Apakah Sudah Aman Dipakai di Produksi Sekarang?

Belum bisa digeneralisasi, karena dukungan compiler untuk contracts masih bertahap per Oktober 2026. Langkah paling aman adalah mengecek status implementasi compiler yang dipakai tim, lalu mencobanya pada modul yang tidak kritis sebelum menyentuh jalur utama.

Yang sudah bisa dipastikan, arah bahasa ini sudah final untuk C++26. Teks aturan contracts ada di draft kerja standar C++, dan proposalnya tersedia publik lewat tautan WG21. Untuk referensi sintaks harian, halaman contracts di cppreference lebih ringkas dibaca daripada dokumen standar. Jadi menyiapkan gaya penulisan sekarang bukan pekerjaan sia-sia, selama ekspektasi waktunya realistis dan tidak mengasumsikan semua compiler siap di hari yang sama.

Untuk pembaca yang mengelola basis kode besar, dua kesimpulan bisa diambil tanpa menebak. Pertama, contracts menyelesaikan masalah nyata, yaitu aturan yang tersebar di tiga tempat berbeda. Kedua, migrasinya sebaiknya bertahap per fungsi, bukan serentak, karena setiap kontrak baru adalah klaim yang harus benar dan bisa salah.

FAQ

Apakah contracts menggantikan assert sepenuhnya?

Tidak. contract_assert memang menggantikan assert di dalam badan fungsi, tapi assert masih berguna untuk pemeriksaan debugging yang cepat dan lokal. Bedanya, assert tidak pernah muncul di antarmuka fungsi, sehingga tidak bisa menjadi kesepakatan antar modul.

Bisakah pelanggaran kontrak ditangani, bukan langsung mematikan program?

Bisa. Desain C++26 menyediakan jalur handler untuk pelanggaran kontrak, sehingga program bisa mencatat informasi pelanggaran sebelum berhenti. Perilaku akhirnya tetap ditentukan oleh konfigurasi build yang dipakai.

Apakah contracts memperlambat program?

Tergantung level evaluasi yang dipilih saat build. Pemeriksaan yang mahal bisa dimatikan di rilis, sementara pemeriksaan murah bisa dibiarkan aktif. Biayanya ditentukan konfigurasi build, bukan otomatis oleh fitur bahasanya.

Apakah kontrak bisa dipakai di fungsi virtual atau template?

Kontrak menempel pada deklarasi fungsi, jadi ia mengikuti aturan deklarasi yang sama seperti spesifikasi lain. Untuk kasus warisan dan template, rujuk aturan di draft standar karena perilaku detailnya bergantung pada bentuk deklarasinya.

Di mana aturan lengkap contracts bisa dibaca?

Di proposal P2900 yang tersedia lewat WG21, dan di draft kerja standar C++ yang memuat teks aturannya. Untuk referensi sintaks sehari-hari, halaman contracts di cppreference dan artikel pengantar di C++ Stories lebih cepat dibaca.

💬 Komentar (0)

Belum ada komentar. Jadilah yang pertama! 💬

Komentar akan muncul setelah moderasi.