Bagaimana cara melempar ArgumentNullException membantu?

27

Katakanlah saya punya metode:

public void DoSomething(ISomeInterface someObject)
{
    if(someObject == null) throw new ArgumentNullException("someObject");
    someObject.DoThisOrThat();
}

Saya telah dilatih untuk percaya bahwa melempar ArgumentNullExceptionis "benar" tetapi "Referensi objek tidak disetel ke instance objek" kesalahan berarti saya memiliki bug.

Mengapa?

Saya tahu bahwa jika saya menyimpan referensi ke someObjectdan menggunakannya nanti, maka lebih baik untuk memeriksa nullity ketika diteruskan, dan gagal lebih awal. Namun, jika saya melakukan dereferensi pada baris berikutnya, mengapa kita harus melakukan pemeriksaan? Ini akan melempar pengecualian dengan satu atau lain cara.

Edit :

Itu baru saja terpikir oleh saya ... apakah ketakutan akan null dereferensi berasal dari bahasa seperti C ++ yang tidak memeriksa Anda (yaitu hanya mencoba menjalankan beberapa metode di lokasi memori nol + metode offset)?

Scott Whitlock
sumber
Membuat pengecualian secara eksplisit membantu menegaskan bahwa ada kemungkinan kesalahan, yang dapat dicatat secara langsung dalam dokumentasi.
zzzzBov

Jawaban:

34

Saya kira jika Anda segera mereferensikan variabel, Anda bisa berdebat dengan cara apa pun, tapi saya masih lebih suka ArgumentNullException.
Ini jauh lebih eksplisit tentang apa yang sedang terjadi. Pengecualian berisi nama variabel yang null, sedangkan NullReferenceException tidak. Khususnya di tingkat API, ArgumentNullException memperjelas bahwa beberapa penelepon tidak menghormati kontrak suatu metode, dan kegagalan itu tidak selalu merupakan kesalahan acak atau beberapa masalah yang lebih dalam (meskipun itu mungkin masih terjadi). Anda harus gagal lebih awal.

Selain itu, apa yang nantinya cenderung dilakukan oleh pengelola? Tambahkan ArgumentNullException jika mereka menambahkan kode sebelum dereference pertama, atau cukup tambahkan kode dan kemudian izinkan NullReferenceException dilemparkan?

Matt H
sumber
Dengan ekstensi, jika ini adalah metode non-publik, atau metode publik pada kelas non-publik, Anda tidak berpikir ada alasan bagus untuk cek nol?
Scott Whitlock
Saya biasanya tidak menambahkan cek ini ke metode pribadi, tetapi saya masih dapat menambahkannya ke metode publik dari kelas privat agar konsisten. Untuk metode pribadi, saya cenderung membuat asumsi bahwa argumen divalidasi sebelumnya pada tingkat panggilan publik.
Matt H
54

Saya telah dilatih untuk percaya bahwa melempar ArgumentNullException adalah "benar" tetapi "Referensi objek tidak disetel ke instance objek" kesalahan berarti saya memiliki bug. Mengapa?

Misalkan saya memanggil metode M(x)yang Anda tulis. Saya lulus nol. Saya mendapatkan ArgumentNullException dengan nama diatur ke "x". Pengecualian itu jelas berarti saya memiliki bug; Saya seharusnya tidak melewati nol untuk x.

Misalkan saya memanggil metode M yang Anda tulis. Saya lulus nol. Saya mendapatkan pengecualian deref nol. Bagaimana saya bisa tahu apakah saya memiliki bug atau Anda memiliki bug atau perpustakaan pihak ketiga yang Anda panggil atas nama saya memiliki bug? Saya tidak tahu apa-apa apakah saya perlu memperbaiki kode saya atau menelepon Anda untuk memberi tahu Anda untuk memperbaiki kode Anda.

Kedua pengecualian merupakan indikasi bug; tidak harus pernah dilempar dalam kode produksi. Pertanyaannya adalah pengecualian mana yang mengkomunikasikan siapa yang memiliki bug lebih baik kepada orang yang melakukan debugging? Pengecualian Null deref hampir tidak memberi tahu Anda; argumen null exception memberi tahu Anda banyak hal. Bersikap baik kepada pengguna Anda; melemparkan pengecualian yang memungkinkan mereka belajar dari kesalahan mereka.

Eric Lippert
sumber
Saya tidak yakin saya mengerti "neither should ever be thrown in production code"jenis kode / situasi apa yang akan mereka gunakan? Haruskah mereka dikompilasi seperti Debug.Assert? Atau maksud Anda jangan membuangnya dari API?
StuperUser
6
@StuperUser: Saya pikir Anda salah mengerti apa yang saya maksud, karena saya menulisnya dengan cara yang membingungkan. Anda harus memiliki throw new ArgumentNullExceptionkode produksi Anda, dan seharusnya tidak pernah berjalan di luar test case . Pengecualian seharusnya tidak benar-benar dibuang karena penelepon tidak boleh memiliki bug itu.
Eric Lippert
1
Tidak hanya berkomunikasi siapa yang memiliki bug, itu berkomunikasi di mana bug itu. Meskipun kedua area tersebut adalah milik saya, tidak selalu mudah untuk memahami dengan tepat variabel apa yang null.
Ohad Schneider
5

Intinya, kode Anda harus melakukan sesuatu yang bermakna.

A NullReferenceExceptiontidak terlalu bermakna, karena bisa berarti segalanya. Tanpa melihat di mana pengecualian dilemparkan (dan kode itu mungkin tidak tersedia bagi saya) saya tidak tahu, apa yang salah.

