Memiliki bendera untuk menunjukkan apakah kita harus melempar kesalahan

64

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.

Nicolas
sumber
22
c # memiliki konvensi untuk rintik Try-Parse ini. info lebih lanjut: docs.microsoft.com/en-us/dotnet/standard/design-guidelines/… Bendera tidak cocok dengan pola ini.
Peter
18
Ini pada dasarnya adalah parameter kontrol dan ini mengubah cara metode dijalankan secara internal. Ini buruk, terlepas dari skenario. martinfowler.com/bliki/FlagArgument.html , softwareengineering.stackexchange.com/questions/147977/… , medium.com/@amlcurran/…
bic
1
Selain komentar Try-Parse dari Peter, di sini adalah artikel bagus tentang pengecualian menjengkelkan: blogs.msdn.microsoft.com/ericlippert/2008/09/10/...
Linaith
2
"Jadi, kita tidak akan ingin melempar pengecualian (dan tidak menanganinya?) Dan minta itu menghapus program ketika bagi pengguna itu berfungsi dengan baik" - Anda tahu Anda bisa menangkap pengecualian, kan?
user253751
1
Saya cukup yakin ini telah dibahas sebelumnya di tempat lain tetapi diberikan contoh Area sederhana saya akan lebih cenderung bertanya-tanya dari mana angka-angka negatif itu akan datang dan apakah Anda mungkin dapat menangani kondisi kesalahan itu di tempat lain (misalnya, apa pun yang membaca file yang berisi panjang dan lebar, misalnya); Namun, "kami mencoba menggunakan perangkat jaringan yang tidak dapat hadir pada saat itu." titik bisa mendapat jawaban yang sama sekali berbeda, apakah ini API pihak ketiga atau sesuatu yang standar industri seperti TCP / UDP?
jrh

Jawaban:

73

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.

mmathis
sumber
1
@Quirk Sangat mengesankan bagaimana Chen berhasil melanggar Prinsip Tanggung Jawab Tunggal hanya dalam 3 atau 4 baris. Itulah masalah sebenarnya. Plus, masalah yang dia bicarakan (programmer gagal memikirkan konsekuensi kesalahan pada setiap baris) selalu ada kemungkinan dengan pengecualian yang tidak dicentang dan hanya kadang - kadang kemungkinan dengan pengecualian yang diperiksa. Saya pikir saya telah melihat semua argumen yang pernah dibuat terhadap pengecualian diperiksa, dan tidak satupun dari mereka yang valid.
No U
@TKK secara pribadi ada beberapa kasus saya pernah bertemu di mana saya akan sangat suka memeriksa pengecualian di .NET. Akan lebih baik jika ada beberapa alat analisis statis high-end yang dapat memastikan apa yang didokumentasikan API sebagai pengecualian yang dilontarkannya akurat, meskipun itu kemungkinan akan sangat tidak mungkin, terutama ketika mengakses sumber daya asli.
jrh
1
@ jrh Ya, alangkah baiknya jika ada sesuatu yang memasukkan beberapa pengecualian keamanan ke dalam .NET, mirip dengan bagaimana TypeScript kludges mengetikkan keselamatan ke dalam JS.
Tidak U
47

Apakah ini cara yang baik untuk menangani pengecualian?

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

  1. mungkin ada beberapa instance kelas dengan konfigurasi berbeda yang beredar di program yang sama pada waktu yang sama, dan,

  2. 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.


Bukankah seharusnya menjadi tanggung jawab penelepon untuk menentukan bagaimana melanjutkan?

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).

Erik Eidt
sumber
Anda tidak benar-benar harus memeriksa pengecualian sama sekali karena Anda harus memeriksa argumen sebelum memanggil metode. Memeriksa hasil terhadap nol tidak membedakan antara argumen hukum 0, 0 dan yang negatif ilegal. API adalah IMHO yang benar-benar mengerikan.
BlackJack
Dorongan Annex K MS untuk C99 dan C ++ iostreams adalah contoh API di mana kait atau panji secara radikal mengubah reaksi terhadap kegagalan.
Deduplicator
37

