Yang mana, dan mengapa, Anda lebih suka kode Pengecualian atau Pengembalian?

92

Pertanyaan saya adalah apa yang paling disukai pengembang untuk penanganan kesalahan, Pengecualian atau Kode Pengembalian Kesalahan. Harap spesifiklah bahasa (atau rumpun bahasa) dan mengapa Anda lebih memilih salah satu daripada yang lain.

Saya menanyakan ini karena penasaran. Secara pribadi saya lebih suka Error Return Codes karena mereka kurang eksplosif dan tidak memaksa kode pengguna untuk membayar penalti kinerja pengecualian jika mereka tidak mau.

update: terima kasih atas semua jawaban! Saya harus mengatakan bahwa meskipun saya tidak suka alur kode yang tidak dapat diprediksi dengan pengecualian. Jawaban tentang kode kembali (dan kakak mereka menangani) memang menambahkan banyak Kebisingan ke kode.

Robert Gould
sumber
1
Masalah ayam atau telur dari dunia perangkat lunak ... bisa diperdebatkan selamanya. :)
Gishu
Mohon maaf tentang itu, tapi semoga memiliki berbagai pendapat akan membantu orang (termasuk saya sendiri) memilih dengan tepat.
Robert Gould

Jawaban:

107

Untuk beberapa bahasa (yaitu C ++) Kebocoran sumber daya seharusnya tidak menjadi alasan

C ++ didasarkan pada RAII.

Jika Anda memiliki kode yang bisa gagal, dikembalikan atau dibuang (yaitu, sebagian besar kode normal), maka Anda harus membungkus penunjuk Anda di dalam penunjuk cerdas (dengan asumsi Anda memiliki alasan yang sangat bagus untuk tidak membuat objek Anda di tumpukan).

Kode pengembalian lebih bertele-tele

Mereka bertele-tele, dan cenderung berkembang menjadi seperti:

if(doSomething())
{
   if(doSomethingElse())
   {
      if(doSomethingElseAgain())
      {
          // etc.
      }
      else
      {
         // react to failure of doSomethingElseAgain
      }
   }
   else
   {
      // react to failure of doSomethingElse
   }
}
else
{
   // react to failure of doSomething
}

Pada akhirnya, kode Anda adalah kumpulan instruksi yang teridentifikasi (saya melihat kode semacam ini dalam kode produksi).

Kode ini dapat dengan baik diterjemahkan menjadi:

try
{
   doSomething() ;
   doSomethingElse() ;
   doSomethingElseAgain() ;
}
catch(const SomethingException & e)
{
   // react to failure of doSomething
}
catch(const SomethingElseException & e)
{
   // react to failure of doSomethingElse
}
catch(const SomethingElseAgainException & e)
{
   // react to failure of doSomethingElseAgain
}

Yang memisahkan kode dan pemrosesan kesalahan dengan rapi, yang bisa menjadi hal yang baik .

Kode pengembalian lebih rapuh

Jika tidak ada peringatan yang tidak jelas dari satu kompilator (lihat komentar "phjr"), mereka dapat dengan mudah diabaikan.

Dengan contoh di atas, asumsikan daripada seseorang lupa menangani kemungkinan kesalahannya (ini terjadi ...). Kesalahan diabaikan saat "dikembalikan", dan mungkin akan meledak nanti (yaitu penunjuk NULL). Masalah yang sama tidak akan terjadi dengan pengecualian.

Kesalahan tidak akan diabaikan. Terkadang, Anda ingin agar tidak meledak ... Jadi, Anda harus memilih dengan hati-hati.

Kode Pengembalian terkadang harus diterjemahkan

