Jika nol adalah jahat, apa yang harus digunakan ketika suatu nilai bisa tidak ada?

26

Ini adalah salah satu aturan yang berulang kali berulang dan membingungkan saya.

Null adalah kejahatan dan harus dihindari sedapat mungkin .

Tapi, tapi - dari kenaifan saya, izinkan saya menjerit - kadang-kadang nilai BISA tidak ada!

Tolong izinkan saya bertanya ini pada contoh yang berasal dari kode mengerikan anti-pola ditunggangi saya sedang mengerjakan saat ini . Ini adalah, pada intinya, permainan berbasis web multiplayer, di mana giliran kedua pemain dijalankan secara bersamaan (seperti di Pokemon, dan sebaliknya untuk Catur).

Setelah setiap belokan, server menyiarkan daftar pembaruan ke kode JS sisi klien. Kelas Pembaruan:

public class GameUpdate
{
    public Player player;
    // other stuff
}

Kelas ini diserialisasi ke JSON dan dikirim ke pemain yang terhubung.

Sebagian besar pembaruan secara alami memiliki Player yang terkait dengannya - lagipula, perlu diketahui pemain mana yang melakukan perubahan ini. Namun, beberapa pembaruan tidak dapat memiliki Pemain yang terkait secara bermakna dengannya. Contoh: Permainan telah diikat secara paksa karena batas belok tanpa tindakan telah terlampaui. Untuk pembaruan seperti itu, saya pikir, sangat berarti memiliki pemain dibatalkan.

Tentu saja, saya bisa "memperbaiki" kode ini dengan memanfaatkan pewarisan:

public class GameUpdate
{
    // stuff
}

public class GamePlayerUpdate : GameUpdate
{
    public Player player;
    // other stuff
}

Namun, saya gagal melihat bagaimana ini merupakan peningkatan, karena dua alasan:

  • Kode JS sekarang hanya akan menerima objek tanpa Player sebagai properti yang ditentukan, yang sama seperti jika itu nol karena kedua kasus memerlukan pemeriksaan apakah nilainya ada atau tidak;
  • Jika bidang nullable lainnya ditambahkan ke kelas GameUpdate, saya harus dapat menggunakan banyak pewarisan untuk melanjutkan desing ini - tetapi MI itu jahat dalam dirinya sendiri (menurut programmer yang lebih berpengalaman) dan yang lebih penting, C # tidak memilikinya jadi saya tidak bisa menggunakannya.

Saya punya firasat bahwa bagian dari kode ini adalah salah satu dari sekian banyak di mana seorang programmer yang baik dan berpengalaman akan berteriak ngeri. Pada saat yang sama saya tidak bisa melihat bagaimana, di tempat ini, apakah ini nol menyakiti apa pun dan apa yang harus dilakukan sebagai gantinya.

Bisakah Anda menjelaskan masalah ini kepada saya?

