Cara menulis pesan pengecualian yang baik

101

Saat ini saya sedang melakukan tinjauan kode dan salah satu hal yang saya perhatikan adalah jumlah pengecualian di mana pesan pengecualian sepertinya mengulangi di mana pengecualian terjadi. misalnya

throw new Exception("BulletListControl: CreateChildControls failed.");

Semua tiga item dalam pesan ini saya dapat bekerja dari sisa pengecualian. Saya tahu kelas dan metode dari jejak stack dan saya tahu itu gagal (karena saya punya pengecualian).

Itu membuat saya berpikir tentang pesan apa yang saya masukkan dalam pesan pengecualian. Pertama saya membuat kelas pengecualian, jika seseorang belum ada, untuk alasan umum (misalnya PropertyNotFoundException- alasannya ), dan kemudian ketika saya melemparkannya pesan menunjukkan apa yang salah (mis. "Tidak dapat menemukan properti 'IDontExist' pada Node 1234 "- apa ). Di mana ada di StackTrace. The ketika mungkin berakhir dalam log (jika ada). The bagaimana bagi pengembang untuk bekerja (dan memperbaiki)

Apakah Anda punya kiat lain untuk melempar pengecualian? Khususnya berkaitan dengan pembuatan jenis baru dan pesan pengecualian.

Colin Mackay
sumber
4
Apakah ini untuk file log atau untuk disajikan kepada pengguna?
Jon Hopkins
5
Hanya untuk debugging. Mereka mungkin berakhir di log. Mereka tidak akan disajikan kepada pengguna. Saya bukan penggemar menyajikan pesan pengecualian kepada pengguna.
Colin Mackay

Jawaban:

72

Saya akan mengarahkan jawaban saya lebih lanjut ke apa yang muncul setelah pengecualian: apa manfaatnya dan bagaimana seharusnya peranti lunak berperilaku, apa yang harus dilakukan pengguna Anda dengan pengecualian? Teknik hebat yang saya temui di awal karier saya adalah selalu melaporkan masalah dan kesalahan dalam 3 bagian: konteks, masalah & solusi. Menggunakan disiplin ini mengubah penanganan kesalahan sangat besar dan membuat perangkat lunak jauh lebih baik bagi operator untuk digunakan.

Berikut beberapa contohnya.

Context: Saving connection pooling configuration changes to disk.
Problem: Write permission denied on file '/xxx/yyy'.
Solution: Grant write permission to the file.

Dalam hal ini, operator tahu persis apa yang harus dilakukan dan ke file mana yang harus terpengaruh. Mereka juga tahu bahwa perubahan pooling koneksi tidak mengambil dan harus diulang.

Context: Sending email to '[email protected]' regarding 'Blah'.
Problem: SMTP connection refused by server 'mail.xyz.com'.
Solution: Contact the mail server administrator to report a service problem.  The email will be sent later. You may want to tell '[email protected]' about this problem.

Saya menulis sistem sisi server dan operator saya umumnya mendukung lini pertama yang mengerti teknologi. Saya akan menulis pesan secara berbeda untuk perangkat lunak desktop yang memiliki audiens yang berbeda tetapi menyertakan informasi yang sama.

Beberapa hal indah terjadi jika seseorang menggunakan teknik ini. Pengembang perangkat lunak sering kali berada di posisi terbaik untuk mengetahui bagaimana menyelesaikan masalah dalam kode mereka sendiri sehingga solusi enkode dengan cara ini saat Anda menulis kode adalah manfaat besar bagi pengguna akhir yang berada pada solusi pencarian yang kurang menguntungkan karena mereka sering kehilangan informasi tentang apa sebenarnya yang dilakukan perangkat lunak. Siapa pun yang pernah membaca pesan kesalahan Oracle akan tahu apa yang saya maksud.