Katakanlah kita memiliki fungsi berikut:

  • doSomething, yang bisa mengembalikan int yang disebut NOT_FOUND_ERROR
  • doSomethingElse, yang dapat mengembalikan bool "false" (untuk gagal)
  • doSomethingElseAgain, yang dapat mengembalikan objek Error (dengan __LINE__, __FILE__ dan setengah variabel stack.
  • doTryToDoSomethingWithAllThisMess yang, yah ... Gunakan fungsi di atas, dan kembalikan kode kesalahan jenis ...

Apa jenis kembalinya doTryToDoSomethingWithAllThisMess jika salah satu fungsi yang dipanggilnya gagal?

Kode Pengembalian bukanlah solusi universal

Operator tidak dapat mengembalikan kode kesalahan. Konstruktor C ++ juga tidak bisa.

Return Codes berarti Anda tidak dapat merangkai ekspresi

Akibat wajar dari poin di atas. Bagaimana jika saya ingin menulis:

CMyType o = add(a, multiply(b, c)) ;

Saya tidak bisa, karena nilai pengembalian sudah digunakan (dan terkadang, tidak bisa diubah). Jadi nilai kembali menjadi parameter pertama, dikirim sebagai referensi ... Atau tidak.

Pengecualian diketik

Anda dapat mengirim kelas yang berbeda untuk setiap jenis pengecualian. Pengecualian sumber daya (mis. Kehabisan memori) harus ringan, tetapi hal lain bisa seberat yang diperlukan (saya suka Java Exception memberi saya seluruh tumpukan).

Setiap tangkapan kemudian dapat dikhususkan.

Jangan pernah menggunakan tangkapan (...) tanpa melempar ulang

Biasanya, Anda tidak boleh menyembunyikan kesalahan. Jika Anda tidak membuang kembali, setidaknya, catat kesalahan dalam file, buka kotak pesan, apa pun ...

Pengecualiannya adalah ... NUKE

Masalah dengan pengecualian adalah bahwa menggunakannya secara berlebihan akan menghasilkan kode yang penuh dengan percobaan / tangkapan. Tapi masalahnya ada di tempat lain: Siapa yang mencoba / menangkap kodenya menggunakan kontainer STL? Namun, kontainer tersebut dapat mengirimkan pengecualian.

Tentu saja, di C ++, jangan pernah membiarkan pengecualian keluar dari destruktor.

Pengecualian adalah ... sinkron

Pastikan untuk menangkapnya sebelum mereka mengeluarkan utas Anda, atau menyebar di dalam loop pesan Windows Anda.

Solusinya bisa mencampurkannya?

Jadi saya kira solusinya adalah membuang ketika sesuatu tidak seharusnya terjadi. Dan ketika sesuatu bisa terjadi, maka gunakan kode kembali atau parameter untuk memungkinkan pengguna bereaksi terhadapnya.

Jadi, satu-satunya pertanyaan adalah "apa yang seharusnya tidak terjadi?"

Itu tergantung pada kontrak fungsi Anda. Jika fungsi menerima pointer, tetapi menentukan pointer harus non-NULL, maka boleh saja untuk membuat pengecualian saat pengguna mengirim pointer NULL (pertanyaannya adalah, dalam C ++, ketika pembuat fungsi tidak menggunakan referensi sebagai gantinya petunjuk, tapi ...)

Solusi lain adalah menunjukkan kesalahan

Terkadang, masalah Anda adalah Anda tidak menginginkan kesalahan. Menggunakan pengecualian atau kode pengembalian kesalahan itu keren, tapi ... Anda ingin mengetahuinya.

Dalam pekerjaan saya, kami menggunakan semacam "Assert". Ini akan, tergantung pada nilai file konfigurasi, tidak peduli opsi kompilasi debug / rilis:

  • catat kesalahannya
  • buka kotak pesan dengan "Hei, kamu punya masalah"
  • buka kotak pesan dengan "Hai, Anda punya masalah, apakah Anda ingin men-debug"

Dalam pengembangan dan pengujian, ini memungkinkan pengguna untuk menunjukkan masalah dengan tepat ketika terdeteksi, dan bukan setelahnya (ketika beberapa kode peduli tentang nilai yang dikembalikan, atau di dalam tangkapan).

Mudah untuk ditambahkan ke kode lama. Sebagai contoh:

void doSomething(CMyObject * p, int iRandomData)
{
   // etc.
}

mengarahkan semacam kode yang mirip dengan:

void doSomething(CMyObject * p, int iRandomData)
{
   if(iRandomData < 32)
   {
      MY_RAISE_ERROR("Hey, iRandomData " << iRandomData << " is lesser than 32. Aborting processing") ;
      return ;
   }

   if(p == NULL)
   {
      MY_RAISE_ERROR("Hey, p is NULL !\niRandomData is equal to " << iRandomData << ". Will throw.") ;
      throw std::some_exception() ;
   }

   if(! p.is Ok())
   {
      MY_RAISE_ERROR("Hey, p is NOT Ok!\np is equal to " << p->toString() << ". Will try to continue anyway") ;
   }

   // etc.
}

(Saya memiliki makro serupa yang hanya aktif di debug).

Perhatikan bahwa pada produksi, file konfigurasi tidak ada, jadi klien tidak pernah melihat hasil makro ini ... Tetapi mudah untuk mengaktifkannya bila diperlukan.

Kesimpulan

Ketika Anda membuat kode menggunakan kode kembali, Anda mempersiapkan diri untuk kegagalan, dan berharap benteng pengujian Anda cukup aman.

Ketika Anda membuat kode menggunakan pengecualian, Anda tahu bahwa kode Anda bisa gagal, dan biasanya menempatkan tangkapan balasan di posisi strategis yang dipilih dalam kode Anda. Tapi biasanya, kode Anda lebih banyak tentang "apa yang harus dilakukan" lalu "apa yang saya khawatirkan akan terjadi".

Tetapi ketika Anda membuat kode sama sekali, Anda harus menggunakan alat terbaik yang Anda inginkan, dan terkadang, itu adalah "Jangan pernah menyembunyikan kesalahan, dan tunjukkan secepat mungkin". Makro yang saya bicarakan di atas mengikuti filosofi ini.

paercebal.dll
sumber
3
Ya, TAPI tentang contoh pertama Anda, itu dapat dengan mudah ditulis sebagai: if( !doSomething() ) { puts( "ERROR - doSomething failed" ) ; return ; // or react to failure of doSomething } if( !doSomethingElse() ) { // react to failure of doSomethingElse() }
bobobobo
Mengapa tidak ... Tapi kemudian, saya masih mencari yang doSomething(); doSomethingElse(); ...lebih baik karena jika saya perlu menambahkan if / while / etc. pernyataan untuk tujuan eksekusi normal, saya tidak ingin mereka dicampur dengan if / while / etc. pernyataan ditambahkan untuk tujuan luar biasa ... Dan karena aturan sebenarnya tentang menggunakan pengecualian adalah melempar , bukan menangkap , pernyataan coba / tangkap biasanya tidak invasif.
paercebal
1
Poin pertama Anda menunjukkan apa masalahnya dengan pengecualian. Aliran kendali Anda menjadi aneh dan terpisah dari masalah sebenarnya. Ini menggantikan beberapa tingkat identifikasi dengan serangkaian tangkapan. Saya akan menggunakan keduanya, mengembalikan kode (atau mengembalikan objek dengan informasi berat) untuk kemungkinan kesalahan dan pengecualian untuk hal-hal yang tidak diharapkan .
Peter
2
@ Peter Weber: Ini tidak aneh. Ini terpisah dari masalah sebenarnya karena ini bukan bagian dari aliran eksekusi normal . Ini adalah eksekusi yang luar biasa . Dan kemudian, sekali lagi, poin tentang pengecualian adalah, jika terjadi kesalahan luar biasa , sering-seringlah membuang , dan jarang menangkap , jika pernah. Jadi blok tangkapan jarang sekali muncul dalam kode.
paercebal
Contoh dalam debat semacam ini sangat sederhana. Biasanya, "doSomething ()" atau "doSomethingElse ()" sebenarnya melakukan sesuatu, seperti mengubah status beberapa objek. Kode pengecualian tidak menjamin mengembalikan objek ke keadaan sebelumnya dan bahkan lebih sedikit ketika tangkapan sangat jauh dari lemparan ... Sebagai contoh, bayangkan doSomething dipanggil dua kali, dan tambahkan penghitung sebelum melempar. Bagaimana Anda tahu saat menangkap pengecualian bahwa Anda harus mengurangi satu atau dua kali? Secara umum, menulis kode pengecualian aman untuk apa pun yang bukan contoh mainan sangat sulit (tidak mungkin?).
xryl669
36

Saya menggunakan keduanya sebenarnya.

Saya menggunakan kode pengembalian jika itu diketahui, kemungkinan kesalahan. Jika itu skenario yang saya tahu bisa, dan akan terjadi, maka ada kode yang dikirim kembali.

Pengecualian hanya digunakan untuk hal-hal yang TIDAK SAYA harapkan.

Stephen Wrighton
sumber
1 untuk cara yang sangat sederhana. Atleast. Cukup pendek sehingga saya bisa membacanya dengan sangat cepat. :-)
Ashish Gupta
1
Pengecualian hanya digunakan untuk hal-hal yang TIDAK AKU harapkan. ” Jika Anda tidak mengharapkannya, lalu mengapa atau bagaimana Anda dapat menggunakannya?
anar khalilov
5
Hanya karena saya tidak mengharapkannya terjadi, tidak berarti saya tidak dapat melihat bagaimana hal itu bisa terjadi. Saya berharap SQL Server saya diaktifkan dan merespons. Tapi saya masih mengkodekan harapan saya sehingga saya bisa gagal dengan anggun jika terjadi downtime yang tidak terduga.
Stephen Wrighton
Bukankah SQL Server yang tidak responsif dapat dengan mudah dikategorikan dalam "diketahui, kemungkinan kesalahan"?
tkburbidge
21

Menurut Bab 7 berjudul "Pengecualian" dalam Panduan Desain Kerangka Kerja: Konvensi, Idiom, dan Pola untuk Pustaka .NET yang Dapat Digunakan Kembali , banyak alasan yang diberikan mengapa menggunakan pengecualian atas nilai yang dikembalikan diperlukan untuk kerangka kerja OO seperti C #.

Mungkin inilah alasan yang paling kuat (halaman 179):

"Pengecualian terintegrasi dengan baik dengan bahasa berorientasi objek. Bahasa berorientasi objek cenderung memberlakukan batasan pada tanda tangan anggota yang tidak dikenakan oleh fungsi dalam bahasa non-OO. Misalnya, dalam kasus konstruktor, operator kelebihan beban, dan properti, pengembang tidak memiliki pilihan dalam nilai pengembalian. Oleh karena itu, tidak mungkin untuk menstandarisasi pelaporan kesalahan berbasis nilai-kembali untuk kerangka kerja berorientasi objek. Metode pelaporan kesalahan, seperti pengecualian, yang berada di luar jalur tanda tangan metode adalah satu-satunya pilihan. "

hutan kecil
sumber
10

Preferensi saya (dalam C ++ dan Python) adalah menggunakan pengecualian. Fasilitas yang disediakan bahasa membuatnya menjadi proses yang terdefinisi dengan baik untuk menaikkan, menangkap, dan (jika perlu) membuang kembali pengecualian, membuat model mudah dilihat dan digunakan. Secara konseptual, ini lebih bersih daripada kode yang dikembalikan, dalam pengecualian spesifik itu dapat ditentukan oleh namanya, dan memiliki informasi tambahan yang menyertainya. Dengan kode pengembalian, Anda dibatasi hanya pada nilai kesalahan (kecuali Anda ingin mendefinisikan objek ReturnStatus atau sesuatu).

Kecuali kode yang Anda tulis sangat menentukan waktu, overhead yang terkait dengan pelepasan tumpukan tidak cukup signifikan untuk dikhawatirkan.

Jason Etheridge
sumber
2
Ingatlah bahwa menggunakan pengecualian membuat analisis program lebih sulit.
Paweł Hajdan
7

Pengecualian hanya boleh dikembalikan jika terjadi sesuatu yang tidak Anda duga.

Titik pengecualian lainnya, secara historis, adalah bahwa kode yang dikembalikan secara inheren merupakan hak milik, terkadang 0 dapat dikembalikan dari fungsi C untuk menunjukkan keberhasilan, terkadang -1, atau salah satu dari mereka untuk gagal dengan 1 untuk kesuksesan. Bahkan ketika mereka dicacah, pencacahan bisa menjadi ambigu.

Pengecualian juga dapat memberikan lebih banyak informasi, dan secara khusus menguraikan dengan baik 'Sesuatu yang Salah, inilah yang, pelacakan tumpukan dan beberapa informasi pendukung untuk konteks'

Karena itu, kode pengembalian yang disebutkan dengan baik dapat berguna untuk serangkaian hasil yang diketahui, 'berikut ini hasil dari fungsi, dan berjalan seperti ini'

johnc
sumber
6

Di Java, saya menggunakan (dengan urutan berikut):

  1. Desain demi kontrak (memastikan prasyarat terpenuhi sebelum mencoba apa pun yang mungkin gagal). Ini menangkap banyak hal dan saya mengembalikan kode kesalahan untuk ini.

  2. Mengembalikan kode kesalahan saat memproses pekerjaan (dan melakukan rollback jika diperlukan).

  3. Pengecualian, tetapi ini hanya digunakan untuk hal-hal yang tidak terduga.

paxdiablo
sumber
1
Bukankah lebih tepat menggunakan pernyataan untuk kontrak? Jika kontrak rusak, tidak ada yang bisa menyelamatkan Anda.
Paweł Hajdan
@ PawełHajdan, pernyataan dinonaktifkan secara default, saya yakin. Ini memiliki masalah yang sama dengan C assertyang tidak akan menemukan masalah dalam kode produksi kecuali Anda menjalankan dengan pernyataan di sepanjang waktu. Saya cenderung melihat pernyataan sebagai cara untuk menangkap masalah selama pengembangan, tetapi hanya untuk hal-hal yang akan menegaskan atau tidak menegaskan secara konsisten (seperti hal-hal dengan konstanta, bukan hal-hal dengan variabel atau apa pun yang dapat berubah saat runtime).
paxdiablo
Dan dua belas tahun untuk menjawab pertanyaan Anda. Saya harus menjalankan meja bantuan :-)
paxdiablo
6

