Saya baru-baru ini mulai bekerja di suatu tempat dengan beberapa pengembang yang jauh lebih tua (sekitar 50+ tahun). Mereka telah bekerja pada aplikasi kritis yang berhubungan dengan penerbangan di mana sistem tidak bisa turun. Akibatnya, programmer yang lebih lama cenderung untuk mengkode seperti ini.
Ia cenderung menempatkan boolean di objek untuk menunjukkan apakah pengecualian harus dibuang atau tidak.
Contoh
public class AreaCalculator
{
AreaCalculator(bool shouldThrowExceptions) { ... }
CalculateArea(int x, int y)
{
if(x < 0 || y < 0)
{
if(shouldThrowExceptions)
throwException;
else
return 0;
}
}
}
(Dalam proyek kami metode ini dapat gagal karena kami mencoba menggunakan perangkat jaringan yang tidak dapat hadir pada saat itu. Contoh area hanyalah contoh dari flag pengecualian)
Bagi saya ini sepertinya bau kode. Tes unit menulis menjadi sedikit lebih rumit karena Anda harus menguji untuk bendera pengecualian setiap kali. Juga, jika terjadi kesalahan, tidakkah Anda ingin segera tahu? Bukankah seharusnya menjadi tanggung jawab penelepon untuk menentukan bagaimana melanjutkan?
Logikanya / alasannya adalah bahwa program kami perlu melakukan 1 hal, menunjukkan data kepada pengguna. Pengecualian lain apa pun yang tidak menghentikan kami untuk melakukannya harus diabaikan. Saya setuju mereka tidak boleh diabaikan, tetapi harus muncul dan ditangani oleh orang yang tepat, dan tidak harus berurusan dengan bendera untuk itu.
Apakah ini cara yang baik untuk menangani pengecualian?
Sunting : Hanya untuk memberikan lebih banyak konteks atas keputusan desain, saya menduga itu karena jika komponen ini gagal, program masih dapat beroperasi dan melakukan tugas utamanya. Dengan demikian kita tidak akan ingin membuang pengecualian (dan tidak menanganinya?) Dan membiarkannya menghapus program ketika bagi pengguna itu berfungsi dengan baik
Sunting 2 : Untuk memberikan lebih banyak konteks, dalam kasus kami metode ini dipanggil untuk mereset kartu jaringan. Masalah muncul ketika kartu jaringan terputus dan terhubung kembali, itu diberikan alamat ip yang berbeda, sehingga Reset akan mengeluarkan pengecualian karena kami akan mencoba untuk mengatur ulang perangkat keras dengan ip lama.
Jawaban:
Masalah dengan pendekatan ini adalah bahwa sementara pengecualian tidak pernah dilempar (dan dengan demikian, aplikasi tidak pernah crash karena pengecualian yang tidak tertangkap), hasil yang dikembalikan belum tentu benar, dan pengguna mungkin tidak pernah tahu bahwa ada masalah dengan data (atau apa masalah itu dan bagaimana cara memperbaikinya).
Agar hasilnya benar dan bermakna, metode pemanggilan harus memeriksa hasil untuk nomor khusus - yaitu, nilai pengembalian spesifik yang digunakan untuk menunjukkan masalah yang muncul saat menjalankan metode. Angka negatif (atau nol) yang dikembalikan untuk jumlah positif pasti (seperti area) adalah contoh utama dari ini dalam kode lama. Namun, jika metode panggilan tidak tahu (atau lupa!) Untuk memeriksa nomor-nomor khusus ini, pemrosesan dapat dilanjutkan tanpa pernah menyadari kesalahan. Data kemudian ditampilkan kepada pengguna yang menunjukkan area 0, yang pengguna tahu salah, tetapi mereka tidak memiliki indikasi apa yang salah, di mana, atau mengapa. Mereka kemudian bertanya-tanya apakah ada nilai-nilai lain yang salah ...
Jika pengecualian dilemparkan, pemrosesan akan berhenti, kesalahan akan (idealnya) dicatat dan pengguna dapat diberitahukan dengan cara tertentu. Pengguna kemudian dapat memperbaiki apa pun yang salah dan coba lagi. Penanganan pengecualian yang tepat (dan pengujian!) Akan memastikan bahwa aplikasi kritis tidak macet atau berakhir dalam keadaan tidak valid.
sumber
Tidak, saya pikir ini praktik yang sangat buruk. Melempar pengecualian vs. mengembalikan nilai adalah perubahan mendasar pada API, mengubah tanda tangan metode, dan membuat metode tersebut berperilaku sangat berbeda dari perspektif antarmuka.
Secara umum, ketika kita mendesain kelas dan API mereka, kita harus mempertimbangkan itu
mungkin ada beberapa instance kelas dengan konfigurasi berbeda yang beredar di program yang sama pada waktu yang sama, dan,
karena injeksi ketergantungan dan sejumlah praktik pemrograman lainnya, satu klien yang mengonsumsi dapat membuat objek dan menyerahkannya kepada yang lain menggunakannya - sehingga sering kali kita memiliki pemisahan antara pembuat objek dan pengguna objek.
Pertimbangkan sekarang apa yang harus dilakukan oleh pemanggil metode untuk menggunakan contoh s / dia telah diserahkan, misalnya untuk memanggil metode penghitungan: pemanggil harus memeriksa daerah menjadi nol serta menangkap pengecualian - aduh! Pertimbangan pengujian tidak hanya untuk kelas itu sendiri, tetapi untuk penanganan kesalahan penelepon juga ...
Kita harus selalu membuat hal-hal semudah mungkin untuk klien yang mengonsumsi; konfigurasi boolean ini di konstruktor yang mengubah API metode contoh adalah kebalikan dari membuat pemrogram klien yang mengkonsumsi (mungkin Anda atau kolega Anda) jatuh ke dalam lubang kesuksesan.
Untuk menawarkan kedua API, Anda jauh lebih baik dan lebih normal untuk menyediakan dua kelas yang berbeda - yang selalu menampilkan kesalahan dan yang selalu mengembalikan 0 pada kesalahan, atau, menyediakan dua metode yang berbeda dengan satu kelas. Dengan cara ini, klien yang mengonsumsi dapat dengan mudah tahu persis bagaimana memeriksa dan menangani kesalahan.
Menggunakan dua kelas yang berbeda atau dua metode yang berbeda, Anda dapat menggunakan IDE untuk menemukan pengguna metode dan fitur refactor, dll. Jauh lebih mudah karena dua kasus penggunaan tidak lagi digabungkan. Membaca kode, menulis, perawatan, ulasan, dan pengujian juga lebih sederhana.
Pada catatan lain, saya pribadi merasa bahwa kita tidak boleh mengambil parameter konfigurasi boolean, di mana semua penelepon sebenarnya hanya memberikan konstanta . Parameterisasi konfigurasi tersebut mengonfigurasi dua kasing terpisah tanpa manfaat nyata.
Lihatlah basis kode Anda dan lihat apakah variabel (atau ekspresi tidak konstan) pernah digunakan untuk parameter konfigurasi boolean di konstruktor! Aku meragukan itu.
Dan pertimbangan lebih lanjut adalah untuk bertanya mengapa komputasi area bisa gagal. Mungkin yang terbaik adalah memasukkan konstruktor, jika perhitungan tidak dapat dilakukan. Namun, jika Anda tidak tahu apakah penghitungan dapat dilakukan hingga objek diinisialisasi lebih lanjut, maka mungkin pertimbangkan untuk menggunakan kelas yang berbeda untuk membedakan negara-negara tersebut (tidak siap untuk menghitung area vs. siap menghitung area).
Saya membaca bahwa situasi kegagalan Anda berorientasi pada remoting, jadi mungkin tidak berlaku; hanya beberapa makanan untuk dipikirkan.
Ya saya setuju. Tampaknya terlalu dini bagi callee untuk memutuskan bahwa area 0 adalah jawaban yang tepat dalam kondisi kesalahan (terutama karena 0 adalah area yang valid sehingga tidak ada cara untuk mengetahui perbedaan antara kesalahan dan 0 aktual, meskipun mungkin tidak berlaku untuk aplikasi Anda).
sumber
Itu adalah pengantar yang menarik, yang memberi saya kesan motivasi di balik desain ini adalah untuk menghindari melemparkan pengecualian dalam beberapa konteks "karena sistem bisa turun" kalau begitu. Tetapi jika sistem "bisa turun karena pengecualian", ini merupakan indikasi yang jelas
Jadi, jika program yang menggunakan
AreaCalculator
buggy, kolega Anda lebih suka untuk tidak memiliki program "crash awal", tetapi untuk mengembalikan beberapa nilai yang salah (berharap tidak ada yang memperhatikannya, atau berharap tidak ada yang melakukan sesuatu yang penting dengan itu). Itu sebenarnya menutupi kesalahan , dan dalam pengalaman saya itu cepat atau lambat akan menyebabkan bug tindak lanjut yang menjadi sulit untuk menemukan akar penyebabnya.IMHO menulis program yang tidak macet dalam keadaan apa pun tetapi menunjukkan data yang salah atau hasil perhitungan biasanya sama sekali tidak lebih baik daripada membiarkan crash program. Satu-satunya pendekatan yang tepat adalah memberi penelepon kesempatan untuk melihat kesalahan, mengatasinya, biarkan dia memutuskan apakah pengguna harus diberi tahu tentang perilaku yang salah, dan apakah aman untuk melanjutkan pemrosesan apa pun, atau jika lebih aman untuk menghentikan program sepenuhnya. Jadi, saya akan merekomendasikan salah satu dari yang berikut:
membuatnya sulit untuk mengabaikan fakta bahwa suatu fungsi dapat melempar pengecualian. Standar dokumentasi dan pengkodean adalah teman Anda di sini, dan tinjauan kode reguler harus mendukung penggunaan komponen yang benar dan penanganan pengecualian yang tepat.
latih tim untuk mengharapkan dan menangani pengecualian ketika mereka menggunakan komponen "kotak hitam", dan pikirkan perilaku global program tersebut.
jika karena beberapa alasan Anda pikir Anda tidak bisa mendapatkan kode panggilan (atau devs yang menulisnya) untuk menggunakan penanganan pengecualian dengan benar, maka, sebagai upaya terakhir, Anda bisa merancang API dengan variabel output kesalahan eksplisit dan tanpa pengecualian sama sekali, seperti
jadi sangat sulit bagi penelepon untuk mengabaikan fungsi bisa gagal. Tapi ini IMHO sangat jelek di C #; ini adalah teknik pemrograman defensif lama dari C di mana tidak ada pengecualian, dan itu seharusnya tidak perlu untuk bekerja saat ini.
sumber
try
/catch
di dalam loop jika Anda perlu memproses untuk melanjutkan jika satu elemen gagal.Setiap fungsi dengan parameter n akan lebih sulit untuk diuji daripada fungsi dengan parameter n-1 . Perluas yang absurd dan argumen menjadi bahwa fungsi seharusnya tidak memiliki parameter sama sekali karena itu membuatnya mudah untuk diuji.
Walaupun ide yang bagus untuk menulis kode yang mudah untuk diuji, itu ide yang buruk untuk menempatkan kesederhanaan tes di atas penulisan kode yang berguna bagi orang-orang yang harus menyebutnya. Jika contoh dalam pertanyaan memiliki sakelar yang menentukan apakah pengecualian dilemparkan atau tidak, ada kemungkinan penelepon yang menginginkan perilaku pantas menambahkannya ke fungsi. Di mana garis batas antara kompleks dan terlalu kompleks terletak adalah panggilan penilaian; siapa pun yang mencoba memberi tahu Anda ada garis terang yang berlaku dalam semua situasi harus diawasi dengan kecurigaan.
Itu tergantung pada definisi Anda yang salah. Contoh dalam pertanyaan mendefinisikan salah sebagai "diberi dimensi kurang dari nol dan
shouldThrowExceptions
benar." Diberi dimensi jika kurang dari nol tidak salah ketikashouldThrowExceptions
salah karena sakelar menginduksi perilaku yang berbeda. Sederhananya, itu bukan situasi yang luar biasa.Masalah sebenarnya di sini adalah bahwa saklar itu tidak disebutkan namanya karena tidak deskriptif tentang apa yang membuat fungsinya dilakukan. Seandainya diberi nama yang lebih baik
treatInvalidDimensionsAsZero
, apakah Anda akan mengajukan pertanyaan ini?Penelepon tidak menentukan bagaimana untuk melanjutkan. Dalam hal ini, ia melakukannya sebelumnya dengan menetapkan atau membersihkan
shouldThrowExceptions
dan fungsi berperilaku sesuai dengan kondisinya.Contohnya adalah patologis-sederhana karena ia melakukan perhitungan tunggal dan kembali. Jika Anda membuatnya sedikit lebih kompleks, seperti menghitung jumlah akar kuadrat dari daftar angka, melemparkan pengecualian dapat memberikan masalah kepada penelepon yang tidak dapat mereka selesaikan. Jika saya memasukkan daftar
[5, 6, -1, 8, 12]
dan fungsi melempar pengecualian-1
, saya tidak punya cara untuk memberitahu fungsi untuk terus berjalan karena itu sudah dibatalkan dan dibuang jumlahnya. Jika daftar adalah kumpulan data yang sangat besar, menghasilkan salinan tanpa angka negatif sebelum memanggil fungsi mungkin tidak praktis, jadi saya terpaksa mengatakan sebelumnya bagaimana angka yang tidak valid harus diperlakukan, baik dalam bentuk "abaikan saja "Beralih atau mungkin menyediakan lambda yang dipanggil untuk membuat keputusan itu.Sekali lagi, tidak ada solusi satu ukuran untuk semua. Dalam contoh tersebut, fungsi itu, mungkin, ditulis ke spec yang mengatakan bagaimana menangani dimensi negatif. Hal terakhir yang ingin Anda lakukan adalah menurunkan rasio signal-to-noise dari log Anda dengan mengisinya dengan pesan yang mengatakan "biasanya, sebuah pengecualian akan dilemparkan ke sini, tetapi penelepon mengatakan tidak mengganggu."
Dan sebagai salah satu programmer yang jauh lebih tua, saya akan meminta agar Anda berbaik hati meninggalkan halaman saya. ;-)
sumber
our program needs to do 1 thing, show data to user. Any other exception that doesn't stop us from doing so should be ignored
pola pikir dapat menyebabkan pengguna membuat keputusan berdasarkan data yang salah (karena sebenarnya program perlu melakukan 1 hal - untuk membantu pengguna dalam membuat keputusan berdasarkan informasi) 2. kasus serupa sepertibool ExecuteJob(bool throwOnError = false)
biasanya sangat kesalahan dan mengarah pada kode yang sulit dipikirkan hanya dengan membacanya.Kode keamanan kritis dan 'normal' dapat menghasilkan ide yang sangat berbeda tentang seperti apa 'praktik yang baik' itu. Ada banyak tumpang tindih - beberapa hal berisiko dan harus dihindari di keduanya - tetapi masih ada perbedaan yang signifikan. Jika Anda menambahkan persyaratan agar dijamin responsif, penyimpangan ini cukup substansial.
Ini sering berhubungan dengan hal-hal yang Anda harapkan:
Untuk git, jawaban yang salah bisa sangat buruk relatif terhadap: mengambil-ke-panjang / menggugurkan / menggantung atau bahkan menabrak (yang secara efektif non-masalah relatif, misalnya, mengubah kode masuk secara tidak sengaja).
Namun: Untuk panel instrumen yang memiliki perhitungan g-force terhenti dan mencegah perhitungan kecepatan udara yang dibuat mungkin tidak dapat diterima.
Beberapa kurang jelas:
Jika Anda telah banyak menguji , hasil urutan pertama (seperti jawaban yang benar) relatif tidak terlalu mengkhawatirkan. Anda tahu pengujian Anda akan membahas hal ini. Namun jika ada keadaan tersembunyi atau aliran kontrol, Anda tidak tahu ini tidak akan menjadi penyebab sesuatu yang jauh lebih halus. Ini sulit untuk dikesampingkan dengan pengujian.
Terlihat aman relatif penting. Tidak banyak pelanggan akan duduk untuk alasan apakah sumber yang mereka beli aman atau tidak. Jika Anda berada di pasar penerbangan di sisi lain ...
Bagaimana ini berlaku untuk contoh Anda:
Saya tidak tahu Ada sejumlah proses pemikiran yang mungkin memiliki peraturan utama seperti "Tidak ada yang melempar kode produksi" yang diadopsi dalam kode kritis keselamatan yang akan sangat konyol dalam situasi yang lebih umum.
Beberapa akan berhubungan dengan tertanam, beberapa keselamatan dan mungkin yang lain ... Beberapa baik (kinerja ketat / batas memori diperlukan) beberapa buruk (kami tidak menangani pengecualian dengan benar sehingga sebaiknya tidak mengambil risiko itu). Sebagian besar waktu bahkan mengetahui mengapa mereka melakukannya tidak akan benar-benar menjawab pertanyaan. Misalnya jika ini berkaitan dengan kemudahan mengaudit kode lebih banyak yang benar-benar membuatnya lebih baik, apakah itu praktik yang baik? Anda benar-benar tidak tahu. Mereka adalah hewan yang berbeda, dan perlu diperlakukan berbeda.
Semua itu mengatakan, sepertinya saya seorang tersangka TETAPI TETAPI :
Keselamatan perangkat lunak kritis dan keputusan desain perangkat lunak mungkin tidak boleh dilakukan oleh orang asing di pertukaran perangkat lunak-rekayasa. Mungkin ada alasan bagus untuk melakukan ini bahkan jika itu bagian dari sistem yang buruk. Jangan membaca terlalu banyak tentang hal ini selain sebagai "makanan untuk dipikirkan".
sumber
Terkadang melempar pengecualian bukanlah metode terbaik. Tidak sedikit karena stack unwinding, tetapi kadang-kadang karena menangkap pengecualian merupakan masalah, terutama di sepanjang lapisan bahasa atau antarmuka.
Cara yang baik untuk menangani ini adalah mengembalikan tipe data yang diperkaya. Tipe data ini memiliki status yang cukup untuk menggambarkan semua jalur bahagia, dan semua jalur tidak bahagia. Intinya adalah, jika Anda berinteraksi dengan fungsi ini (anggota / global / sebaliknya), Anda akan dipaksa untuk menangani hasilnya.
Karena itu dikatakan tipe data yang diperkaya ini tidak boleh memaksa tindakan. Bayangkan dalam contoh daerah Anda sesuatu seperti
var area_calc = new AreaCalculator(); var volume = area_calc.CalculateArea(x, y) * z;
. Tampaknya bergunavolume
harus mengandung luas dikalikan dengan kedalaman - yang bisa berupa kubus, silinder, dll ...Tetapi bagaimana jika layanan area_calc sedang down? Kemudian
area_calc .CalculateArea(x, y)
mengembalikan tipe data kaya yang mengandung kesalahan. Apakah legal untuk memperbanyak ituz
? Ini pertanyaan yang bagus. Anda bisa memaksa pengguna untuk segera menangani pemeriksaan. Namun ini memecah logika dengan penanganan kesalahan.vs.
Logika dasarnya tersebar di dua baris dan dibagi dengan penanganan kesalahan dalam kasus pertama, sedangkan kasus kedua memiliki semua logika yang relevan pada satu baris diikuti oleh penanganan kesalahan.
Dalam kasus kedua
volume
adalah tipe data yang kaya. Bukan hanya angka. Ini membuat penyimpanan lebih besar, danvolume
masih perlu diselidiki untuk kondisi kesalahan. Selain ituvolume
mungkin memberi makan perhitungan lain sebelum pengguna memilih untuk menangani kesalahan, memungkinkan untuk terwujud di beberapa lokasi yang berbeda. Ini mungkin baik, atau buruk tergantung pada situasi spesifik.Bergantian
volume
bisa hanya tipe data biasa - hanya angka, tetapi lalu apa yang terjadi dengan kondisi kesalahan? Bisa jadi nilai secara implisit dikonversi jika dalam kondisi bahagia. Jika berada dalam kondisi tidak bahagia mungkin akan mengembalikan nilai default / error (untuk area 0 atau -1 mungkin tampak masuk akal). Bergantian bisa membuat pengecualian pada sisi batas antarmuka / bahasa ini.vs.
Dengan membagikan nilai yang buruk, atau mungkin buruk, itu menempatkan banyak tanggung jawab pada pengguna untuk memvalidasi data. Ini adalah sumber bug, karena sejauh menyangkut kompiler nilai kembali adalah integer yang sah. Jika sesuatu tidak diperiksa, Anda akan menemukannya ketika ada masalah. Kasus kedua memadukan yang terbaik dari kedua dunia dengan memungkinkan pengecualian untuk menangani jalur yang tidak bahagia, sementara jalur yang bahagia mengikuti proses normal. Sayangnya itu memaksa pengguna untuk menangani pengecualian dengan bijak, yang sulit.
Hanya untuk memperjelas jalur yang tidak bahagia adalah kasus yang tidak diketahui oleh logika bisnis (domain pengecualian), gagal memvalidasi adalah jalan yang bahagia karena Anda tahu bagaimana menanganinya dengan aturan bisnis (domain aturan).
Solusi utama akan menjadi salah satu yang memungkinkan semua skenario (masuk akal).
Sesuatu seperti:
sumber
Saya akan menjawab dari sudut pandang C ++. Saya cukup yakin semua konsep inti dapat ditransfer ke C #.
Sepertinya gaya pilihan Anda adalah "selalu melempar pengecualian":
Ini bisa menjadi masalah untuk kode C ++ karena penanganan pengecualian berat - ini membuat case kegagalan berjalan lambat, dan membuat case kegagalan mengalokasikan memori (yang kadang-kadang bahkan tidak tersedia), dan umumnya membuat hal-hal yang kurang dapat diprediksi. Beratnya EH adalah salah satu alasan Anda mendengar orang mengatakan hal-hal seperti "Jangan gunakan pengecualian untuk aliran kontrol."
Jadi beberapa perpustakaan (seperti
<filesystem>
) menggunakan apa yang C ++ sebut sebagai "API ganda," atau apa yang C # sebutTry-Parse
polanya (terima kasih Peter atas tipnya!)Anda dapat melihat masalah dengan "API ganda" segera: banyak duplikasi kode, tidak ada panduan bagi pengguna tentang API mana yang "benar" digunakan, dan pengguna harus membuat pilihan sulit antara pesan kesalahan yang berguna (
CalculateArea
) dan speed (TryCalculateArea
) karena versi yang lebih cepat mengambil"negative side lengths"
pengecualian berguna kita dan meratakannya menjadi tidak bergunafalse
- "ada yang salah, jangan tanya saya apa atau di mana." (Beberapa API ganda menggunakan jenis kesalahan yang lebih ekspresif, sepertiint errno
atau C ++ 'sstd::error_code
, tapi yang masih tidak memberitahu Anda di mana kesalahan terjadi - hanya saja memang terjadi di suatu tempat.)Jika Anda tidak dapat memutuskan bagaimana perilaku kode Anda, Anda selalu dapat menendang keputusan sampai ke pemanggil!
Ini pada dasarnya adalah apa yang dilakukan rekan kerja Anda; kecuali bahwa dia memfaktorkan "penangan kesalahan" ke dalam variabel global:
Memindahkan parameter penting dari parameter fungsi eksplisit ke status global hampir selalu merupakan ide yang buruk. Saya tidak merekomendasikannya. (Fakta bahwa itu bukan negara global dalam kasus Anda tetapi hanya negara anggota contoh-lebar sedikit mengurangi kejahatan, tapi tidak banyak.)
Selain itu, rekan kerja Anda tidak perlu membatasi jumlah perilaku penanganan kesalahan yang mungkin terjadi. Daripada mengizinkan setiap lambda penanganan kesalahan, dia memutuskan hanya dua:
Ini mungkin "titik asam" dari semua strategi yang mungkin ini. Anda telah mengambil semua fleksibilitas dari pengguna akhir dengan memaksa mereka untuk menggunakan salah satu dari dua panggilan balik penanganan kesalahan Anda; dan Anda memiliki semua masalah negara global bersama; dan Anda masih membayar untuk cabang bersyarat itu di mana-mana.
Akhirnya, solusi umum dalam C ++ (atau bahasa apa pun dengan kompilasi bersyarat) akan memaksa pengguna untuk membuat keputusan untuk seluruh program mereka, secara global, pada waktu kompilasi, sehingga codepath yang tidak diambil dapat dioptimalkan sepenuhnya:
Contoh dari sesuatu yang bekerja dengan cara ini adalah
assert
makro di C dan C ++, yang mengkondisikan perilakunya pada makro preprosesorNDEBUG
.sumber
std::optional
dariTryCalculateArea()
gantinya, mudah untuk menyatukan implementasi kedua bagian dari antarmuka ganda dalam satu fungsi-templat dengan flag waktu-kompilasi.std::expected
. Dengan adilstd::optional
, kecuali jika saya salah memahami solusi yang Anda usulkan, itu masih akan menderita dari apa yang saya katakan: pengguna harus membuat pilihan sulit antara pesan kesalahan yang berguna dan kecepatan, karena versi yang lebih cepat mengambil"negative side lengths"
pengecualian berguna kami dan meratakannya menjadi tidak bergunafalse
- " ada yang salah, jangan tanya saya apa atau di mana. "std::error_code *ec
semua jalan turun melalui setiap tingkat API, dan kemudian di bagian bawah melakukan moral yang setaraif (ec == nullptr) throw something; else *ec = some error code
. (Ini mengabstraksi aktualif
menjadi sesuatu yang disebutErrorHandler
, tapi itu ide dasar yang sama.)Saya merasa harus disebutkan dari mana rekan Anda mendapatkan polanya.
Saat ini, C # memiliki pola TryGet
public bool TryThing(out result)
. Ini memungkinkan Anda mendapatkan hasil Anda, sambil tetap memberi tahu Anda apakah hasil itu bahkan merupakan nilai yang valid. (Misalnya, semuaint
nilai adalah hasil yang valid untukMath.sum(int, int)
, tetapi jika nilainya melimpah, hasil khusus ini bisa menjadi sampah). Ini adalah pola yang relatif baru.Sebelum
out
kata kunci, Anda harus melemparkan pengecualian (mahal, dan penelepon harus menangkapnya atau mematikan keseluruhan program), membuat struct khusus (kelas sebelum kelas atau obat generik benar-benar suatu hal) untuk setiap hasil untuk mewakili nilai dan kemungkinan kesalahan (memakan waktu untuk membuat dan mengasapi perangkat lunak), atau mengembalikan nilai "kesalahan" default (yang mungkin bukan kesalahan).Pendekatan yang digunakan kolega Anda memberi mereka kegagalan awal perkecualian saat menguji / men-debug fitur baru, sambil memberi mereka keamanan dan kinerja runtime (kinerja adalah masalah penting sepanjang waktu ~ 30 tahun yang lalu) dengan hanya mengembalikan nilai kesalahan default. Sekarang ini adalah pola yang ditulis perangkat lunak, dan pola yang diharapkan bergerak maju, jadi wajar untuk tetap melakukannya dengan cara ini meskipun ada cara yang lebih baik sekarang. Kemungkinan besar pola ini diwarisi sejak zaman perangkat lunak, atau pola yang tidak pernah tumbuh di perguruan tinggi Anda (kebiasaan lama sulit dihilangkan).
Jawaban lain sudah mencakup mengapa ini dianggap praktik yang buruk, jadi saya hanya akan merekomendasikan Anda membaca pada pola TryGet (mungkin juga enkapsulasi untuk janji-janji apa yang harus dibuat oleh objek terhadap peneleponnya).
sumber
out
kata kunci, Anda akan menulisbool
fungsi yang membawa penunjuk ke hasilnya, yaituref
parameter. Anda bisa melakukannya di VB6 pada tahun 1998. Kataout
kunci hanya memberi Anda kepastian waktu kompilasi bahwa parameter tersebut ditetapkan ketika fungsi kembali, hanya itu yang ada di sana. Ini adalah pola yang bagus & bermanfaat.Ada saat-saat ketika Anda ingin melakukan pendekatannya, tetapi saya tidak akan menganggap mereka sebagai situasi "normal". Kunci untuk menentukan di mana Anda berada adalah:
Periksa persyaratannya. Jika persyaratan Anda benar-benar mengatakan bahwa Anda memiliki satu pekerjaan, yaitu menunjukkan data kepada pengguna, maka ia benar. Namun, dalam pengalaman saya, sebagian besar waktu pengguna juga peduli dengan data apa yang ditampilkan. Mereka menginginkan data yang benar. Beberapa sistem hanya ingin gagal secara diam-diam dan membiarkan pengguna mengetahui bahwa ada yang tidak beres, tapi saya akan menganggap mereka pengecualian untuk aturan tersebut.
Pertanyaan kunci yang akan saya tanyakan setelah kegagalan adalah "Apakah sistem dalam keadaan di mana harapan pengguna dan invarian perangkat lunak itu valid?" Jika demikian, maka tentu saja kembali dan terus berjalan. Secara praktis ini bukan yang terjadi di sebagian besar program.
Sedangkan untuk flag itu sendiri, flag pengecualian biasanya dianggap sebagai bau kode karena pengguna perlu entah bagaimana mengetahui mode modul apa yang ada agar dapat memahami bagaimana fungsinya beroperasi. Jika sedang dalam
!shouldThrowExceptions
mode, pengguna perlu tahu bahwa mereka bertanggung jawab untuk mendeteksi kesalahan dan mempertahankan harapan dan invarian ketika terjadi. Mereka juga bertanggung jawab saat itu juga, di garis di mana fungsi dipanggil. Bendera seperti ini biasanya sangat membingungkan.Namun, itu memang terjadi. Pertimbangkan bahwa banyak prosesor mengizinkan perubahan perilaku floating point dalam program. Suatu program yang ingin memiliki standar yang lebih santai dapat melakukannya hanya dengan mengubah register (yang secara efektif merupakan bendera). Caranya adalah Anda harus sangat berhati-hati, agar tidak sengaja menginjak jari kaki orang lain. Kode akan sering memeriksa bendera saat ini, mengaturnya ke pengaturan yang diinginkan, melakukan operasi, lalu mengaturnya kembali. Dengan begitu tidak ada yang terkejut dengan perubahan itu.
sumber
Contoh khusus ini memiliki fitur menarik yang dapat memengaruhi aturan ...
Apa yang saya lihat di sini adalah pemeriksaan prasyarat . Pemeriksaan prekondisi yang gagal menyiratkan bug yang lebih tinggi di tumpukan panggilan. Jadi, pertanyaannya adalah apakah kode ini bertanggung jawab untuk melaporkan bug yang berlokasi di tempat lain?
Beberapa ketegangan di sini disebabkan oleh fakta bahwa antarmuka ini menunjukkan obsesi primitif -
x
dany
mungkin dianggap mewakili pengukuran panjang nyata. Dalam konteks pemrograman di mana jenis domain tertentu merupakan pilihan yang wajar, kami akan memindahkan pemeriksaan prakondisi lebih dekat ke sumber data - dengan kata lain, menendang tanggung jawab untuk integritas data lebih jauh ke tumpukan panggilan, di mana kami memiliki akal yang lebih baik untuk konteksnya.Yang mengatakan, saya tidak melihat ada kesalahan mendasar dengan memiliki dua strategi berbeda untuk mengelola cek gagal. Preferensi saya adalah menggunakan komposisi untuk menentukan strategi mana yang digunakan; bendera fitur akan digunakan dalam root komposisi, daripada dalam implementasi metode perpustakaan.
Dewan Lalu Lintas dan Keselamatan Nasional sangat bagus; Saya mungkin menyarankan teknik implementasi alternatif untuk graybeards, tapi saya tidak cenderung berdebat dengan mereka tentang mendesain sekat dalam subsistem pelaporan kesalahan.
Secara lebih luas: berapa biaya untuk bisnis? Jauh lebih murah untuk menabrak situs web daripada sistem kritis kehidupan.
sumber
Metode menangani pengecualian atau tidak, tidak perlu ada flag dalam bahasa seperti C #.
Jika ada yang salah dalam ... kode maka pengecualian itu perlu ditangani oleh penelepon. Jika tidak ada yang menangani kesalahan, program akan berakhir.
Dalam hal ini, jika sesuatu yang buruk terjadi dalam ... kode, Method1 sedang menangani masalah dan program harus melanjutkan.
Tempat Anda menangani pengecualian terserah Anda. Tentu saja Anda dapat mengabaikannya dengan menangkap dan tidak melakukan apa pun. Namun, saya akan memastikan Anda hanya mengabaikan jenis pengecualian tertentu yang dapat Anda harapkan terjadi. Mengabaikan (
exception ex
) berbahaya karena beberapa pengecualian Anda tidak ingin mengabaikan seperti pengecualian sistem tentang kehabisan memori dan semacamnya.sumber
Pendekatan ini menghancurkan filosofi "gagal cepat, gagal keras".
Mengapa Anda ingin gagal dengan cepat:
Kerugian tidak gagal cepat dan keras:
Terakhir, penanganan kesalahan berbasis pengecualian yang sebenarnya memiliki manfaat yang dapat Anda berikan pada pesan atau objek kesalahan "out of band", Anda dapat dengan mudah menghubungkan pencatatan kesalahan, memberi peringatan, dll. Tanpa menulis satu baris tambahan dalam kode domain Anda .
Jadi, tidak hanya alasan teknis yang sederhana, tetapi juga alasan "sistem" yang membuatnya sangat cepat gagal.
Pada akhir hari, tidak gagal keras dan cepat di zaman kita saat ini, di mana penanganan pengecualian ringan dan sangat stabil hanya setengah kriminal. Saya mengerti dengan sempurna dari mana pemikiran bahwa baik untuk menekan pengecualian berasal, tetapi itu tidak berlaku lagi.
Terutama dalam kasus tertentu, di mana Anda bahkan memberikan pilihan apakah akan membuang pengecualian atau tidak: ini berarti bahwa pemanggil harus memutuskan lagian . Jadi tidak ada kekurangan sama sekali untuk membuat penelepon menangkap pengecualian dan menanganinya dengan tepat.
Satu poin muncul dalam komentar:
Gagal cepat dan sulit tidak berarti seluruh aplikasi Anda mogok. Ini berarti bahwa pada titik di mana kesalahan terjadi, itu gagal secara lokal. Dalam contoh OP, metode level rendah yang menghitung beberapa area tidak boleh secara diam-diam mengganti kesalahan dengan nilai yang salah. Itu harus gagal dengan jelas.
Beberapa penelepon rantai jelas harus menangkap kesalahan / pengecualian dan menanganinya dengan tepat. Jika metode ini digunakan dalam pesawat terbang, ini mungkin akan menyebabkan beberapa kesalahan LED menyala, atau setidaknya untuk menampilkan "area penghitungan kesalahan" bukannya area yang salah.
sumber