Praktik terbaik: melempar pengecualian dari properti

111

Kapan tepat untuk melempar pengecualian dari dalam pengambil atau penyetel properti? Kapan tidak sesuai? Mengapa? Tautan ke dokumen eksternal tentang masalah ini akan sangat membantu ... Ternyata Google sangat sedikit.

Jon Seigel
sumber
Juga terkait: stackoverflow.com/questions/1488322/…
Brian Rasmussen
1
Saya membaca kedua pertanyaan itu, tetapi tidak menjawab pertanyaan ini sepenuhnya IMO.
Jon Seigel
Kapanpun diperlukan. Baik pertanyaan maupun jawaban sebelumnya menunjukkan bahwa diperbolehkan dan dihargai untuk memunculkan pengecualian dari pengambil atau penyetel, sehingga Anda dapat dengan mudah "menjadi pintar".
Lex Li

Jawaban:

135

Microsoft memiliki rekomendasinya tentang cara merancang properti di http://msdn.microsoft.com/en-us/library/ms229006.aspx

Pada dasarnya, mereka merekomendasikan agar pengambil properti menjadi pengakses ringan yang selalu aman untuk dipanggil. Mereka merekomendasikan mendesain ulang getter menjadi metode jika pengecualian adalah sesuatu yang perlu Anda buang. Untuk penyetel, mereka menunjukkan bahwa pengecualian adalah strategi penanganan kesalahan yang sesuai dan dapat diterima.

Untuk pengindeks, Microsoft menunjukkan bahwa getter dan setter boleh melempar pengecualian. Dan faktanya, banyak pengindeks di pustaka .NET melakukan ini. Pengecualian yang paling umum ArgumentOutOfRangeException.

Ada beberapa alasan yang cukup bagus mengapa Anda tidak ingin memberikan pengecualian pada pengambil properti:

  • Karena properti "tampak" berupa bidang, tidak selalu terlihat bahwa properti dapat memunculkan pengecualian (menurut desain); sedangkan dengan metode, pemrogram dilatih untuk mengharapkan dan menyelidiki apakah pengecualian merupakan konsekuensi yang diharapkan dari pemanggilan metode tersebut.
  • Getters digunakan oleh banyak infrastruktur .NET, seperti serializers dan databinding (di WinForms dan WPF misalnya) - menangani pengecualian dalam konteks seperti itu dapat dengan cepat menjadi masalah.
  • Pengambil properti secara otomatis dievaluasi oleh debugger saat Anda melihat atau memeriksa suatu objek. Pengecualian di sini dapat membingungkan dan memperlambat upaya debugging Anda. Juga tidak diinginkan untuk melakukan operasi mahal lainnya di properti (seperti mengakses database) karena alasan yang sama.
  • Properti sering digunakan dalam konvensi perangkaian: obj.PropA.AnotherProp.YetAnother- dengan sintaks semacam ini, menjadi masalah untuk memutuskan di mana harus memasukkan pernyataan pengecualian catch.

Sebagai catatan tambahan, orang harus menyadari bahwa hanya karena properti tidak dirancang untuk memberikan pengecualian, bukan berarti tidak akan; itu bisa dengan mudah memanggil kode yang melakukannya. Bahkan tindakan sederhana untuk mengalokasikan objek baru (seperti string) dapat menghasilkan pengecualian. Anda harus selalu menulis kode Anda secara defensif dan mengharapkan pengecualian dari apa pun yang Anda panggil.