Kode yang dikembalikan gagal dalam tes " Lubang Kesuksesan " bagi saya hampir setiap saat.

  • Terlalu mudah untuk lupa memeriksa kode pengembalian dan kemudian mengalami kesalahan besar di kemudian hari.
  • Kode pengembalian tidak memiliki informasi debugging yang bagus seperti tumpukan panggilan, pengecualian dalam.
  • Kode pengembalian tidak menyebar yang, bersama dengan poin di atas, cenderung mendorong pencatatan diagnostik yang berlebihan dan terjalin alih-alih pencatatan di satu tempat terpusat (penangan pengecualian tingkat aplikasi dan utas).
  • Kode yang dikembalikan cenderung mendorong kode yang berantakan dalam bentuk blok 'if' bersarang
  • Waktu pengembang dihabiskan untuk men-debug masalah yang tidak diketahui yang seharusnya menjadi pengecualian yang jelas (lubang kesuksesan) IS mahal.
  • Jika tim di belakang C # tidak bermaksud untuk pengecualian untuk mengatur aliran kontrol, eksekusi tidak akan diketik, tidak akan ada filter "when" pada pernyataan catch, dan tidak perlu pernyataan 'throw' tanpa parameter .

Mengenai Kinerja:

  • Pengecualian mungkin mahal secara komputasi RELATIF untuk tidak melempar sama sekali, tetapi mereka disebut PENGECUALIAN karena suatu alasan. Perbandingan kecepatan selalu berhasil mengasumsikan tingkat pengecualian 100% yang seharusnya tidak demikian. Bahkan jika pengecualian 100x lebih lambat, seberapa penting hal itu jika hanya terjadi 1% setiap saat?
  • Kecuali kita berbicara aritmatika floating point untuk aplikasi grafik atau yang serupa, siklus CPU lebih murah dibandingkan dengan waktu pengembang.
  • Biaya dari perspektif waktu membawa argumen yang sama. Sehubungan dengan kueri database atau panggilan layanan web atau pemuatan file, waktu aplikasi normal akan mengurangi waktu pengecualian. Pengecualian hampir di bawah MIKROdetik di tahun 2006
    • Saya menantang siapa pun yang bekerja di .net, untuk mengatur debugger Anda untuk merusak semua pengecualian dan menonaktifkan hanya kode saya dan melihat berapa banyak pengecualian yang sudah terjadi yang bahkan tidak Anda ketahui.