Hal luar biasa kedua yang terlintas dalam pikiran adalah ketika Anda menemukan diri Anda mencoba menggambarkan solusi dalam pengecualian Anda dan Anda sedang menulis "Periksa X dan jika A maka B lagi C". Ini adalah tanda yang sangat jelas dan jelas bahwa pengecualian Anda sedang diperiksa di tempat yang salah. Anda programmer memiliki kapasitas untuk membandingkan hal-hal dalam kode sehingga "jika" pernyataan harus dijalankan dalam kode, mengapa melibatkan pengguna dalam sesuatu yang dapat diotomatisasi? Kemungkinannya adalah dari kode yang lebih dalam dan seseorang telah melakukan hal yang malas dan melempar IOException dari sejumlah metode dan menangkap potensi kesalahan dari semuanya dalam blok kode panggilan yang tidak dapat secara memadai menggambarkan apa yang salah, apa yang spesifikkonteksnya dan bagaimana cara memperbaikinya. Ini mendorong Anda untuk menulis kesalahan butir yang lebih halus, menangkap dan menanganinya di tempat yang tepat dalam kode Anda sehingga Anda dapat mengartikulasikan dengan benar langkah-langkah yang harus diambil operator.

Di satu perusahaan, kami memiliki operator terbaik yang mengenal perangkat lunak dengan sangat baik dan menyimpan "buku bacaan" mereka sendiri yang menambah pelaporan kesalahan kami dan menyarankan solusi. Untuk mengenali hal ini, perangkat lunak mulai menyertakan tautan wiki ke run book dalam pengecualian sehingga tersedia penjelasan dasar serta tautan ke diskusi dan pengamatan yang lebih maju oleh operator dari waktu ke waktu.

Jika Anda memiliki disiplin untuk mencoba teknik ini, akan jauh lebih jelas apa yang harus Anda beri nama pengecualian Anda dalam kode saat membuat Anda sendiri. NonRecoverableConfigurationReadFailedException menjadi sedikit singkatan untuk apa yang akan Anda gambarkan lebih lengkap kepada operator. Saya suka menjadi verbose dan saya pikir itu akan lebih mudah bagi pengembang berikutnya yang menyentuh kode saya untuk menafsirkan.

Pak Wobin
sumber
1
+1 Ini adalah sistem yang baik. Mana yang lebih penting: Memastikan informasi menyebar, atau menggunakan kata-kata pendek?
Michael K
3
+1 untuk saya suka solusi termasuk, konteks, masalah, solusi
WebDev
1
Teknik ini sangat membantu. Saya pasti akan menggunakannya.
Kid Diamond
Konteks adalah data yang tidak perlu. Ini sudah ada di tumpukan jejak. Memiliki solusi lebih disukai tetapi tidak selalu memungkinkan / bermanfaat. Sebagian besar masalah adalah jenis aplikasi berhenti anggun atau mengabaikan operasi yang tertunda dan kembali ke loop eksekusi utama aplikasi berharap bahwa lain kali Anda berhasil ... Nama kelas pengecualian harus sedemikian rupa sehingga solusi akan menjadi jelas, untuk FileNotFoundatau ConnectExceptionAnda tahu apa yang harus dilakukan))
gavenkoa
2
@ThomasFlinkow Dalam contoh jejak Anda akan memiliki init (), jalankan () dan pembersihan () di tumpukan jejak. Dengan skema penamaan yang baik di perpustakaan dan API bersih / dapat dimengerti, Anda tidak perlu penjelasan string. Dan gagal dengan cepat, jangan membawa kondisi rusak di seluruh sistem. Jejak dan catatan pendataan dengan ID unik dapat menjelaskan aliran / status aplikasi.
gavenkoa
23

Dalam pertanyaan yang lebih baru ini saya menyatakan bahwa pengecualian tidak boleh mengandung pesan sama sekali. Menurut pendapat saya, fakta yang mereka lakukan adalah kesalahpahaman yang sangat besar. Yang saya usulkan adalah itu

"Pesan" dari pengecualian adalah nama kelas (yang memenuhi syarat) pengecualian.

