Debug.Assert vs Exception Throwing

93

Saya telah membaca banyak artikel (dan beberapa pertanyaan serupa lainnya yang diposting di StackOverflow) tentang bagaimana dan kapan menggunakan pernyataan, dan saya memahaminya dengan baik. Tapi tetap saja, saya tidak mengerti motivasi seperti apa yang harus mendorong saya untuk menggunakan Debug.Assertalih-alih memberikan pengecualian. Apa yang saya maksud adalah, di NET respon default untuk pernyataan yang gagal adalah untuk "menghentikan dunia" dan menampilkan kotak pesan kepada pengguna. Meskipun perilaku semacam ini dapat dimodifikasi, saya merasa sangat menjengkelkan dan mubazir untuk melakukan itu, sementara saya dapat, hanya memberikan pengecualian yang sesuai. Dengan cara ini, saya dapat dengan mudah menulis kesalahan ke log aplikasi tepat sebelum saya mengeluarkan pengecualian, dan plus, aplikasi saya tidak selalu macet.

Jadi, mengapa saya, jika ada, harus menggunakan Debug.Assertsebagai pengganti pengecualian biasa? Menempatkan pernyataan di tempat yang tidak semestinya dapat menyebabkan semua jenis "perilaku yang tidak diinginkan", jadi dalam sudut pandang saya, saya benar-benar tidak mendapatkan apa-apa dengan menggunakan pernyataan alih-alih memberikan pengecualian. Apakah Anda setuju dengan saya, atau saya melewatkan sesuatu di sini?

Catatan: Saya memahami sepenuhnya apa perbedaannya "dalam teori" (Debug vs Rilis, pola penggunaan, dll.), Tetapi seperti yang saya lihat, saya akan lebih baik memberikan pengecualian daripada melakukan pernyataan. Karena jika bug ditemukan pada rilis produksi, saya masih ingin "pernyataan" gagal (lagipula, "overhead" sangat kecil), jadi lebih baik saya memberikan pengecualian saja.


Sunting: Cara saya melihatnya, jika pernyataan gagal, itu berarti bahwa aplikasi memasuki semacam keadaan tak terduga yang rusak. Jadi mengapa saya ingin melanjutkan eksekusi? Tidak masalah jika aplikasi berjalan pada versi debug atau rilis. Hal yang sama berlaku untuk keduanya

Komunitas
sumber
1
Untuk hal-hal yang Anda katakan "jika bug ditemukan pada rilis produksi, saya masih ingin bahwa" pernyataan "akan gagal", pengecualian adalah apa yang harus Anda gunakan
Tom Neyland
1
Performa adalah satu-satunya alasan. Tidak memeriksa semuanya setiap saat dapat menurunkan kecepatan, meskipun mungkin sama sekali tidak terlalu mencolok. Ini terutama untuk kasus-kasus yang seharusnya tidak pernah terjadi, misalnya Anda tahu bahwa Anda sudah tidak memeriksanya di fungsi sebelumnya, tidak ada gunanya membuang-buang siklus untuk memeriksanya lagi. Debug.assert secara efektif bertindak seperti uji unit kesempatan terakhir untuk memberi tahu Anda.
gulungan

Jawaban:

176

Meskipun saya setuju bahwa alasan Anda masuk akal - yaitu, jika suatu pernyataan dilanggar secara tidak terduga, masuk akal untuk menghentikan eksekusi dengan melempar - Saya pribadi tidak akan menggunakan pengecualian sebagai pengganti pernyataan. Inilah alasannya:

Seperti yang dikatakan orang lain, pernyataan harus mendokumentasikan situasi yang tidak mungkin , sedemikian rupa sehingga jika situasi yang diduga tidak mungkin terjadi, pengembang diberi tahu. Pengecualian, sebaliknya, memberikan mekanisme aliran kontrol untuk situasi yang luar biasa, tidak mungkin, atau salah, tetapi bukan situasi yang tidak mungkin. Bagi saya, perbedaan utamanya adalah ini:

  • Seharusnya SELALU dimungkinkan untuk menghasilkan kasus uji yang melatih pernyataan lemparan yang diberikan. Jika tidak mungkin untuk menghasilkan kasus uji seperti itu maka Anda memiliki jalur kode dalam program Anda yang tidak pernah dijalankan, dan itu harus dihapus sebagai kode mati.

  • Seharusnya TIDAK PERNAH mungkin untuk menghasilkan kasus uji yang menyebabkan pernyataan diaktifkan. Jika sebuah pernyataan diaktifkan, kode salah atau pernyataan salah; bagaimanapun juga, sesuatu perlu diubah dalam kode.