b_levitt
sumber
4

Sebuah nasihat hebat yang saya dapatkan dari The Pragmatic Programmer adalah sesuatu yang sejalan dengan "program Anda harus dapat melakukan semua fungsi utamanya tanpa menggunakan pengecualian sama sekali".

Smashery
sumber
4
Anda salah menafsirkannya. Yang mereka maksud adalah "jika program Anda memberikan pengecualian dalam alur normalnya, itu salah". Dengan kata lain "hanya gunakan pengecualian untuk hal-hal luar biasa".
Paweł Hajdan
4

Saya menulis posting blog tentang ini beberapa waktu yang lalu.

Overhead kinerja dari melakukan pengecualian seharusnya tidak memainkan peran apa pun dalam keputusan Anda. Jika Anda melakukannya dengan benar, bagaimanapun juga, pengecualian adalah pengecualian .

Thomas
sumber
Posting blog yang ditautkan lebih banyak tentang melihat sebelum Anda melompat (memeriksa) vs. lebih mudah meminta maaf daripada izin (pengecualian atau kode pengembalian). Saya telah menjawab di sana dengan pemikiran saya tentang masalah itu (petunjuk: TOCTTOU). Tetapi pertanyaan ini tentang masalah yang berbeda, yaitu dalam kondisi apa menggunakan mekanisme pengecualian bahasa daripada mengembalikan nilai dengan properti khusus.
Damian Yerrick
Saya sangat setuju. Sepertinya saya telah belajar satu atau dua hal dalam sembilan tahun terakhir;)
Thomas
4

