Ketika pemrograman dengan kontrak, suatu fungsi atau metode pertama kali memeriksa apakah prasyaratnya terpenuhi, sebelum mulai mengerjakan tanggung jawabnya, bukan? Dua cara paling menonjol untuk melakukan pemeriksaan ini adalah by assert
and by exception
.
- menegaskan gagal hanya dalam mode debug. Untuk memastikan sangat penting untuk (unit) menguji semua prasyarat kontrak terpisah untuk melihat apakah mereka benar-benar gagal.
- pengecualian gagal dalam mode debug dan rilis. Keuntungannya adalah perilaku debug yang diuji identik dengan perilaku rilis, tetapi menimbulkan penalti performa waktu proses.
Mana yang menurut Anda lebih disukai?
Lihat pertanyaan yang dirilis di sini
exception
assert
design-by-contract
andreas buykx
sumber
sumber
Jawaban:
Menonaktifkan assert dalam build rilis sama saja dengan mengatakan "Saya tidak akan pernah mengalami masalah apa pun dalam build rilis", yang seringkali tidak terjadi. Jadi, assert tidak boleh dinonaktifkan dalam build rilis. Tapi Anda juga tidak ingin rilis build mengalami error setiap kali terjadi error, bukan?
Jadi gunakan pengecualian dan gunakan dengan baik. Gunakan hierarki pengecualian yang baik dan solid dan pastikan bahwa Anda menangkap dan Anda dapat memasang kait pada pengecualian yang melempar debugger Anda untuk menangkapnya, dan dalam mode rilis Anda dapat mengkompensasi kesalahan daripada langsung crash. Ini cara yang lebih aman untuk pergi.
sumber
Aturan praktisnya adalah Anda harus menggunakan pernyataan saat mencoba menangkap kesalahan Anda sendiri, dan pengecualian saat mencoba menangkap kesalahan orang lain. Dengan kata lain, Anda harus menggunakan pengecualian untuk memeriksa prasyarat untuk fungsi API publik, dan setiap kali Anda mendapatkan data yang ada di luar sistem Anda. Anda harus menggunakan asserts untuk fungsi atau data internal sistem Anda.
sumber
Prinsip yang saya ikuti adalah ini: Jika suatu situasi dapat dihindari secara realistis dengan pengkodean, maka gunakan pernyataan. Jika tidak, gunakan pengecualian.
Pernyataan adalah untuk memastikan bahwa Kontrak dipatuhi. Kontrak harus adil, sehingga klien harus berada dalam posisi untuk memastikan kepatuhannya. Misalnya, Anda dapat menyatakan dalam kontrak bahwa URL harus valid karena aturan tentang apa yang ada dan bukan URL yang valid diketahui dan konsisten.
Pengecualian berlaku untuk situasi yang berada di luar kendali klien dan server. Pengecualian berarti ada sesuatu yang tidak beres, dan tidak ada yang bisa dilakukan untuk menghindarinya. Misalnya, konektivitas jaringan berada di luar kendali aplikasi sehingga tidak ada yang dapat dilakukan untuk menghindari kesalahan jaringan.
Saya ingin menambahkan bahwa perbedaan Assertion / Exception sebenarnya bukanlah cara terbaik untuk memikirkannya. Apa yang benar-benar ingin Anda pikirkan adalah tentang kontrak dan bagaimana hal itu dapat diberlakukan. Dalam contoh URL saya di atas, hal terbaik yang harus dilakukan adalah memiliki kelas yang merangkum URL dan berupa Null atau URL yang valid. Ini adalah konversi string menjadi URL yang memberlakukan kontrak, dan pengecualian dilemparkan jika tidak valid. Metode dengan parameter URL jauh lebih jelas daripada metode dengan parameter String dan pernyataan yang menentukan URL.
sumber
Pernyataan adalah untuk menangkap kesalahan yang telah dilakukan pengembang (bukan hanya Anda sendiri - juga pengembang lain di tim Anda). Jika wajar jika kesalahan pengguna dapat membuat kondisi ini, maka itu harus menjadi pengecualian.
Pikirkan juga konsekuensinya. Sebuah assert biasanya mematikan aplikasi. Jika terdapat ekspektasi realistis bahwa kondisi tersebut dapat dipulihkan, Anda mungkin harus menggunakan pengecualian.
Di sisi lain, jika masalahnya hanya disebabkan oleh kesalahan programmer maka gunakan assert, karena Anda ingin mengetahuinya secepat mungkin. Pengecualian mungkin tertangkap dan ditangani, dan Anda tidak akan pernah mengetahuinya. Dan ya, Anda harus menonaktifkan pernyataan dalam kode rilis karena di sana Anda ingin aplikasi dipulihkan jika ada kemungkinan kecil. Bahkan jika status program Anda sangat rusak, pengguna mungkin dapat menyimpan pekerjaan mereka.
sumber
Tidak sepenuhnya benar bahwa "assert hanya gagal dalam mode debug".
Dalam Konstruksi Perangkat Lunak Berorientasi Objek, Edisi ke-2 oleh Bertrand Meyer, penulis membiarkan pintu terbuka untuk memeriksa prasyarat dalam mode rilis. Dalam kasus tersebut, apa yang terjadi saat pernyataan gagal adalah bahwa ... pengecualian pelanggaran pernyataan dimunculkan! Dalam kasus ini, tidak ada pemulihan dari situasi tersebut: sesuatu yang berguna dapat dilakukan, dan itu adalah untuk secara otomatis menghasilkan laporan kesalahan dan, dalam beberapa kasus, untuk memulai ulang aplikasi.
Motivasi di balik ini adalah bahwa prasyarat biasanya lebih murah untuk diuji daripada invariant dan postconditions, dan bahwa dalam beberapa kasus kebenaran dan "keamanan" dalam rilis build lebih penting daripada kecepatan. yaitu Untuk banyak aplikasi, kecepatan bukanlah masalah, tetapi ketahanan (kemampuan program untuk berperilaku dengan cara yang aman ketika perilakunya tidak benar, yaitu ketika kontrak diputus).
Haruskah Anda selalu membiarkan pemeriksaan prasyarat diaktifkan? Tergantung. Terserah kamu. Tidak ada jawaban universal. Jika Anda membuat perangkat lunak untuk bank, mungkin lebih baik menghentikan eksekusi dengan pesan yang mengkhawatirkan daripada mentransfer $ 1.000.000 daripada $ 1.000. Tetapi bagaimana jika Anda memprogram game? Mungkin Anda membutuhkan semua kecepatan yang bisa Anda dapatkan, dan jika seseorang mendapat 1000 poin, bukan 10 karena bug yang tidak dapat ditangkap prasyaratnya (karena tidak diaktifkan), sial.
Dalam kedua kasus, Anda idealnya telah menangkap bug itu selama pengujian, dan Anda harus melakukan bagian penting dari pengujian Anda dengan pernyataan diaktifkan. Apa yang dibahas di sini adalah kebijakan apa yang terbaik untuk kasus yang jarang terjadi di mana prasyarat gagal dalam kode produksi dalam skenario yang tidak terdeteksi sebelumnya karena pengujian yang tidak lengkap.
Untuk meringkas, Anda dapat memiliki pernyataan dan masih mendapatkan pengecualian secara otomatis , jika Anda membiarkannya diaktifkan - setidaknya di Eiffel. Saya pikir untuk melakukan hal yang sama di C ++ Anda perlu mengetiknya sendiri.
Lihat juga: Kapan pernyataan harus tetap berada dalam kode produksi?
sumber
Ada benang yang sangat besar tentang pengaktifan / penonaktifan pernyataan dalam rilis build di comp.lang.c ++. Dimoderasi, yang jika Anda punya waktu beberapa minggu, Anda dapat melihat seberapa beragam pendapat tentang hal ini. :)
Berlawanan dengan coppro , saya percaya bahwa jika Anda tidak yakin bahwa sebuah pernyataan dapat dinonaktifkan dalam build rilis, maka pernyataan tersebut seharusnya tidak menjadi sebuah assert. Assertion adalah untuk melindungi dari kerusakan program invariants. Dalam kasus seperti itu, sejauh menyangkut klien kode Anda, akan ada salah satu dari dua kemungkinan hasil:
Tidak ada perbedaan bagi pengguna, namun, pernyataan tersebut mungkin saja menambahkan biaya performa yang tidak perlu dalam kode yang ada di sebagian besar proses yang kode tidak gagal.
Jawaban atas pertanyaan sebenarnya lebih bergantung pada siapa klien API nantinya. Jika Anda menulis perpustakaan yang menyediakan API, Anda memerlukan beberapa bentuk mekanisme untuk memberi tahu pelanggan Anda bahwa mereka telah menggunakan API secara tidak benar. Kecuali Anda menyediakan dua versi pustaka (satu dengan asserts, satu tanpa) maka assert sangat tidak mungkin merupakan pilihan yang tepat.
Secara pribadi, bagaimanapun, saya tidak yakin apakah saya akan pergi dengan pengecualian untuk kasus ini juga. Pengecualian lebih cocok untuk di mana bentuk pemulihan yang sesuai dapat dilakukan. Misalnya, Anda mungkin mencoba mengalokasikan memori. Saat Anda menemukan pengecualian 'std :: bad_alloc', Anda mungkin dapat mengosongkan memori dan mencoba lagi.
sumber
Saya menguraikan pandangan saya tentang keadaan masalah di sini: Bagaimana Anda memvalidasi keadaan internal suatu objek? . Umumnya, tegaskan klaim Anda dan ajukan pelanggaran oleh orang lain. Untuk menonaktifkan assert dalam build rilis, Anda dapat melakukan:
Tentu saja, dalam build rilis, assertion yang gagal dan pengecualian yang tidak tertangkap harus ditangani dengan cara lain selain dalam build debug (yang bisa dipanggil std :: abort). Tulis log kesalahan di suatu tempat (mungkin ke dalam file), beri tahu pelanggan bahwa terjadi kesalahan internal. Pelanggan akan dapat mengirimkan file log kepada Anda.
sumber
Anda bertanya tentang perbedaan antara kesalahan desain-waktu dan run-time.
menegaskan adalah pemberitahuan 'hai programmer, ini rusak', mereka ada di sana untuk mengingatkan Anda tentang bug yang tidak akan Anda perhatikan ketika terjadi.
pengecualian adalah pemberitahuan 'hai pengguna, ada yang salah' (jelas Anda dapat membuat kode untuk menangkapnya sehingga pengguna tidak pernah diberi tahu) tetapi ini dirancang untuk terjadi pada waktu proses ketika pengguna Joe menggunakan aplikasi.
Jadi, jika menurut Anda Anda bisa mengatasi semua bug, gunakan pengecualian saja. Jika Anda berpikir Anda tidak bisa ..... gunakan pengecualian. Anda masih bisa menggunakan pernyataan debug untuk mengurangi jumlah pengecualian tentunya.
Jangan lupa bahwa banyak prasyarat adalah data yang disediakan pengguna, jadi Anda memerlukan cara yang baik untuk memberi tahu pengguna bahwa datanya tidak baik. Untuk melakukan itu, Anda sering kali harus mengembalikan data kesalahan ke tumpukan panggilan ke bit yang berinteraksi dengannya. Asserts tidak akan berguna kemudian - jadi dua kali lipat jika aplikasi Anda n-tier.
Terakhir, saya tidak akan menggunakan keduanya - kode kesalahan jauh lebih unggul untuk kesalahan yang menurut Anda akan terjadi secara teratur. :)
sumber
Saya lebih suka yang kedua. Meskipun pengujian Anda mungkin berjalan dengan baik, Murphy mengatakan bahwa sesuatu yang tidak terduga akan menjadi salah. Jadi, alih-alih mendapatkan pengecualian pada panggilan metode salah yang sebenarnya, Anda malah menelusuri NullPointerException (atau yang setara) 10 tumpukan frame lebih dalam.
sumber
Jawaban sebelumnya benar: gunakan pengecualian untuk fungsi API publik. Satu-satunya saat Anda mungkin ingin membengkokkan aturan ini adalah saat cek tersebut mahal secara komputasi. Dalam hal ini, Anda dapat memasukkannya ke dalam assert.
Jika menurut Anda pelanggaran terhadap prasyarat tersebut kemungkinan besar terjadi, simpan sebagai pengecualian, atau perbaiki prasyarat tersebut.
sumber
Anda harus menggunakan keduanya. Asserts adalah untuk kenyamanan Anda sebagai pengembang. Pengecualian menangkap hal-hal yang Anda lewatkan atau tidak harapkan selama runtime.
Saya semakin menyukai fungsi pelaporan kesalahan glib alih-alih pernyataan lama yang biasa. Mereka berperilaku seperti pernyataan assert tetapi bukannya menghentikan program, mereka hanya mengembalikan nilai dan membiarkan program berlanjut. Ini bekerja dengan sangat baik, dan sebagai bonus Anda bisa melihat apa yang terjadi pada program Anda lainnya ketika suatu fungsi tidak mengembalikan "apa yang seharusnya". Jika macet, Anda tahu bahwa pemeriksaan kesalahan Anda longgar di tempat lain di jalan.
Dalam proyek terakhir saya, saya menggunakan gaya fungsi ini untuk mengimplementasikan pemeriksaan prekondisi, dan jika salah satunya gagal, saya akan mencetak jejak tumpukan ke file log tetapi terus berjalan. Menghemat banyak waktu debugging saya ketika orang lain menghadapi masalah saat menjalankan build debug saya.
Jika saya memerlukan pemeriksaan runtime argumen, saya akan melakukan ini:
sumber
Saya mencoba mensintesis beberapa jawaban lain di sini dengan pandangan saya sendiri.
Gunakan pernyataan untuk kasus di mana Anda ingin menonaktifkannya dalam produksi, keliru membiarkannya masuk. Satu-satunya alasan sebenarnya untuk menonaktifkan dalam produksi, tetapi tidak dalam pengembangan, adalah untuk mempercepat program. Dalam kebanyakan kasus, percepatan ini tidak akan signifikan, tetapi terkadang kode sangat membutuhkan waktu atau pengujian mahal secara komputasi. Jika kode sangat penting untuk misi, maka pengecualian mungkin yang terbaik meskipun terjadi perlambatan.
Jika ada peluang nyata untuk pulih, gunakan pengecualian karena pernyataan tidak dirancang untuk dipulihkan. Misalnya, kode jarang dirancang untuk memulihkan kesalahan pemrograman, tetapi dirancang untuk memulihkan dari faktor-faktor seperti kegagalan jaringan atau file yang terkunci. Kesalahan tidak boleh ditangani sebagai pengecualian hanya karena berada di luar kendali programmer. Sebaliknya, prediktabilitas kesalahan ini, dibandingkan dengan kesalahan pengkodean, membuatnya lebih ramah untuk pemulihan.
Argumen ulang bahwa lebih mudah untuk men-debug pernyataan: Jejak tumpukan dari pengecualian bernama benar semudah membaca pernyataan. Kode yang baik seharusnya hanya menangkap jenis pengecualian tertentu, jadi pengecualian tidak boleh luput dari perhatian karena tertangkap. Namun, menurut saya Java terkadang memaksa Anda untuk menangkap semua pengecualian.
sumber
Aturan praktisnya, bagi saya, adalah menggunakan ekspresi assert untuk menemukan kesalahan internal dan pengecualian untuk kesalahan eksternal. Anda bisa mendapatkan banyak keuntungan dari diskusi berikut oleh Greg dari sini .
PS: Anda mungkin ingin memeriksa pertanyaan serupa: Exception Vs Assertion .
sumber
Lihat juga pertanyaan ini :
sumber