Itulah mengapa saya tidak akan mengganti pernyataan dengan pengecualian. Jika pernyataan tidak dapat benar-benar diaktifkan, maka menggantinya dengan pengecualian berarti Anda memiliki jalur kode yang tidak dapat diuji dalam program Anda . Saya tidak suka jalur kode yang tidak dapat diuji.

Eric Lippert
sumber
16
Masalah dengan asersi adalah mereka tidak ada dalam pembuatan produksi. Gagal dalam kondisi yang diasumsikan berarti program Anda telah memasuki lahan perilaku tidak terdefinisi, dalam hal ini program yang bertanggung jawab harus menghentikan eksekusi sesegera mungkin (melepas tumpukan juga agak berbahaya, tergantung seberapa keras Anda ingin mendapatkannya). Ya, pernyataan biasanya tidak mungkin untuk diaktifkan, tetapi Anda tidak tahu apa yang mungkin dilakukan ketika ada yang keluar di alam liar. Apa yang Anda anggap tidak mungkin mungkin terjadi dalam produksi, dan program yang bertanggung jawab harus mendeteksi asumsi yang dilanggar dan segera bertindak.
kizzx2
2
@ kizzx2: Oke, jadi berapa banyak pengecualian yang tidak mungkin per baris kode produksi yang Anda tulis?
Eric Lippert
4
@ kixxx2: Ini adalah C #, jadi Anda dapat menyimpan pernyataan dalam kode produksi dengan menggunakan Trace.Assert. Anda bahkan dapat menggunakan file app.config untuk mengarahkan kembali pernyataan produksi ke file teks daripada bersikap kasar kepada pengguna akhir.
HTTP 410
3
@AnorZaken: Poin Anda menggambarkan cacat desain dalam pengecualian. Seperti yang telah saya catat di tempat lain, pengecualian adalah (1) bencana fatal, (2) kesalahan tulang yang seharusnya tidak pernah terjadi, (3) kegagalan desain di mana pengecualian digunakan untuk menandakan kondisi non-pengecualian, atau (4) kondisi eksogen yang tidak terduga . Mengapa keempat hal yang sangat berbeda ini semuanya diwakili oleh pengecualian? Jika saya memiliki druthers saya, pengecualian "null telah dereferensi" yang kaku tidak akan bisa ditangkap sama sekali . Itu tidak pernah benar, dan itu harus menghentikan program Anda sebelum lebih merugikan . Mereka harus lebih seperti yang ditegaskan.
Eric Lippert
2
@Backwards_Dave: pernyataan palsu itu buruk, bukan yang benar. Pernyataan memungkinkan Anda menjalankan pemeriksaan verifikasi mahal yang tidak ingin Anda jalankan dalam produksi. Dan jika pernyataan dilanggar dalam produksi, apa yang harus dilakukan pengguna akhir?
Eric Lippert
17

Pernyataan digunakan untuk memeriksa pemahaman pemrogram tentang dunia. Sebuah pernyataan seharusnya gagal hanya jika pemrogram telah melakukan kesalahan. Misalnya, jangan pernah menggunakan pernyataan untuk memeriksa input pengguna.

Menegaskan pengujian untuk kondisi yang "tidak dapat terjadi". Pengecualian adalah untuk kondisi yang "seharusnya tidak terjadi, tetapi lakukan".

Pernyataan berguna karena pada waktu pembuatan (atau bahkan waktu proses) Anda dapat mengubah perilakunya. Misalnya, sering kali dalam build rilis, pernyataan bahkan tidak dicentang, karena menimbulkan overhead yang tidak diperlukan. Ini juga sesuatu yang harus diwaspadai: pengujian Anda bahkan mungkin tidak dijalankan.