Saya tidak suka kode balik karena menyebabkan pola berikut menjamur di seluruh kode Anda

CRetType obReturn = CODE_SUCCESS;
obReturn = CallMyFunctionWhichReturnsCodes();
if (obReturn == CODE_BLOW_UP)
{
  // bail out
  goto FunctionExit;
}

Segera pemanggilan metode yang terdiri dari 4 pemanggilan fungsi membengkak dengan 12 baris penanganan kesalahan .. Beberapa di antaranya tidak akan pernah terjadi. Jika dan kasus sakelar berlimpah.

Pengecualian lebih bersih jika Anda menggunakannya dengan baik ... untuk menandakan peristiwa luar biasa .. setelah itu jalur eksekusi tidak dapat dilanjutkan. Mereka seringkali lebih deskriptif dan informasi daripada kode kesalahan.

Jika Anda memiliki beberapa status setelah panggilan metode yang harus ditangani secara berbeda (dan bukan kasus luar biasa), gunakan kode kesalahan atau parameter keluar. Meskipun Personaly, saya menganggap ini langka ..

Saya telah mencari sedikit tentang argumen 'penalti kinerja' .. lebih banyak di dunia C ++ / COM tetapi dalam bahasa yang lebih baru, saya pikir perbedaannya tidak terlalu banyak. Bagaimanapun, ketika sesuatu meledak, masalah kinerja dialihkan ke backburner :)

Gishu
sumber
3

Dengan pengecualian lingkungan runtime atau compiler yang layak, tidak ada hukuman yang signifikan. Ini kurang lebih seperti pernyataan GOTO yang melompat ke penangan pengecualian. Selain itu, memiliki pengecualian yang ditangkap oleh lingkungan runtime (seperti JVM) membantu mengisolasi dan memperbaiki bug jauh lebih mudah. Saya akan menggunakan NullPointerException di Java daripada segfault di C setiap hari.

Kyle Cronin
sumber
2
Pengecualian sangat mahal. Mereka harus berjalan di tumpukan untuk menemukan penangan pengecualian potensial. Jalan tumpukan ini tidak murah. Jika pelacakan tumpukan dibuat, biayanya bahkan lebih mahal, karena seluruh tumpukan harus diurai.
Taman Derek
Saya terkejut bahwa kompiler tidak dapat menentukan di mana pengecualian akan ditangkap setidaknya untuk beberapa waktu. Selain itu, fakta bahwa pengecualian mengubah aliran kode membuatnya lebih mudah untuk mengidentifikasi secara tepat di mana kesalahan terjadi, menurut pendapat saya, menggantikan hukuman kinerja.
Kyle Cronin
Tumpukan panggilan bisa menjadi sangat kompleks pada waktu proses, dan kompiler umumnya tidak melakukan analisis semacam itu. Bahkan jika mereka melakukannya, Anda masih harus berjalan di tumpukan untuk mendapatkan jejak. Anda juga masih harus melepas tumpukan untuk menangani finallyblok dan penghancur untuk objek yang dialokasikan oleh tumpukan.
Derek Park
Saya setuju bahwa manfaat debugging dari pengecualian sering kali menutupi biaya kinerja.
Derek Park
1
Taman Derak, pengecualian mahal bila terjadi. Inilah alasan mengapa mereka tidak boleh digunakan secara berlebihan. Tetapi jika itu tidak terjadi, hampir tidak ada biaya.
paercebal
3

Saya memiliki seperangkat aturan sederhana:

1) Gunakan kode balasan untuk hal-hal yang Anda harapkan akan ditanggapi oleh penelepon langsung Anda.

2) Gunakan pengecualian untuk kesalahan yang lebih luas cakupannya, dan mungkin wajar diharapkan untuk ditangani oleh sesuatu yang banyak tingkat di atas pemanggil sehingga kesadaran kesalahan tidak harus meresap melalui banyak lapisan, membuat kode lebih kompleks.

Di Java saya hanya pernah menggunakan pengecualian yang tidak dicentang, pengecualian yang dicentang akhirnya hanya menjadi bentuk lain dari kode pengembalian dan menurut pengalaman saya dualitas dari apa yang mungkin "dikembalikan" oleh panggilan metode umumnya lebih merupakan penghalang daripada bantuan.

Kendall Helmstetter Gelner
sumber
3

Saya menggunakan Pengecualian di python baik dalam keadaan Luar Biasa, maupun non-Luar Biasa.

Seringkali menyenangkan dapat menggunakan Exception untuk menunjukkan "permintaan tidak dapat dilakukan", sebagai lawan mengembalikan nilai Error. Ini berarti Anda / selalu / tahu bahwa nilai yang dikembalikan adalah tipe yang benar, bukan secara arbitarily None atau NotFoundSingleton atau semacamnya. Berikut adalah contoh bagus di mana saya lebih suka menggunakan penangan pengecualian daripada kondisional pada nilai yang dikembalikan.

try:
    dataobj = datastore.fetch(obj_id)
except LookupError:
    # could not find object, create it.
    dataobj = datastore.create(....)

