Cara terbaik untuk menangani null di Jawa? [Tutup]


21

Saya memiliki beberapa kode yang gagal karena NullPointerException. Suatu metode dipanggil pada objek yang objeknya tidak ada.

Namun, ini membuat saya berpikir tentang cara terbaik untuk memperbaikinya. Apakah saya selalu kode untuk nulls defensif sehingga saya kode bukti masa depan untuk pengecualian null pointer, atau haruskah saya memperbaiki penyebab nol sehingga tidak akan terjadi hilir.

Apa yang kamu pikirkan?


5
Mengapa Anda tidak "memperbaiki penyebab nol"? Bisakah Anda memberikan contoh mengapa ini bukan satu-satunya pilihan yang masuk akal? Jelas, sesuatu harus mendorong Anda menjauh dari yang sudah jelas. Apa itu? Mengapa tidak memperbaiki root menyebabkan pilihan pertama Anda?
S.Lott

Memperbaiki dengan baik "akar penyebab" masalah ini dapat membantu dalam jangka pendek, tetapi jika kode tersebut masih tidak secara defensif memeriksa nol dan digunakan kembali oleh proses lain yang mungkin menghasilkan null, saya harus memperbaikinya lagi.
Shaun F

2
@ Samun F: Jika kodenya rusak, itu rusak. Saya tidak mengerti bagaimana mungkin kode menghasilkan nol yang tidak terduga dan tidak diperbaiki. Jelas, ada beberapa hal "manajemen bodoh" atau "politik" yang terjadi. Atau sesuatu yang memungkinkan kode yang salah bisa diterima. Bisakah Anda menjelaskan bagaimana kode yang salah tidak diperbaiki? Apa latar belakang politik yang membuat Anda mengajukan pertanyaan ini?
S.Lott

1
"rentan terhadap pembuatan data kemudian yang memiliki nol di dalamnya" Apa artinya itu? Jika "Saya dapat memperbaiki rutinitas pembuatan data" maka tidak ada lagi kerentanan. Saya tidak dapat memahami apa artinya "pembuatan data nanti yang memiliki null" ini dapat berarti? Perangkat lunak Buggy?
S.Lott

1
@ S.Lott, enkapsulasi dan tanggung jawab tunggal. Semua kode Anda tidak "tahu" apa yang dilakukan semua kode Anda yang lain. Jika suatu kelas menerima Froobinator, itu membuat asumsi sesedikit mungkin tentang siapa yang menciptakan Froobinator dan bagaimana. Itu dibuat dari kode "eksternal", atau itu hanya berasal dari bagian berbeda dari basis kode. Saya tidak mengatakan Anda tidak boleh memperbaiki kode kereta; tetapi cari "pemrograman defensif" dan Anda akan menemukan apa yang dimaksud OP.
Paul Draper

Jawaban:


45

Jika nol adalah parameter input yang masuk akal untuk metode Anda, perbaiki metode tersebut. Jika tidak, perbaiki pemanggil. "Masuk akal" adalah istilah yang fleksibel, jadi saya mengusulkan tes berikut: Bagaimana metode ini harus memberikan input nol? Jika Anda menemukan lebih dari satu jawaban yang mungkin, maka nol bukanlah input yang masuk akal.


1
Ini sangat sederhana.
biziclop

3
Jambu biji memiliki beberapa metode pembantu yang sangat bagus sehingga pemeriksaan nol sesederhana Precondition.checkNotNull(...). Lihat stackoverflow.com/questions/3022319/…

Aturan saya adalah menginisialisasi semuanya menjadi default yang masuk akal jika mungkin dan khawatir tentang pengecualian nol setelahnya.
davidk01

Thorbjørn: Jadi itu menggantikan NullPointerException yang tidak disengaja dengan NullPointerException yang disengaja? Dalam beberapa kasus, melakukan pemeriksaan awal mungkin bermanfaat atau bahkan perlu; tapi saya takut banyak cek kosong yang tidak masuk akal yang menambah sedikit nilai dan hanya membuat program lebih besar.
user281377

