Jadi saya tidak tahu apakah ini desain kode yang baik atau buruk, jadi saya pikir saya sebaiknya bertanya.
Saya sering membuat metode yang melakukan pengolahan data yang melibatkan kelas dan saya sering melakukan banyak pemeriksaan dalam metode untuk memastikan saya tidak mendapatkan referensi nol atau kesalahan lain sebelumnya.
Untuk contoh yang sangat mendasar:
// fields and properties
private Entity _someEntity;
public Entity SomeEntity => _someEntity;
public void AssignEntity(Entity entity){
_someEntity = entity;
}
public void SetName(string name)
{
if (_someEntity == null) return; //check to avoid null ref
_someEntity.Name = name;
label.SetText(_someEntity.Name);
}
Jadi seperti yang Anda lihat, saya memeriksa null setiap kali. Tetapi haruskah metode ini tidak memiliki pemeriksaan ini?
Misalnya, kode eksternal harus membersihkan data sebelumnya sehingga metode tidak perlu divalidasi seperti di bawah ini:
if(entity != null) // this makes the null checks redundant in the methods
{
Manager.AssignEntity(entity);
Manager.SetName("Test");
}
Singkatnya, metode harus "memvalidasi data" dan kemudian melakukan pemrosesan pada data, atau harus dijamin sebelum Anda memanggil metode, dan jika Anda gagal untuk memvalidasi sebelum memanggil metode, itu harus menimbulkan kesalahan (atau menangkap kesalahan)?
sumber
Jawaban:
Masalah dengan contoh dasar Anda bukan cek nol, itu gagal diam .
Null pointer / kesalahan referensi, lebih sering daripada tidak, adalah kesalahan programmer. Kesalahan pemrogram sering kali paling baik diatasi dengan gagal dengan segera dan keras. Anda memiliki tiga cara umum untuk mengatasi masalah dalam situasi ini:
Solusi ketiga adalah lebih banyak pekerjaan tetapi jauh lebih kuat:
_someEntity
berada dalam keadaan tidak valid. Salah satu cara untuk melakukannya adalah dengan menyingkirkanAssignEntity
dan memerlukannya sebagai parameter untuk instantiasi. Teknik Injeksi Ketergantungan Lainnya dapat bermanfaat juga.Dalam beberapa situasi, masuk akal untuk memeriksa semua argumen fungsi untuk validitas sebelum pekerjaan apa pun yang Anda lakukan, dan dalam situasi lain masuk akal untuk memberi tahu pemanggil bahwa mereka bertanggung jawab untuk memastikan input mereka valid, dan tidak memeriksa. Di mana ujung spektrum yang Anda gunakan akan bergantung pada domain masalah Anda. Opsi ketiga memiliki manfaat yang signifikan karena Anda dapat, sampai batas tertentu, membuat kompiler memastikan bahwa pemanggil melakukan semuanya dengan benar.
Jika opsi ketiga bukan opsi, maka menurut saya itu tidak masalah asalkan tidak diam-diam gagal. Jika tidak memeriksa null menyebabkan program meledak secara instan, baik, tetapi jika malah diam-diam merusak data yang menyebabkan masalah di jalan, maka yang terbaik adalah memeriksa dan menanganinya saat itu juga.
sumber
null
itu salah atau tidak valid karena di pustaka hipotetis saya, saya telah menentukan tipe data dan saya telah menentukan apa yang valid dan apa yang tidak. Anda menyebutnya "pemaksaan asumsi" tetapi coba tebak: itulah yang dilakukan oleh setiap perpustakaan perangkat lunak. Ada kalanya null adalah nilai yang valid, tetapi itu tidak "selalu".null
dengan benar" - Ini adalah kesalahpahaman tentang arti penanganan yang tepat. Bagi kebanyakan programmer Java, itu berarti melemparkan pengecualian untuk setiap input tidak valid. Menggunakan@ParametersAreNonnullByDefault
, itu juga berarti untuk setiapnull
, kecuali argumen ditandai sebagai@Nullable
. Melakukan hal lain berarti memperpanjang jalan sampai akhirnya berhembus di tempat lain dan dengan demikian juga memperpanjang waktu yang terbuang untuk debugging. Nah, dengan mengabaikan masalah, Anda mungkin mencapai bahwa itu tidak pernah meledak, tetapi perilaku yang salah kemudian cukup terjamin.Exception
adalah debat yang sama sekali tidak terkait. (Saya setuju keduanya merepotkan.) Untuk contoh khusus ini,null
jelas tidak masuk akal di sini jika tujuan kelas adalah untuk memanggil metode pada objek.Karena
_someEntity
dapat dimodifikasi pada tahap apa pun, maka masuk akal untuk mengujinya setiap kaliSetName
dipanggil. Bagaimanapun, itu bisa saja berubah sejak terakhir kali metode itu dipanggil. Namun ketahuilah bahwa kodeSetName
ini tidak aman untuk digunakan, sehingga Anda dapat melakukan pemeriksaan tersebut dalam satu utas, telah_someEntity
ditetapkannull
oleh utas lainnya dan kemudian kode tersebut akan muntahNullReferenceException
.Karena itu, salah satu pendekatan untuk masalah ini adalah menjadi lebih defensif dan melakukan salah satu dari yang berikut:
_someEntity
dan referensi yang menyalin melalui metode ini,_someEntity
untuk memastikan itu tidak berubah saat metode dijalankan,try/catch
dan lakukan tindakan yang sama pada apa punNullReferenceException
yang terjadi dalam metode seperti yang akan Anda lakukan dalam pemeriksaan nol awal (dalam hal ini,nop
tindakan).Tetapi yang harus Anda lakukan adalah berhenti dan tanyakan pada diri sendiri pertanyaan: apakah Anda benar-benar perlu untuk
_someEntity
ditimpa? Mengapa tidak mengaturnya sekali saja, melalui konstruktor. Dengan begitu, Anda hanya perlu melakukan itu nol periksa sekali:Jika memungkinkan, ini adalah pendekatan yang saya sarankan dalam situasi seperti itu. Jika Anda tidak bisa memperhatikan keamanan utas saat memilih cara menangani kemungkinan nol.
Sebagai komentar bonus, saya merasa kode Anda aneh dan dapat ditingkatkan. Semua:
dapat diganti dengan:
Yang memiliki fungsi yang sama, simpan untuk dapat mengatur bidang melalui setter properti, daripada metode dengan nama yang berbeda.
sumber
Ini adalah Anda pilihan.
Dengan membuat
public
metode, Anda menawarkan kesempatan kepada publik untuk menyebutnya. Ini selalu disertai dengan kontrak implisit tentang cara memanggil metode itu dan apa yang diharapkan ketika melakukannya. Kontrak ini mungkin (atau mungkin tidak) termasuk " yeah, uhhm, jika Anda lulusnull
sebagai nilai parameter, itu akan meledak tepat di wajah Anda. ". Bagian lain dari kontrak itu mungkin " oh, BTW: setiap kali dua utas melaksanakan metode ini pada saat yang sama, seekor anak kucing mati ".Jenis kontrak apa yang ingin Anda rancang terserah Anda. Yang penting adalah untuk
buat API yang bermanfaat dan konsisten dan
untuk mendokumentasikannya dengan baik, terutama untuk kasus tepi tersebut.
Apa kemungkinan kasus bagaimana metode Anda dipanggil?
sumber
Jawaban lainnya baik; Saya ingin memperluasnya dengan membuat komentar tentang pengubah aksesibilitas. Pertama, apa yang harus dilakukan jika
null
tidak pernah valid:metode umum dan terlindungi adalah metode di mana Anda tidak mengontrol penelepon. Mereka harus membuang ketika melewati nol. Dengan begitu Anda melatih penelepon Anda untuk tidak pernah memberi Anda nol, karena mereka ingin program mereka tidak crash.
intern dan swasta metode adalah mereka di mana Anda tidak mengontrol pemanggil. Mereka harus menegaskan ketika lulus nol; mereka mungkin akan crash nanti, tetapi setidaknya Anda mendapatkan pernyataan pertama, dan kesempatan untuk masuk ke debugger. Sekali lagi, Anda ingin melatih penelepon Anda untuk memanggil Anda dengan benar dengan membuatnya sakit ketika mereka tidak.
Sekarang, apa yang Anda lakukan jika mungkin valid untuk null dilewatkan? Saya akan menghindari situasi ini dengan pola objek nol . Yaitu: membuat instance khusus dari jenis dan mengharuskan itu digunakan setiap saat bahwa objek yang tidak valid diperlukan . Sekarang Anda kembali ke dunia menyenangkan melempar / menegaskan setiap penggunaan nol.
Misalnya, Anda tidak ingin berada dalam situasi ini:
dan di situs panggilan:
Karena (1) Anda melatih penelepon Anda bahwa null adalah nilai yang baik, dan (2) tidak jelas apa arti semantik dari nilai itu. Sebaliknya, lakukan ini:
Dan sekarang situs panggilan terlihat seperti ini:
dan sekarang pembaca kode tahu apa artinya.
sumber
if
s untuk objek nol tetapi menggunakan polimorfisme untuk membuatnya berperilaku benar.EmptyQueue
tipe yang melempar ketika dequeued, dan sebagainya.Person
kelas tidak dapat diubah, dan tidak mengandung konstruktor argumen nol, bagaimana Anda membuat instanceApproverNotRequired
atau AndaApproverUnknown
? Dengan kata lain, nilai apa yang akan Anda lewati konstruktor?Jawaban lain menunjukkan bahwa kode Anda dapat dibersihkan hingga tidak memerlukan pemeriksaan nol di mana Anda memilikinya, namun, untuk jawaban umum tentang apa yang dapat dilakukan pemeriksaan nol berguna untuk mempertimbangkan kode sampel berikut:
Ini memberi Anda keuntungan untuk pemeliharaan. Jika pernah terjadi kerusakan dalam kode Anda di mana sesuatu melewati objek nol ke metode Anda akan mendapatkan informasi yang berguna dari metode yang parameternya nol. Tanpa cek, Anda akan mendapatkan NullReferenceException di telepon:
Dan (mengingat bahwa properti Something adalah tipe nilai) a atau b bisa menjadi nol dan Anda tidak akan memiliki cara untuk mengetahui mana dari jejak stack. Dengan cek, Anda akan tahu persis objek mana yang nol, yang bisa sangat berguna ketika mencoba membongkar cacat.
Saya akan perhatikan bahwa pola dalam kode asli Anda:
Dapat memiliki kegunaannya dalam keadaan tertentu, juga dapat (mungkin lebih sering) memberi Anda cara yang sangat baik untuk menutupi masalah mendasar dalam basis kode Anda.
sumber
Tidak tidak sama sekali, tapi itu tergantung.
Anda hanya melakukan pemeriksaan referensi nol jika Anda ingin bereaksi.
Jika Anda melakukan pemeriksaan referensi nol dan melempar pengecualian yang diperiksa , Anda memaksa pengguna metode untuk bereaksi dan membiarkannya pulih. Program masih berjalan seperti yang diharapkan.
Jika Anda tidak melakukan pemeriksaan referensi nol (atau melemparkan pengecualian yang tidak dicentang), itu mungkin membuang yang tidak dicentang
NullReferenceException
, yang biasanya tidak ditangani oleh pengguna metode ini dan bahkan mungkin mematikan aplikasi. Ini berarti bahwa fungsinya jelas-jelas disalahpahami dan oleh karena itu aplikasinya salah.sumber
Sesuatu dari ekstensi ke jawaban @ null, tetapi dalam pengalaman saya, lakukan pemeriksaan nol, apa pun yang terjadi.
Pemrograman defensif yang baik adalah sikap yang Anda kode rutin saat ini dalam isolasi, dengan asumsi bahwa seluruh program mencoba untuk menghentikannya. Ketika Anda sedang menulis kode kritis misi di mana kegagalan bukanlah suatu pilihan, ini adalah satu-satunya cara untuk pergi.
Sekarang, bagaimana Anda benar-benar berurusan dengan suatu
null
nilai, itu sangat tergantung pada kasus penggunaan Anda.Jika
null
merupakan nilai yang dapat diterima, misalnya parameter kedua hingga kelima ke rutin MSVC _splitpath (), maka secara diam-diam berurusan dengannya adalah perilaku yang benar.Jika
null
seharusnya tidak pernah terjadi ketika memanggil rutin yang dimaksud, yaitu dalam kasus OP, kontrak APIAssignEntity()
harus telah dipanggil dengan valid,Entity
lalu melemparkan pengecualian, atau gagal memastikan Anda membuat banyak suara dalam proses.Jangan pernah khawatir tentang biaya cek nol, mereka sangat murah jika terjadi, dan dengan kompiler modern, analisis kode statis dapat dan akan menghilangkannya sepenuhnya jika kompiler menentukan bahwa itu tidak akan terjadi.
sumber