Kembali di akhir 90-an saya bekerja sedikit dengan basis kode yang menggunakan pengecualian sebagai kontrol aliran. Ini menerapkan mesin negara hingga untuk menggerakkan aplikasi telepon. Akhir-akhir ini saya teringat akan hari-hari itu karena saya sudah melakukan aplikasi web MVC.
Mereka berdua memiliki Controller
yang memutuskan ke mana harus pergi berikutnya dan menyediakan data ke logika tujuan. Tindakan pengguna dari domain telepon sekolah lama, seperti nada DTMF, menjadi parameter untuk metode tindakan, tetapi alih-alih mengembalikan sesuatu seperti a ViewResult
, mereka melemparkan a StateTransitionException
.
Saya pikir perbedaan utama adalah bahwa metode tindakan adalah void
fungsi. Saya tidak ingat semua hal yang saya lakukan dengan fakta ini, tetapi saya ragu untuk mengingat banyak hal karena sejak pekerjaan itu, seperti 15 tahun yang lalu, saya tidak pernah melihat ini dalam kode produksi di pekerjaan lain . Saya berasumsi ini adalah tanda bahwa itu disebut anti-pola.
Apakah ini masalahnya, dan jika demikian, mengapa?
Pembaruan: ketika saya mengajukan pertanyaan, saya sudah memikirkan jawaban @ MasonWheeler jadi saya pergi dengan jawaban yang paling menambah pengetahuan saya. Saya pikir itu adalah jawaban yang bagus juga.
sumber
Jawaban:
Ada diskusi terperinci tentang ini di Ward's Wiki . Umumnya, penggunaan pengecualian untuk aliran kontrol adalah anti-pola, dengan banyak situasi penting - dan bahasa-spesifik (lihat misalnya Python ) batuk pengecualian batuk .
Sebagai ringkasan singkat mengapa, pada umumnya, ini merupakan anti-pola:
Baca diskusi di wiki Ward untuk informasi lebih mendalam.
Lihat juga duplikat dari pertanyaan ini, di sini
sumber
if
danforeach
juga canggihGOTO
. Terus terang, saya pikir perbandingan dengan goto tidak membantu; hampir seperti istilah yang merendahkan. Penggunaan GOTO secara intrinsik tidak jahat - ia memiliki masalah nyata, dan pengecualian mungkin berbagi, tetapi mereka mungkin tidak. Akan lebih membantu mendengar masalah-masalah itu.Kasus penggunaan yang dirancang untuk pengecualian adalah "Saya baru saja menghadapi situasi yang tidak dapat saya tangani dengan benar pada saat ini, karena saya tidak memiliki konteks yang cukup untuk menanganinya, tetapi rutinitas yang memanggil saya (atau sesuatu yang lebih jauh dari panggilan itu) tumpukan) harus tahu bagaimana menanganinya. "
Kasus penggunaan sekunder adalah "Saya baru saja mengalami kesalahan serius, dan saat ini keluar dari aliran kontrol ini untuk mencegah korupsi data atau kerusakan lainnya lebih penting daripada mencoba melanjutkan."
Jika Anda tidak menggunakan pengecualian karena salah satu dari dua alasan ini, mungkin ada cara yang lebih baik untuk melakukannya.
sumber
const myvar = theFunction
karena myvar harus dibuat dari try-catch, oleh karena itu tidak ada lagi yang konstan. Itu tidak berarti saya tidak menggunakannya dalam C # karena ini adalah arus utama di sana untuk alasan apa pun, tapi saya tetap berusaha menguranginya.Pengecualian sekuat Lanjutan dan
GOTO
. Mereka adalah konstruk aliran kontrol universal.Dalam beberapa bahasa, mereka adalah satu - satunya konstruksi aliran kontrol universal. JavaScript, misalnya, tidak memiliki Lanjutan atau
GOTO
bahkan tidak memiliki Panggilan Ekor yang Benar. Jadi, jika Anda ingin menerapkan aliran kontrol canggih dalam JavaScript, Anda harus menggunakan Pengecualian.Proyek Microsoft Volta adalah proyek penelitian (sekarang dihentikan) untuk mengkompilasi kode .NET sewenang-wenang ke JavaScript. .NET memiliki Pengecualian yang semantiknya tidak benar-benar memetakan JavaScript, tetapi yang lebih penting, ia memiliki Threads , dan Anda harus memetakannya entah bagaimana ke JavaScript. Volta melakukan ini dengan menerapkan Kelanjutan Volta menggunakan Pengecualian JavaScript dan kemudian mengimplementasikan semua konstruksi aliran kontrol .NET dalam kaitannya dengan Kelanjutan Volta. Mereka harus menggunakan Pengecualian sebagai aliran kontrol, karena tidak ada aliran kontrol lain yang cukup kuat.
Anda menyebutkan Mesin Negara. SM sepele untuk diterapkan dengan Panggilan Ekor yang Benar: setiap negara bagian adalah subrutin, setiap transisi negara bagian adalah panggilan subrutin. SM juga dapat dengan mudah diimplementasikan dengan
GOTO
atau Coroutine atau Lanjutan. Namun, Java tidak memiliki salah satu dari mereka empat, tetapi tidak memiliki Pengecualian. Jadi, sangat diterima untuk menggunakan mereka sebagai aliran kontrol. (Ya, sebenarnya, pilihan yang tepat mungkin akan menggunakan bahasa dengan konstruk aliran kontrol yang tepat, tetapi kadang-kadang Anda mungkin terjebak dengan Java.)sumber
Seperti yang disebutkan orang lain secara berulang-ulang, ( misalnya dalam pertanyaan Stack Overflow ) ini, prinsip yang paling mengejutkan adalah melarang Anda menggunakan pengecualian secara berlebihan untuk tujuan hanya mengontrol aliran. Di sisi lain, tidak ada aturan yang 100% benar, dan selalu ada kasus di mana pengecualian adalah "alat yang tepat" - sama seperti
goto
itu sendiri, omong-omong, yang dikirimkan dalam bentukbreak
dancontinue
dalam bahasa seperti Jawa, yang sering kali merupakan cara sempurna untuk melompat keluar dari lilitan bersarang, yang tidak selalu dapat dihindari.Posting blog berikut menjelaskan kasus penggunaan yang agak rumit tetapi juga menarik untuk non-lokal
ControlFlowException
:Ini menjelaskan bagaimana di dalam jOOQ (pustaka abstraksi SQL untuk Java) (penafian: Saya bekerja untuk vendor), pengecualian seperti itu kadang-kadang digunakan untuk membatalkan proses rendering SQL lebih awal ketika beberapa kondisi "langka" terpenuhi.
Contoh dari kondisi tersebut adalah:
Nilai ikatan terlalu banyak ditemui. Beberapa database tidak mendukung angka mengikat nilai acak dalam pernyataan SQL mereka (SQLite: 999, Ingres 10.1.0: 1024, Sybase ASE 15.5: 2000, SQL Server 2008: 2100). Dalam kasus-kasus tersebut, JOOQ membatalkan fase rendering SQL dan merender kembali pernyataan SQL dengan nilai-nilai binding yang digarisbawahi. Contoh:
Jika kami secara eksplisit mengekstraksi nilai ikat dari kueri AST untuk menghitungnya setiap waktu, kami akan membuang siklus CPU yang berharga untuk 99,9% kueri yang tidak mengalami masalah ini.
Beberapa logika hanya tersedia secara tidak langsung melalui API yang kami ingin jalankan hanya "sebagian". The
UpdatableRecord.store()
metode menghasilkanINSERT
atauUPDATE
pernyataan, tergantung padaRecord
's bendera internal. Dari "luar", kita tidak tahu jenis logika apa yang terkandungstore()
(misalnya penguncian optimistis, penanganan pendengar acara, dll.) Sehingga kami tidak ingin mengulangi logika itu ketika kami menyimpan beberapa catatan dalam pernyataan kumpulan, di mana kami inginstore()
hanya menghasilkan pernyataan SQL, tidak benar-benar menjalankannya. Contoh:Jika kami telah meng-eksternal-kan
store()
logikanya ke dalam API "yang dapat digunakan kembali" yang dapat dikustomisasi untuk tidak mengeksekusi SQL, kami akan mencari cara membuat API yang agak sulit dipertahankan dan sulit digunakan kembali.Kesimpulan
Pada intinya, penggunaan non-lokal
goto
ini sesuai dengan apa yang dikatakan Mason Wheeler dalam jawabannya:Kedua penggunaan
ControlFlowExceptions
agak mudah diimplementasikan dibandingkan dengan alternatif mereka, memungkinkan kami untuk menggunakan kembali berbagai logika tanpa refactoring keluar dari internal yang relevan.Tapi perasaan ini menjadi sedikit kejutan bagi pemelihara masa depan tetap ada. Kode terasa agak rumit dan meskipun itu adalah pilihan yang tepat dalam kasus ini, kami selalu memilih untuk tidak menggunakan pengecualian untuk aliran kontrol lokal , di mana mudah untuk menghindari menggunakan percabangan biasa
if - else
.sumber
Menggunakan pengecualian untuk aliran kontrol umumnya dianggap sebagai anti-pola, tetapi ada pengecualian (tidak ada permainan kata pun yang dimaksudkan).
Telah dikatakan ribuan kali, bahwa pengecualian dimaksudkan untuk kondisi luar biasa. Koneksi basis data yang rusak adalah kondisi yang luar biasa. Pengguna memasukkan huruf dalam bidang input yang seharusnya hanya memperbolehkan angka tidak .
Bug dalam perangkat lunak Anda yang menyebabkan suatu fungsi dipanggil dengan argumen ilegal, misalnya
null
jika tidak memungkinkan, adalah kondisi yang luar biasa.Dengan menggunakan pengecualian untuk sesuatu yang tidak luar biasa, Anda menggunakan abstraksi yang tidak pantas untuk masalah yang Anda coba selesaikan.
Tetapi bisa juga ada penalti kinerja. Beberapa bahasa memiliki implementasi penanganan pengecualian yang kurang lebih efisien, jadi jika bahasa pilihan Anda tidak memiliki penanganan pengecualian yang efisien, itu bisa sangat mahal, dari segi kinerja *.
Tetapi bahasa lain, misalnya Ruby, memiliki sintaks seperti pengecualian untuk aliran kontrol. Situasi yang luar biasa ditangani oleh
raise
/rescue
operator. Tetapi Anda dapat menggunakanthrow
/catch
untuk konstruksi aliran kontrol pengecualian-suka **.Jadi, meskipun pengecualian umumnya tidak digunakan untuk aliran kontrol, bahasa pilihan Anda mungkin memiliki idiom lain.
* Sebagai contoh dari penggunaan pengecualian yang mahal: Saya pernah mengatur untuk mengoptimalkan aplikasi Formulir Web ASP.NET yang berkinerja buruk. Ternyata, rendering dari sebuah meja besar memanggil
int.Parse()
sekitar. seribu string kosong pada halaman rata-rata, menghasilkan sekitar. seribu pengecualian sedang ditangani. Dengan mengganti kode denganint.TryParse()
saya mencukur satu detik! Untuk setiap permintaan satu halaman!** Ini bisa sangat membingungkan bagi seorang programmer yang datang ke Ruby dari bahasa lain, karena keduanya
throw
dancatch
merupakan kata kunci yang terkait dengan pengecualian dalam banyak bahasa lain.sumber
Sangat mungkin untuk menangani kondisi kesalahan tanpa menggunakan pengecualian. Beberapa bahasa, terutama C, bahkan tidak memiliki pengecualian, dan orang masih berhasil membuat aplikasi yang cukup rumit dengannya. Alasan pengecualian berguna adalah mereka memungkinkan Anda untuk secara ringkas menentukan dua aliran kontrol independen pada dasarnya dalam kode yang sama: satu jika terjadi kesalahan dan satu jika tidak. Tanpa mereka, Anda berakhir dengan kode di semua tempat yang terlihat seperti ini:
Atau setara dalam bahasa Anda, seperti mengembalikan tupel dengan satu nilai sebagai status kesalahan, dll. Seringkali orang yang menunjukkan seberapa mahal penanganan pengecualian, mengabaikan semua
if
pernyataan tambahan seperti di atas yang harus Anda tambahkan jika Anda tidak t gunakan pengecualian.Meskipun pola ini paling sering terjadi ketika menangani kesalahan atau "kondisi luar biasa" lainnya, menurut saya jika Anda mulai melihat kode boilerplate seperti ini dalam keadaan lain, Anda memiliki argumen yang cukup bagus untuk menggunakan pengecualian. Bergantung pada situasi dan implementasinya, saya dapat melihat pengecualian digunakan secara sah di mesin negara, karena Anda memiliki dua aliran kontrol ortogonal: satu yang mengubah negara dan satu untuk peristiwa yang terjadi di dalam negara.
Namun, situasi itu jarang terjadi, dan jika Anda akan membuat pengecualian (pun intended) pada aturan, Anda sebaiknya bersiap untuk menunjukkan keunggulannya terhadap solusi lain. Penyimpangan tanpa pembenaran seperti itu dengan tepat disebut sebagai anti-pola.
sumber
Dalam Python, pengecualian digunakan untuk penghentian generator dan iterasi. Python memiliki coba / kecuali blok yang sangat efisien, tetapi sebenarnya menaikkan pengecualian memiliki beberapa overhead.
Karena kurangnya jeda multi-level atau pernyataan goto dalam Python, saya terkadang menggunakan pengecualian:
sumber
return
menggantikannyagoto
.try:
blok 1 atau 2 baris tolong, tidak pernahtry: # Do lots of stuff
.Mari kita gambarkan penggunaan Pengecualian seperti itu:
Algoritma mencari secara rekursif sampai sesuatu ditemukan. Jadi, kembali dari rekursi, kita harus memeriksa hasilnya untuk ditemukan, dan kembali lagi, jika tidak, lanjutkan. Dan itu berulang kali kembali dari kedalaman rekursi.
Selain membutuhkan boolean tambahan
found
(untuk dikemas dalam kelas, di mana jika tidak mungkin hanya int akan dikembalikan), dan untuk kedalaman rekursi, penutupan yang sama terjadi.Seperti unwinding dari panggilan stack hanya apa pengecualian adalah untuk. Jadi menurut saya cara pengkodean yang tidak seperti goto, lebih cepat dan tepat. Tidak diperlukan, penggunaan yang jarang, mungkin gaya yang buruk, tetapi to-the-point. Sebanding dengan operasi pemotongan Prolog.
sumber
Pemrograman adalah tentang pekerjaan
Saya pikir cara termudah untuk menjawab ini adalah untuk memahami kemajuan yang telah dibuat OOP selama bertahun-tahun. Segala sesuatu yang dilakukan dalam OOP (dan sebagian besar paradigma pemrograman, dalam hal ini) dimodelkan sekitar membutuhkan pekerjaan yang dilakukan .
Setiap kali suatu metode dipanggil, penelepon itu mengatakan, "Saya tidak tahu bagaimana melakukan pekerjaan ini, tetapi Anda tahu caranya, jadi Anda melakukannya untuk saya."
Ini menimbulkan kesulitan: apa yang terjadi ketika metode yang dipanggil umumnya tahu bagaimana melakukan pekerjaan, tetapi tidak selalu? Kami membutuhkan cara untuk berkomunikasi, "Saya ingin membantu Anda, saya benar-benar melakukannya, tetapi saya tidak bisa melakukannya."
Metodologi awal untuk mengomunikasikannya adalah mengembalikan nilai "sampah". Mungkin Anda mengharapkan bilangan bulat positif, sehingga metode yang dipanggil mengembalikan angka negatif. Cara lain untuk mencapai ini adalah dengan menetapkan nilai kesalahan di suatu tempat. Sayangnya, kedua cara ini menghasilkan kode boiler -let-me-check-over-di sini-untuk-memastikan-semuanya-halal . Ketika hal-hal menjadi lebih rumit, sistem ini berantakan.
Analogi Luar Biasa
Katakanlah Anda memiliki tukang kayu, tukang ledeng, dan tukang listrik. Anda ingin tukang ledeng untuk memperbaiki wastafel Anda, jadi dia memeriksanya. Tidak terlalu berguna jika dia hanya memberi tahu Anda, "Maaf, saya tidak bisa memperbaikinya. Sudah rusak." Sial, bahkan lebih buruk jika dia melihat, pergi, dan mengirimi Anda surat yang mengatakan ia tidak bisa memperbaikinya. Sekarang Anda harus memeriksa surat Anda bahkan sebelum Anda tahu dia tidak melakukan apa yang Anda inginkan.
Apa yang Anda inginkan adalah meminta dia memberi tahu Anda, "Dengar, saya tidak bisa memperbaikinya karena sepertinya pompa Anda tidak berfungsi."
Dengan informasi ini, Anda dapat menyimpulkan bahwa Anda ingin tukang listrik melihat masalahnya. Mungkin tukang listrik akan menemukan sesuatu yang berhubungan dengan tukang kayu, dan Anda harus memperbaikinya.
Heck, Anda mungkin bahkan tidak tahu Anda membutuhkan tukang listrik, Anda mungkin tidak tahu siapa yang Anda butuhkan. Anda hanya manajemen menengah dalam bisnis perbaikan rumah, dan fokus Anda adalah plumbing. Jadi, Anda memberi tahu bos tentang masalahnya, lalu dia memberi tahu tukang listrik untuk memperbaikinya.
Inilah yang menjadi pemodelan pengecualian: mode kegagalan kompleks dengan cara yang terpisah. Tukang ledeng tidak perlu tahu tentang tukang listrik - dia bahkan tidak perlu tahu bahwa seseorang yang berada di rantai dapat memperbaiki masalahnya. Dia hanya melaporkan masalah yang dia temui.
Jadi ... sebuah pola anti?
Oke, jadi memahami titik pengecualian adalah langkah pertama. Selanjutnya adalah memahami apa itu pola anti.
Untuk memenuhi syarat sebagai anti-pola, perlu
Poin pertama mudah dipenuhi - sistem bekerja, kan?
Poin kedua lebih lengket. Alasan utama untuk menggunakan pengecualian sebagai aliran kontrol normal buruk adalah karena itu bukan tujuan mereka. Setiap fungsionalitas yang diberikan dalam suatu program harus memiliki tujuan yang relatif jelas, dan mengkooptasi tujuan itu menyebabkan kebingungan yang tidak perlu.
Tapi itu bukan kerugian yang pasti . Ini cara yang buruk untuk melakukan sesuatu, dan aneh, tetapi anti-pola? Tidak. Hanya ... aneh.
sumber