Jika Anda menggunakan pengecualian alih-alih menegaskan, Anda kehilangan beberapa nilai:

  1. Kode ini lebih bertele-tele, karena menguji dan melempar pengecualian setidaknya dua baris, sementara pernyataan hanya satu.

  2. Kode pengujian dan lemparan Anda akan selalu berjalan, sementara pernyataan dapat dikompilasi.

  3. Anda kehilangan beberapa komunikasi dengan pengembang lain, karena menegaskan memiliki arti yang berbeda dari kode produk yang memeriksa dan melempar. Jika Anda benar-benar menguji pernyataan pemrograman, gunakan pernyataan.

Selengkapnya di sini: http://nedbatchelder.com/text/assert.html

Ned Batchelder
sumber
Jika itu "tidak bisa terjadi", lalu mengapa menulis pernyataan. Bukankah itu berlebihan? Jika itu benar-benar dapat terjadi tetapi tidak seharusnya, maka bukankah ini sama dengan "seharusnya tidak terjadi tetapi lakukan" yang merupakan pengecualian?
David Klempfner
2
"Tidak dapat terjadi" dalam tanda kutip karena suatu alasan: ini hanya dapat terjadi jika pemrogram telah melakukan kesalahan di bagian lain dari program. Assert adalah pemeriksaan terhadap kesalahan programmer.
Ned Batchelder
@NedBatchelder Istilah programmer agak ambigu ketika Anda mengembangkan perpustakaan. Apakah benar bahwa "situasi yang tidak mungkin" itu tidak mungkin dilakukan oleh pengguna perpustakaan, tetapi bisa jadi mungkin ketika penulis perpustakaan melakukan kesalahan?
Bruno Zell
12

EDIT: Menanggapi edit / catatan yang Anda buat di posting Anda: Sepertinya menggunakan pengecualian adalah hal yang benar untuk digunakan selama menggunakan pernyataan untuk jenis hal yang Anda coba capai. Saya pikir hambatan mental yang Anda hadapi adalah bahwa Anda mempertimbangkan pengecualian dan pernyataan untuk memenuhi tujuan yang sama, jadi Anda mencoba mencari tahu mana yang 'benar' untuk digunakan. Meskipun mungkin ada beberapa tumpang tindih dalam cara penggunaan pernyataan dan pengecualian, jangan bingung bahwa bagi mereka solusi yang berbeda untuk masalah yang sama - sebenarnya tidak. Penegasan dan Pengecualian masing-masing memiliki tujuan, kekuatan, dan kelemahannya sendiri.

Saya akan mengetik jawaban dengan kata-kata saya sendiri tetapi ini memberikan konsep keadilan yang lebih baik daripada yang saya miliki:

C # Station: Pernyataan

Penggunaan pernyataan assert dapat menjadi cara yang efektif untuk menangkap kesalahan logika program pada saat runtime, namun dengan mudah disaring dari kode produksi. Setelah pengembangan selesai, biaya waktu proses dari pengujian redundan untuk kesalahan pengkodean ini dapat dihilangkan hanya dengan menentukan simbol preprocessor NDEBUG [yang menonaktifkan semua pernyataan] selama kompilasi. Namun, pastikan untuk mengingat bahwa kode yang ditempatkan di assert itu sendiri akan dihilangkan dalam versi produksi.

Pernyataan paling baik digunakan untuk menguji suatu kondisi hanya jika semua ketentuan berikut berlaku:

* the condition should never be false if the code is correct,
* the condition is not so trivial so as to obviously be always true, and
* the condition is in some sense internal to a body of software.

Pernyataan tidak boleh digunakan untuk mendeteksi situasi yang muncul selama operasi normal perangkat lunak. Misalnya, biasanya pernyataan tidak boleh digunakan untuk memeriksa kesalahan dalam input pengguna. Namun, mungkin masuk akal untuk menggunakan pernyataan untuk memverifikasi bahwa pemanggil telah memeriksa masukan pengguna.