Mereka telah bekerja pada aplikasi kritis yang berhubungan dengan penerbangan di mana sistem tidak bisa turun. Hasil dari ...

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

  • pengecualian tidak ditangani dengan benar , setidaknya atau kaku.

Jadi, jika program yang menggunakan AreaCalculatorbuggy, 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

    CalculateArea(int x, int y, out ErrorCode err)

    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.

Doc Brown
sumber
3
"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 program macet" Saya sepenuhnya setuju secara umum meskipun saya bisa membayangkan bahwa dalam penerbangan saya mungkin lebih suka memiliki pesawat masih berjalan dengan instrumen yang menunjukkan nilai yang salah dibandingkan dengan mematikan komputer pesawat. Untuk semua aplikasi yang kurang kritis, lebih baik tidak menutupi kesalahan.
Trilarion
18
@Trilarion: jika sebuah program untuk komputer penerbangan tidak mengandung penanganan pengecualian yang tepat, "memperbaiki ini" dengan membuat komponen tidak membuang pengecualian adalah pendekatan yang sangat salah arah. Jika program macet, harus ada beberapa sistem cadangan yang berlebihan yang dapat mengambil alih. Jika program tidak crash dan menunjukkan ketinggian yang salah, misalnya, pilot mungkin berpikir "semuanya baik-baik saja" sementara pesawat bergegas ke gunung berikutnya.
Doc Brown
7
@Trilarion: jika komputer penerbangan menunjukkan ketinggian yang salah dan pesawat akan jatuh karena hal ini, itu juga tidak akan membantu Anda (terutama ketika sistem cadangan ada dan tidak mendapat informasi yang diperlukan untuk mengambil alih). Sistem cadangan untuk komputer pesawat bukan ide baru, google untuk "sistem cadangan komputer pesawat", saya cukup yakin insinyur di seluruh dunia selalu membangun sistem redundan ke dalam sistem kritis kehidupan nyata (dan jika itu hanya karena tidak kehilangan asuransi).
Doc Brown
4
Ini. Jika Anda tidak mampu untuk crash program, Anda tidak mampu untuk diam-diam memberikan jawaban yang salah juga. Jawaban yang benar adalah memiliki penanganan pengecualian yang sesuai dalam semua kasus. Untuk situs web, itu berarti penangan global yang mengubah kesalahan tak terduga menjadi 500. Anda mungkin juga memiliki penangan tambahan untuk situasi yang lebih spesifik, seperti memiliki try/ catchdi dalam loop jika Anda perlu memproses untuk melanjutkan jika satu elemen gagal.
jpmc26
2
Mendapatkan hasil yang salah selalu merupakan jenis kegagalan terburuk; yang mengingatkan saya pada aturan tentang optimasi: "lakukan dengan benar sebelum Anda mengoptimalkan, karena mendapatkan jawaban yang salah lebih cepat masih bermanfaat bagi siapa pun."
Toby Speight
13

Tes unit menulis menjadi sedikit lebih rumit karena Anda harus menguji untuk bendera pengecualian setiap kali.

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.

Juga, jika terjadi kesalahan, tidakkah Anda ingin segera tahu?

Itu tergantung pada definisi Anda yang salah. Contoh dalam pertanyaan mendefinisikan salah sebagai "diberi dimensi kurang dari nol dan shouldThrowExceptionsbenar." Diberi dimensi jika kurang dari nol tidak salah ketika shouldThrowExceptionssalah 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?

Bukankah seharusnya menjadi tanggung jawab penelepon untuk menentukan bagaimana melanjutkan?

Penelepon tidak menentukan bagaimana untuk melanjutkan. Dalam hal ini, ia melakukannya sebelumnya dengan menetapkan atau membersihkan shouldThrowExceptionsdan 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.

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.

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. ;-)