6
Jika null bukan parameter input yang masuk akal untuk metode Anda, tetapi panggilan metode tersebut kemungkinan akan melewati null secara tidak sengaja (terutama jika metode ini bersifat publik), Anda mungkin juga ingin agar metode Anda melempar nilai IllegalArgumentExceptionketika diberikan nol. Ini memberi sinyal kepada penelepon tentang metode bahwa bug ada dalam kode mereka (bukan dalam metode itu sendiri).
Brian

20

Jangan gunakan null, gunakan Opsional

Seperti yang telah Anda tunjukkan, salah satu masalah terbesar nulldi Jawa adalah dapat digunakan di mana saja , atau setidaknya untuk semua jenis referensi.

Tidak mungkin mengatakan itu bisa nulldan apa yang tidak bisa.

Java 8 memperkenalkan pola yang lebih baik banyak: Optional.

Dan contoh dari Oracle:

String version = "UNKNOWN";
if(computer != null) {
  Soundcard soundcard = computer.getSoundcard();
  if(soundcard != null) {
    USB usb = soundcard.getUSB();
    if(usb != null) {
      version = usb.getVersion();
    }
  }
}

Jika masing-masing dari ini dapat atau tidak dapat mengembalikan nilai yang berhasil, Anda dapat mengubah API ke Optionals:

String name = computer.flatMap(Computer::getSoundcard)
    .flatMap(Soundcard::getUSB)
    .map(USB::getVersion)
    .orElse("UNKNOWN");

Dengan secara eksplisit menyandikan opsionalitas dalam tipe, antarmuka Anda akan jauh lebih baik, dan kode Anda akan lebih bersih.

Jika Anda tidak menggunakan Java 8, Anda dapat melihatnya com.google.common.base.Optionaldi Google Guava.

Penjelasan yang baik oleh tim Guava: https://github.com/google/guava/wiki/UsingAndAvoidingNullExplained

Penjelasan yang lebih umum tentang kerugian hingga nol, dengan contoh-contoh dari beberapa bahasa: https://www.lucidchart.com/techblog/2015/08/31/the-worst-mistake-of-computer-science/


@Nullull, @Nullable

Java 8 menambahkan anotasi ini untuk membantu alat pengecekan kode seperti IDE menangkap masalah. Mereka cukup terbatas dalam efektivitasnya.


Periksa kapan masuk akal

Jangan menulis 50% dari kode Anda memeriksa nol, terutama jika tidak ada yang masuk akal kode Anda dapat dilakukan dengan nullnilai.

Di sisi lain, jika nullbisa digunakan dan berarti sesuatu, pastikan untuk menggunakannya.


Pada akhirnya, Anda jelas tidak dapat menghapus nulldari Jawa. Saya sangat menyarankan untuk mengganti Optionalabstraksi jika memungkinkan, dan memeriksa nullwaktu lain yang Anda bisa lakukan sesuatu yang masuk akal tentangnya.


2
Biasanya, jawaban pertanyaan empat tahun akhirnya dihapus karena kualitasnya rendah. Pekerjaan bagus berbicara tentang bagaimana Java 8 (yang tidak ada saat itu) dapat menyelesaikan masalah.

tetapi tidak relevan dengan pertanyaan aslinya tentu saja. Dan apa yang mengatakan argumen itu memang opsional?
jwenting

5
@jwenting Ini benar-benar relevan dengan pertanyaan awal. Jawaban untuk "Apa cara terbaik untuk menggerakkan kuku dengan obeng" adalah "Gunakan palu, bukan obeng".
Daenyth

@ jwenting, saya pikir saya membahasnya, tetapi untuk kejelasan: apakah argumennya bukan opsional, saya biasanya tidak akan memeriksa nol. Anda akan memiliki 100 baris pemeriksaan nol dan 40 baris zat. Periksa null di lebih banyak antarmuka publik, atau ketika null benar-benar masuk akal (mis. Argumen itu opsional).
Paul Draper