Efek sampingnya adalah ketika datastore.fetch (obj_id) dijalankan, Anda tidak perlu memeriksa apakah nilai kembaliannya adalah None, Anda langsung mendapatkan kesalahan itu secara gratis. Ini berlawanan dengan argumen, "program Anda harus dapat menjalankan semua fungsi utamanya tanpa menggunakan pengecualian sama sekali".

Berikut adalah contoh lain di mana pengecualian 'sangat berguna', untuk menulis kode untuk menangani sistem file yang tidak tunduk pada kondisi balapan.

# wrong way:
if os.path.exists(directory_to_remove):
    # race condition is here.
    os.path.rmdir(directory_to_remove)

# right way:
try: 
    os.path.rmdir(directory_to_remove)
except OSError:
    # directory didn't exist, good.
    pass

Satu panggilan sistem, bukan dua, tidak ada kondisi balapan. Ini adalah contoh yang buruk karena jelas ini akan gagal dengan OSError dalam lebih banyak keadaan daripada direktori yang tidak ada, tetapi ini adalah solusi yang 'cukup baik' untuk banyak situasi yang dikontrol dengan ketat.

Jerub
sumber
Contoh kedua menyesatkan. Cara yang salah seharusnya salah karena kode os.path.rmdir dirancang untuk membuang pengecualian. Implementasi yang benar dalam aliran kode balik akan menjadi 'if rmdir (...) == FAILED: pass'
MaR
3

Saya percaya kode kembali menambah kebisingan kode. Misalnya, saya selalu membenci tampilan kode COM / ATL karena kode yang dikembalikan. Harus ada pemeriksaan HRESULT untuk setiap baris kode. Saya menganggap kode pengembalian kesalahan adalah salah satu keputusan buruk yang dibuat oleh arsitek COM. Ini membuat sulit untuk melakukan pengelompokan kode secara logis, sehingga peninjauan kode menjadi sulit.

Saya tidak yakin tentang perbandingan kinerja ketika ada pemeriksaan eksplisit untuk kode pengembalian setiap baris.

rpattabi.dll
sumber
1
Gumpalan COM dirancang untuk dapat digunakan oleh bahasa yang tidak mendukung pengecualian.
Kevin
Itu poin yang bagus. Masuk akal untuk menangani kode kesalahan untuk bahasa skrip. Setidaknya VB6 menyembunyikan detail kode kesalahan dengan baik dengan merangkumnya dalam objek Err, yang agak membantu dalam kode yang lebih bersih.
rpattabi
Saya tidak setuju: VB6 hanya mencatat kesalahan terakhir. Dikombinasikan dengan "pada resume kesalahan berikutnya" yang terkenal, Anda akan kehilangan sumber masalah Anda sama sekali, pada saat Anda melihatnya. Perhatikan bahwa ini adalah dasar penanganan kesalahan di Win32 APII (lihat fungsi GetLastError)
paercebal
2

Saya lebih suka menggunakan pengecualian untuk penanganan kesalahan dan mengembalikan nilai (atau parameter) sebagai hasil normal dari suatu fungsi. Ini memberikan skema penanganan kesalahan yang mudah dan konsisten dan jika dilakukan dengan benar itu membuat kode tampak lebih bersih.

Trent
sumber
2

Salah satu perbedaan besar adalah bahwa pengecualian memaksa Anda untuk menangani kesalahan, sedangkan kode pengembalian kesalahan bisa tidak dicentang.

Kesalahan mengembalikan kode, jika sering digunakan, juga dapat menyebabkan kode yang sangat jelek dengan banyak tes if yang mirip dengan formulir ini:

if(function(call) != ERROR_CODE) {
    do_right_thing();
}
else {
    handle_error();
}

Secara pribadi saya lebih suka menggunakan pengecualian untuk kesalahan yang HARUS atau HARUS ditindaklanjuti oleh kode panggilan, dan hanya menggunakan kode kesalahan untuk "kegagalan yang diharapkan" di mana mengembalikan sesuatu benar-benar valid dan mungkin.

Daniel Bruce
sumber
Setidaknya di C / C ++ dan gcc Anda dapat memberikan fungsi atribut yang akan menghasilkan peringatan ketika nilai kembaliannya diabaikan.
Paweł Hajdan
phjr: Meskipun saya tidak setuju dengan pola "return error code", komentar Anda mungkin harus menjadi jawaban lengkap. Saya merasa cukup menarik. Setidaknya, itu memberi saya informasi yang berguna.
paercebal
2

Ada banyak alasan untuk memilih Pengecualian daripada kode pengembalian:

  • Biasanya, untuk keterbacaan, orang mencoba meminimalkan jumlah pernyataan pengembalian dalam suatu metode. Dengan melakukan itu, pengecualian mencegah untuk melakukan beberapa pekerjaan tambahan saat dalam keadaan tidak terkoreksi, dan dengan demikian mencegah untuk berpotensi merusak lebih banyak data.
  • Pengecualian umumnya lebih bertele-tele dan lebih mudah dikembangkan daripada nilai pengembalian. Asumsikan bahwa metode mengembalikan bilangan asli dan Anda menggunakan bilangan negatif sebagai kode pengembalian ketika terjadi kesalahan, jika lingkup metode Anda berubah dan sekarang mengembalikan bilangan bulat, Anda harus mengubah semua pemanggilan metode alih-alih hanya mengubah sedikit pengecualian.
  • Pengecualian memungkinkan lebih mudah untuk memisahkan penanganan kesalahan dari perilaku normal. Mereka memungkinkan untuk memastikan bahwa beberapa operasi berfungsi sebagai operasi atom.