LBushkin
sumber
41
Jika Anda mengalami pengecualian fatal seperti "kehabisan memori", tidak masalah apakah Anda mendapatkan pengecualian di properti atau di tempat lain. Jika Anda tidak mendapatkannya di properti, Anda hanya akan mendapatkannya beberapa nanodetik kemudian dari hal berikutnya yang mengalokasikan memori. Pertanyaannya bukan, "bisakah properti membuat pengecualian?" Hampir semua kode dapat memunculkan pengecualian karena kondisi yang fatal. Pertanyaannya adalah apakah sebuah properti harus secara sengaja mengeluarkan pengecualian sebagai bagian dari kontrak spesifiknya.
Eric Lippert
1
Saya tidak yakin saya memahami argumen dalam jawaban ini. Sebagai contoh, mengenai penyatuan data - baik WinForms dan WPF secara khusus ditulis untuk menangani pengecualian yang dilemparkan oleh properti, dan untuk memperlakukannya sebagai kegagalan validasi - yang merupakan cara yang sangat baik (beberapa bahkan percaya itu adalah cara terbaik) untuk memberikan validasi model domain .
Pavel Minaev
6
@Pavel - meskipun WinForms dan WPF dapat pulih dengan baik dari pengecualian di pengakses properti, tidak selalu mudah untuk mengidentifikasi dan memulihkan dari kesalahan semacam itu. Dalam kasus tertentu, (seperti saat di WPF penyetel Template Kontrol melontarkan pengecualian) pengecualian ditelan secara diam-diam. Ini dapat menyebabkan sesi debugging yang menyakitkan jika Anda belum pernah mengalami kasus seperti itu sebelumnya.
LBushkin
1
@ Steven: Lalu berapa banyak kegunaan kelas itu bagi Anda dalam kasus luar biasa? Jika Anda kemudian harus menulis kode defensif untuk menangani semua pengecualian itu karena satu kegagalan, dan mungkin menyediakan default yang sesuai, mengapa tidak memberikan default tersebut di tangkapan Anda? Atau jika pengecualian properti dilemparkan ke pengguna mengapa tidak membuang "InvalidArgumentException" asli atau serupa sehingga mereka dapat menyediakan file pengaturan yang hilang?
Zhaph - Ben Duguid
6
Ada alasan mengapa ini adalah pedoman dan bukan aturan; tidak ada pedoman yang mencakup semua kasus tepi gila. Saya mungkin akan membuat metode ini dan bukan properti sendiri, tetapi ini adalah keputusan penilaian.
Eric Lippert
34

Tidak ada yang salah dengan memberikan pengecualian dari setter. Lagi pula, cara apa yang lebih baik untuk menunjukkan bahwa nilainya tidak valid untuk properti tertentu?

Untuk pengambil, umumnya tidak disukai, dan itu bisa dijelaskan dengan mudah: pengambil properti, secara umum, melaporkan status saat ini dari suatu objek; dengan demikian, satu-satunya kasus di mana masuk akal bagi getter untuk melempar adalah ketika status tidak valid. Tetapi secara umum juga dianggap sebagai ide yang baik untuk mendesain kelas Anda sedemikian rupa sehingga tidak mungkin untuk mendapatkan objek yang tidak valid pada awalnya, atau memasukkannya ke dalam keadaan tidak valid melalui cara normal (yaitu, selalu pastikan inisialisasi penuh dalam konstruktor, dan coba buat metode pengecualian-aman sehubungan dengan validitas status dan invarian kelas). Selama Anda tetap berpegang pada aturan itu, pengambil properti Anda tidak boleh masuk ke situasi di mana mereka harus melaporkan keadaan tidak valid, dan karenanya tidak pernah melempar.

Ada satu pengecualian yang saya tahu, dan itu sebenarnya yang agak utama: objek apa pun yang diimplementasikan IDisposable. Disposesecara khusus dimaksudkan sebagai cara untuk membawa objek ke status tidak valid, dan bahkan ada kelas pengecualian khusus ObjectDisposedException,, untuk digunakan dalam kasus itu. Sangat normal untuk membuang ObjectDisposedExceptiondari anggota kelas mana pun, termasuk pengambil properti (dan tidak termasuk Disposedirinya sendiri), setelah objek dibuang.