Pada dasarnya, gunakan pengecualian untuk hal-hal yang perlu ditangkap / ditangani dalam aplikasi produksi, gunakan pernyataan untuk melakukan pemeriksaan logis yang akan berguna untuk pengembangan tetapi dinonaktifkan dalam produksi.

Tom Neyland
sumber
Saya menyadari semua itu. Tapi masalahnya, pernyataan yang sama yang Anda tandai sebagai tebal juga berlaku untuk pengecualian. Jadi menurut cara saya melihatnya, alih-alih sebuah pernyataan, saya hanya bisa melempar pengecualian (karena jika "situasi yang tidak akan pernah terjadi" terjadi pada versi yang diterapkan, saya masih ingin diberi tahu tentang itu [plus, aplikasi dapat memasuki keadaan rusak, jadi saya pengecualian cocok, saya mungkin tidak ingin melanjutkan aliran eksekusi normal)
1
Pernyataan harus digunakan pada invarian; pengecualian harus digunakan ketika, katakanlah, sesuatu tidak boleh null, tetapi akan menjadi (seperti parameter ke metode).
Ed S.
Saya kira itu semua tergantung pada seberapa defensif Anda ingin membuat kode.
Ned Batchelder
Saya setuju, untuk apa yang tampaknya Anda butuhkan, pengecualian adalah cara yang harus ditempuh. Anda mengatakan Anda ingin: Kegagalan terdeteksi dalam produksi, kemampuan untuk mencatat informasi tentang kesalahan, dan kontrol aliran eksekusi, dll. Tiga hal itu membuat saya berpikir bahwa yang perlu Anda lakukan adalah membuang beberapa pengecualian.
Tom Neyland
7

Saya pikir contoh praktis (dibuat-buat) dapat membantu menjelaskan perbedaannya:

(diadaptasi dari ekstensi Batch MoreLinq )

// 'public facing' method
public int DoSomething(List<string> stuff, object doohickey, int limit) {

    // validate user input and report problems externally with exceptions

    if(stuff == null) throw new ArgumentNullException("stuff");
    if(doohickey == null) throw new ArgumentNullException("doohickey");
    if(limit <= 0) throw new ArgumentOutOfRangeException("limit", limit, "Should be > 0");

    return DoSomethingImpl(stuff, doohickey, limit);
}

// 'developer only' method
private static int DoSomethingImpl(List<string> stuff, object doohickey, int limit) {

    // validate input that should only come from other programming methods
    // which we have control over (e.g. we already validated user input in
    // the calling method above), so anything using this method shouldn't
    // need to report problems externally, and compilation mode can remove
    // this "unnecessary" check from production

    Debug.Assert(stuff != null);
    Debug.Assert(doohickey != null);
    Debug.Assert(limit > 0);

    /* now do the actual work... */
}

Jadi seperti yang dikatakan Eric Lippert dkk, Anda hanya menegaskan hal-hal yang Anda harapkan benar, kalau-kalau Anda (pengembang) secara tidak sengaja salah menggunakannya di tempat lain, sehingga Anda dapat memperbaiki kode Anda. Anda pada dasarnya melempar pengecualian ketika Anda tidak memiliki kendali atas atau tidak dapat mengantisipasi apa yang masuk, misalnya untuk input pengguna , sehingga apa pun yang memberikannya data yang buruk dapat merespons dengan tepat (misalnya pengguna).

drzaus
sumber
Bukankah 3 Asserts Anda benar-benar berlebihan? Tidak mungkin parameter mereka dievaluasi ke false.
David Klempfner
1
Itulah intinya - pernyataan ada untuk mendokumentasikan hal-hal yang tidak mungkin. Kenapa kamu ingin melakukan itu? Karena Anda mungkin memiliki sesuatu seperti ReSharper yang memperingatkan Anda di dalam metode DoSomethingImpl bahwa "Anda mungkin dereferencing nol di sini" dan Anda ingin mengatakannya "Saya tahu apa yang saya lakukan, ini tidak akan pernah nol". Ini juga merupakan indikasi untuk beberapa programmer kemudian, yang mungkin tidak segera menyadari hubungan antara DoSomething dan DoSomethingImpl, terutama jika jaraknya ratusan baris.
Marcel Popescu
4

Nugget lain dari Code Complete :

"Assertion adalah fungsi atau makro yang mengeluh keras jika asumsi tidak benar. Gunakan pernyataan untuk mendokumentasikan asumsi yang dibuat dalam kode dan untuk menghilangkan kondisi yang tidak terduga. ...

"Selama pengembangan, pernyataan menghilangkan asumsi kontradiktif, kondisi tak terduga, nilai buruk diteruskan ke rutinitas, dan sebagainya."

Dia melanjutkan dengan menambahkan beberapa pedoman tentang apa yang harus dan tidak boleh ditegaskan.

Di sisi lain, pengecualian:

"Gunakan penanganan pengecualian untuk menarik perhatian ke kasus yang tidak terduga. Kasus luar biasa harus ditangani dengan cara yang membuatnya jelas selama pengembangan dan dapat dipulihkan saat kode produksi berjalan."

Jika Anda tidak memiliki buku ini, Anda harus membelinya.

Andrew Cowenhoven
sumber
2
Saya sudah membaca bukunya, itu luar biasa. Namun .. Anda tidak menjawab pertanyaan saya :)
Anda benar, saya tidak menjawabnya. Jawaban saya adalah tidak, saya tidak setuju dengan Anda. Pernyataan dan Pengecualian adalah hewan yang berbeda seperti yang dibahas di atas dan beberapa jawaban yang diposting lainnya di sini.
Andrew Cowenhoven
0