4
@ J-Boss, bagaimana, di Jawa, Anda tidak akan dicentang NullPointerException? A NullPointerExceptiondapat terjadi secara harfiah setiap kali Anda memanggil metode instance di Java. Anda akan memiliki throws NullPointerExceptionhampir di setiap metode tunggal.
Paul Draper

8

Ada beberapa cara untuk menangani ini, membumbui kode Anda dengan if (obj != null) {}tidak ideal, berantakan, menambah kebisingan ketika membaca kode nanti selama siklus pemeliharaan dan rentan kesalahan, karena mudah untuk lupa melakukan pembungkus pelat ketel ini.

Itu tergantung pada apakah Anda ingin kode untuk melanjutkan eksekusi diam-diam atau gagal. Apakah nullada kesalahan atau kondisi yang diharapkan.

Apa itu null?

Dalam setiap definisi dan kasus, Null mewakili kurangnya data. Nulls dalam database mewakili kurangnya nilai untuk kolom itu. nol Stringtidak sama dengan kosong String, nol inttidak sama dengan NOL, secara teori. Dalam prakteknya "itu tergantung". Kosong Stringdapat membuat Null Objectimplementasi yang baik untuk kelas String, karena Integeritu tergantung pada logika bisnis.

Alternatifnya adalah:

  1. The Null Objectpola. Buat instance objek Anda yang mewakili nullnegara, dan inisialisasi semua referensi untuk jenis itu dengan referensi untuk Nullimplementasi. Ini berguna untuk objek tipe nilai sederhana yang tidak memiliki banyak referensi ke objek lain yang juga bisa nulldan diharapkan nullsebagai keadaan yang valid.

  2. Gunakan alat Berorientasi Aspek untuk menenun metode dengan Null Checkeraspek yang mencegah parameter menjadi nol. Ini untuk kasus di mana nullada kesalahan.

  3. Gunakan assert()tidak jauh lebih baik daripada if (obj != null){}tetapi lebih sedikit noise.

  4. Gunakan alat Penegakan Kontrak seperti Kontrak Untuk Jawa . Kasus penggunaan yang sama dengan sesuatu seperti AspectJ tetapi lebih baru dan menggunakan Anotasi alih-alih file konfigurasi eksternal. Terbaik dari kedua karya Aspects dan Asserts.

1 adalah solusi ideal ketika data yang masuk diketahui nulldan perlu diganti dengan beberapa nilai default sehingga konsumen hulu tidak perlu berurusan dengan semua kode boilerplate null checking. Memeriksa nilai-nilai default yang diketahui akan lebih ekspresif juga.

2, 3, dan 4 hanyalah generator Exception alternatif yang nyaman untuk diganti NullPointerExceptiondengan sesuatu yang lebih informatif, yang selalu dan perbaikan.

Pada akhirnya

nulldi Jawa hampir selalu merupakan kesalahan logika. Anda harus selalu berusaha menghilangkan akar penyebab NullPointerExceptions. Anda harus berusaha untuk tidak menggunakan nullkondisi sebagai logika bisnis. if (x == null) { i = someDefault; }buat saja assigment awal ke instance objek default itu.


Jika memungkinkan, pola objek nol adalah caranya, itu adalah cara paling anggun untuk menangani kasus nol. Jarang bahwa Anda ingin null menyebabkan barang meledak dalam produksi!
Richard Miskin

@ Richard: kecuali jika itu nulltidak terduga maka itu adalah kesalahan sejati, maka semuanya akan berhenti total.

Null dalam database dan kode pemrograman ditangani secara berbeda dalam satu aspek penting: Dalam pemrograman, null == null(bahkan dalam PHP), tetapi dalam Database, null != nullkarena dalam database itu mewakili nilai yang tidak diketahui daripada "tidak ada". Dua yang tidak diketahui tidak harus sama, sedangkan dua tidak sama.
Simon Forsberg

8

Menambahkan cek nol dapat membuat pengujian bermasalah. Lihat kuliah hebat ini ...