Pengecualian harus mengandung dalam variabel anggotanya sendiri sebanyak mungkin detail tentang apa yang terjadi; misalnya, IndexOutOfRangeExceptionharus berisi nilai indeks yang ditemukan tidak valid, serta nilai-nilai atas dan bawah yang berlaku pada saat pengecualian dilemparkan. Dengan cara ini, menggunakan refleksi Anda dapat memiliki pesan yang dibuat secara otomatis yang berbunyi seperti ini: IndexOutOfRangeException: index = -1; min=0; max=5dan ini, bersama dengan jejak tumpukan, harus menjadi semua informasi objektif yang Anda butuhkan untuk memecahkan masalah. Memformatnya menjadi pesan cantik seperti "index -1 tidak antara 0 dan 5" tidak menambah nilai apa pun.

Dalam contoh khusus Anda, NodePropertyNotFoundExceptionkelas akan berisi nama properti yang tidak ditemukan, dan referensi ke simpul yang tidak mengandung properti. Hal ini penting: harus tidak berisi nama node; itu harus berisi referensi ke simpul yang sebenarnya. Dalam kasus khusus Anda ini mungkin tidak perlu, tetapi ini adalah masalah prinsip dan cara berpikir yang lebih disukai: perhatian utama ketika membangun pengecualian adalah bahwa itu harus dapat digunakan oleh kode yang mungkin menangkapnya. Kegunaan oleh manusia adalah hal yang penting, tetapi hanya masalah sekunder.

Ini menangani situasi yang sangat membuat frustrasi yang mungkin Anda saksikan pada suatu saat dalam karir Anda, di mana Anda mungkin telah menangkap pengecualian yang berisi informasi penting tentang apa yang terjadi dalam teks pesan, tetapi tidak di dalam variabel anggotanya, sehingga Anda harus melakukan penguraian string teks untuk mencari tahu apa yang terjadi, berharap bahwa teks pesan akan tetap sama di versi masa depan dari lapisan yang mendasarinya, dan berdoa agar teks pesan tidak akan dalam beberapa bahasa asing ketika program Anda dijalankan di negara lain.

Tentu saja, karena nama kelas pengecualian adalah pesan pengecualian, (dan variabel anggota pengecualian adalah detail spesifik), ini berarti bahwa Anda memerlukan banyak dan banyak pengecualian berbeda untuk menyampaikan semua pesan yang berbeda, dan ini baik saja.

Sekarang, kadang-kadang, ketika kita menulis kode, kita menemukan situasi yang salah di mana kita hanya ingin cepat kode throwpernyataan dan melanjutkan penulisan kode kita daripada harus mengganggu apa yang kita lakukan untuk membuat kelas pengecualian baru sehingga kita bisa lempar ke sana. Untuk kasus ini, saya memiliki GenericExceptionkelas yang sebenarnya menerima pesan string sebagai parameter waktu konstruksi, tetapi konstruktor dari kelas pengecualian ini dihiasi dengan FIXME XXX TODOkomentar ungu terang besar yang sangat besar yang menyatakan bahwa setiap instance dari kelas ini harus diganti dengan instantiasi dari beberapa kelas pengecualian yang lebih terspesialisasi sebelum sistem perangkat lunak dirilis, lebih disukai sebelum kode dikomit.

Mike Nakis
sumber
8
Jika Anda menggunakan bahasa yang tidak memiliki GC, seperti C ++, Anda harus sangat berhati-hati dalam menempatkan referensi ke data arbitrer ke dalam pengecualian yang Anda kirim ke tumpukan. Kemungkinannya adalah, apa pun yang Anda referensikan telah dihancurkan pada saat pengecualian ditangkap.
Sebastian Redl
4
@SebastianRedl Benar. Dan hal yang sama dapat berlaku untuk C # dan Java jika nodeobjek dijaga oleh klausa use-disposable (dalam C #) atau coba-dengan-sumber daya (di Jawa): objek yang disimpan dengan pengecualian akan dibuang / ditutup, menjadikannya ilegal untuk mengaksesnya untuk mendapatkan informasi yang berguna darinya di tempat pengecualian ditangani. Saya berasumsi bahwa dalam kasus ini semacam ringkasan objek harus disimpan dalam pengecualian, bukan objek itu sendiri. Saya tidak bisa memikirkan cara bukti bodoh untuk secara umum menangani ini untuk semua kasus.
Mike Nakis
13

