Topik wawancara representatif

C++26 Contracts: Bagaimana cara aman merilis preconditions, postconditions, dan contract_assert?

CodingSulit
Tim Redaksi Offer.ccDipublikasikan Diperbarui

Pertanyaan

Bagaimana Anda menjelaskan pre, post, dan contract_assert serta memigrasikan penggunaan assert yang ada saat dukungan kompiler belum lengkap?

Perintah dan cakupan

Anda mengelola sebuah library pesanan dan ingin C++26 Contracts untuk mengekspresikan prakondisi API, pascakondisi, dan invarian badan fungsi. Jelaskan batas semantik dari pre, post, dan contract_assert, mode evaluasi, penanganan pelanggaran, dan jalur migrasi dari penggunaan assert yang ada tanpa menghilangkan validasi input yang tidak tepercaya. Sertakan strategi rilis saat dukungan kompiler belum lengkap.

Hal yang diuji oleh pewawancara

  • Memisahkan dokumentasi kontrak, diagnostik runtime, dan validasi input bisnis.
  • Menjelaskan mengapa predikat dapat dihilangkan (elided) atau dievaluasi lebih dari satu kali, sehingga harus bebas dari efek samping.
  • Merancang penangan pelanggaran (violation handler), telemetri, dan isolasi kegagalan tanpa mengasumsikan setiap pelanggaran melempar eksepsi.
  • Menggunakan matriks kapabilitas kompiler dan peluncuran bertahap untuk mengelola perbedaan dukungan C++26.

Pertanyaan klarifikasi

  1. Apakah pemanggil adalah kode library yang tepercaya atau penangan permintaan jaringan langsung?
  2. Apakah produksi harus mengamati (observe) pelanggaran, menghentikan proses, atau mengisolasi permintaan dan melanjutkan?
  3. Kompiler, versi pustaka standar, dan ritme rilis ABI mana yang masuk dalam cakupan?

Kerangka jawaban 30 detik

Mulailah dengan tanggung jawab: pre menyatakan apa yang harus dipenuhi pemanggil, post menyatakan apa yang dijanjikan oleh fungsi yang dipanggil saat kembali, dan contract_assert menyatakan kontrak lokal badan fungsi. Kemudian jelaskan bahwa predikat tidak boleh memiliki efek samping karena implementasi dapat menghilangkan atau mengulang evaluasi; pelanggaran melewati penangan dan kebijakan penerapan. Terakhir, pertahankan validasi eksplisit untuk input yang tidak tepercaya dan gunakan deteksi fitur, matriks kompiler, serta canary untuk migrasi.

Jawaban mendalam

1. Menetapkan lapisan kontrak

Prakondisi adalah tanggung jawab pemanggil; pascakondisi adalah tanggung jawab fungsi yang dipanggil. Keduanya mengekspresikan batasan API yang dapat dikomposisikan seperti kapasitas positif atau jaminan pengurutan. contract_assert pada badan fungsi mengekspresikan invarian lokal atau asumsi tahapan algoritma. Ketiganya harus dapat dibaca dalam tinjauan kode dan dipisahkan dari kebijakan pemulihan.

2. Menjaga predikat bebas dari efek samping

Sebuah predikat seharusnya hanya membaca status dan menghitung nilai Boolean. Jangan menambah penghitung (counters), memutasi cache, melepaskan sumber daya, atau bergantung pada keacakan satu kali di dalamnya. Semantik kontrak C++26 memungkinkan mode evaluasi yang berbeda: beberapa dapat menghilangkan evaluasi dan yang lain dapat mengevaluasi lebih dari sekali. Efek samping akan membuat perilaku program yang benar bergantung pada konfigurasi build.

cpp
int withdraw(Account& a, int amount)
  pre (amount > 0)
  pre (amount <= a.balance())
  post (a.balance() == old_balance - amount);
{
  contract_assert(a.is_open());
  return a.debit(amount);
}

old_balance dalam cuplikan kode menyoroti masalah desain; C++26 tidak menyediakan penangkapan pascakondisi umum, jadi jangan berasumsi sintaks old(...) tersedia. Jika nilai lama diperlukan, simpan secara eksplisit di dalam fungsi dan konfirmasikan bahwa penyimpanan tersebut tidak mengubah semantik bisnis, atau tunggu ekstensi standar berikutnya.

3. Memilih evaluasi dan penanganan pelanggaran

Tentukan perilaku untuk mode seperti observe dan enforce. Pengembangan dan pengujian dapat mengumpulkan diagnostik; layanan kritis dapat langsung gagal (fail fast) di bawah penegakan; batas permintaan dapat mengubah kegagalan menjadi hasil yang dapat diamati dan diisolasi. Jangan berjanji bahwa pelanggaran selalu melempar eksepsi: perilaku bergantung pada implementasi dan konfigurasi build. Penangan harus mencatat lokasi kontrak, ID korelasi permintaan, dan versi, sambil menghindari masuknya rekursif ke dalam jalur kontrak yang sama.