Pavel Minaev
sumber
4
Terima kasih Pavel. Jawaban ini masuk ke 'mengapa' alih-alih hanya menyatakan lagi bahwa bukanlah ide yang baik untuk membuat pengecualian dari properti.
SolutionYogi
1
Saya tidak menyukai gagasan bahwa benar-benar semua anggota an IDisposableharus dibuat tidak berguna setelah a Dispose. Jika memanggil anggota akan membutuhkan penggunaan sumber daya yang Disposetelah dibuat tidak tersedia (misalnya anggota akan membaca data dari aliran yang telah ditutup) anggota harus membuang ObjectDisposedExceptiondaripada membocorkan misalnya ArgumentException, tetapi jika seseorang memiliki formulir dengan properti yang mewakili nilai dalam bidang tertentu, akan tampak jauh lebih membantu untuk memungkinkan properti tersebut dibaca setelah pembuangan (menghasilkan nilai yang diketik terakhir) daripada membutuhkan ...
supercat
1
... yang Disposeditangguhkan sampai semua properti tersebut telah dibaca. Dalam beberapa kasus di mana satu utas mungkin menggunakan pembacaan pemblokiran pada suatu objek sementara yang lain menutupnya, dan di mana data mungkin tiba kapan saja sebelumnya Dispose, mungkin berguna untuk Disposememotong data yang masuk, tetapi memungkinkan data yang diterima sebelumnya untuk dibaca. Seseorang tidak boleh memaksakan perbedaan buatan antara Closedan Disposedalam situasi di mana tidak ada yang perlu ada.
supercat
Memahami alasan aturan memungkinkan Anda mengetahui kapan harus melanggar aturan (Raymond Chen). Dalam kasus ini, kita dapat melihat bahwa jika ada kesalahan jenis apa pun yang tidak dapat dipulihkan, Anda tidak boleh menyembunyikannya di getter, karena dalam kasus seperti itu aplikasi harus keluar secepatnya.
Ben
Hal yang ingin saya sampaikan adalah bahwa pengambil properti Anda biasanya tidak boleh berisi logika yang memungkinkan terjadinya kesalahan yang tidak dapat dipulihkan. Jika ya, mungkin itu lebih baik sebagai Get...metode saja. Pengecualian di sini adalah ketika Anda harus mengimplementasikan antarmuka yang ada yang mengharuskan Anda menyediakan properti.
Pavel Minaev
24

Ini hampir tidak pernah cocok untuk pengambil, dan kadang-kadang cocok untuk penyetel.

Sumber daya terbaik untuk pertanyaan semacam ini adalah "Panduan Desain Kerangka Kerja" oleh Cwalina dan Abrams; itu tersedia sebagai buku terikat, dan sebagian besar juga tersedia secara online.

Dari bagian 5.2: Desain Properti

HINDARI melempar pengecualian dari pengambil properti. Pengambil properti harus berupa operasi sederhana dan tidak boleh memiliki prasyarat. Jika getter bisa melontarkan pengecualian, itu mungkin harus didesain ulang menjadi sebuah metode. Perhatikan bahwa aturan ini tidak berlaku untuk pengindeks, di mana kami mengharapkan pengecualian sebagai hasil dari memvalidasi argumen.

Perhatikan bahwa pedoman ini hanya berlaku untuk pengambil properti. Tidak masalah untuk membuat pengecualian dalam penyetel properti.