alat
sumber
2

Pengecualian bukan untuk penanganan kesalahan, IMO. Pengecualian hanya itu; peristiwa luar biasa yang tidak Anda duga. Gunakan dengan hati-hati kataku.

Kode kesalahan bisa saja OK, tetapi mengembalikan 404 atau 200 dari metode itu buruk, IMO. Gunakan enums (.Net) sebagai gantinya, yang membuat kode lebih mudah dibaca dan lebih mudah digunakan untuk pengembang lain. Anda juga tidak perlu mempertahankan tabel atas angka dan deskripsi.

Juga; pola coba-tangkap-akhirnya adalah anti-pola dalam buku saya. Coba-akhirnya bisa bagus, coba-tangkap juga bisa bagus tapi coba-tangkap-akhirnya tidak pernah bagus. try-akhirnya sering kali dapat diganti dengan pernyataan "using" (IDispose pattern), yang merupakan IMO yang lebih baik. Dan Coba-tangkap di mana Anda benar-benar menangkap pengecualian yang dapat Anda tangani itu baik, atau jika Anda melakukan ini:

try{
    db.UpdateAll(somevalue);
}
catch (Exception ex) {
    logger.Exception(ex, "UpdateAll method failed");
    throw;
}

Jadi selama Anda membiarkan pengecualian terus menggelembung, tidak apa-apa. Contoh lainnya adalah ini:

try{
    dbHasBeenUpdated = db.UpdateAll(somevalue); // true/false
}
catch (ConnectionException ex) {
    logger.Exception(ex, "Connection failed");
    dbHasBeenUpdated = false;
}

Di sini saya benar-benar menangani pengecualian; apa yang saya lakukan di luar uji coba ketika metode pembaruan gagal adalah cerita lain, tetapi saya pikir maksud saya telah dibuat. :)

Mengapa kemudian coba-tangkap-akhirnya menjadi anti-pola? Inilah alasannya:

try{
    db.UpdateAll(somevalue);
}
catch (Exception ex) {
    logger.Exception(ex, "UpdateAll method failed");
    throw;
}
finally {
    db.Close();
}

Apa yang terjadi jika objek db telah ditutup? Pengecualian baru dilemparkan dan itu harus ditangani! Ini lebih baik:

try{
    using(IDatabase db = DatabaseFactory.CreateDatabase()) {
        db.UpdateAll(somevalue);
    }
}
catch (Exception ex) {
    logger.Exception(ex, "UpdateAll method failed");
    throw;
}

Atau, jika objek db tidak mengimplementasikan IDisposable lakukan ini:

try{
    try {
        IDatabase db = DatabaseFactory.CreateDatabase();
        db.UpdateAll(somevalue);
    }
    finally{
        db.Close();
    }
}
catch (DatabaseAlreadyClosedException dbClosedEx) {
    logger.Exception(dbClosedEx, "Database connection was closed already.");
}
catch (Exception ex) {
    logger.Exception(ex, "UpdateAll method failed");
    throw;
}

Itu juga 2 sen saya! :)

noosit
sumber
Akan aneh jika sebuah objek memiliki .Close () tetapi tidak memiliki .Dispose ()
abatishchev
Saya hanya menggunakan Close () sebagai contoh. Jangan ragu untuk menganggapnya sebagai hal lain. Seperti yang saya nyatakan; pola penggunaan harus digunakan (doh!) jika tersedia. Ini tentu saja menyiratkan bahwa kelas mengimplementasikan IDisposable dan karena itu Anda dapat memanggil Dispose.
noocyte
1

Saya hanya menggunakan pengecualian, tidak ada kode pengembalian. Saya berbicara tentang Java di sini.

Aturan umum yang saya ikuti adalah jika saya memiliki metode yang dipanggil doFoo()maka diikuti bahwa jika tidak "melakukan foo", sebagaimana adanya, maka sesuatu yang luar biasa telah terjadi dan Exception harus dilemparkan.

SCdF
sumber
1

Satu hal yang saya takuti tentang pengecualian adalah bahwa memberikan pengecualian akan mengacaukan aliran kode. Misalnya jika Anda melakukannya

void foo()
{
  MyPointer* p = NULL;
  try{
    p = new PointedStuff();
    //I'm a module user and  I'm doing stuff that might throw or not

  }
  catch(...)
  {
    //should I delete the pointer?
  }
}

Atau lebih buruk lagi bagaimana jika saya menghapus sesuatu yang seharusnya tidak saya miliki, tetapi terlempar sebelum saya melakukan pembersihan selanjutnya. Melempar memberi banyak beban pada IMHO pengguna yang buruk.

Robert Gould
sumber
Untuk itulah pernyataan <code> akhirnya </code>. Tapi, sayangnya, ini tidak dalam standar C ++ ...
Thomas
Di C ++ Anda harus tetap berpegang pada aturan praktis "Dapatkan sumber daya di motor konstruksi dan lepaskan di destrucotor. Untuk kasus khusus ini auto_ptr akan bekerja dengan sempurna.
Serge
Thomas, Anda salah. C ++ belum akhirnya karena tidak membutuhkannya. Ia memiliki RAII sebagai gantinya. Solusi dari Serge adalah salah satu solusi dengan menggunakan RAII.
paercebal
Robert, gunakan solusi Serge, dan masalah Anda akan hilang. Sekarang, jika Anda menulis lebih banyak coba / tangkap daripada lemparan, maka (dengan menilai komentar Anda) mungkin Anda memiliki masalah dalam kode Anda. Tentu saja, menggunakan tangkap (...) tanpa lemparan ulang biasanya buruk, karena menyembunyikan kesalahan agar lebih baik mengabaikannya.
paercebal
1