4. Mempertahankan validasi input di batas sistem

Field jaringan, jumlah pengguna, dan izin tidak tepercaya. Validasi secara eksplisit dan kembalikan kesalahan bisnis yang diharapkan sebelum memanggil fungsi berkontrak internal. Kontrak dapat menangkap kesalahan pemrogram di antara pemanggil tepercaya, tetapi mereka tidak menggantikan autentikasi, otorisasi, pembatasan laju (rate limiting), atau pemeriksaan format dan tidak boleh menjadi satu-satunya pertahanan pembersihan data.

5. Merencanakan migrasi dan rilis

Bangun matriks kapabilitas berdasarkan makro fitur dan versi kompiler, uji sintaksis, penangan, informasi debug, dan build yang dioptimalkan secara terpisah. Aktifkan mode observe dalam pengujian dan canary, bandingkan tingkat pelanggaran dan overhead, lalu tingkatkan penegakan (enforcement) secara bertahap. assert tradisional memiliki ekspansi makro, NDEBUG, dan asumsi efek samping yang tidak dapat disamakan secara mekanis; audit setiap penggunaan, pertahankan semantik kegagalan, dan gunakan adaptor ketika pelaporan seragam diperlukan.

Contoh jawaban berkualitas tinggi

Saya memperlakukan Kontrak sebagai batasan desain yang dapat dieksekusi, bukan sebagai validasi input atau sistem eksepsi. pre membatasi pemanggil, post membatasi fungsi yang dipanggil, dan contract_assert membatasi invarian badan fungsi. Setiap predikat tetap bersifat hanya-baca karena implementasi dapat menghilangkan atau mengulang evaluasi, sehingga saya melarang I/O, mutasi penghitung, dan pelepasan sumber daya. Pelanggaran melewati satu penangan yang memancarkan diagnostik terstruktur; kebijakan build kemudian memilih pengamatan, penegakan, atau perilaku fail-fast, dan kode tidak mengasumsikan adanya eksepsi. Batas permintaan tetap melakukan autentikasi, otorisasi, dan pemeriksaan data yang tidak tepercaya. Untuk migrasi, saya membuat matriks kapabilitas kompiler, memulai dengan pengujian dan canary, serta meningkatkan penegakan secara bertahap; saya mengaudit NDEBUG dan efek samping makro daripada mengganti assert secara mekanis. Ini memberikan pemeriksaan internal yang stabil tanpa membuat pemulihan online bergantung pada perilaku implementasi yang tidak seragam.

Kesalahan umum

  • Memperlakukan pre sebagai pemeriksaan keamanan lengkap untuk setiap input.
  • Melakukan pencatatan log, menambah metrik, atau memutasi objek di dalam predikat.
  • Mengklaim bahwa pelanggaran harus melempar eksepsi atau harus menghentikan program, terlepas dari modenya.
  • Mengganti setiap assert dengan contract_assert dan melewatkan NDEBUG, argumen makro, atau efek samping.
  • Menjelaskan pascakondisi C++26 dengan sintaks umum old(...) yang tidak ada.

Pertanyaan lanjutan dan tanggapan

Bagaimana jika pewawancara bertanya, "Mengapa tidak menggunakan eksepsi di mana saja?"

Kontrak menyatakan tanggung jawab pemanggil dan fungsi yang dipanggil serta dapat berpartisipasi dalam tinjauan dan kebijakan build; eksepsi mendefinisikan alur kontrol dan pemulihan. Keduanya saling melengkapi dan tidak dapat dipertukarkan.

Bagaimana jika evaluasi predikat yang berulang terlalu mahal?

Pertahankan predikat tetap hanya-baca dan ringan, lalu ukur setiap mode build. Tempatkan diagnostik yang mahal pada jalur eksplisit alih-alih menyembunyikan efek samping dalam kontrak.

Bagaimana jika mereka bertanya apakah fungsi virtual dapat membawa kontrak secara langsung?

C++26 memiliki batasan di sini: roadmap WG21 mencantumkan dukungan fungsi virtual sebagai ekstensi berikutnya. Periksa kompiler target dan versi standar daripada menyajikan proposal masa depan sebagai perilaku C++26 yang ada saat ini.

Sumber publik

Pertanyaan terkait

Alat wawancara terkait

Gunakan Tangkapan Layar untuk perintah coding

Ambil tangkapan layar soal, lalu telusuri batasan, solusi, kode, edge case, dan kompleksitas secara berurutan.

Lihat alat