An ArgumentNullExceptionbermakna, karena memberi tahu saya: operasi gagal karena argumen someObjectitu null. Saya tidak perlu melihat kode Anda untuk mencari tahu.

Juga meningkatkan pengecualian ini berarti, bahwa Anda secara eksplisit menangani null, meskipun satu-satunya cara Anda untuk melakukannya adalah dengan mengatakan, bahwa Anda tidak dapat menanganinya, sementara hanya membiarkan kode crash berpotensi berarti bahwa, Anda mungkin benar-benar dapat menangani null , tetapi lupa mengimplementasikan kasus ini.

Tujuan pengecualian adalah untuk mengkomunikasikan informasi jika terjadi kegagalan, yang dapat ditangani pada saat runtime untuk pulih dari kegagalan, atau pada waktu debug untuk lebih memahami apa yang salah.

back2dos
sumber
2

Saya percaya satu-satunya keuntungan adalah Anda dapat memberikan diagnosa yang lebih baik dalam pengecualian. Itu Anda tahu, bahwa pemanggil fungsi lewat nol, sementara jika Anda membiarkan dereference membuangnya, Anda tidak akan yakin apakah pemanggil melewati data yang salah atau ada bug dalam fungsi itu sendiri.

Saya akan melakukannya jika orang lain akan memanggil fungsi untuk membuatnya lebih mudah digunakan (men-debug jika terjadi kesalahan) dan tidak melakukannya dalam kode internal saya, karena runtime akan memeriksanya.

Jan Hudec
sumber
1

Saya telah dilatih untuk percaya bahwa melempar ArgumentNullException adalah "benar" tetapi "Referensi objek tidak disetel ke instance objek" kesalahan berarti saya memiliki bug.

Anda memiliki bug jika kode tidak berfungsi seperti yang dirancang. Sebuah NullReferenceExceptiondilemparkan oleh sistem dan fakta itu benar-benar membuat lebih banyak bug daripada fitur. Bukan hanya karena Anda tidak menetapkan pengecualian khusus untuk ditangani. Juga karena pengembang membaca kode Anda mungkin menganggap Anda tidak memperhitungkan situasi yang terjadi.

Untuk alasan yang sama bahwa ApplicationExceptionmilik aplikasi Anda vs seorang jenderal Exceptionyang milik sistem operasi. Jenis pengecualian yang dilemparkan menunjukkan dari mana asalnya pengecualian.

Jika Anda telah menghitung nol, lebih baik Anda menanganinya dengan anggun dengan melemparkan pengecualian tertentu atau jika itu merupakan input yang dapat diterima, maka jangan melempar pengecualian karena harganya mahal. Tangani dengan melakukan operasi dengan standar yang baik atau tanpa operasi sama sekali (mis. Tidak diperlukan pembaruan).

Akhirnya, jangan abaikan kemampuan penelepon untuk menangani situasi. Dengan memberi mereka pengecualian yang lebih spesifik, ArgumentExceptionmereka dapat membuat percobaan / tangkapan mereka sedemikian rupa untuk menangani kesalahan ini sebelum yang lebih umum NullReferenceExceptionyang mungkin disebabkan oleh sejumlah alasan lain yang terjadi di tempat lain, seperti kegagalan untuk menginisialisasi variabel konstruktor.

P.Brian.Mackey
sumber
1

Pemeriksaan membuatnya lebih eksplisit apa yang terjadi, tetapi masalah sebenarnya datang dengan kode seperti contoh ini (dibuat-buat):

public void DoSomething(Foo someOtherObject, ISomeInterface someObject)
{
    someOtherObject.DoThisOrThat(this, someObject);
}

Di sini ada rantai panggilan yang terlibat dan tanpa cek nol Anda hanya melihat kesalahan di someOtherObject dan bukan di mana masalah pertama kali terungkap dengan sendirinya - yang secara instan berarti waktu terbuang untuk debugging atau menafsirkan tumpukan panggilan.

Secara umum lebih baik konsisten dengan hal semacam ini dan selalu menambahkan cek nol - yang membuatnya lebih mudah dikenali jika ada yang hilang daripada memilih dan memilih berdasarkan kasus per kasus (dengan asumsi Anda tahu bahwa nol adalah selalu tidak valid).

FinnNk
sumber
1

Alasan lain untuk dipertimbangkan adalah bagaimana jika beberapa pemrosesan terjadi sebelum objek nol digunakan?

Katakanlah Anda memiliki file data ID perusahaan terkait (Cid) dan ID orang (Pid). Ini disimpan sebagai aliran pasangan nomor:

Cid Pid Cid Pid Cid Pid Cid Pid Cid Pid

Dan fungsi VB ini menulis catatan ke file:

Sub WriteRecord(Company As CompanyInfo, Person As PersonInfo)
    Using File As New StringWriter(DataFileName)
        File.Write(Company.ID)
        File.Write(Person.ID)
    End Using
End Sub

Jika Personmerupakan referensi nol, baris tersebut File.Write(Person.ID)akan mengeluarkan pengecualian. Perangkat lunak dapat menangkap pengecualian dan pulih, tetapi ini menyisakan masalah. ID perusahaan telah ditulis tanpa ID orang yang terkait. Jika kenyataannya, saat Anda memanggil fungsi ini selanjutnya, Anda akan meletakkan ID perusahaan di mana ID orang sebelumnya diharapkan. Selain catatan yang salah, setiap catatan selanjutnya akan memiliki ID orang diikuti oleh ID perusahaan, yang merupakan jalan yang salah.

Cid Pid Cid Pid Cid Cid Pid Cid Pid Cid

Dengan memvalidasi parameter di awal fungsi, Anda mencegah segala proses terjadi ketika metode dijamin gagal.

Makanan Tangan
sumber