Eric Lippert
sumber
2
Meskipun (secara umum) saya setuju dengan pedoman semacam itu, menurut saya berguna untuk memberikan beberapa wawasan tambahan tentang mengapa pedoman tersebut harus diikuti - dan konsekuensi seperti apa yang dapat muncul bila diabaikan.
LBushkin
3
Bagaimana hal ini berhubungan dengan objek sekali pakai dan panduan yang harus Anda pertimbangkan untuk dibuang ObjectDisposedExceptionsetelah objek tersebut Dispose()memanggil dan sesuatu yang kemudian meminta nilai properti? Sepertinya panduannya harus "hindari melempar pengecualian dari pengambil properti, kecuali jika objek telah dibuang dalam hal ini Anda harus mempertimbangkan untuk melempar ObjectDisposedExcpetion".
Scott Dorman
4
Desain adalah seni dan ilmu untuk menemukan kompromi yang masuk akal dalam menghadapi persyaratan yang saling bertentangan. Apa pun cara yang digunakan sepertinya merupakan kompromi yang masuk akal; Saya tidak akan terkejut jika ada benda yang dibuang ke sebuah properti; saya juga tidak akan terkejut jika tidak. Karena menggunakan benda yang dibuang adalah praktik pemrograman yang buruk, tidak bijaksana untuk memiliki harapan apa pun.
Eric Lippert
1
Skenario lain di mana benar-benar valid untuk membuang pengecualian dari dalam getter adalah ketika sebuah objek menggunakan invarian kelas untuk memvalidasi status internalnya, yang perlu diperiksa setiap kali akses publik dibuat, tidak peduli itu metode atau properti
Perangkap
2

Salah satu pendekatan yang bagus untuk Pengecualian adalah menggunakannya untuk mendokumentasikan kode untuk Anda sendiri dan pengembang lain sebagai berikut:

Pengecualian harus untuk status program yang luar biasa. Artinya tidak masalah untuk menulisnya di mana pun Anda mau!

Salah satu alasan Anda mungkin ingin menempatkannya di getter adalah untuk mendokumentasikan API kelas - jika perangkat lunak mengeluarkan pengecualian segera setelah programmer mencoba menggunakannya dengan salah maka mereka tidak akan salah menggunakannya! Misalnya jika Anda memiliki validasi selama proses membaca data, mungkin tidak masuk akal untuk dapat melanjutkan dan mengakses hasil proses jika ada kesalahan fatal dalam data. Dalam hal ini Anda mungkin ingin mendapatkan keluaran jika ada kesalahan untuk memastikan bahwa programmer lain memeriksa kondisi ini.

Mereka adalah cara untuk mendokumentasikan asumsi dan batasan dari subsistem / metode / apapun. Dalam kasus umum, mereka tidak boleh ditangkap! Ini juga karena mereka tidak pernah dilemparkan jika sistem bekerja sama dengan cara yang diharapkan: Jika terjadi pengecualian, ini menunjukkan bahwa asumsi sepotong kode tidak terpenuhi - misalnya tidak berinteraksi dengan dunia di sekitarnya dengan cara itu awalnya dimaksudkan untuk. Jika Anda menemukan pengecualian yang ditulis untuk tujuan ini, itu mungkin berarti sistem telah memasuki keadaan yang tidak dapat diprediksi / tidak konsisten - ini pada akhirnya dapat menyebabkan crash atau kerusakan data atau serupa yang kemungkinan akan jauh lebih sulit untuk dideteksi / debug.

Pesan pengecualian adalah cara yang sangat kasar untuk melaporkan kesalahan - pesan tersebut tidak dapat dikumpulkan secara massal dan hanya benar-benar berisi string. Ini membuat mereka tidak cocok untuk melaporkan masalah dalam input data. Dalam menjalankan normal, sistem itu sendiri tidak boleh memasuki status kesalahan. Akibatnya, pesan di dalamnya harus dirancang untuk pemrogram dan bukan untuk pengguna - hal-hal yang salah dalam data masukan dapat ditemukan dan diteruskan ke pengguna dalam format yang lebih sesuai (khusus).

Pengecualian (haha!) Untuk aturan ini adalah hal-hal seperti IO di mana pengecualian tidak berada di bawah kendali Anda dan tidak dapat diperiksa terlebih dahulu.

JonnyRaa
sumber
2
Bagaimana jawaban yang valid dan relevan ini mendapat suara negatif? Seharusnya tidak ada politik di StackOverflow, dan jika jawaban ini tampaknya tidak tepat sasaran, tambahkan komentar untuk efek tersebut. Downvoting adalah untuk jawaban yang tidak relevan atau salah.
debater
1