Lihat kuliah ceramah Google Tech: "Pembicaraan Kode Bersih - Jangan Mencari Hal-Hal!" dia membicarakannya sekitar menit 24

http://www.youtube.com/watch?v=RlfLCWKxHJ0&list=PL693EFD059797C21E

Pemrograman paranoid melibatkan menambahkan cek nol di mana-mana. Sepertinya ide yang baik pada awalnya namun dari perspektif pengujian, itu membuat pengujian jenis cek nol Anda sulit untuk dihadapi.

Juga, ketika Anda membuat prasyarat untuk keberadaan beberapa objek seperti

class House(Door door){

    .. null check here and throw exception if Door is NULL
    this.door = door
}

itu mencegah Anda membuat House karena Anda akan melemparkan pengecualian. Anggap kasus pengujian Anda membuat benda tiruan untuk menguji sesuatu selain Door, yah, Anda tidak dapat melakukan ini karena pintu diperlukan

Mereka yang telah menderita melalui neraka penciptaan Mock sangat menyadari jenis gangguan ini.

Singkatnya, rangkaian uji Anda harus cukup kuat untuk menguji Pintu, Rumah, Atap atau apa pun tanpa harus paranoid karenanya. Seroiusly, betapa sulitnya menambahkan tes cek nol untuk objek tertentu dalam pengujian Anda :)

Anda harus selalu lebih suka aplikasi yang berfungsi karena Anda memiliki beberapa tes yang MEMBUKTIKAN kerjanya, daripada BERHARAP berfungsi hanya karena Anda memiliki sejumlah besar cek nol prasyarat di semua tempat


4

tl; dr - BAIK untuk memeriksa hal-hal yang tidak terduga nulltetapi BURUK untuk suatu aplikasi untuk mencoba menjadikannya baik.

Detail

Jelas, ada situasi di mana nullinput atau output yang valid untuk suatu metode, dan yang lain di mana tidak.

Aturan # 1:

Javadoc untuk metode yang memungkinkan nullparameter atau mengembalikan nullnilai harus dengan jelas mendokumentasikan ini, dan menjelaskan apa nullartinya.

Aturan # 2:

Metode API tidak boleh ditentukan sebagai menerima atau mengembalikan nullkecuali ada alasan bagus untuk melakukannya.

Dengan spesifikasi yang jelas dari "kontrak" metode vis-a-vis null, ini merupakan kesalahan pemrograman untuk melewatkan atau mengembalikan nulltempat yang seharusnya tidak Anda miliki.

Aturan # 3:

Aplikasi seharusnya tidak berusaha untuk "membuat kesalahan pemrograman".

Jika suatu metode mendeteksi suatu nullyang seharusnya tidak ada di sana, itu seharusnya tidak berusaha untuk memperbaiki masalah dengan mengubahnya menjadi sesuatu yang lain. Itu hanya menyembunyikan masalah dari programmer. Sebagai gantinya, ini harus memungkinkan NPE terjadi, dan menyebabkan kegagalan sehingga programmer dapat mengetahui apa penyebab root dan memperbaikinya. Semoga kegagalan akan diperhatikan selama pengujian. Jika tidak, itu berarti sesuatu tentang metodologi pengujian Anda.

Aturan # 4:

Jika memungkinkan, tulis kode Anda untuk mendeteksi kesalahan pemrograman lebih awal.

Jika Anda memiliki bug dalam kode Anda yang menghasilkan banyak NPE, hal yang paling sulit adalah mencari tahu dari mana nullnilai - nilai itu berasal. Salah satu cara untuk membuat diagnosis lebih mudah adalah dengan menulis kode Anda sehingga nullterdeteksi sesegera mungkin. Seringkali Anda dapat melakukan ini bersamaan dengan cek lainnya; misalnya

public setName(String name) {
    // This also detects `null` as an (intended) side-effect
    if (name.length() == 0) {
        throw new IllegalArgumentException("empty name");
    }
}

