Bahasa Go Google tidak memiliki pengecualian sebagai pilihan desain, dan ketenaran Linus dari Linux menyebut pengecualian sebagai omong kosong. Mengapa?
Bahasa Go Google tidak memiliki pengecualian sebagai pilihan desain, dan ketenaran Linus dari Linux menyebut pengecualian sebagai omong kosong. Mengapa?
Jawaban:
Pengecualian membuatnya sangat mudah untuk menulis kode di mana pengecualian yang dilemparkan akan merusak invarian dan membiarkan objek dalam keadaan tidak konsisten. Mereka pada dasarnya memaksa Anda untuk mengingat bahwa hampir setiap pernyataan yang Anda buat berpotensi melempar, dan menanganinya dengan benar. Melakukannya bisa jadi rumit dan kontra-intuitif.
Pertimbangkan sesuatu seperti ini sebagai contoh sederhana:
class Frobber
{
int m_NumberOfFrobs;
FrobManager m_FrobManager;
public:
void Frob()
{
m_NumberOfFrobs++;
m_FrobManager.HandleFrob(new FrobObject());
}
};
Dengan asumsi FrobManager
akan delete
yang FrobObject
, penampilan OK ini, kan? Atau mungkin tidak ... Bayangkan jika salah satu FrobManager::HandleFrob()
atau operator new
melempar pengecualian. Dalam contoh ini, kenaikan m_NumberOfFrobs
tidak dapat diputar kembali. Jadi, siapa pun yang menggunakan contoh Frobber
ini akan memiliki objek yang mungkin rusak.
Contoh ini mungkin tampak bodoh (oke, saya harus sedikit meregangkan diri untuk membuatnya :-)), tetapi, kesimpulannya adalah jika seorang programmer tidak terus-menerus memikirkan pengecualian, dan memastikan bahwa setiap permutasi status digulung kembali setiap kali ada lemparan, Anda mendapat masalah dengan cara ini.
Sebagai contoh, Anda dapat menganggapnya seperti Anda memikirkan mutex. Di dalam bagian kritis, Anda mengandalkan beberapa pernyataan untuk memastikan bahwa struktur data tidak rusak dan utas lain tidak dapat melihat nilai antara Anda. Jika salah satu dari pernyataan itu tidak berjalan secara acak, Anda berakhir di dunia yang menyakitkan. Sekarang singkirkan kunci dan konkurensi, dan pikirkan tentang setiap metode seperti itu. Pikirkan setiap metode sebagai transaksi permutasi pada status objek, jika Anda mau. Pada awal pemanggilan metode Anda, objek harus dalam keadaan bersih, dan pada akhirnya juga harus ada keadaan bersih. Di antara, variabel foo
mungkin tidak konsisten denganbar
, tetapi kode Anda pada akhirnya akan memperbaikinya. Yang dimaksud pengecualian adalah bahwa salah satu pernyataan Anda dapat mengganggu Anda kapan saja. Tanggung jawab ada pada Anda dalam setiap metode untuk melakukannya dengan benar dan memutar kembali ketika itu terjadi, atau memesan operasi Anda sehingga lemparan tidak memengaruhi status objek. Jika Anda salah (dan mudah membuat kesalahan seperti ini), penelepon akhirnya melihat nilai tengah Anda.
Metode seperti RAII, yang sering disebut oleh programmer C ++ sebagai solusi pamungkas untuk masalah ini, sangat membantu untuk melindungi dari hal ini. Tapi itu bukan peluru perak. Ini akan memastikan Anda melepaskan sumber daya dalam sekejap, tetapi tidak membebaskan Anda dari keharusan memikirkan kerusakan status objek dan pemanggil melihat nilai perantara. Jadi, bagi banyak orang, lebih mudah mengatakannya, berdasarkan gaya pengkodean, tanpa pengecualian . Jika Anda membatasi jenis kode yang Anda tulis, lebih sulit untuk memperkenalkan bug ini. Jika tidak, cukup mudah untuk membuat kesalahan.
Seluruh buku telah ditulis tentang pengecualian kode aman dalam C ++. Banyak ahli yang salah. Jika memang serumit itu dan memiliki begitu banyak nuansa, mungkin itu pertanda baik bahwa Anda perlu mengabaikan fitur itu. :-)
Alasan Go tidak memiliki pengecualian dijelaskan di FAQ desain bahasa Go:
Pengecualian adalah cerita yang serupa. Sejumlah desain untuk pengecualian telah diusulkan tetapi masing-masing menambahkan kompleksitas yang signifikan pada bahasa dan waktu proses. Pada dasarnya, pengecualian mencakup fungsi dan bahkan mungkin goroutine; mereka memiliki implikasi yang luas. Ada juga kekhawatiran tentang pengaruhnya terhadap perpustakaan. Mereka, menurut definisi, luar biasa namun memiliki pengalaman dengan bahasa lain yang mendukungnya menunjukkan bahwa mereka memiliki efek yang besar pada spesifikasi pustaka dan antarmuka. Alangkah baiknya menemukan desain yang memungkinkan mereka menjadi benar-benar luar biasa tanpa mendorong kesalahan umum untuk berubah menjadi aliran kontrol khusus yang mengharuskan setiap programmer untuk mengimbanginya.
Seperti obat generik, pengecualian tetap menjadi masalah terbuka.
Dengan kata lain, mereka belum menemukan cara untuk mendukung pengecualian di Go dengan cara yang menurut mereka memuaskan. Mereka tidak mengatakan bahwa Pengecualian buruk per se ;
UPDATE - Mei 2012
Para desainer Go kini telah turun dari pagar. FAQ mereka sekarang mengatakan ini:
Kami percaya bahwa pengecualian penggandengan ke struktur kontrol, seperti dalam idiom coba-tangkap-akhirnya, menghasilkan kode yang berbelit-belit. Ini juga cenderung mendorong pemrogram untuk memberi label terlalu banyak kesalahan biasa, seperti gagal membuka file, sebagai pengecualian.
Go mengambil pendekatan berbeda. Untuk penanganan kesalahan biasa, pengembalian multi-nilai Go memudahkan untuk melaporkan kesalahan tanpa membebani nilai pengembalian. Jenis kesalahan kanonis, ditambah dengan fitur Go lainnya, membuat penanganan kesalahan menyenangkan tetapi sangat berbeda dari yang ada di bahasa lain.
Go juga memiliki beberapa fungsi bawaan untuk memberi sinyal dan memulihkan dari kondisi yang benar-benar luar biasa. Mekanisme pemulihan dijalankan hanya sebagai bagian dari status fungsi yang dihancurkan setelah terjadi kesalahan, yang cukup untuk menangani bencana tetapi tidak memerlukan struktur kontrol tambahan dan, jika digunakan dengan baik, dapat menghasilkan kode penanganan kesalahan yang bersih.
Lihat artikel Tunda, Panik, dan Pulihkan untuk detailnya.
Jadi jawaban singkatnya adalah mereka dapat melakukannya secara berbeda menggunakan pengembalian multi-nilai. (Dan mereka memiliki bentuk penanganan pengecualian.)
... dan ketenaran Linus dari Linux menyebut pengecualian sebagai omong kosong.
Jika Anda ingin tahu mengapa Linus menganggap pengecualian itu omong kosong, hal terbaik adalah mencari tulisannya tentang topik tersebut. Satu-satunya hal yang saya lacak sejauh ini adalah kutipan ini yang disematkan di beberapa email di C ++ :
"Keseluruhan penanganan pengecualian C ++ pada dasarnya rusak. Ini terutama rusak untuk kernel."
Anda akan melihat bahwa dia berbicara tentang pengecualian C ++ secara khusus, dan bukan pengecualian secara umum. (Dan C ++ pengecualian yang tampaknya memiliki beberapa masalah yang membuat mereka sulit untuk menggunakan dengan benar.)
Kesimpulan saya adalah bahwa Linus tidak menyebut pengecualian (secara umum) sebagai "omong kosong" sama sekali!
Pengecualian memang tidak buruk, tetapi jika Anda tahu itu akan sering terjadi, hal itu bisa mahal dalam hal kinerja.
Aturan praktisnya adalah bahwa pengecualian harus menandai kondisi luar biasa, dan Anda tidak boleh menggunakannya untuk mengontrol aliran program.
Saya tidak setuju dengan "hanya memberikan pengecualian dalam situasi yang luar biasa." Meskipun secara umum benar, ini menyesatkan. Pengecualian untuk kondisi kesalahan (kegagalan eksekusi).
Terlepas dari bahasa yang Anda gunakan, ambil salinan Panduan Desain Framework : Konvensi, Idiom, dan Pola untuk Perpustakaan .NET yang Dapat Digunakan Kembali (Edisi ke-2). Bab tentang pengecualian melempar tanpa rekan. Beberapa kutipan dari edisi pertama (yang ke-2 di tempat kerja saya):
Ada halaman catatan tentang manfaat pengecualian (konsistensi API, pilihan lokasi kode penanganan kesalahan, peningkatan ketahanan, dll.) Ada bagian tentang kinerja yang mencakup beberapa pola (Penguji-Pelaku, Coba-Parse).
Pengecualian dan penanganan pengecualian tidak buruk. Seperti fitur lainnya, fitur tersebut dapat disalahgunakan.
Dari perspektif golang, saya rasa tidak memiliki penanganan pengecualian membuat proses kompilasi tetap sederhana dan aman.
Dari perspektif Linus, saya memahami bahwa kode kernel SEMUA tentang kasus sudut. Jadi masuk akal untuk menolak pengecualian.
Pengecualian masuk akal dalam kode adalah tidak masalah untuk menjatuhkan tugas saat ini ke lantai, dan di mana kode kasus umum lebih penting daripada penanganan kesalahan. Tetapi mereka membutuhkan pembuatan kode dari kompiler.
Misalnya, mereka baik-baik saja di sebagian besar kode tingkat tinggi yang menghadap pengguna, seperti kode aplikasi web dan desktop.
Pengecualian di dalam dan dari dirinya sendiri tidaklah "buruk", cara penanganan pengecualian yang terkadang cenderung buruk. Ada beberapa pedoman yang dapat diterapkan saat menangani pengecualian untuk membantu meringankan beberapa masalah ini. Beberapa di antaranya termasuk (tetapi pasti tidak terbatas pada):
Option<T>
bukan null
saat ini. Baru saja diperkenalkan di Java 8 misalnya, mengambil petunjuk dari Guava (dan lainnya).
Argumen umumnya adalah bahwa tidak ada cara untuk mengetahui pengecualian apa yang akan keluar dari potongan kode tertentu (bergantung pada bahasa) dan bahwa mereka terlalu mirip goto
s, sehingga sulit untuk melacak eksekusi secara mental.
http://www.joelonsoftware.com/items/2003/10/13.html
Jelas tidak ada konsensus tentang masalah ini. Saya akan mengatakan bahwa dari sudut pandang programmer C hard-core seperti Linus, pengecualian jelas merupakan ide yang buruk. Seorang programmer Java yang khas berada dalam situasi yang sangat berbeda.
setjmp
/ longjmp
barang, yang sangat buruk.
Pengecualian tidak buruk. Mereka cocok dengan model RAII C ++, yang merupakan hal paling elegan tentang C ++. Jika Anda sudah memiliki banyak kode yang tidak terkecuali aman, maka kode itu buruk dalam konteks itu. Jika Anda menulis perangkat lunak tingkat rendah, seperti OS linux, maka itu buruk. Jika Anda suka mengotori kode Anda dengan sekumpulan pemeriksaan kesalahan, maka itu tidak membantu. Jika Anda tidak memiliki rencana untuk kontrol sumber daya saat pengecualian dilempar (yang disediakan oleh destruktor C ++) maka itu buruk.
Kasus penggunaan yang bagus untuk pengecualian adalah ....
Katakanlah Anda sedang mengerjakan sebuah proyek dan setiap pengontrol (sekitar 20 pengontrol utama yang berbeda) memperluas pengontrol superclass tunggal dengan metode tindakan. Kemudian setiap pengontrol melakukan banyak hal yang berbeda satu sama lain memanggil objek B, C, D dalam satu kasus dan F, G, D dalam kasus lain. Pengecualian datang untuk menyelamatkan di sini dalam banyak kasus di mana ada banyak kode kembali dan SETIAP pengontrol menanganinya secara berbeda. Saya memukul semua kode itu, melemparkan pengecualian yang tepat dari "D", menangkapnya dalam metode aksi pengontrol superclass dan sekarang semua pengontrol kami konsisten. Sebelumnya D mengembalikan null untuk MULTIPLE kasus kesalahan berbeda yang ingin kami beri tahu pengguna akhir tetapi tidak bisa dan saya tidak melakukannya
ya, kita harus mengkhawatirkan setiap level dan pembersihan / kebocoran resource, tetapi secara umum tidak ada pengontrol kami yang memiliki resource untuk dibersihkan setelahnya.
Alhamdulillah kami memiliki pengecualian atau saya akan mengalami refactor besar dan membuang terlalu banyak waktu untuk sesuatu yang seharusnya menjadi masalah pemrograman sederhana.
Secara teoritis mereka sangat buruk. Dalam dunia matematika yang sempurna, Anda tidak bisa mendapatkan situasi pengecualian. Lihatlah bahasa fungsional, mereka tidak memiliki efek samping, jadi mereka hampir tidak memiliki sumber untuk situasi yang tidak terkecuali.
Tapi, kenyataannya adalah cerita lain. Kami selalu menghadapi situasi yang "tidak terduga". Inilah mengapa kami membutuhkan pengecualian.
Saya pikir kita dapat menganggap pengecualian sebagai gula sintaks untuk ExceptionSituationObserver. Anda baru saja mendapatkan pemberitahuan tentang pengecualian. Tidak ada lagi.
Dengan Go, saya pikir mereka akan memperkenalkan sesuatu yang akan menghadapi situasi "tak terduga". Saya dapat menebak bahwa mereka akan mencoba membuatnya terdengar kurang destruktif sebagai pengecualian dan lebih sebagai logika aplikasi. Tapi ini hanya tebakanku.
Paradigma penanganan-pengecualian dari C ++, yang menjadi dasar parsial untuk Java, dan pada gilirannya .net, memperkenalkan beberapa konsep yang baik, tetapi juga memiliki beberapa keterbatasan yang parah. Salah satu tujuan desain utama dari penanganan pengecualian adalah mengizinkan metode untuk memastikan bahwa metode tersebut akan memenuhi kondisi pascanya atau melontarkan pengecualian, dan juga memastikan bahwa pembersihan apa pun yang perlu dilakukan sebelum metode dapat keluar, akan terjadi. Sayangnya, paradigma penanganan pengecualian C ++, Java, dan .net semuanya gagal memberikan cara yang baik untuk menangani situasi di mana faktor-faktor tak terduga mencegah pembersihan yang diharapkan dilakukan. Ini pada gilirannya berarti bahwa seseorang harus mengambil risiko membuat semuanya berhenti menderu jika sesuatu yang tidak terduga terjadi (pendekatan C ++ untuk menangani pengecualian terjadi selama pelepasan tumpukan),
Meskipun penanganan pengecualian secara umum akan baik, bukan tidak masuk akal untuk menganggap paradigma penanganan pengecualian yang tidak dapat diterima yang gagal memberikan cara yang baik untuk menangani masalah yang terjadi saat membersihkan masalah lain. Itu tidak berarti bahwa kerangka kerja tidak dapat dirancang dengan paradigma penanganan pengecualian yang dapat memastikan perilaku yang masuk akal bahkan dalam skenario banyak kegagalan, tetapi belum ada bahasa atau kerangka kerja teratas yang dapat melakukannya.
Saya belum membaca semua jawaban lainnya, jadi masalah ini telah disebutkan, tetapi satu kritik adalah bahwa mereka menyebabkan program terputus dalam rantai panjang, sehingga sulit untuk melacak kesalahan saat men-debug kode. Misalnya, jika Foo () memanggil Bar () yang memanggil Wah () yang memanggil ToString () lalu secara tidak sengaja mendorong data yang salah ke ToString () berakhir seperti kesalahan di Foo (), fungsi yang hampir tidak berhubungan sama sekali.
Oke, jawaban yang membosankan di sini. Saya kira itu benar-benar tergantung pada bahasanya. Jika pengecualian dapat meninggalkan sumber daya yang dialokasikan, mereka harus dihindari. Dalam bahasa skrip, mereka hanya meninggalkan atau melompati bagian dari aliran aplikasi. Itu sendiri tidak disukai, namun menghindari kesalahan yang hampir fatal dengan pengecualian adalah ide yang dapat diterima.
Untuk pemberian sinyal kesalahan, saya biasanya lebih menyukai sinyal kesalahan. Semua bergantung pada API, kasus penggunaan, dan tingkat keparahan, atau jika logging sudah cukup. Juga saya mencoba untuk mendefinisikan ulang perilaku dan throw Phonebooks()
sebaliknya. Idenya adalah bahwa "Pengecualian" sering kali menemui jalan buntu, tetapi "Buku Telepon" berisi informasi bermanfaat tentang pemulihan kesalahan atau rute eksekusi alternatif. (Belum menemukan kasus penggunaan yang baik, tapi teruslah mencoba.)
Bagi saya masalahnya sangat sederhana. Banyak programmer menggunakan penangan pengecualian secara tidak tepat. Lebih banyak sumber daya bahasa lebih baik. Mampu menangani pengecualian itu baik. Salah satu contoh penggunaan yang buruk adalah nilai bilangan bulat yang harus tidak diverifikasi, atau masukan lain yang mungkin membagi dan tidak diperiksa untuk pembagian nol ... penanganan pengecualian mungkin cara mudah untuk menghindari lebih banyak pekerjaan dan pemikiran keras, programmer mungkin ingin melakukan pintasan kotor dan menerapkan penanganan pengecualian ... Pernyataan: "kode profesional TIDAK PERNAH gagal" mungkin merupakan ilusi, jika beberapa masalah yang diproses oleh algoritme tidak pasti oleh sifatnya sendiri. Mungkin dalam situasi yang tidak diketahui secara alami baik ikut bermain pengendali pengecualian. Praktik pemrograman yang baik adalah bahan perdebatan.