Blrfl
sumber
Saya setuju bahwa penamaan dan niat sangat penting dan dalam hal ini nama parameter yang tepat dapat benar-benar mengubah tabel, jadi +1, tetapi 1. our program needs to do 1 thing, show data to user. Any other exception that doesn't stop us from doing so should be ignoredpola 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 seperti bool ExecuteJob(bool throwOnError = false)biasanya sangat kesalahan dan mengarah pada kode yang sulit dipikirkan hanya dengan membacanya.
Eugene Podskal
@EugenePodskal Saya pikir asumsinya adalah "tampilkan data" berarti "tampilkan data yang benar." Penanya tidak mengatakan bahwa produk jadi tidak berfungsi, hanya saja itu mungkin ditulis "salah." Saya harus melihat beberapa data keras pada poin kedua. Saya memiliki beberapa fungsi yang banyak digunakan dalam proyek saya saat ini yang memiliki tombol throw / no-throw dan tidak lebih sulit untuk dipikirkan daripada fungsi lainnya, tetapi itu adalah satu titik data.
Blrfl
Jawaban yang bagus, saya pikir logika ini berlaku untuk rentang keadaan yang jauh lebih luas daripada hanya OP. BTW, cpp baru memiliki versi throw / no-throw untuk alasan-alasan ini. Saya tahu ada beberapa perbedaan kecil tapi ...
drjpizzle
8

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".

drjpizzle
sumber
7

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 berguna volumeharus 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 itu z? Ini pertanyaan yang bagus. Anda bisa memaksa pengguna untuk segera menangani pemeriksaan. Namun ini memecah logika dengan penanganan kesalahan.

var area_calc = new AreaCalculator();
var area_result = area_calc.CalculateArea(x, y);
if (area_result.bad())
{
    //handle unhappy path
}
var volume = area_result.value() * z;

vs.

var area_calc = new AreaCalculator();
var volume = area_calc.CalculateArea(x, y) * z;
if (volume.bad())
{
    //handle unhappy path
}

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 volumeadalah tipe data yang kaya. Bukan hanya angka. Ini membuat penyimpanan lebih besar, dan volumemasih perlu diselidiki untuk kondisi kesalahan. Selain itu volumemungkin 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 volumebisa 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.

... foo() {
   var area_calc = new AreaCalculator();
   return area_calc.CalculateArea(x, y) * z;
}
var volume = foo();
if (volume <= 0)
{
    //handle error
}

vs.

... foo() {
   var area_calc = new AreaCalculator();
   return area_calc.CalculateArea(x, y) * z;
}

try { var volume = foo(); }
catch(...)
{
    //handle error
}

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).

  • Pengguna harus dapat meminta kondisi buruk, dan menanganinya segera
  • Pengguna harus dapat beroperasi pada tipe yang diperkaya seolah-olah jalan bahagia telah diikuti dan menyebarkan detail kesalahan.
  • Pengguna harus dapat mengekstraksi nilai jalur bahagia melalui casting (implisit / eksplisit seperti yang wajar), menghasilkan pengecualian untuk jalur tidak bahagia.
  • Pengguna harus dapat mengekstraksi nilai happy path, atau menggunakan default (disediakan atau tidak)

Sesuatu seperti:

Rich::value_type value_or_default(Rich&, Rich::value_type default_value = ...);
bool bad(Rich&);
...unhappy path report... bad_state(Rich&);
Rich& assert_not_bad(Rich&);
class Rich
{
public:
   typedef ... value_type;

   operator value_type() { assert_not_bad(*this); return ...value...; }
   operator X(...) { if (bad(*this)) return ...propagate badness to new value...; /*operate and generate new value*/; }
}

//check
if (bad(x))
{
    var report = bad_state(x);
    //handle error
}

//rethrow
assert_not_bad(x);
var result = (assert_not_bad(x) + 23) / 45;

//propogate
var y = x * 23;

//implicit throw
Rich::value_type val = x;
var val = ((Rich::value_type)x) + 34;
var val2 = static_cast<Rich::value_type>(x) % 3;

