Perilaku default assert
di dalam C ++ adalah tidak melakukan apa pun di build rilis. Saya kira ini dilakukan karena alasan kinerja dan mungkin untuk mencegah pengguna melihat pesan kesalahan yang tidak menyenangkan.
Namun, saya berpendapat bahwa situasi di mana sebuah assert
akan dipecat tetapi dinonaktifkan bahkan lebih menyusahkan karena aplikasi kemudian mungkin akan crash dalam cara yang lebih buruk di telepon karena beberapa invarian rusak.
Selain itu, argumen kinerja bagi saya hanya diperhitungkan ketika itu adalah masalah yang dapat diukur. Sebagian besar assert
dalam kode saya tidak lebih rumit dari
assert(ptr != nullptr);
yang akan berdampak kecil pada sebagian besar kode.
Ini mengarahkan saya ke pertanyaan: Haruskah pernyataan (yang artinya konsep, bukan implementasi spesifik) menjadi aktif dalam rilis rilis? Kenapa tidak)?
Harap dicatat bahwa pertanyaan ini bukan tentang cara mengaktifkan #undef _NDEBUG
assert dalam build rilis (suka atau menggunakan implementasi menegaskan sendiri). Selain itu, ini bukan tentang mengaktifkan pernyataan di pihak ketiga / kode perpustakaan standar tetapi dalam kode yang dikendalikan oleh saya.
sumber
Jawaban:
Klasik
assert
adalah alat dari pustaka C standar lama, bukan dari C ++. Ini masih tersedia di C ++, setidaknya untuk alasan kompatibilitas ke belakang.Saya tidak punya garis waktu yang tepat untuk lib standar C yang ada, tetapi saya cukup yakin
assert
tersedia tidak lama setelah waktu ketika K&R C mulai beroperasi (sekitar 1978). Dalam C klasik, untuk menulis program yang kuat, menambahkan tes pointer NULL dan memeriksa batas array harus dilakukan jauh lebih sering daripada di C ++. Gross dari tes pointer NULL dapat dihindari dengan menggunakan referensi dan / atau smart pointer daripada pointer, dan dengan menggunakanstd::vector
, pemeriksaan batas array seringkali tidak diperlukan. Selain itu, kinerja yang mencapai pada 1980 jelas jauh lebih penting daripada hari ini. Jadi saya pikir itu sangat mungkin alasan mengapa "menegaskan" dirancang untuk menjadi aktif hanya dalam membangun debug secara default.Selain itu, untuk penanganan kesalahan nyata dalam kode produksi, suatu fungsi yang hanya menguji beberapa kondisi atau invarian, dan crash program jika kondisi tidak terpenuhi, dalam banyak kasus tidak cukup fleksibel. Untuk debugging mungkin ok, karena orang yang menjalankan program dan mengamati kesalahan biasanya memiliki debugger untuk menganalisis apa yang terjadi. Untuk kode produksi, bagaimanapun, solusi yang masuk akal perlu menjadi fungsi atau mekanisme yang
menguji beberapa kondisi (dan menghentikan eksekusi pada lingkup di mana kondisi gagal)
memberikan pesan kesalahan yang jelas jika kondisi tidak berlaku
memungkinkan lingkup luar untuk mengambil pesan kesalahan dan mengeluarkannya ke saluran komunikasi tertentu. Saluran ini mungkin berupa stderr, file log standar, kotak pesan dalam program GUI, kesalahan umum menangani panggilan balik, saluran kesalahan yang didukung jaringan, atau apa pun yang paling cocok dengan perangkat lunak tertentu.
memungkinkan lingkup luar pada basis per-kasus untuk memutuskan apakah program harus berakhir dengan anggun, atau jika harus dilanjutkan.
(Tentu saja, ada juga situasi di mana mengakhiri program segera jika kondisi yang tidak terpenuhi adalah satu-satunya pilihan yang masuk akal, tetapi dalam kasus seperti itu, itu harus terjadi dalam rilis rilis juga, bukan hanya di debug build).
Karena klasik
assert
tidak menyediakan fitur ini, itu tidak cocok untuk rilis build, anggap build rilis adalah apa yang digunakan seseorang untuk diproduksi.Sekarang Anda dapat bertanya mengapa tidak ada fungsi atau mekanisme seperti itu di lib standar C yang menyediakan fleksibilitas semacam ini. Sebenarnya, di C ++, ada mekanisme standar yang memiliki semua fitur ini (dan banyak lagi), dan Anda tahu itu: itu disebut pengecualian .
Namun, dalam C, sulit untuk mengimplementasikan mekanisme standar tujuan umum yang baik untuk penanganan kesalahan dengan semua fitur yang disebutkan karena kurangnya pengecualian sebagai bagian dari bahasa pemrograman. Jadi sebagian besar program C memiliki mekanisme penanganan kesalahan sendiri dengan kode kembali, atau "goto", atau "lompatan panjang", atau campuran dari itu. Ini sering merupakan solusi pragmatis yang sesuai dengan jenis program tertentu, tetapi tidak "cukup tujuan umum" untuk masuk ke perpustakaan standar C.
sumber
#include <cassert>
). Itu benar-benar masuk akal sekarang.assert
alat yang salah untuk ini juga (melemparkan pengecualian mungkin juga merupakan reaksi yang salah ). Namun, saya cukup yakin pada kenyataannyaassert
juga digunakan untuk banyak tes di C di mana di C ++ orang akan menggunakan pengecualian hari ini.Jika Anda mendapati diri Anda ingin menegaskan diaktifkan dalam rilis Anda telah meminta menegaskan untuk melakukan pekerjaan yang salah.
Maksud dari penegasan adalah bahwa mereka tidak diaktifkan dalam rilis. Hal ini memungkinkan untuk menguji invarian selama pengembangan dengan kode yang seharusnya harus kode perancah. Kode yang harus dihapus sebelum dirilis.
Jika Anda memiliki sesuatu yang Anda rasa harus diuji bahkan selama rilis kemudian tulis kode yang mengujinya. The
If throw
membangun bekerja sangat baik. Jika Anda ingin mengatakan sesuatu yang berbeda dari lemparan lainnya cukup gunakan pengecualian deskriptif yang mengatakan apa yang ingin Anda katakan.Bukannya Anda tidak bisa mengubah cara Anda menggunakan pernyataan. Melakukan hal itu tidak memberi Anda sesuatu yang berguna, bertentangan dengan harapan, dan membuat Anda tidak memiliki cara yang bersih untuk melakukan apa yang dimaksudkan untuk dilakukan. Tambahkan tes yang tidak aktif dalam rilis.
Mengapa tidak ada relase_assert? Terus terang karena menegaskan tidak cukup baik untuk produksi. Ya ada kebutuhan tetapi tidak ada yang memenuhi kebutuhan itu dengan baik. Oh tentu Anda bisa mendesain sendiri. Secara otomatis fungsi throwIf Anda hanya membutuhkan bool dan pengecualian untuk melempar. Dan itu mungkin sesuai dengan kebutuhan Anda. Tapi Anda benar-benar membatasi desain. Itu sebabnya tidak mengejutkan saya bahwa tidak ada yang dipanggang dalam pernyataan seperti melempar sistem pengecualian di perpustakaan bahasa Anda. Jelas bukan karena Anda tidak bisa melakukannya. Yang lainnya punya . Tetapi berurusan dengan kasus di mana kesalahan terjadi adalah 80% pekerjaan untuk sebagian besar program. Dan sejauh ini belum ada yang menunjukkan kepada kami ukuran yang bagus untuk semua solusi. Berurusan secara efektif dengan kasus-kasus ini dapat menjadi rumit. Jika kita memiliki sistem release_assert kalengan yang tidak memenuhi kebutuhan kita, saya pikir itu akan melakukan lebih banyak kerusakan daripada bagus. Anda meminta abstraksi yang bagus yang berarti Anda tidak perlu memikirkan masalah ini. Saya juga menginginkannya, tetapi sepertinya belum sampai.
Mengapa pernyataan dinonaktifkan dalam rilis? Pemberitahuan dibuat pada puncak usia kode perancah. Kode kami harus dihapus karena tahu kami tidak menginginkannya dalam produksi tetapi tahu kami ingin menjalankan dalam pengembangan untuk membantu kami menemukan bug. Pemberitahuan adalah alternatif yang lebih bersih dari
if (DEBUG)
pola yang memungkinkan kami meninggalkan kode tetapi menonaktifkannya. Ini sebelum pengujian unit berjalan sebagai cara utama untuk memisahkan kode pengujian dari kode produksi. Asserts masih digunakan hari ini, bahkan oleh penguji unit ahli, baik untuk membuat harapan yang jelas dan untuk menutupi kasus bahwa mereka masih melakukan lebih baik daripada pengujian unit.Mengapa tidak membiarkan kode debug dalam produksi saja? Karena kode produksi perlu tidak mempermalukan perusahaan, tidak memformat hard drive, tidak merusak database, dan tidak mengirim presiden mengancam email. Singkatnya itu bagus untuk dapat menulis kode debugging di tempat yang aman di mana Anda tidak perlu khawatir tentang itu.
sumber
catch(...)
.if (DEBUG)
untuk mengontrol sesuatu selain kode debugging. Optimalisasi mikro mungkin tidak ada artinya dalam kasus Anda. Tetapi idiom itu tidak boleh dirusak hanya karena Anda tidak membutuhkannya.assert
tetapi konsep penegasan. Saya tidak ingin menyalahgunakanassert
atau membingungkan pembaca. Saya bermaksud bertanya mengapa ini terjadi sejak awal. Mengapa tidak ada tambahanrelease_assert
? Apakah tidak perlu untuk itu? Apa alasan di balik pernyataan dilumpuhkan dalam rilis?You're asking for a good abstraction.
Saya tidak yakin tentang hal itu. Saya terutama ingin berurusan dengan masalah di mana tidak ada pemulihan dan itu seharusnya tidak pernah terjadi. Ambil ini bersama-sama denganBecause production code needs to [...] not format the hard drive [...]
dan saya akan mengatakan untuk kasus-kasus di mana invarian rusak, saya akan menegaskan di UB kapan saja.Pernyataan adalah alat debugging , bukan teknik pemrograman defensif. Jika Anda ingin melakukan validasi dalam semua keadaan, maka lakukan validasi dengan menulis kondisional - atau buat makro Anda sendiri untuk mengurangi pelat tungku.
sumber
assert
adalah bentuk dokumentasi, seperti komentar. Seperti komentar, Anda biasanya tidak mengirimkannya ke pelanggan - mereka tidak termasuk dalam kode rilis.Tetapi masalah dengan komentar adalah bahwa mereka bisa menjadi ketinggalan zaman, namun mereka tertinggal. Itu sebabnya pernyataan bagus - mereka diperiksa dalam mode debug. Ketika pernyataan tersebut menjadi usang, Anda dengan cepat menemukannya, dan masih akan tahu bagaimana cara memperbaiki pernyataan tersebut. Komentar itu yang sudah ketinggalan zaman 3 tahun lalu? Tebakan siapa saja.
sumber
Jika Anda tidak ingin "pernyataan" dimatikan, jangan ragu untuk menulis fungsi sederhana yang memiliki efek serupa:
Artinya,
assert
adalah untuk tes di mana Anda lakukan ingin mereka pergi dalam produk dikirimkan. Jika Anda ingin tes itu menjadi bagian dari perilaku program yang ditentukan,assert
adalah alat yang salah.sumber
Tidak ada gunanya untuk berdebat apa yang harus dilakukan, itu melakukan apa yang dilakukannya. Jika Anda menginginkan sesuatu yang berbeda, tulis fungsi Anda sendiri. Sebagai contoh, saya memiliki Assert yang berhenti di debugger atau tidak melakukan apa-apa, saya memiliki AssertFatal yang akan membuat crash aplikasi, saya memiliki fungsi bool yang Ditegaskan dan AssertionFailed yang menegaskan dan mengembalikan hasilnya sehingga saya dapat menegaskan dan menangani situasi.
Untuk masalah yang tidak terduga, Anda perlu memutuskan apa cara terbaik bagi pengembang dan pengguna untuk menanganinya.
sumber
verify(cond)
cara normal untuk menegaskan dan mengembalikan hasilnya?Seperti yang ditunjukkan orang lain,
assert
adalah semacam benteng pertahanan terakhir Anda terhadap kesalahan programmer yang seharusnya tidak pernah terjadi. Itu adalah pemeriksaan kewarasan yang diharapkan tidak akan gagal ke kiri dan ke kanan pada saat Anda mengirim.Itu juga dirancang untuk dihilangkan dari rilis rilis stabil, untuk alasan apa pun yang mungkin berguna bagi pengembang: estetika, kinerja, apa pun yang mereka inginkan. Ini adalah bagian dari apa yang memisahkan debug build dari build rilis, dan menurut definisi build rilis tidak memiliki pernyataan seperti itu. Jadi ada subversi desain jika Anda ingin merilis analog "rilis build dengan pernyataan di tempat" yang akan menjadi upaya rilis rilis dengan
_DEBUG
definisi preprocessor dan tidakNDEBUG
terdefinisi; ini sebenarnya bukan rilis build lagi.Desainnya bahkan meluas ke perpustakaan standar. Sebagai contoh yang sangat mendasar di antara banyak, banyak implementasi
std::vector::operator[]
akanassert
pemeriksaan kewarasan untuk memastikan Anda tidak memeriksa vektor di luar batas. Dan perpustakaan standar akan mulai berkinerja jauh, jauh lebih buruk jika Anda mengaktifkan pemeriksaan seperti itu dalam rilis rilis. Patokanvector
menggunakanoperator[]
dan ctor pengisian dengan pernyataan seperti itu yang disertakan pada array dinamis lama yang biasa akan sering menunjukkan array dinamis menjadi jauh lebih cepat hingga Anda menonaktifkan pemeriksaan tersebut, sehingga mereka sering melakukan dampak kinerja jauh, jauh dari cara sepele. Pemeriksaan null pointer di sini dan pemeriksaan di luar batas sebenarnya dapat menjadi beban besar jika pemeriksaan seperti itu diterapkan jutaan kali pada setiap frame dalam loop kritis sebelum kode semudah mendereferensi pointer cerdas atau mengakses array.Jadi, Anda kemungkinan besar menginginkan alat yang berbeda untuk pekerjaan itu dan yang tidak dirancang untuk dihilangkan dari rilis build jika Anda ingin rilis build yang melakukan pemeriksaan kewarasan seperti itu di bidang utama. Yang paling berguna yang saya temukan secara pribadi adalah logging. Dalam hal itu, ketika pengguna melaporkan bug, banyak hal menjadi lebih mudah jika mereka melampirkan log dan baris terakhir dari log memberi saya petunjuk besar tentang di mana bug itu terjadi dan apa yang mungkin terjadi. Kemudian, setelah mereproduksi langkah-langkah mereka dalam membangun debug, saya mungkin juga mendapatkan kegagalan pernyataan, dan bahwa kegagalan pernyataan lebih lanjut memberi saya petunjuk besar untuk merampingkan waktu saya. Namun karena logging relatif mahal, saya tidak menggunakannya untuk menerapkan pemeriksaan kewarasan tingkat sangat rendah seperti memastikan array tidak diakses di luar batas dalam struktur data generik.
Namun akhirnya, dan agak setuju dengan Anda, saya bisa melihat kasus yang masuk akal di mana Anda mungkin benar-benar ingin memberikan sesuatu kepada penguji seperti build debug selama pengujian alpha, misalnya, dengan sekelompok kecil penguji alfa yang, misalnya, menandatangani NDA . Di sana mungkin merampingkan pengujian alfa jika Anda memberikan sesuatu kepada penguji Anda selain versi rilis lengkap dengan beberapa info debug yang dilampirkan bersama dengan beberapa fitur debug / pengembangan seperti tes yang dapat dijalankan dan lebih banyak keluaran verbose saat mereka menjalankan perangkat lunak. Saya setidaknya melihat beberapa perusahaan game besar melakukan hal seperti itu untuk alpha. Tapi itu untuk sesuatu seperti pengujian alfa atau internal di mana Anda benar-benar mencoba untuk memberikan sesuatu kepada penguji selain dari rilis rilis. Jika Anda benar-benar mencoba mengirimkan rilis build, maka menurut definisi, seharusnya tidak
_DEBUG
didefinisikan atau yang benar-benar membingungkan perbedaan antara "debug" dan "rilis" build.Seperti yang ditunjukkan di atas, cek tidak selalu sepele dari sudut pandang kinerja. Banyak yang mungkin sepele, tetapi sekali lagi, bahkan lib standar menggunakannya dan itu dapat mempengaruhi kinerja dengan cara yang tidak dapat diterima bagi banyak orang dalam banyak kasus jika, katakanlah, akses acak melintasi
std::vector
memakan waktu 4 kali lebih lama dalam apa yang seharusnya menjadi rilis rilis yang dioptimalkan karena batas-batasnya memeriksa yang seharusnya tidak pernah gagal.Dalam mantan tim kami benar-benar harus membuat matriks dan pustaka vektor kami mengecualikan beberapa pernyataan di jalur kritis tertentu hanya untuk membuat debug membangun berjalan lebih cepat, karena pernyataan itu memperlambat operasi matematika dengan lebih dari urutan besarnya ke titik di mana itu mulai mengharuskan kita untuk menunggu 15 menit sebelum kita bahkan dapat melacak kode yang diinginkan. Rekan kerja saya sebenarnya ingin menghapus saja
asserts
langsung karena mereka menemukan bahwa melakukan itu membuat perbedaan besar. Sebaliknya, kami memutuskan untuk membuat jalur debug kritis untuk menghindarinya. Ketika kami membuat jalur kritis tersebut menggunakan data vektor / matriks secara langsung tanpa melalui pemeriksaan batas, waktu yang diperlukan untuk melakukan operasi penuh (yang mencakup lebih dari sekadar vektor / matriks matematika) berkurang dari menit ke detik. Jadi itu adalah kasus yang ekstrem tetapi yang pasti penegasan tidak selalu diabaikan dari sudut pandang kinerja, bahkan tidak dekat.Tapi juga hanya caranya saja
asserts
yang dirancang. Jika mereka tidak memiliki dampak kinerja yang sangat besar di seluruh papan, maka saya mungkin lebih suka jika mereka dirancang sebagai lebih dari fitur membangun debug atau kita dapat menggunakanvector::at
yang mencakup batas memeriksa bahkan dalam rilis rilis membangun dan membuang di luar batas akses, misalnya (belum dengan hit kinerja besar). Tetapi saat ini saya menemukan desain mereka jauh lebih berguna, mengingat dampak kinerja besar mereka dalam kasus saya, sebagai fitur debug-build-only yang dihilangkan ketikaNDEBUG
didefinisikan. Untuk kasus-kasus yang pernah saya tangani, itu membuat perbedaan besar untuk build rilis untuk mengecualikan cek kewarasan yang seharusnya tidak pernah benar-benar gagal sejak awal.vector::at
vs.vector::operator[]
Saya pikir perbedaan dari dua metode ini menjadi inti dari ini juga sebagai alternatif: pengecualian.
vector::operator[]
implementasi biasanyaassert
untuk memastikan bahwa akses di luar batas akan memicu kesalahan yang mudah direproduksi ketika mencoba mengakses vektor di luar batas. Tetapi pelaksana perpustakaan melakukan ini dengan asumsi bahwa tidak akan ada biaya sepeser pun dalam rilis rilis yang dioptimalkan.Sementara
vector::at
itu disediakan yang selalu melakukan out of bounds memeriksa dan melempar bahkan dalam rilis rilis, tetapi memiliki penalti kinerja ke titik di mana saya sering melihat kode jauh lebih banyak menggunakanvector::operator[]
daripadavector::at
. Banyak desain C ++ menggemakan gagasan "membayar untuk apa yang Anda gunakan / butuhkan", dan banyak orang sering mendukungoperator[]
, yang bahkan tidak repot-repot dengan batas-batas memeriksa membangun rilis, berdasarkan pada gagasan bahwa mereka tidak perlu batas memeriksa di rilis rilis dioptimalkan mereka. Tiba-tiba jika pernyataan diaktifkan dalam rilis build, kinerja keduanya akan identik, dan penggunaan vektor akan selalu lebih lambat dari array dinamis. Jadi sebagian besar dari desain dan manfaat pernyataan didasarkan pada gagasan bahwa mereka menjadi bebas dalam rilis rilis.release_assert
Ini menarik setelah menemukan niat ini. Secara alami setiap kasus penggunaan akan berbeda, tetapi saya pikir saya akan menemukan beberapa kegunaan untuk
release_assert
yang melakukan pengecekan dan akan merusak perangkat lunak yang menampilkan nomor baris dan pesan kesalahan bahkan dalam versi rilis.Untuk beberapa kasus yang tidak jelas dalam kasus saya di mana saya tidak ingin perangkat lunak pulih dengan anggun seperti jika ada pengecualian. Saya ingin crash bahkan dalam rilis dalam kasus-kasus sehingga pengguna dapat diberi nomor baris untuk melaporkan ketika perangkat lunak menemukan sesuatu yang seharusnya tidak pernah terjadi, masih dalam ranah kewarasan memeriksa kesalahan programmer, bukan kesalahan input eksternal seperti pengecualian, tetapi cukup murah untuk dilakukan tanpa khawatir tentang biaya dalam rilis.
Sebenarnya ada beberapa kasus di mana saya akan menemukan crash keras dengan nomor baris dan pesan kesalahan lebih baik untuk pulih dari pengecualian dilemparkan yang mungkin cukup murah untuk disimpan dalam rilis. Dan ada beberapa kasus di mana tidak mungkin untuk pulih dari pengecualian, seperti kesalahan yang ditemui saat mencoba memulihkan dari yang sudah ada. Di sana saya akan menemukan yang sempurna untuk
release_assert(!"This should never, ever happen! The software failed to fail!");
dan tentu saja itu akan menjadi murah karena cek akan dilakukan di dalam jalur yang luar biasa di tempat pertama dan tidak akan dikenakan biaya apa pun di jalur eksekusi normal.sumber
Additionally, the performance argument for me only counts when it is a measurable problem.
Jadi idenya adalah memutuskan berdasarkan kasus per kasus kapan harus mengeluarkan pernyataan dari rilis.assert
yang bergantung pada_DEBUG/NDEBUG
def preprocessor yang sama dengan apa yang Anda gunakan saat Anda menggunakanassert
makro. Jadi tidak ada cara untuk mengaktifkan pernyataan secara selektif hanya untuk satu bagian kode tanpa mengaktifkannya untuk lib standar juga, misalnya, dengan asumsi ia menggunakan lib standar.assert
.vector::operator[]
danvector::at
, misalnya, akan memiliki kinerja yang hampir sama jika pernyataan diaktifkan dalam rilis build, dan tidak akan ada gunanya menggunakanoperator[]
lagi dari sudut pandang kinerja karena itu tetap akan melakukan pemeriksaan batas.Saya sedang mengkode dalam Ruby, bukan C / C ++, jadi saya tidak akan berbicara tentang perbedaan antara pernyataan dan pengecualian, tetapi saya ingin membicarakannya sebagai hal yang menghentikan runtime . Saya tidak setuju dengan sebagian besar jawaban di atas, karena menghentikan program dengan mencetak backtrace bekerja dengan baik bagi saya sebagai teknik pemrograman defensif.
Jika ada cara untuk menegaskan rutin (sama sekali tidak peduli bagaimana itu ditulis secara sintaksis dan jika kata "menegaskan" digunakan atau ada dalam bahasa pemrograman atau dsl sama sekali) untuk dipanggil yang berarti beberapa pekerjaan harus dilakukan dan produk segera kembali dari "dirilis, siap pakai" ke "perlu tambalan" - sekarang baik menulis ulang untuk penanganan pengecualian nyata atau memperbaiki bug yang menyebabkan data yang salah muncul.
Maksud saya Assert bukanlah hal yang harus Anda jalani dan sering hubungi - itu adalah sinyal berhenti yang menunjukkan bahwa Anda harus melakukan sesuatu untuk membuatnya tidak pernah terjadi lagi. Dan mengatakan bahwa "rilis build seharusnya tidak memiliki penegasan" seperti mengatakan "rilis build seharusnya tidak memiliki bug" - bung, hampir mustahil, atasi itu.
Atau pikirkan tentang mereka sebagai "gagal unittests yang dibuat dan dijalankan oleh pengguna akhir". Anda tidak dapat memprediksi semua hal yang akan dilakukan pengguna dengan program Anda, tetapi jika terlalu serius kesalahannya harus dihentikan - ini mirip dengan cara Anda membangun jaringan pipa - Anda menghentikan proses dan tidak menerbitkan, apakah Anda ? Penegasan memaksa pengguna untuk berhenti, melaporkan dan menunggu bantuan Anda.
sumber