(Jelas ada kasus di mana aturan 3 dan 4 harus diimbangi dengan kenyataan. Misalnya (aturan 3), beberapa jenis aplikasi harus berusaha untuk melanjutkan setelah mendeteksi kemungkinan kesalahan pemrograman. Dan (aturan 4) terlalu banyak memeriksa parameter buruk dapat memiliki dampak kinerja.)


3

Saya akan merekomendasikan memperbaiki metode menjadi defensif. Contohnya:

String go(String s){  
    return s.toString();  
}

Harus lebih seperti ini:

String go(String s){  
    if(s == null){  
       return "";  
    }     
    return s.toString();  
}

Saya menyadari ini benar-benar sepele, tetapi jika penyerang mengharapkan suatu objek memberi mereka objek default yang tidak akan menyebabkan nol untuk diteruskan.


10
Ini memiliki efek samping yang buruk karena penelepon (pemrogram yang menentang API ini) dapat mengembangkan kebiasaan melewatkan null sebagai parameter untuk mendapatkan perilaku "default".
Goran Jovic

2
Jika ini Java, Anda seharusnya tidak menggunakan new String()sama sekali.
biziclop

1
@biziclop sebenarnya jauh lebih penting daripada yang disadari kebanyakan orang.
Woot4Moo

1
Menurut pendapat saya, lebih masuk akal untuk menegaskan dan melemparkan pengecualian jika argumennya nol, karena jelas kesalahan penelepon. Jangan diam-diam "memperbaiki" kesalahan pemanggil; sebaliknya, buat mereka sadar akan hal itu.
Andres F.

1
@ Woot4Moo Jadi tulis kode Anda sendiri yang melempar pengecualian. Yang penting adalah memberi tahu penelepon bahwa argumen yang mereka sampaikan salah, dan memberi tahu mereka sesegera mungkin. Mengoreksi nulls secara diam-diam adalah opsi yang paling buruk, lebih buruk daripada melempar NPE.
Andres F.

2

Aturan umum berikut tentang null telah banyak membantu saya sejauh ini:

  1. Jika data berasal dari luar kendali Anda, periksa secara sistematis nol dan bertindak dengan tepat. Ini berarti melempar pengecualian yang masuk akal ke fungsi (dicentang atau tidak dicentang, pastikan nama pengecualian memberi tahu Anda dengan tepat apa yang sedang terjadi.). Tapi JANGAN PERNAH jangan sampai ada nilai yang longgar di sistem Anda yang berpotensi membawa kejutan.

  2. Jika Null berada dalam domain nilai yang sesuai untuk model data Anda, tangani dengan tepat.

  3. Ketika mengembalikan nilai, cobalah untuk tidak mengembalikan nol kapan pun memungkinkan. Selalu lebih suka daftar kosong, string kosong, pola objek kosong. Pertahankan nulls sebagai nilai yang dikembalikan saat ini adalah representasi data terbaik untuk kasus penggunaan tertentu.

  4. Mungkin yang paling penting dari semuanya ... Tes, tes, dan tes lagi. Saat menguji kode Anda, jangan mengujinya sebagai pembuat kode, teskan itu sebagai dominatrix psikopat Nazi dan coba bayangkan segala macam cara untuk menyiksa kode itu.

Ini cenderung sedikit di sisi paranoid tentang nulls sering mengarah ke fasad dan proksi yang menghubungkan sistem ke dunia luar dan nilai-nilai yang dikontrol ketat di dalam dengan redundansi berlimpah. Dunia luar di sini berarti hampir semua yang saya sendiri tidak kode. Memang menanggung runtime biaya tetapi sejauh ini saya jarang harus mengoptimalkan ini dengan membuat "bagian aman nol" dari kode. Saya harus mengatakan bahwa saya kebanyakan membuat sistem berjalan lama untuk perawatan kesehatan dan hal terakhir yang saya inginkan adalah subsistem antarmuka yang membawa alergi yodium Anda ke pemindai CT macet karena null pointer yang tidak terduga karena orang lain dalam sistem lain tidak pernah menyadari bahwa nama dapat mengandung apostrof atau karakter seperti 但 耒耨。