//default value
var defaulted = value_or_default(x);
var defaulted_to = value_or_default(x, 55);
Kain0_0
sumber
@TobySpeight Cukup adil, hal-hal ini sensitif terhadap konteks dan jangkauannya.
Kain0_0
Saya pikir masalahnya di sini adalah blok 'assert_not_bad'. Saya pikir ini akan berakhir seperti di tempat yang sama dengan kode asli mencoba untuk dipecahkan. Namun, dalam pengujian ini perlu diperhatikan, jika mereka benar-benar menegaskan, mereka harus dilucuti sebelum produksi pada pesawat nyata. jika tidak, beberapa poin bagus.
drjpizzle
@ rjpizzle Saya berpendapat bahwa jika itu cukup penting untuk menambahkan penjaga untuk pengujian, itu cukup penting untuk meninggalkan penjaga di tempat ketika berjalan dalam produksi. Kehadiran penjaga itu sendiri menyiratkan keraguan. Jika Anda meragukan kode cukup untuk menjaganya selama pengujian, Anda meragukannya karena alasan teknis. yaitu, Kondisi tersebut dapat / tidak terjadi secara realistis. Pengujian yang dilakukan tidak membuktikan bahwa kondisi tersebut tidak akan pernah tercapai dalam produksi. Itu berarti ada kondisi yang diketahui, yang mungkin terjadi, yang perlu ditangani di suatu tempat entah bagaimana. Saya pikir bagaimana ini ditangani, masalahnya.
Kain0_0
3

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":

int CalculateArea(int x, int y) {
    if (x < 0 || y < 0) {
        throw Exception("negative side lengths");
    }
    return x * y;
}

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 # sebut Try-Parsepolanya (terima kasih Peter atas tipnya!)

int CalculateArea(int x, int y) {
    if (x < 0 || y < 0) {
        throw Exception("negative side lengths");
    }
    return x * y;
}

bool TryCalculateArea(int x, int y, int& result) {
    if (x < 0 || y < 0) {
        return false;
    }
    result = x * y;
    return true;
}

int a1 = CalculateArea(x, y);
int a2;
if (TryCalculateArea(x, y, a2)) {
    // use a2
}

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 berguna false- "ada yang salah, jangan tanya saya apa atau di mana." (Beberapa API ganda menggunakan jenis kesalahan yang lebih ekspresif, seperti int errnoatau C ++ 's std::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!

template<class F>
int CalculateArea(int x, int y, F errorCallback) {
    if (x < 0 || y < 0) {
        return errorCallback(x, y, "negative side lengths");
    }
    return x * y;
}

int a1 = CalculateArea(x, y, [](auto...) { return 0; });
int a2 = CalculateArea(x, y, [](int, int, auto msg) { throw Exception(msg); });
int a3 = CalculateArea(x, y, [](int, int, auto) { return x * y; });

Ini pada dasarnya adalah apa yang dilakukan rekan kerja Anda; kecuali bahwa dia memfaktorkan "penangan kesalahan" ke dalam variabel global:

std::function<int(const char *)> g_errorCallback;

int CalculateArea(int x, int y) {
    if (x < 0 || y < 0) {
        return g_errorCallback("negative side lengths");
    }
    return x * y;
}

g_errorCallback = [](auto) { return 0; };
int a1 = CalculateArea(x, y);
g_errorCallback = [](const char *msg) { throw Exception(msg); };
int a2 = CalculateArea(x, y);

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:

bool g_errorViaException;

int CalculateArea(int x, int y) {
    if (x < 0 || y < 0) {
        return g_errorViaException ? throw Exception("negative side lengths") : 0;
    }
    return x * y;
}

g_errorViaException = false;
int a1 = CalculateArea(x, y);
g_errorViaException = true;
int a2 = CalculateArea(x, y);

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:

int CalculateArea(int x, int y) {
    if (x < 0 || y < 0) {
#ifdef NEXCEPTIONS
        return 0;
#else
        throw Exception("negative side lengths");
#endif
    }
    return x * y;
}

// Now these two function calls *must* have the same behavior,
// which is a nice property for a program to have.
// Improves understandability.
//
int a1 = CalculateArea(x, y);
int a2 = CalculateArea(x, y);

Contoh dari sesuatu yang bekerja dengan cara ini adalah assertmakro di C dan C ++, yang mengkondisikan perilakunya pada makro preprosesor NDEBUG.

Quuxplusone
sumber
Jika seseorang mengembalikan sebuah std::optionaldari TryCalculateArea()gantinya, mudah untuk menyatukan implementasi kedua bagian dari antarmuka ganda dalam satu fungsi-templat dengan flag waktu-kompilasi.
Deduplicator
@Dupuplikator: Mungkin dengan std::expected. Dengan adil std::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 berguna false- " ada yang salah, jangan tanya saya apa atau di mana. "
Quuxplusone
Itu sebabnya libc ++ <filesystem> benar-benar melakukan sesuatu yang sangat dekat dengan pola rekan kerja OP: memipis std::error_code *ecsemua jalan turun melalui setiap tingkat API, dan kemudian di bagian bawah melakukan moral yang setara if (ec == nullptr) throw something; else *ec = some error code. (Ini mengabstraksi aktual ifmenjadi sesuatu yang disebut ErrorHandler, tapi itu ide dasar yang sama.)
Quuxplusone
Nah itu akan menjadi opsi untuk menjaga informasi kesalahan yang diperluas tanpa membuang. Mungkin sesuai, atau tidak sebanding dengan potensi biaya tambahan.
Deduplicator
1
Begitu banyak pikiran baik yang terkandung dalam jawaban ini ... Pasti membutuhkan lebih banyak upvotes :-)
cmaster
1

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, semua intnilai adalah hasil yang valid untuk Math.sum(int, int), tetapi jika nilainya melimpah, hasil khusus ini bisa menjadi sampah). Ini adalah pola yang relatif baru.