Debug.Assert secara default hanya akan bekerja dalam build debug, jadi jika Anda ingin menangkap segala jenis perilaku buruk yang tidak terduga dalam build rilis Anda, Anda perlu menggunakan pengecualian atau mengaktifkan konstanta debug di properti project Anda (yang umum bukan ide yang bagus).

Mez
sumber
Kalimat parsial pertama benar, sisanya secara umum merupakan ide yang buruk: pernyataan adalah asumsi dan tidak ada validasi (seperti yang dinyatakan di atas), mengaktifkan debug dalam rilis sebenarnya bukan pilihan.
Marc Wittke
0

Gunakan pernyataan untuk hal-hal yang ADALAH mungkin tetapi tidak boleh terjadi (jika tidak mungkin, mengapa Anda memberikan pernyataan?).

Bukankah itu terdengar seperti kasus untuk menggunakan Exception? Mengapa Anda menggunakan pernyataan, bukan Exception?

Karena harus ada kode yang dipanggil sebelum pernyataan Anda yang akan menghentikan parameter pernyataan menjadi salah.

Biasanya tidak ada kode sebelum Anda Exceptionyang menjamin bahwa itu tidak akan dibuang.

Mengapa baik yang Debug.Assert()dikumpulkan dalam prod? Jika Anda ingin mengetahuinya di debug, tidakkah Anda ingin mengetahuinya di prod?

Anda menginginkannya hanya selama pengembangan, karena begitu Anda menemukan Debug.Assert(false)situasi, Anda kemudian menulis kode untuk menjamin bahwa Debug.Assert(false)itu tidak akan terjadi lagi. Setelah pengembangan selesai, dengan asumsi Anda telah menemukan Debug.Assert(false)situasi dan memperbaikinya, Debug.Assert()dapat dengan aman dikompilasi karena sekarang berlebihan.

David Klempfner
sumber
0

Misalkan Anda adalah anggota tim yang cukup besar dan ada beberapa orang yang semuanya bekerja pada basis kode umum yang sama, termasuk tumpang tindih pada kelas. Anda dapat membuat metode yang dipanggil oleh beberapa metode lain, dan untuk menghindari pertentangan kunci, Anda tidak menambahkan kunci terpisah padanya, melainkan "menganggap" itu sebelumnya dikunci oleh metode panggilan dengan kunci tertentu. Seperti, Debug.Assert (RepositoryLock.IsReadLockHeld || RepositoryLock.IsWriteLockHeld); Pengembang lain mungkin mengabaikan komentar yang mengatakan metode panggilan harus menggunakan kunci, tetapi mereka tidak dapat mengabaikan ini.

daniel
sumber