gaazkam
sumber
2
Apakah pertanyaan ini khusus untuk JavaScript? Banyak bahasa memiliki tipe opsional untuk tujuan ini, tetapi saya tidak tahu apakah js memiliki satu (Meskipun Anda bisa membuatnya) atau apakah itu akan berfungsi dengan json (Meskipun itu adalah salah satu bagian dari pemeriksaan nol yang tidak menyenangkan, bukan seluruh kode Anda
Richard Tingle
9
Aturan itu salah. Saya agak heran bahwa seseorang akan berlangganan gagasan itu. Ada beberapa alternatif yang baik untuk membatalkan dalam beberapa kasus. null benar-benar baik untuk mewakili nilai yang hilang. Jangan biarkan aturan internet acak seperti itu mencegah Anda memercayai penilaian Anda sendiri yang beralasan.
usr
1
@ Usr - Saya setuju sepenuhnya. Null adalah temanmu. IMHO, tidak ada cara yang lebih jelas untuk mewakili nilai yang hilang. Ini dapat membantu Anda menemukan bug, dapat membantu Anda menangani kondisi kesalahan, dapat membantu Anda meminta maaf (mencoba / menangkap). Apa yang tidak disukai ?!
Scuba Steve
4
salah satu aturan pertama dalam mendesain perangkat lunak adalah "Selalu periksa nol". Mengapa ini bahkan menjadi masalah adalah pikiran membingungkan bagi saya.
MattE
1
@MATE - Tentu saja. Mengapa saya merancang pola desain padahal sebenarnya, Null adalah hal yang baik untuk dimiliki di kotak peralatan dasar Anda. Apa yang disarankan Graham, IMHO, menurut saya sebagai rekayasa berlebihan.
Scuba Steve

Jawaban:

20

Banyak hal yang lebih baik untuk dikembalikan daripada nol.

  • String kosong ("")
  • Koleksi kosong
  • Monad "opsional" atau "mungkin"
  • Fungsi yang diam-diam tidak melakukan apa pun
  • Objek penuh metode yang diam-diam tidak melakukan apa pun
  • Nilai yang berarti yang menolak premis yang salah dari pertanyaan
    (yang membuat Anda dianggap nol sejak awal)

Kuncinya adalah menyadari kapan harus melakukan ini. Null benar-benar bagus dalam meledakkan di wajah Anda, tetapi hanya ketika Anda dot off tanpa memeriksanya. Tidak bagus menjelaskan mengapa.

Ketika Anda tidak perlu meledak, dan Anda tidak ingin memeriksa nol, gunakan salah satu dari alternatif ini. Mungkin aneh untuk mengembalikan koleksi jika hanya memiliki 0 atau 1 elemen, tetapi sangat bagus untuk membiarkan Anda secara diam-diam menangani nilai yang hilang.

Monads terdengar mewah tetapi di sini yang mereka lakukan hanyalah membiarkan Anda memperjelas bahwa ukuran yang valid untuk koleksi adalah 0 dan 1. Jika Anda memilikinya, pertimbangkanlah.

Semua ini melakukan pekerjaan yang lebih baik untuk membuat maksud Anda lebih jelas daripada null. Itulah perbedaan terpenting.

Mungkin membantu untuk memahami mengapa Anda kembali sama sekali daripada hanya melemparkan pengecualian. Pengecualian pada dasarnya adalah cara untuk menolak asumsi (mereka bukan satu-satunya cara). Ketika Anda meminta titik di mana dua garis berpotongan Anda mengasumsikan mereka berpotongan. Mereka mungkin paralel. Haruskah Anda melemparkan pengecualian "garis paralel"? Yah Anda bisa, tetapi sekarang Anda harus menangani pengecualian itu di tempat lain atau membiarkannya menghentikan sistem.

Jika Anda lebih suka tinggal di tempat Anda berada, Anda dapat memanggang penolakan terhadap asumsi itu menjadi semacam nilai yang dapat mengekspresikan hasil atau penolakan. Itu tidak aneh. Kami melakukannya dalam aljabar sepanjang waktu. Triknya adalah menjadikan nilai itu bermakna sehingga kita dapat memahami apa yang terjadi.

Jika nilai yang dikembalikan dapat mengekspresikan hasil dan penolakan, maka perlu dikirim ke kode yang dapat menangani keduanya. Polimorfisme sangat kuat untuk digunakan di sini. Daripada hanya memperdagangkan cek kosong untuk isPresent()cek, Anda dapat menulis kode yang berperilaku baik. Baik dengan mengganti nilai kosong dengan nilai default atau diam-diam tidak melakukan apa pun.

Masalah dengan null adalah bisa berarti terlalu banyak hal. Jadi itu hanya menghancurkan informasi.


Dalam eksperimen tanpa pikiran favorit saya, saya meminta Anda membayangkan mesin Rube Goldbergian kompleks yang memberi sinyal pesan yang disandikan menggunakan cahaya berwarna dengan mengambil bola lampu berwarna dari sabuk konveyor, memasukkannya ke dalam soket, dan menyalakannya. Sinyal dikontrol oleh berbagai warna bohlam yang ditempatkan pada ban berjalan.

Anda telah diminta untuk membuat hal yang sangat mahal ini sesuai dengan RFC3.14159 yang menyatakan bahwa harus ada penanda di antara pesan-pesan. Penanda seharusnya tidak ada cahaya sama sekali. Itu harus berlangsung tepat satu pulsa. Jumlah waktu yang sama dengan cahaya satu warna biasanya bersinar.

Anda tidak dapat meninggalkan ruang di antara pesan karena alat ini mematikan alarm dan berhenti jika tidak menemukan bohlam untuk dimasukkan ke dalam soket.

Semua orang yang akrab dengan proyek ini ngeri saat menyentuh mesin atau itu kode kontrol. Tidak mudah untuk berubah. Apa yang kamu kerjakan? Anda bisa menyelam dan mulai merusak barang-barang. Mungkin memperbarui hal-hal ini desain yang mengerikan. Ya Anda bisa membuatnya jauh lebih baik. Atau Anda bisa berbicara dengan petugas kebersihan dan mulai mengumpulkan umbi yang terbakar.

Itu solusi polimorfik. Sistem bahkan tidak perlu tahu ada yang berubah.


Sangat menyenangkan jika Anda bisa melakukannya. Dengan menyandikan penolakan bermakna terhadap asumsi menjadi nilai, Anda tidak perlu memaksakan pertanyaan untuk berubah.

Berapa banyak apel yang akan Anda beri saya jika saya memberi Anda 1 apel? -1. Karena aku berutang 2 apel padamu dari kemarin. Di sini asumsi pertanyaan, "Anda akan berutang padaku", ditolak dengan angka negatif. Meskipun pertanyaannya salah, info yang bermakna disandikan dalam jawaban ini. Dengan menolaknya dengan cara yang berarti pertanyaan itu tidak dipaksa untuk berubah.

Namun, terkadang mengubah pertanyaan sebenarnya adalah jalan yang harus ditempuh. Jika asumsi dapat dibuat lebih dapat diandalkan, sementara masih bermanfaat, pertimbangkan untuk melakukannya.

Masalah Anda adalah, biasanya, pembaruan datang dari pemain. Tetapi Anda telah menemukan kebutuhan untuk pembaruan yang tidak datang dari pemain 1 atau pemain 2. Anda perlu pembaruan yang mengatakan waktu telah kedaluwarsa. Tidak ada pemain yang mengatakan ini, jadi apa yang harus Anda lakukan? Meninggalkan pemain null sepertinya sangat menggoda. Tetapi itu menghancurkan informasi. Anda mencoba menyandikan pengetahuan dalam lubang hitam.

Bidang pemain tidak memberi tahu Anda yang baru saja pindah. Anda pikir memang demikian, tetapi masalah ini membuktikan bahwa itu bohong. Bidang pemain memberi tahu Anda dari mana pembaruan itu berasal. Pembaruan waktu kedaluwarsa Anda datang dari jam permainan. Rekomendasi saya adalah memberi jam kredit yang layak dan mengubah nama playerbidang menjadi sesuatu seperti source.

Dengan cara ini jika server dimatikan dan harus menangguhkan permainan, ia dapat mengirimkan pembaruan yang melaporkan apa yang terjadi dan mencantumkan dirinya sebagai sumber pembaruan.

Apakah ini berarti Anda tidak boleh meninggalkan sesuatu begitu saja? Tidak. Tapi Anda perlu mempertimbangkan seberapa baik tidak ada yang dapat dianggap benar-benar berarti satu nilai standar baik yang terkenal. Tidak ada yang benar-benar mudah digunakan secara berlebihan. Jadi berhati-hatilah saat Anda menggunakannya. Ada sebuah mazhab pemikiran yang mencakup tidak mendukung apa pun . Ini disebut konvensi atas konfigurasi .

Saya suka itu sendiri tetapi hanya ketika konvensi jelas dikomunikasikan dalam beberapa cara. Seharusnya bukan cara untuk kabut pemula. Tetapi bahkan jika Anda menggunakannya, null masih bukan cara terbaik untuk melakukannya. Null adalah berbahaya apa-apa . Null berpura-pura menjadi sesuatu yang dapat Anda gunakan sampai Anda menggunakannya, lalu ia menghancurkan lubang di alam semesta (menghancurkan model semantik Anda). Kecuali jika meniup lubang di alam semesta adalah perilaku yang Anda butuhkan, mengapa Anda mengacaukan ini? Gunakan sesuatu yang lain.

candied_orange
sumber
9
"kembalikan koleksi jika hanya memiliki 0 atau 1 elemen" - Saya minta maaf tapi saya rasa saya tidak mendapatkannya :( Dari POV saya melakukan ini (a) menambahkan status tidak valid yang tidak perlu ke kelas (b) mengharuskan mendokumentasikan bahwa koleksi harus memiliki paling banyak 1 elemen (c) tampaknya tidak menyelesaikan masalah, karena cheking jika koleksi mengandung satu-satunya elemen sebelum mengaksesnya tetap diperlukan, atau pengecualian akan dilemparkan?
gaazkam
2
@gaazkam lihat? Saya bilang itu mengganggu orang. Namun itulah yang Anda lakukan setiap kali menggunakan string kosong.
candied_orange
16
Apa yang terjadi ketika ada nilai valid yang merupakan string atau set kosong?
Blrfl
5
Ngomong-ngomong @gaazkam, Anda tidak secara eksplisit memeriksa untuk melihat apakah koleksi memiliki item. Looping (atau pemetaan) pada daftar kosong adalah tindakan aman yang tidak berpengaruh.
RubberDuck
3
Saya tahu Anda menjawab pertanyaan tentang apa yang harus dilakukan seseorang alih-alih mengembalikan nol tetapi saya benar-benar tidak setuju dengan banyak poin ini. Namun karena kami tidak spesifik bahasa dan ada alasan untuk melanggar setiap aturan saya menolak untuk menurunkan
Liath
15

nullsebenarnya bukan masalah di sini. Masalahnya adalah bagaimana kebanyakan bahasa pemrograman saat ini menangani informasi yang hilang; mereka tidak menganggap serius masalah ini (atau setidaknya tidak memberikan dukungan dan keamanan yang paling dibutuhkan oleh penduduk bumi untuk menghindari perangkap). Ini mengarah ke semua masalah yang telah kita pelajari untuk ditakuti (Javas NullPointerException, Segfaults, ...).

Mari kita lihat bagaimana menangani masalah itu dengan cara yang lebih baik.

Lainnya menyarankan Pola Null-Object . Meskipun saya pikir itu kadang-kadang berlaku, ada banyak skenario di mana tidak ada nilai standar satu ukuran untuk semua. Dalam kasus tersebut, konsumen dari informasi yang mungkin tidak hadir harus dapat memutuskan sendiri apa yang harus dilakukan jika informasi tersebut hilang.

Ada bahasa pemrograman yang memberikan keamanan terhadap null pointer (disebut void safety) pada waktu kompilasi. Untuk memberikan beberapa contoh modern: Kotlin, Rust, Swift. Dalam bahasa tersebut, masalah informasi yang hilang telah dianggap serius dan menggunakan null benar-benar tidak menimbulkan rasa sakit - kompiler akan menghentikan Anda dari mendereferensi nullpointers.
Di Jawa, upaya telah dilakukan untuk membuat anotasi @ Nullable / @ NotNull bersama-sama dengan plugin compiler + IDE untuk mencapai efek yang sama. Itu tidak benar-benar berhasil tetapi itu di luar jangkauan untuk jawaban ini.

Tipe eksplisit dan generik yang menunjukkan kemungkinan tidak ada adalah cara lain untuk menghadapinya. Misal di Jawa ada java.util.Optional. Ini dapat bekerja:
Pada tingkat proyek atau perusahaan, Anda dapat menetapkan aturan / pedoman

  • hanya menggunakan perpustakaan pihak ke-3 berkualitas tinggi
  • selalu gunakan java.util.Optionalbukannull

Komunitas / ekosistem bahasa Anda mungkin telah menemukan cara lain untuk mengatasi masalah ini dengan lebih baik daripada kompiler / penerjemah.

marstato
sumber
Bisakah Anda menjelaskan, bagaimana Java Opsional lebih baik daripada nol? Saat saya membaca dokumentasi , mencoba mendapatkan nilai dari opsional menghasilkan pengecualian jika nilai tidak ada; jadi sepertinya kita berada di tempat yang sama: diperlukan cek dan jika kita lupa, kita kacau?
gaazkam
3
@gaazkam Anda benar, itu tidak memberikan keamanan waktu kompilasi. Juga, Opsional itu sendiri bisa nol, masalah lain. Tapi itu eksplisit (dibandingkan dengan variabel yang mungkin hanya komentar null / doc). Ini hanya memberikan manfaat nyata jika, untuk seluruh basis kode, aturan pengkodean diatur. Anda tidak boleh menetapkan hal-hal nol. Jika informasi mungkin tidak ada, gunakan Opsional. Itu memaksa kesaksian. Null-cheque biasa kemudian menjadi panggilan keOptional.isPresent
marstato
8
@marstato menggantikan cek kosong dengan isPresent() bukan penggunaan Opsional yang
direkomendasikan
10

Saya tidak melihat ada kelebihan dalam pernyataan bahwa null itu buruk. Saya memeriksa diskusi tentang hal itu dan mereka hanya membuat saya tidak sabar dan jengkel.

Null dapat digunakan salah, sebagai "nilai ajaib", ketika itu sebenarnya berarti sesuatu. Tetapi Anda harus melakukan beberapa pemrograman yang cukup memutar untuk sampai ke sana.

Null harus berarti "tidak ada", yang belum dibuat (belum) atau hilang (sudah) atau tidak tersedia jika itu argumen. Ini bukan keadaan, semuanya hilang. Dalam pengaturan OO di mana segala sesuatu dibuat dan dihancurkan sepanjang waktu yang merupakan kondisi yang valid dan bermakna berbeda dari negara bagian mana pun.

Dengan nol, Anda mendapatkan pukul saat runtime di mana Anda mendapatkan pukul pada waktu kompilasi dengan semacam alternatif nol jenis-aman.

Tidak sepenuhnya benar, saya bisa mendapatkan peringatan untuk tidak memeriksa nol sebelum menggunakan variabel. Dan itu tidak sama. Saya mungkin tidak ingin menyimpan sumber daya dari suatu objek yang tidak memiliki tujuan. Dan yang lebih penting, saya harus memeriksa alternatif yang sama untuk mendapatkan perilaku yang diinginkan. Jika saya lupa melakukan itu saya lebih suka mendapatkan NullReferenceException saat runtime daripada beberapa perilaku yang tidak ditentukan / tidak diinginkan.

Ada satu masalah yang harus diperhatikan. Dalam konteks multi-utas Anda harus melindungi terhadap kondisi balapan dengan menugaskan variabel lokal terlebih dahulu sebelum melakukan pemeriksaan untuk nol.

Masalah dengan null adalah bisa sangat berarti bagi banyak hal. Jadi itu hanya menghancurkan informasi.

Tidak, itu tidak / seharusnya tidak berarti apa-apa sehingga tidak ada yang bisa dihancurkan. Nama dan tipe variabel / argumen akan selalu memberi tahu apa yang hilang, hanya itu yang perlu diketahui.

Edit:

Saya setengah dari video yang di- link oleh candied_orange di salah satu jawaban lainnya tentang pemrograman fungsional dan ini sangat menarik. Saya bisa melihat manfaatnya dalam skenario tertentu. Pendekatan fungsional yang telah saya lihat sejauh ini, dengan potongan-potongan kode terbang di sekitar, biasanya membuat saya ngeri ketika digunakan dalam pengaturan OO. Tapi saya kira tempatnya. Di suatu tempat. Dan saya kira akan ada bahasa yang melakukan pekerjaan dengan baik menyembunyikan apa yang saya anggap sebagai kekacauan yang membingungkan setiap kali saya menemukannya di C #, bahasa yang menyediakan model yang bersih dan bisa diterapkan sebagai gantinya.

Jika model fungsional itu adalah pola pikir / kerangka kerja Anda, benar-benar tidak ada alternatif untuk mendorong kesalahan apa pun ke depan sebagai deskripsi kesalahan yang terdokumentasi dan pada akhirnya membiarkan penelepon berurusan dengan akumulasi sampah yang diseret sepanjang jalan kembali ke titik inisiasi. Rupanya ini hanya cara kerja pemrograman fungsional. Sebut saja batasan, sebut saja paradigma baru. Anda mungkin menganggapnya sebagai peretasan untuk murid fungsional yang memungkinkan mereka untuk melanjutkan di jalan yang tidak suci sedikit lebih jauh, Anda mungkin senang dengan cara pemrograman baru ini yang melepaskan kemungkinan baru yang menarik. Apa pun, tampaknya tidak ada gunanya bagi saya untuk melangkah ke dunia itu, kembali ke cara tradisional OO dalam melakukan sesuatu (ya, kita bisa melempar pengecualian, maaf), mengambil beberapa konsep darinya,

Konsep apa pun bisa digunakan dengan cara yang salah. Dari tempat saya berdiri, "solusi" yang disajikan bukanlah alternatif untuk penggunaan null yang tepat. Mereka adalah konsep dari dunia lain.

Martin Maat
sumber
9
Null menghancurkan pengetahuan tentang apa yang tidak diwakilinya. Ada jenis apa pun yang harus diabaikan dengan tenang. Jenis tidak ada yang perlu menghentikan sistem untuk menghindari keadaan yang tidak valid. Jenis tidak ada yang berarti programmer gagal dan perlu memperbaiki kodenya. Jenis tidak ada yang berarti pengguna meminta sesuatu yang tidak ada dan perlu diberitahukan dengan sopan. Maaf tidak. Semua ini telah dikodekan sebagai nol. Karena itu jika Anda mengkodekan semua ini sebagai nol maka Anda tidak perlu terkejut jika tidak ada yang tahu apa yang Anda maksud. Anda memaksa kami untuk menebak dari konteks.
candied_orange
3
@candied_orange Anda dapat membuat argumen yang sama persis tentang semua tipe opsional: Apakah None mewakili….
Deduplicator
3
@candied_orange Saya masih belum mengerti maksud Anda. Berbagai macam apa-apa? Jika Anda menggunakan null saat Anda seharusnya menggunakan enum, mungkin? Hanya ada satu jenis tidak ada. Alasan sesuatu menjadi tidak ada bisa bervariasi tetapi itu tidak mengubah ketiadaan atau respons yang tepat untuk sesuatu menjadi tidak ada. Jika Anda mengharapkan nilai di toko yang tidak ada di sana dan yang patut dicatat, Anda melempar atau masuk saat Anda meminta dan tidak mendapatkan apa-apa, Anda tidak memberikan null sebagai argumen ke suatu metode dan kemudian dalam metode itu mencoba mencari tahu mengapa Anda mendapat nol. Null tidak akan menjadi kesalahan.
Martin Maat
2
Null adalah kesalahan karena Anda memiliki kesempatan untuk menyandikan arti dari ketiadaan. Pemahaman Anda tentang apa-apa tidak menuntut penanganan segera atas apa-apa. 0, null, NaN, string kosong, set kosong, batal, objek nol, tidak berlaku, tidak diterapkan pengecualian, tidak ada yang datang dalam banyak banyak bentuk. Anda tidak harus melupakan apa yang Anda ketahui sekarang hanya karena Anda tidak ingin berurusan dengan apa yang Anda ketahui sekarang. Jika saya meminta Anda untuk mengambil akar kuadrat dari -1 Anda bisa melemparkan pengecualian untuk memberi tahu saya bahwa itu tidak mungkin dan melupakan semuanya atau menciptakan angka imajiner dan mempertahankan apa yang Anda ketahui.
candied_orange
1
@ MartinMaat Anda membuatnya terdengar seperti fungsi harus mengeluarkan pengecualian setiap kali harapan Anda dilanggar. Ini tidak benar. Sering ada kasus sudut untuk ditangani. Jika Anda benar-benar tidak dapat melihat itu baca ini
candied_orange
7

Pertanyaanmu.

Tapi, tapi - dari kenaifan saya, izinkan saya menjerit - kadang-kadang nilai BISA tidak ada!

Sepenuhnya dengan Anda di sana. Namun, ada beberapa peringatan yang akan saya bahas dalam sedetik. Tapi pertama-tama:

Null itu jahat dan harus dihindari sebisa mungkin.

Saya pikir ini adalah pernyataan yang bermaksud baik tetapi terlalu umum.

Null bukanlah kejahatan. Mereka bukan antipattern. Mereka bukan sesuatu yang harus dihindari bagaimanapun caranya. Namun, nol adalah cenderung rentan terhadap kesalahan (karena gagal memeriksa nol).

Tapi saya tidak mengerti bagaimana kegagalan untuk memeriksa null adalah bukti bahwa null secara inheren salah untuk digunakan.

Jika null jahat, maka indeks array dan pointer C ++ juga sama. Mereka tidak. Mereka mudah menembak diri sendiri dengan kaki, tetapi mereka tidak seharusnya dihindari dengan cara apa pun.

Untuk sisa jawaban ini, saya akan menyesuaikan niat "nulls are evil" ke yang lebih bernuansa " nulls harus ditangani secara bertanggung jawab ".


Solusi Anda.

Sementara saya setuju dengan pendapat Anda tentang penggunaan null sebagai aturan global; Saya tidak setuju dengan penggunaan nol Anda dalam kasus khusus ini.

Untuk meringkas umpan balik di bawah ini: Anda salah memahami tujuan pewarisan dan polimorfisme. Sementara argumen Anda untuk menggunakan null memiliki validitas, "bukti" selanjutnya tentang mengapa cara lain yang buruk dibangun di atas penyalahgunaan warisan, menjadikan poin Anda agak tidak relevan dengan masalah nol.

Sebagian besar pembaruan secara alami memiliki Player yang terkait dengannya - lagipula, perlu diketahui pemain mana yang melakukan perubahan ini. Namun, beberapa pembaruan tidak dapat memiliki Pemain yang terkait secara bermakna dengannya.

Jika pembaruan ini berbeda bermakna (yang mana), maka Anda memerlukan dua jenis pembaruan yang terpisah.

Pertimbangkan pesan, misalnya. Anda memiliki pesan kesalahan dan pesan obrolan IM. Tetapi sementara mereka mungkin berisi data yang sangat mirip (pesan string dan pengirim), mereka tidak berperilaku dengan cara yang sama. Mereka harus digunakan secara terpisah, sebagai kelas yang berbeda.

Apa yang Anda lakukan sekarang adalah membedakan secara efektif antara dua jenis pembaruan berdasarkan kekosongan properti Player. Itu sama dengan memutuskan antara pesan IM dan pesan kesalahan berdasarkan keberadaan kode kesalahan. Itu bekerja, tapi itu bukan pendekatan terbaik.

  • Kode JS sekarang hanya akan menerima objek tanpa Player sebagai properti yang ditentukan, yang sama seperti jika itu nol karena kedua kasus memerlukan pemeriksaan apakah nilainya ada atau tidak;

Argumen Anda bergantung pada kesalahan polimorfisme. Jika Anda memiliki metode yang mengembalikan GameUpdateobjek, umumnya tidak perlu peduli untuk membedakan GameUpdate, PlayerGameUpdateatau NewlyDevelopedGameUpdate.

Kelas dasar harus memiliki kontrak yang berfungsi penuh, yaitu propertinya didefinisikan pada kelas dasar dan bukan pada kelas turunan (yang dikatakan, nilai properti-properti dasar ini tentu saja dapat ditimpa dalam kelas turunan).

Ini membuat argumen Anda bisa diperdebatkan. Dalam praktik yang baik, Anda tidak boleh peduli bahwa GameUpdateobjek Anda memiliki atau tidak memiliki pemain. Jika Anda mencoba mengakses Playerproperti a GameUpdate, maka Anda melakukan sesuatu yang tidak masuk akal.

Bahasa yang diketik seperti C # bahkan tidak akan memungkinkan Anda untuk mencoba dan mengakses Playerproperti GameUpdatekarena GameUpdatetidak memiliki properti. Javascript jauh lebih longgar dalam pendekatannya (dan tidak memerlukan prakompilasi) sehingga Javascript tidak repot-repot mengoreksi Anda dan malah membuatnya meledak saat runtime.

  • Jika bidang nullable lainnya ditambahkan ke kelas GameUpdate, saya harus dapat menggunakan banyak pewarisan untuk melanjutkan desing ini - tetapi MI itu jahat dalam dirinya sendiri (menurut programmer yang lebih berpengalaman) dan yang lebih penting, C # tidak memilikinya jadi saya tidak bisa menggunakannya.

Anda seharusnya tidak membuat kelas berdasarkan penambahan properti yang dapat dibatalkan. Anda harus membuat kelas berdasarkan jenis pembaruan game yang unik dan fungsional .


Bagaimana cara menghindari masalah dengan null secara umum?

Saya pikir itu relevan untuk menguraikan bagaimana Anda dapat secara bermakna mengekspresikan tidak adanya nilai. Ini adalah kumpulan solusi (atau pendekatan buruk) tanpa urutan tertentu.

1. Tetap gunakan null.

Ini adalah pendekatan termudah. Namun, Anda mendaftar sendiri untuk satu ton cek nol dan (ketika gagal melakukannya) pemecahan masalah pengecualian referensi nol.

2. Gunakan nilai tidak berarti yang bukan nol

Contoh sederhana di sini adalah indexOf():

"abcde".indexOf("b"); // 1
"abcde".indexOf("f"); // -1

Alih-alih null, Anda dapatkan -1. Pada tingkat teknis, ini adalah nilai integer yang valid. Namun, nilai negatif adalah jawaban yang tidak masuk akal karena indeks diharapkan bilangan bulat positif.

Secara logis, ini hanya dapat digunakan untuk kasus-kasus di mana tipe pengembalian memungkinkan nilai-nilai yang lebih mungkin daripada yang dapat dikembalikan secara bermakna (indexOf = bilangan bulat positif; int = bilangan bulat positif dan positif)

Untuk jenis referensi, Anda masih bisa menerapkan prinsip ini. Misalnya, jika Anda memiliki entitas dengan ID integer, Anda bisa mengembalikan objek dummy dengan ID yang disetel ke -1, sebagai kebalikan dari nol.
Anda hanya perlu mendefinisikan "dummy state" di mana Anda dapat mengukur apakah suatu objek benar-benar dapat digunakan atau tidak.

Perhatikan bahwa Anda tidak harus bergantung pada nilai string yang telah ditentukan jika Anda tidak mengontrol nilai string. Anda mungkin mempertimbangkan untuk menetapkan nama pengguna ke "null" ketika Anda ingin mengembalikan objek kosong, tetapi Anda akan mengalami masalah ketika Christopher Null mendaftar untuk layanan Anda .

3. Lempar pengecualian sebagai gantinya.

Tidak, tidak. Pengecualian tidak harus ditangani untuk aliran logis.

Yang sedang berkata, ada beberapa kasus di mana Anda tidak ingin menyembunyikan pengecualian (nullreference), tetapi menunjukkan pengecualian yang sesuai. Sebagai contoh:

var existingPerson = personRepository.GetById(123);

Ini bisa melempar PersonNotFoundException(atau serupa) ketika tidak ada orang dengan ID 123.

Agak jelas, ini hanya dapat diterima untuk kasus-kasus di mana tidak menemukan item membuat mustahil untuk melakukan pemrosesan lebih lanjut. Jika tidak menemukan item mungkin dan aplikasi dapat dilanjutkan, maka Anda TIDAK PERNAH harus melemparkan pengecualian . Saya tidak bisa cukup menekankan hal ini.

Flater
sumber
5

Titik mengapa null buruk adalah bahwa nilai yang hilang dikodekan sebagai nol, tetapi nilai referensi apa pun bisa menjadi nol. Jika Anda memiliki C #, Anda mungkin masih kalah. Saya tidak melihat poin untuk menggunakan beberapa Opsional di sana karena jenis referensi sudah opsional. Tetapi untuk Java ada anotasi @Notnull, yang tampaknya dapat Anda atur di tingkat paket , kemudian untuk nilai yang bisa dilewatkan Anda bisa menggunakan anotasi @Nullable.

max630
sumber
Iya nih! Masalahnya adalah default yang tidak masuk akal.
Deduplicator
Saya tidak setuju. Pemrograman dalam gaya yang menghindari nol menyebabkan lebih sedikit variabel referensi menjadi nol, mengarah ke lebih sedikit variabel referensi yang tidak boleh nol menjadi nol. Ini bukan 100% tapi mungkin 90%, yang layak untuk IMO.
Pasang kembali Monica
1

Nulls bukanlah kejahatan, secara realistis tidak ada cara untuk mengimplementasikan salah satu alternatif tanpa pada titik tertentu berurusan dengan sesuatu yang terlihat seperti nol.

Namun, Nulls berbahaya . Sama seperti konstruksi tingkat rendah lainnya jika Anda salah tidak ada yang di bawah ini untuk menangkap Anda.

Saya pikir ada banyak argumen yang valid terhadap null:

  • Bahwa jika Anda sudah berada di domain tingkat tinggi, ada pola yang lebih aman daripada menggunakan model tingkat rendah.

  • Kecuali jika Anda membutuhkan keunggulan sistem tingkat rendah dan siap untuk menjadi hati-hati yang cocok maka mungkin Anda tidak boleh menggunakan bahasa tingkat rendah.

  • dll ...

Ini semua valid dan selanjutnya tidak harus membuat upaya menulis getter dan setter dll dipandang sebagai alasan umum untuk tidak mengikuti saran itu. Tidak terlalu mengejutkan banyak programmer yang sampai pada kesimpulan bahwa nol itu berbahaya dan malas (kombinasi yang buruk!), Tetapi seperti begitu banyak nasihat "universal" lainnya, mereka tidak seuniversal yang diucapkan.

Orang-orang baik yang menulis bahasa itu berpikir mereka akan berguna bagi seseorang. Jika Anda memahami keberatan dan masih menganggapnya tepat untuk Anda, tidak ada orang lain yang akan tahu.

drjpizzle
sumber
Masalahnya, "saran universal" biasanya tidak diucapkan sebagai "universal" seperti yang diklaim orang. Jika Anda menemukan sumber asli saran tersebut, mereka biasanya berbicara tentang peringatan yang diketahui oleh programmer berpengalaman. Apa yang terjadi adalah bahwa, ketika orang lain membaca saran, mereka cenderung mengabaikan peringatan itu dan hanya mengingat bagian "nasihat universal", jadi ketika mereka menghubungkan nasihat itu dengan orang lain, itu keluar jauh lebih "universal" daripada yang dimaksudkan pencetusnya. .
Nicol Bolas
1
@NicolBolas, Anda benar, tetapi sumber aslinya bukan saran yang dikutip kebanyakan orang, ini adalah pesan yang disampaikan. Poin yang ingin saya sampaikan hanyalah bahwa banyak programmer telah diberitahu 'tidak pernah melakukan X, X itu jahat / buruk / apa pun' dan, persis seperti yang Anda katakan, itu hilang banyak peringatan. Mungkin mereka dijatuhkan, mungkin mereka tidak pernah ada, hasil akhirnya sama.
drjpizzle
0

Java memiliki Optionalkelas dengan ifPresent+ lambda eksplisit untuk mengakses nilai apa pun dengan aman. Ini dapat dibuat di JS juga:

Lihat pertanyaan StackOverflow ini .

By the way: banyak purist akan menemukan penggunaan ini meragukan, tapi saya rasa tidak. Fitur opsional memang ada.

Joop Eggen
sumber
Tidak bisakah referensi java.lang.Optionalmenjadi nullterlalu?
Deduplicator
@Deduplicator Tidak, Optional.of(null)akan melempar pengecualian, Optional.ofNullable(null)akan memberikan Optional.empty()- nilai tidak ada.
Joop Eggen
Dan Optional<Type> a = null?
Deduplicator
@Deduplicator benar, tetapi di sana Anda harus menggunakan Optional<Optional<Type>> a = Optional.empty();;) - Dengan kata lain dalam JS (dan bahasa lain) hal-hal seperti itu tidak akan layak. Scala dengan nilai default akan dilakukan. Jawa mungkin dengan enum. JS saya ragu: Optional<Type> a = Optional.empty();.
Joop Eggen
Jadi, kami telah beralih dari satu tipuan dan potensial nullatau kosong, ke dua ditambah beberapa metode tambahan pada objek yang dimasukkan. Itu sebuah pencapaian.
Deduplicator
0

CAR Hoare menyebut nulls sebagai "kesalahan miliar dolar". Namun, penting untuk dicatat bahwa ia berpikir solusinya bukan "tidak ada null di mana saja" tetapi sebaliknya "pointer non-nullable sebagai default dan nulls hanya di mana mereka diperlukan secara semantik".

Masalah dengan null dalam bahasa yang mengulang kesalahannya adalah Anda tidak bisa mengatakan bahwa suatu fungsi mengharapkan data yang tidak dapat dibatalkan; itu pada dasarnya tidak bisa diungkapkan dalam bahasa. Yang memaksa fungsi untuk memasukkan banyak boilerplate null-handling asing yang tidak berguna, dan melupakan cek null adalah sumber umum bug.

Untuk meretas kurangnya ekspresi yang mendasar, beberapa komunitas (mis. Komunitas Scala) tidak menggunakan null. Dalam Scala, jika Anda menginginkan nilai tipe nullable T, Anda mengambil argumen tipe Option[T], dan jika Anda menginginkan nilai non-nullable, Anda hanya mengambil a T. Jika seseorang memasukkan nol ke dalam kode Anda, kode Anda akan meledak. Namun, itu dianggap bahwa kode panggilan adalah kode kereta. Optionadalah pengganti yang cukup bagus untuk null, karena pola penanganan-nol yang umum diekstraksi ke fungsi perpustakaan seperti .map(f)atau .getOrElse(someDefault).

pengguna311781
sumber