Sebelum outkata 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).

Tezra
sumber
Sebelum outkata kunci, Anda akan menulis boolfungsi yang membawa penunjuk ke hasilnya, yaitu refparameter. Anda bisa melakukannya di VB6 pada tahun 1998. Kata outkunci 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.
Mathieu Guindon
@MathieuGuindon Yeay, tapi GetTry belum menjadi pola yang dikenal / mapan, dan bahkan jika itu, saya tidak sepenuhnya yakin itu akan digunakan. Lagi pula, bagian dari petunjuk hingga Y2K adalah bahwa menyimpan sesuatu yang lebih besar dari 0-99 tidak dapat diterima.
Tezra
0

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:

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.

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 !shouldThrowExceptionsmode, 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.

Cort Ammon
sumber
0

Contoh khusus ini memiliki fitur menarik yang dapat memengaruhi aturan ...

CalculateArea(int x, int y)
{
    if(x < 0 || y < 0)
    {
        if(shouldThrowExceptions) 
            throwException;
        else
            return 0;
    }
}

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 - xdan ymungkin 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.

// Configurable dependencies
AreaCalculator(PreconditionFailureStrategy strategy)

CalculateArea(int x, int y)
{
    if (x < 0 || y < 0) {
        return this.strategy.fail(0);
    }
    // ...
}

Mereka telah bekerja pada aplikasi kritis yang berhubungan dengan penerbangan di mana sistem tidak bisa turun.

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.

VoiceOfUnasonason
sumber
Saya suka saran cara alternatif untuk mempertahankan fleksibilitas yang diinginkan sambil tetap memodernisasi.
drjpizzle
-1

Metode menangani pengecualian atau tidak, tidak perlu ada flag dalam bahasa seperti C #.

public int Method1()
{
  ...code

 return 0;
}

Jika ada yang salah dalam ... kode maka pengecualian itu perlu ditangani oleh penelepon. Jika tidak ada yang menangani kesalahan, program akan berakhir.

public int Method1()
{
try {  
...code
}
catch {}
 ...Handle error 
}
return 0;
}

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.