Ini semua didokumentasikan dalam MSDN (sebagaimana ditautkan dalam jawaban lain) tetapi berikut adalah aturan umum ...

Di penyetel, jika properti Anda harus divalidasi di atas dan di luar tipe. Misalnya, properti bernama PhoneNumber mungkin harus memiliki validasi regex dan akan menampilkan kesalahan jika formatnya tidak valid.

Untuk getter, mungkin jika nilainya null, tetapi kemungkinan besar itu adalah sesuatu yang ingin Anda tangani pada kode panggilan (sesuai pedoman desain).

David Stratton
sumber
0

Ini adalah pertanyaan dan jawaban yang sangat kompleks tergantung pada bagaimana objek Anda digunakan. Prinsipnya, pengambil dan penyetel properti yang "mengikat terlambat" tidak boleh menampilkan pengecualian, sementara properti dengan "pengikatan awal" eksklusif harus menampilkan pengecualian saat diperlukan. BTW, alat analisis kode Microsoft mendefinisikan penggunaan properti terlalu sempit menurut saya.

"pengikatan terlambat" berarti bahwa properti ditemukan melalui refleksi. Misalnya atribut "Serializeable" digunakan untuk membuat serial / deserialisasi objek melalui propertinya. Melempar pengecualian selama situasi seperti ini akan merusak berbagai hal dengan cara yang sangat berbahaya dan bukan cara yang baik untuk menggunakan pengecualian untuk membuat kode yang lebih kuat.

"pengikatan awal" berarti bahwa penggunaan properti diikat dalam kode oleh kompilator. Misalnya ketika beberapa kode yang Anda tulis mereferensikan pengambil properti. Dalam hal ini tidak masalah untuk memberikan pengecualian jika masuk akal.

Objek dengan atribut internal memiliki status yang ditentukan oleh nilai atribut tersebut. Properti yang mengekspresikan atribut yang peka dan peka terhadap keadaan internal objek tidak boleh digunakan untuk pengikatan terlambat. Misalnya, Anda memiliki objek yang harus dibuka, diakses, lalu ditutup. Dalam kasus ini, mengakses properti tanpa memanggil buka terlebih dahulu akan menghasilkan pengecualian. Misalkan, dalam kasus ini, kita tidak melempar eksepsi dan kita mengizinkan kode mengakses suatu nilai tanpa mengeluarkan eksepsi? Kode akan tampak senang meskipun mendapat nilai dari pengambil yang tidak masuk akal. Sekarang kita telah meletakkan kode yang memanggil pengambil dalam situasi yang buruk karena harus tahu bagaimana memeriksa nilai untuk melihat apakah itu tidak masuk akal. Ini berarti bahwa kode harus membuat asumsi tentang nilai yang didapatnya dari pengambil properti untuk memvalidasinya. Beginilah cara penulisan kode yang buruk.

Jack D Menendez
sumber
0

Saya memiliki kode ini di mana saya tidak yakin pengecualian mana yang harus dilemparkan.

public Person
{
    public string Name { get; set; }
    public boolean HasPets { get; set; }
}

public void Foo(Person person)
{
    if (person.Name == null) {
        throw new Exception("Name of person is null.");
        // I was unsure of which exception to throw here.
    }

    Console.WriteLine("Name is: " + person.Name);
}

Saya mencegah model memiliki properti menjadi null di tempat pertama dengan memaksanya sebagai argumen di konstruktor.

public Person
{
    public Person(string name)
    {
        if (name == null) {
            throw new ArgumentNullException(nameof(name));
        }
        Name = name;
    }

    public string Name { get; private set; }
    public boolean HasPets { get; set; }
}

public void Foo(Person person)
{
    Console.WriteLine("Name is: " + person.Name);
}
Fred
sumber