Sebagai aturan umum, pengecualian harus membantu pengembang menentukan penyebab dengan memberikan informasi yang berguna (nilai yang diharapkan, nilai aktual, kemungkinan penyebab / solusi, dll.).

Tipe pengecualian baru harus dibuat ketika tidak ada tipe bawaan yang masuk akal . Jenis tertentu memungkinkan pengembang lain untuk menangkap pengecualian tertentu dan menanganinya. Jika pengembang tahu cara menangani pengecualian Anda, tetapi tipenya adalah Exception, ia tidak akan bisa menanganinya dengan benar.

mbillard
sumber
+1 - nilai yang diharapkan vs nilai aktual sangat berguna. Dalam contoh yang diberikan dalam pertanyaan, Anda tidak boleh hanya mengatakan bahwa suatu metode gagal, tetapi mengapa metode itu gagal (pada dasarnya, perintah persis yang gagal, dan keadaan yang menyebabkan kegagalan itu.)
Felix Dombek
4

Dalam. NET tidak pernah throw new Exception("...")(seperti yang ditunjukkan oleh penulis pertanyaan). Exception adalah tipe Exception root dan tidak seharusnya dibuang secara langsung. Alih-alih melempar salah satu jenis pengecualian .NET yang diturunkan atau membuat pengecualian kustom Anda sendiri yang berasal dari Pengecualian (atau jenis pengecualian lain).

Mengapa tidak membuang Pengecualian? Karena melemparkan Exception tidak melakukan apa pun untuk menggambarkan pengecualian Anda dan memaksa kode panggilan Anda untuk menulis kode seperti catch(Exception ex) { ... }yang umumnya bukan hal yang baik! :-).

bytedev
sumber
2

Hal-hal yang ingin Anda cari untuk "ditambahkan" ke pengecualian adalah elemen-elemen data yang tidak melekat dalam pengecualian atau jejak tumpukan. Apakah itu bagian dari "pesan" atau perlu dilampirkan saat login adalah pertanyaan yang menarik.

Seperti yang telah Anda catat, pengecualian mungkin memberi tahu Anda apa, stacktrace mungkin memberi tahu Anda di mana tetapi "mengapa" mungkin lebih terlibat (seharusnya, orang akan berharap) daripada hanya mengintip satu atau dua baris dan berkata " doh! Tentu saja ". Ini bahkan lebih benar ketika mencatat kesalahan dalam kode produksi - Saya sudah terlalu sering digigit oleh data buruk sehingga menemukan jalannya ke sistem hidup yang tidak ada dalam sistem pengujian kami. Sesuatu yang sesederhana mengetahui ID apa dari catatan dalam database yang menyebabkan (atau berkontribusi) kesalahan dapat menghemat banyak waktu.

Jadi ... Entah terdaftar atau, untuk .NET, ditambahkan ke pengumpulan data pengecualian yang dicatat (cf @Plip!):

  • Parameter (ini bisa sedikit menarik - Anda tidak dapat menambahkan ke pengumpulan data jika tidak akan bersambung dan kadang-kadang satu parameter bisa sangat kompleks)
  • Data tambahan dikembalikan oleh ADO.NET atau Linq ke SQL atau serupa (ini juga bisa sedikit menarik!).
  • Apa pun yang lain mungkin tidak terlihat.

Beberapa hal, tentu saja, Anda tidak akan tahu Anda perlu sampai Anda tidak memilikinya dalam laporan / log kesalahan awal Anda. Beberapa hal yang tidak Anda sadari bisa Anda dapatkan sampai Anda menemukan Anda membutuhkannya.

Murph
sumber
0

Apa Pengecualian untuk ?

(1) Memberitahu pengguna ada yang salah?

Ini harus menjadi pilihan terakhir, karena kode Anda harus bersyafaat dan menunjukkan kepada mereka sesuatu yang "lebih baik" daripada Pengecualian.

Pesan "kesalahan" harus secara jelas dan ringkas menyatakan apa yang salah dan apa, jika ada, yang dapat dilakukan pengguna untuk pulih dari kondisi kesalahan.