Jon Raynor
sumber
3
Pengaturan saat ini yang diposting OP berkaitan dengan memutuskan apakah akan dengan sukarela melempar pengecualian. Kode OP tidak mengarah pada menelan hal-hal yang tidak diinginkan seperti pengecualian di luar memori. Jika ada, pernyataan bahwa pengecualian merusak sistem menyiratkan bahwa basis kode tidak menangkap pengecualian dan dengan demikian tidak akan menelan pengecualian apa pun; baik yang sengaja maupun tidak dilontarkan oleh logika bisnis OP.
Flater
-1

Pendekatan ini menghancurkan filosofi "gagal cepat, gagal keras".

Mengapa Anda ingin gagal dengan cepat:

  • Semakin cepat Anda gagal, semakin dekat gejala yang terlihat dari kegagalan tersebut adalah penyebab sebenarnya dari kegagalan tersebut. Ini membuat proses debug jauh lebih mudah - dalam kasus terbaik, Anda memiliki garis kesalahan tepat di baris pertama jejak tumpukan Anda.
  • Semakin cepat Anda gagal (dan menangkap kesalahan dengan tepat), semakin kecil kemungkinannya Anda mengacaukan sisa program Anda.
  • Semakin keras Anda gagal (yaitu, melemparkan pengecualian alih-alih hanya mengembalikan kode "-1" atau sesuatu seperti itu), semakin besar kemungkinan bahwa penelepon benar-benar peduli dengan kesalahan, dan tidak hanya terus bekerja dengan nilai yang salah.

Kerugian tidak gagal cepat dan keras:

  • Jika Anda menghindari kegagalan yang terlihat, yaitu berpura-pura semuanya baik-baik saja, Anda cenderung membuatnya sangat sulit untuk menemukan kesalahan yang sebenarnya. Bayangkan bahwa nilai pengembalian contoh Anda adalah bagian dari beberapa rutin yang menghitung jumlah 100 area; yaitu, memanggil fungsi itu 100 kali dan menjumlahkan nilai yang dikembalikan. Jika Anda diam-diam menekan kesalahan, tidak ada cara sama sekali untuk menemukan di mana kesalahan yang sebenarnya terjadi; dan semua perhitungan berikut akan salah secara diam-diam.
  • Jika Anda menunda kegagalan (dengan mengembalikan nilai pengembalian yang tidak mungkin seperti "-1" untuk suatu area), Anda meningkatkan kemungkinan penelepon fungsi Anda tidak mempedulikannya dan lupa untuk menangani kesalahan; walaupun mereka memiliki informasi tentang kegagalan yang dihadapi.

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:

Telah ditunjukkan dalam jawaban lain bahwa gagal cepat dan keras tidak diinginkan dalam aplikasi kritis ketika perangkat keras yang Anda jalankan adalah pesawat terbang.

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.

AnoE
sumber
3
Telah ditunjukkan dalam jawaban lain bahwa gagal cepat dan keras tidak diinginkan dalam aplikasi kritis ketika perangkat keras yang Anda jalankan adalah pesawat terbang.
HAEM
1
@ HAEM, maka itu adalah kesalahpahaman tentang apa yang berarti gagal cepat dan sulit. Saya telah menambahkan satu paragraf tentang ini ke jawabannya.
AnoE
Sekalipun niatnya untuk tidak gagal 'sekeras itu', masih dianggap berisiko untuk bermain dengan api semacam itu.
drjpizzle
Itulah intinya, @drjpizzle. Jika Anda terbiasa gagal-gagal dengan cepat, itu tidak "berisiko" atau "bermain dengan api semacam itu" dalam pengalaman saya. Au contraire. Itu berarti Anda terbiasa berpikir tentang "apa yang akan terjadi jika saya mendapat pengecualian di sini", di mana "di sini" berarti di mana-mana , dan Anda cenderung menyadari apakah tempat di mana Anda saat ini memprogram akan memiliki masalah besar ( kecelakaan pesawat, apa pun dalam kasus itu). Bermain dengan api adalah mengharapkan semuanya berjalan dengan baik, dan setiap komponen, dalam keraguan, berpura-pura semuanya baik-baik saja ...
AnoE