lagian .... 2 sen saya


1

Saya akan mengusulkan menggunakan pola Option / Some / None dari bahasa fungsional. Saya bukan spesialis Java, tetapi saya secara intensif menggunakan implementasi saya sendiri dari pola ini dalam proyek C # saya, dan saya yakin itu bisa dikonversi ke dunia Java.

Gagasan di balik pola ini adalah: jika secara logis memiliki situasi ketika ada kemungkinan tidak adanya nilai (misalnya ketika mengambil kembali dari database dengan id) Anda memberikan objek dengan tipe Option [T], di mana T adalah nilai yang mungkin . I kasus tidak adanya objek nilai kelas Tidak ada [T] kembali, dalam kasus jika ada nilai - objek Beberapa [T] kembali, mengandung nilai.

Dalam hal ini, Anda harus menangani kemungkinan tidak adanya nilai, dan jika Anda melakukan peninjauan kode, Anda dapat dengan mudah menemukan placec penanganan yang salah. Untuk mendapatkan inspirasi dari implementasi bahasa C # lihat repositori bitbucket saya https://bitbucket.org/mikegirkin/optionsomenone

Jika Anda mengembalikan nilai nol, dan secara logis setara dengan kesalahan (misalnya tidak ada file, atau tidak dapat terhubung) Anda harus melempar pengecualian, atau menggunakan pola penanganan kesalahan lainnya. Gagasan di balik ini, sekali lagi, Anda berakhir dengan solusi, ketika Anda harus menangani situasi tidak adanya nilai, dan dapat dengan mudah menemukan tempat-tempat penanganan yang salah dalam kode.


0

Nah jika salah satu hasil yang mungkin dari metode Anda adalah nilai nol maka Anda harus kode defensif untuk itu, tetapi jika suatu metode seharusnya mengembalikan non-nol tetapi bukankah saya akan memperbaikinya pasti.

Seperti biasa itu tergantung pada kasusnya, seperti kebanyakan hal dalam hidup :)



0
  1. Jangan menggunakan jenis Opsional, kecuali jika itu benar-benar opsional, sering kali jalan keluar lebih baik ditangani sebagai pengecualian, kecuali Anda benar-benar mengharapkan null sebagai opsi, dan bukan karena Anda secara teratur menulis kode kereta.

  2. Masalahnya bukan nol sebagai jenis penggunaannya, seperti yang ditunjukkan artikel Google. Masalahnya adalah bahwa nulls harus diperiksa dan ditangani, seringkali mereka dapat keluar dengan anggun.

  3. Ada sejumlah kasus nol yang mewakili kondisi tidak valid di area di luar operasi rutin program (input pengguna tidak valid, masalah database, kegagalan jaringan, file yang hilang, data korup), yang merupakan pengecualian untuk memeriksa, menangani mereka, bahkan jika itu hanya untuk login saja.

  4. Penanganan pengecualian diizinkan berbagai prioritas dan hak istimewa operasional di JVM sebagai lawan dari jenis, seperti Opsional, yang masuk akal untuk sifat luar biasa dari kejadiannya, termasuk dukungan awal untuk pemuatan malas penangan pawang yang diprioritaskan ke dalam memori, karena Anda tidak membutuhkannya berkeliaran sepanjang waktu.

  5. Anda tidak perlu menulis penangan nol di semua tempat, hanya di mana mereka mungkin terjadi, di mana pun Anda berpotensi mengakses layanan data yang tidak dapat diandalkan, dan karena sebagian besar ini jatuh ke dalam beberapa pola umum yang dapat dengan mudah diabstraksi, Anda hanya perlu panggilan pawang, kecuali dalam kasus yang jarang terjadi.

Jadi saya kira jawaban saya akan membungkusnya dalam pengecualian diperiksa dan menanganinya, atau memperbaiki kode tidak dapat diandalkan jika itu sesuai kemampuan Anda.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.