mis. "Tolong jangan tekan tombol ini lagi"

(2) Memberitahu Pengembang ketika terjadi kesalahan?

Ini adalah jenis hal yang Anda login ke file untuk analisis selanjutnya.
The Stack Trace akan memberitahu Developer mana kode pecah; pesan itu harus, sekali lagi, menunjukkan apa yang salah.

(3) Memberi tahu penangan Exception (kode yaitu) bahwa ada yang salah?

The Jenis dari Eksepsi akan menentukan handler pengecualian akan mendapatkan untuk melihat itu dan sifat didefinisikan pada objek Exception akan memungkinkan pawang untuk menghadapinya.

Pesan Pengecualian sama sekali tidak relevan .

Phill W.
sumber
-3

Jangan membuat tipe baru jika Anda bisa membantu. Mereka dapat menyebabkan kebingungan ekstra, kerumitan dan menyebabkan lebih banyak kode untuk dipelihara. Mereka mungkin membuat kode Anda harus diperluas. Mendesain hierarki pengecualian membutuhkan banyak waktu dan pengujian. Ini bukan pemikiran setelah. Seringkali yang terbaik adalah menggunakan hierarki pengecualian bahasa bawaan.

Isi pesan pengecualian bergantung pada penerima pesan - jadi Anda harus menempatkan diri pada posisi orang tersebut.

Seorang insinyur pendukung harus dapat mengidentifikasi sumber kesalahan secepat mungkin. Sertakan string deskriptif pendek plus data apa pun yang dapat membantu memecahkan masalah. Selalu sertakan jejak tumpukan - jika Anda bisa - ini akan menjadi satu-satunya sumber informasi yang sebenarnya.

Menyajikan kesalahan kepada pengguna umum sistem Anda tergantung pada jenis kesalahan: jika pengguna dapat memperbaiki masalah, dengan memberikan input yang berbeda misalnya, maka pesan deskriptif singkat diperlukan. Jika pengguna tidak dapat memperbaiki masalah, maka yang terbaik adalah menyatakan kesalahan telah terjadi dan mencatat / mengirim kesalahan untuk mendukung (menggunakan pedoman di atas).

Juga - jangan muntah besar "HALT ERROR!" ikon. Ini adalah kesalahan - ini bukan akhir dari dunia.

Jadi secara ringkas: pikirkan tentang para aktor dan gunakan kasing untuk sistem Anda. Tempatkan diri Anda pada posisi pengguna tersebut. Berguna. Bersikap baik. Pikirkan ini di depan dalam desain sistem Anda. Dari perspektif pengguna Anda - kasus pengecualian ini dan bagaimana sistem menanganinya, sama pentingnya dengan kasus normal di sistem Anda.

Conor
sumber
10
Saya tidak setuju. Ada banyak alasan bagus untuk menerapkan pengecualian Anda sendiri ketika API bahasa tidak mencakup kebutuhan Anda yang sebenarnya. Salah satu alasannya adalah bahwa dalam metode di mana banyak hal mungkin gagal, Anda dapat menulis klausa tangkapan yang berbeda untuk berbagai jenis pengecualian, di mana Anda dapat bereaksi terhadap masalah yang sebenarnya. Lain adalah bahwa Anda dapat memisahkan beberapa lapisan pengecualian yang mewakili berbagai lapisan abstraksi, di mana lapisan yang tepat milik pengecualian dapat dikodekan dalam jenisnya. Hanya menggunakan "Pengecualian" atau "IllegalStateException" dan string pesan tidak banyak membantu di sana.
Felix Dombek
1
Saya juga tidak setuju. Jenis pengecualian harus masuk akal ke kode panggilan yang akan mengkonsumsinya. Misalnya jika saya memanggil kerangka kerja dan ini menyebabkan FileDoesNotExistException secara internal maka ini mungkin tidak masuk akal bagi saya sebagai penelepon kerangka kerja. Sebagai gantinya mungkin lebih baik untuk membuat pengecualian khusus dan meneruskan pengecualian yang dilemparkan sebagai pengecualian dalam.
bytedev