Aturan umum saya dalam argumen pengecualian vs. kode pengembalian:

  • Gunakan kode kesalahan saat Anda memerlukan lokalisasi / internasionalisasi - di .NET, Anda dapat menggunakan kode kesalahan ini untuk merujuk file sumber daya yang kemudian akan menampilkan kesalahan dalam bahasa yang sesuai. Jika tidak, gunakan pengecualian
  • Gunakan pengecualian hanya untuk kesalahan yang benar - benar luar biasa. Jika itu adalah sesuatu yang cukup sering terjadi, gunakan boolean atau kode kesalahan enum.
Jon Limjap
sumber
Tidak ada alasan Anda tidak dapat menggunakan pengecualian saat Anda menggunakan l10n / i18n. Pengecualian juga dapat berisi informasi yang dilokalkan.
alat
1

Saya tidak menemukan kode pengembalian kurang jelek dari pada pengecualian. Dengan pengecualian, Anda memiliki try{} catch() {} finally {}tempat seperti dengan kode pengembalian yang Anda miliki if(){}. Saya dulu takut akan pengecualian karena alasan yang diberikan di pos; Anda tidak tahu apakah penunjuk perlu dibersihkan, apa yang Anda miliki. Tapi saya pikir Anda memiliki masalah yang sama dalam hal kode pengembalian. Anda tidak mengetahui status parameter kecuali Anda mengetahui beberapa detail tentang fungsi / metode yang dimaksud.

Terlepas dari itu, Anda harus menangani kesalahan jika memungkinkan. Anda dapat dengan mudah membiarkan pengecualian menyebar ke tingkat atas seperti mengabaikan kode yang dikembalikan dan membiarkan program segfault.

Saya menyukai gagasan mengembalikan nilai (pencacahan?) Untuk hasil dan pengecualian untuk kasus luar biasa.

Jonathan Adelson
sumber
0

Untuk bahasa seperti Java, saya akan menggunakan Exception karena kompilator memberikan kesalahan waktu kompilasi jika pengecualian tidak ditangani. Ini memaksa fungsi pemanggil untuk menangani / membuang pengecualian.

Untuk Python, saya lebih bingung. Tidak ada kompilator sehingga pemanggil mungkin tidak menangani pengecualian yang dilemparkan oleh fungsi yang mengarah ke pengecualian waktu proses. Jika Anda menggunakan kode pengembalian, Anda mungkin memiliki perilaku yang tidak terduga jika tidak ditangani dengan benar dan jika Anda menggunakan pengecualian, Anda mungkin mendapatkan pengecualian waktu proses.

2ank3
sumber
0

Saya biasanya lebih suka kode pengembalian karena mereka membiarkan pemanggil memutuskan apakah kegagalannya luar biasa .

Pendekatan ini tipikal dalam bahasa Elixir.

# I care whether this succeeds. If it doesn't return :ok, raise an exception.
:ok = File.write(path, content)

# I don't care whether this succeeds. Don't check the return value.
File.write(path, content)

# This had better not succeed - the path should be read-only to me.
# If I get anything other than this error, raise an exception.
{:error, :erofs} = File.write(path, content)

# I want this to succeed but I can handle its failure
case File.write(path, content) do
  :ok => handle_success()
  error => handle_error(error)
end

Orang-orang menyebutkan bahwa kode yang dikembalikan dapat menyebabkan Anda memiliki banyak ifpernyataan bersarang , tetapi itu dapat ditangani dengan sintaks yang lebih baik. Di Elixir, withpernyataan tersebut memungkinkan kita dengan mudah memisahkan serangkaian nilai kembalian jalur bahagia dari kegagalan apa pun.

with {:ok, content} <- get_content(),
  :ok <- File.write(path, content) do
    IO.puts "everything worked, happy path code goes here"
else
  # Here we can use a single catch-all failure clause
  # or match every kind of failure individually
  # or match subsets of them however we like
  _some_error => IO.puts "one of those steps failed"
  _other_error => IO.puts "one of those steps failed"
end

Elixir masih memiliki fungsi yang memunculkan pengecualian. Kembali ke contoh pertama saya, saya dapat melakukan salah satu dari ini untuk mengajukan pengecualian jika file tidak dapat ditulis.

# Raises a generic MatchError because the return value isn't :ok
:ok = File.write(path, content)

# Raises a File.Error with a descriptive error message - eg, saying
# that the file is read-only
File.write!(path, content)

Jika saya, sebagai penelepon, tahu bahwa saya ingin memunculkan kesalahan jika penulisan gagal, saya dapat memilih untuk menelepon File.write!daripada File.write. Atau saya dapat memilih untuk menelepon File.writedan menangani setiap kemungkinan alasan kegagalan secara berbeda.

Tentu saja selalu mungkin untuk rescuepengecualian jika kita mau. Tetapi dibandingkan dengan menangani nilai pengembalian yang informatif, rasanya canggung bagi saya. Jika saya tahu bahwa panggilan fungsi bisa gagal atau bahkan harus gagal, kegagalannya bukanlah kasus yang luar biasa.

Nathan Long
sumber