Gesaan dan skop
Anda memiliki perpustakaan pesanan dan mahukan C++26 Contracts untuk menyatakan prakondisi API, pascakondisi, dan invarian badan fungsi. Terangkan sempadan semantik bagi pre, post, dan contract_assert, mod penilaian, pengendalian pelanggaran, dan laluan migrasi daripada penggunaan assert sedia ada yang tidak mengabaikan pengesahan input yang tidak dipercayai. Sertakan strategi keluaran semasa sokongan pengkompil masih belum lengkap.
Perkara yang diuji oleh penemu duga
- Mengasingkan dokumentasi kontrak, diagnostik masa jalan (runtime), dan pengesahan input perniagaan.
- Menerangkan mengapa predikat mungkin ditiadakan (elided) atau dinilai lebih daripada sekali, jadi ia mestilah bebas kesan sampingan.
- Mereka bentuk pengendali pelanggaran, telemetri, dan pengasingan kerosakan tanpa menganggap setiap pelanggaran melontarkan pengecualian (exception).
- Menggunakan matriks keupayaan pengkompil dan pelancaran berperingkat untuk mengurus perbezaan sokongan C++26.
Soalan penjelasan
- Adakah pemanggil merupakan kod perpustakaan yang dipercayai atau pengendali permintaan rangkaian secara langsung?
- Patutkah pengeluaran memerhati pelanggaran, menamatkan proses, atau mengasingkan permintaan dan meneruskannya?
- Pengkompil, versi perpustakaan standard, dan kekerapan keluaran ABI yang manakah dalam skop?
Rangka kerja jawapan 30 saat
Mulakan dengan tanggungjawab: pre menyatakan perkara yang mesti dipenuhi oleh pemanggil, post menyatakan perkara yang dijanjikan oleh fungsi yang dipanggil semasa kembali, dan contract_assert menyatakan kontrak badan fungsi tempatan. Kemudian terangkan bahawa predikat tidak boleh mempunyai kesan sampingan kerana pelaksanaan mungkin meniadakan atau mengulangi penilaian; pelanggaran melalui pengendali dan dasar penggunaan. Akhir sekali, kekalkan pengesahan eksplisit untuk input yang tidak dipercayai dan gunakan pengesanan ciri, matriks pengkompil, serta canary untuk migrasi.
Jawapan mendalam
1. Mewujudkan lapisan kontrak
Prakondisi adalah tanggungjawab pemanggil; pascakondisi adalah tanggungjawab fungsi yang dipanggil. Ia menyatakan kekangan API yang boleh digabungkan seperti kapasiti positif atau jaminan susunan. contract_assert badan fungsi menyatakan invarian tempatan atau andaian peringkat algoritma. Ketiga-tiganya harus boleh dibaca dalam semakan kod dan diasingkan daripada dasar pemulihan.
2. Memastikan predikat bebas kesan sampingan
Predikat hanya perlu membaca keadaan dan mengira nilai Boolean. Jangan tingkatkan pembilang (counters), mutasikan cache, lepaskan sumber, atau bergantung pada kerawakkan sekali guna di dalamnya. Semantik kontrak C++26 membenarkan mod penilaian yang berbeza: sesetengahnya mungkin meniadakan penilaian dan yang lain mungkin menilai lebih daripada sekali. Kesan sampingan akan menyebabkan tingkah laku program yang betul bergantung pada konfigurasi binaan.
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 keratan kod menyerlahkan isu reka bentuk; C++26 tidak menyediakan penangkapan pascakondisi umum, jadi jangan mengandaikan sintaks old(...) wujud. Jika nilai lama diperlukan, simpannya secara eksplisit dalam fungsi dan sahkan bahawa penyimpanan tersebut tidak mengubah semantik perniagaan, atau tunggu sambungan standard yang terkemudian.
3. Memilih penilaian dan pengendalian pelanggaran
Tentukan tingkah laku untuk mod seperti observe dan enforce. Pembangunan dan ujian boleh mengumpul diagnostik; perkhidmatan kritikal boleh gagal pantas (fail fast) di bawah penguatkuasaan; sempadan permintaan boleh menukar kegagalan kepada hasil yang boleh diperhatikan dan diasingkan. Jangan berjanji bahawa pelanggaran sentiasa melontarkan pengecualian: tingkah laku bergantung pada pelaksanaan dan konfigurasi binaan. Pengendali harus merekodkan lokasi kontrak, ID korelasi permintaan, dan versi, sambil mengelakkan kemasukan rekursif ke dalam laluan kontrak yang sama.
4. Mengekalkan pengesahan input pada sempadan
Medan rangkaian, amaun pengguna, dan kebenaran adalah tidak dipercayai. Sahkannya secara eksplisit dan kembalikan ralat perniagaan yang dijangkakan sebelum memanggil fungsi berkontrak dalaman. Kontrak boleh menangkap kesilapan pengaturcara dalam kalangan pemanggil yang dipercayai, tetapi ia tidak menggantikan pengesahan, pembenaran, pengehadan kadar (rate limiting), atau pemeriksaan format dan tidak boleh menjadi satu-satunya pertahanan pembersihan data.
5. Merancang migrasi dan keluaran
Bina matriks keupayaan mengikut makro ciri dan versi pengkompil, menguji sintaks, pengendali, maklumat nyahpepijat, dan binaan yang dioptimumkan secara berasingan. Dayakan mod observe dalam ujian dan canary, bandingkan kadar pelanggaran dan overhed, kemudian tingkatkan penguatkuasaan secara beransur-ansur. assert tradisional mempunyai pengembangan makro, NDEBUG, dan andaian kesan sampingan yang tidak boleh disamakan secara mekanikal; audit setiap penggunaan, kekalkan semantik kegagalan, dan gunakan penyesuai apabila pelaporan seragam diperlukan.
Contoh jawapan berkualiti tinggi
Saya menganggap Kontrak sebagai kekangan reka bentuk yang boleh dilaksanakan, bukan sebagai pengesahan input atau sistem pengecualian. pre mengekang pemanggil, post mengekang fungsi yang dipanggil, dan contract_assert mengekang invarian badan fungsi. Setiap predikat kekal baca sahaja kerana pelaksanaan mungkin meniadakan atau mengulangi penilaian, jadi saya melarang I/O, mutasi pembilang, dan pelepasan sumber. Pelanggaran melalui satu pengendali yang mengeluarkan diagnostik berstruktur; dasar binaan kemudiannya memilih pemerhatian, penguatkuasaan, atau tingkah laku fail-fast, dan kod tidak mengandaikan pengecualian. Sempadan permintaan masih melakukan pengesahan, pembenaran, dan pemeriksaan data yang tidak dipercayai. Untuk migrasi, saya mencipta matriks keupayaan pengkompil, bermula dengan ujian dan canary, dan meningkatkan penguatkuasaan secara beransur-ansur; saya mengaudit NDEBUG dan kesan sampingan makro daripada menggantikan assert secara mekanikal. Ini memberikan pemeriksaan dalaman yang stabil tanpa menjadikan pemulihan dalam talian bergantung pada tingkah laku pelaksanaan yang tidak seragam.
Kesilapan biasa
- Menganggap
presebagai pemeriksaan keselamatan yang lengkap untuk setiap input. - Melakukan pengelogan, meningkatkan metrik, atau memutasikan objek di dalam predikat.
- Mendakwa bahawa pelanggaran mesti melontarkan pengecualian atau mesti ditamatkan, tanpa mengira mod.
- Menggantikan setiap
assertdengancontract_assertdan terlepas pandangNDEBUG, hujah makro, atau kesan sampingan. - Menerangkan pascakondisi C++26 dengan sintaks umum
old(...)yang tidak wujud.
Soalan susulan dan jawapan
Bagaimana jika penemu duga bertanya, "Mengapa tidak menggunakan pengecualian di semua tempat?"
Kontrak menyatakan tanggungjawab pemanggil dan fungsi yang dipanggil serta boleh mengambil bahagian dalam semakan dan dasar binaan; pengecualian mentakrifkan aliran kawalan dan pemulihan. Kedua-duanya saling melengkapi dan tidak boleh ditukar ganti.
Bagaimana jika penilaian predikat berulang terlalu mahal?
Kekalkan predikat sebagai baca sahaja dan ringan, kemudian ukur setiap mod binaan. Letakkan diagnostik yang mahal pada laluan eksplisit dan bukannya menyembunyikan kesan sampingan dalam kontrak.
Bagaimana jika mereka bertanya sama ada fungsi maya boleh membawa kontrak secara langsung?
C++26 mempunyai sempadan di sini: pelan tindakan WG21 menyenaraikan sokongan fungsi maya sebagai sambungan kemudian. Semak pengkompil sasaran dan versi standard dan bukannya membentangkan cadangan masa hadapan sebagai tingkah laku C++26 sedia ada.