Apakah lebih baik mengembalikan koleksi kosong atau kosong?

420

Itu semacam pertanyaan umum (tapi saya menggunakan C #), apa cara terbaik (praktik terbaik), apakah Anda mengembalikan koleksi kosong atau kosong untuk metode yang memiliki koleksi sebagai tipe pengembalian?

Omu
sumber
5
Um, tidak cukup CPerkins. rads.stackoverflow.com/amzn/click/0321545613
3
@CPerkins - Ya, ada. Hal ini jelas dinyatakan dalam pedoman desain kerangka kerja .NET Microsoft. Lihat jawaban RichardOD untuk detailnya.
Greg Beech
51
HANYA jika artinya "Saya tidak bisa menghitung hasilnya" jika Anda mengembalikan nol. Null seharusnya tidak memiliki semantik "kosong", hanya "hilang" atau "tidak dikenal". Lebih detail dalam artikel saya tentang subjek: blogs.msdn.com/ericlippert/archive/2009/05/14/…
Eric Lippert
3
Bozho: "any" adalah banyak sekali bahasa. Bagaimana dengan Common Lisp, di mana daftar kosong persis sama dengan nilai nol? :-)
Ken
9
Sebenarnya, "duplikat" adalah tentang metode yang mengembalikan objek, bukan koleksi. Ini adalah skenario yang berbeda dengan jawaban yang berbeda.
GalacticCowboy

Jawaban:

499

Koleksi kosong. Selalu.

Ini menyebalkan:

if(myInstance.CollectionProperty != null)
{
  foreach(var item in myInstance.CollectionProperty)
    /* arrgh */
}

Ini dianggap sebagai praktik terbaik untuk TIDAK PERNAH kembali nullketika mengembalikan koleksi atau dihitung. SELALU mengembalikan enumerable / koleksi kosong. Ini mencegah omong kosong yang disebutkan di atas, dan mencegah mobil Anda diganggu oleh rekan kerja dan pengguna kelas Anda.

Saat berbicara tentang properti, selalu atur properti Anda sekali dan lupakan

public List<Foo> Foos {public get; private set;}

public Bar() { Foos = new List<Foo>(); }

Di .NET 4.6.1, Anda bisa memadatkan ini cukup banyak:

public List<Foo> Foos { get; } = new List<Foo>();

Saat berbicara tentang metode yang mengembalikan enumerable, Anda dapat dengan mudah mengembalikan enumerable kosong daripada null...

public IEnumerable<Foo> GetMyFoos()
{
  return InnerGetFoos() ?? Enumerable.Empty<Foo>();
}

Menggunakan Enumerable.Empty<T>()dapat dilihat sebagai lebih efisien daripada mengembalikan, misalnya, koleksi atau array kosong baru.

Ryan Lundy
sumber
24
Saya setuju dengan Will, tetapi saya pikir "selalu" sedikit berlebihan. Sementara koleksi kosong mungkin berarti "0 item", mengembalikan Null dapat berarti "tidak ada koleksi sama sekali" - mis. jika Anda mem-parsing HTML, mencari <ul> dengan id = "foo", <ul id = "foo"> </ul> dapat mengembalikan koleksi kosong; jika tidak ada <ul> dengan id = "foo" pengembalian nol akan lebih baik (kecuali jika Anda ingin menangani kasus ini dengan pengecualian)
Patonza
30
itu tidak selalu pertanyaan apakah atau tidak "Anda dapat dengan mudah mengembalikan array kosong", melainkan apakah array kosong mungkin menyesatkan dalam konteks saat ini. Array kosong sebenarnya berarti sesuatu, seperti halnya null. Untuk mengatakan bahwa Anda harus selalu mengembalikan array kosong daripada nol, hampir sesat seperti mengatakan Anda metode boolean harus selalu mengembalikan true. Kedua nilai yang mungkin menyampaikan makna.
David Hedlund
31
Anda seharusnya lebih suka mengembalikan System.Linq.Enumerable.Empty <Foo> () daripada Foo baru [0]. Itu lebih eksplisit dan menghemat satu alokasi memori (setidaknya, dalam implementasi .NET saya yang diinstal).
Trillian
4
@ Will: OP: "apakah Anda mengembalikan koleksi kosong atau kosong untuk metode yang memiliki koleksi sebagai tipe pengembalian". Saya pikir dalam kasus ini apakah itu penting IEnumerableatau ICollectiontidak. Ngomong-ngomong, jika kamu memilih sesuatu yang bertipe ICollectionmereka juga kembali null... Aku ingin mereka mengembalikan koleksi kosong, tapi aku bertemu mereka kembali null, jadi kupikir aku akan menyebutkannya di sini. Saya akan mengatakan bahwa koleksi default enumerable kosong, bukan null. Saya tidak tahu itu subjek yang sensitif.
Matthijs Wessels
7
Terkait (artikel CodeProject): Apakah Sebaiknya 'Mengembalikan Daftar Kosong Bukan Nihil'?
sampathsris
154

Dari Framework Design Guidelines 2nd Edition (hal 256):

JANGAN mengembalikan nilai nol dari properti koleksi atau dari metode pengembalian koleksi. Kembalikan koleksi kosong atau array kosong sebagai gantinya.

Inilah artikel menarik lainnya tentang manfaat tidak mengembalikan nol (saya mencoba menemukan sesuatu di blog Brad Abram, dan dia menautkan ke artikel itu).

Sunting - seperti Eric Lippert sekarang telah mengomentari pertanyaan awal, saya juga ingin menautkan ke artikelnya yang sangat bagus .

RichardOD
sumber
33
+1. Itu selalu praktik terbaik untuk mengikuti pedoman desain kerangka kerja kecuali Anda memiliki alasan yang SANGAT bagus untuk tidak melakukannya.
@ Akan, tentu saja. Itu yang saya ikuti. Saya tidak pernah menemukan perlu melakukan sebaliknya
RichardOD
yup, itu yang kamu ikuti. Tetapi dalam kasus-kasus dimana API yang Anda andalkan tidak mengikutinya, Anda 'terjebak';)
Bozho
@ Bozho- yeap. Jawaban Anda memberikan beberapa jawaban bagus untuk kasus pinggiran itu.
RichardOD
90

Tergantung pada kontrak Anda dan kasing beton Anda . Umumnya yang terbaik adalah mengembalikan koleksi kosong , tetapi kadang-kadang ( jarang ):

  • null mungkin berarti sesuatu yang lebih spesifik;
  • API (kontrak) Anda mungkin memaksa Anda untuk kembali null.

Beberapa contoh nyata:

  • komponen UI (dari perpustakaan di luar kendali Anda), mungkin merender tabel kosong jika koleksi kosong dilewatkan, atau tidak ada tabel sama sekali, jika nol dilewatkan.
  • dalam Object-to-XML (JSON / apa pun), di mana nullartinya elemen tersebut hilang, sementara koleksi kosong akan membuat redundan (dan mungkin salah)<collection />
  • Anda menggunakan atau mengimplementasikan API yang secara eksplisit menyatakan bahwa null harus dikembalikan / diteruskan
Bozho
sumber
4
@ Bozho- beberapa contoh menarik di sini.
RichardOD
1
Saya agak melihat poin Anda walaupun saya tidak berpikir Anda harus membangun kode Anda di sekitar kesalahan lain dan dengan definisi 'nol' di c # tidak pernah berarti sesuatu yang spesifik. Nilai nol didefinisikan sebagai "tidak ada informasi" dan karenanya mengatakan bahwa ia membawa informasi spesifik adalah sebuah oximoron. Itulah sebabnya pedoman .NET menyatakan bahwa Anda harus mengembalikan set kosong jika set memang kosong. null kembali mengatakan: "Saya tidak tahu di mana set yang diharapkan pergi"
Rune FS
13
tidak ada, itu berarti "tidak ada set" daripada "set tidak memiliki unsur-unsur"
Bozho
Saya dulu percaya itu, maka saya harus menulis banyak TSQL dan belajar bahwa itu tidak selalu terjadi, heheh.
1
Bagian dengan Object-To-Xml tepat
Mihai Caracostea
36

Ada satu hal lain yang belum disebutkan. Pertimbangkan kode berikut:

    public static IEnumerable<string> GetFavoriteEmoSongs()
    {
        yield break;
    }

Bahasa C # akan mengembalikan enumerator kosong saat memanggil metode ini. Oleh karena itu, agar konsisten dengan desain bahasa (dan, dengan demikian, harapan programmer) koleksi kosong harus dikembalikan.

Jeffrey L Whitledge
sumber
1
Secara teknis saya tidak berpikir bahwa ini mengembalikan koleksi kosong.
FryGuy
3
@FryGuy - Tidak, tidak. Ini mengembalikan objek Enumerable, yang metode GetEnumerator () mengembalikan Enumerator yang (dengan analagy dengan koleksi) kosong. Yaitu, metode Enumerator's MoveNext () selalu mengembalikan false. Memanggil metode contoh tidak mengembalikan null, juga tidak mengembalikan objek Enumerable yang metode GetEnumerator () mengembalikan nol.
Jeffrey L Whitledge
31

Kosong jauh lebih ramah konsumen.

Ada metode yang jelas untuk membuat enumerable kosong:

Enumerable.Empty<Element>()
George Polevoy
sumber
2
Terima kasih atas triknya. Saya sedang instantiating Daftar kosong <T> () seperti orang idiot tetapi ini terlihat jauh lebih bersih dan mungkin sedikit lebih efisien juga.
Solusi Data Jacobs
18

Tampak bagi saya bahwa Anda harus mengembalikan nilai yang secara semantik benar dalam konteks, apa pun itu. Aturan yang mengatakan "selalu kembalikan koleksi kosong" tampaknya sedikit sederhana bagi saya.

Misalkan dalam, katakanlah, sebuah sistem untuk rumah sakit, kami memiliki fungsi yang seharusnya mengembalikan daftar semua rawat inap sebelumnya selama 5 tahun terakhir. Jika pelanggan belum masuk rumah sakit, masuk akal untuk mengembalikan daftar kosong. Tetapi bagaimana jika pelanggan membiarkan bagian dari formulir penerimaan itu kosong? Kami membutuhkan nilai yang berbeda untuk membedakan "daftar kosong" dari "tidak ada jawaban" atau "tidak tahu". Kita bisa melempar pengecualian, tetapi itu tidak selalu merupakan kondisi kesalahan, dan itu tidak serta merta membuat kita keluar dari aliran program normal.

Saya sering frustrasi oleh sistem yang tidak dapat membedakan antara nol dan tidak ada jawaban. Saya sudah beberapa kali di mana suatu sistem meminta saya untuk memasukkan beberapa angka, saya memasukkan nol, dan saya mendapatkan pesan kesalahan yang memberi tahu saya bahwa saya harus memasukkan nilai dalam bidang ini. Saya baru saja melakukannya: Saya memasukkan nol! Tetapi itu tidak akan menerima nol karena tidak dapat membedakannya dari tidak ada jawaban.


Balas ke Saunders:

Ya, saya berasumsi bahwa ada perbedaan antara "Orang tidak menjawab pertanyaan" dan "Jawabannya nol." Itulah poin paragraf terakhir dari jawaban saya. Banyak program tidak dapat membedakan "tidak tahu" dari kosong atau nol, yang menurut saya merupakan potensi kesalahan serius. Sebagai contoh, saya berbelanja untuk rumah sekitar setahun yang lalu. Saya pergi ke situs web real estat dan ada banyak rumah terdaftar dengan harga $ 0. Kedengarannya cukup bagus bagi saya: Mereka memberikan rumah-rumah ini secara gratis! Tapi saya yakin kenyataan menyedihkannya adalah mereka belum memasukkan harganya. Dalam hal ini Anda dapat berkata, "Yah, SANGAT nol berarti mereka tidak memasukkan harga - tidak ada yang akan memberikan rumah secara gratis." Tetapi situs tersebut juga mencantumkan harga jual dan permintaan rata-rata rumah di berbagai kota. Saya bertanya-tanya apakah rata-rata tidak termasuk nol, sehingga memberikan rata-rata rendah yang salah untuk beberapa tempat. yaitu berapa rata-rata $ 100.000; $ 120.000; dan "tidak tahu"? Secara teknis jawabannya adalah "tidak tahu". Yang mungkin ingin kami lihat adalah $ 110.000. Tapi yang mungkin akan kita dapatkan adalah $ 73.333, yang akan sepenuhnya salah. Juga, bagaimana jika kami memiliki masalah ini di situs tempat pengguna dapat memesan secara online? (Tidak mungkin untuk real estat, tapi saya yakin Anda pernah melihatnya melakukannya untuk banyak produk lain.) Apakah kita benar-benar ingin "harga belum ditentukan" diartikan sebagai "gratis"? sehingga memberikan rata-rata rendah yang salah untuk beberapa tempat. yaitu berapa rata-rata $ 100.000; $ 120.000; dan "tidak tahu"? Secara teknis jawabannya adalah "tidak tahu". Yang mungkin ingin kami lihat adalah $ 110.000. Tapi yang mungkin akan kita dapatkan adalah $ 73.333, yang akan sepenuhnya salah. Juga, bagaimana jika kami memiliki masalah ini di situs tempat pengguna dapat memesan secara online? (Tidak mungkin untuk real estat, tetapi saya yakin Anda telah melihatnya dilakukan untuk banyak produk lain.) Apakah kita benar-benar ingin "harga belum ditentukan" diartikan sebagai "gratis"? sehingga memberikan rata-rata rendah yang salah untuk beberapa tempat. yaitu berapa rata-rata $ 100.000; $ 120.000; dan "tidak tahu"? Secara teknis jawabannya adalah "tidak tahu". Yang mungkin ingin kami lihat adalah $ 110.000. Tapi yang mungkin akan kita dapatkan adalah $ 73.333, yang akan sepenuhnya salah. Juga, bagaimana jika kami memiliki masalah ini di situs tempat pengguna dapat memesan secara online? (Tidak mungkin untuk real estat, tetapi saya yakin Anda telah melihatnya dilakukan untuk banyak produk lain.) Apakah kita benar-benar ingin "harga belum ditentukan" diartikan sebagai "gratis"? yang akan sepenuhnya salah. Juga, bagaimana jika kami memiliki masalah ini di situs tempat pengguna dapat memesan secara online? (Tidak mungkin untuk real estat, tapi saya yakin Anda pernah melihatnya melakukannya untuk banyak produk lain.) Apakah kita benar-benar ingin "harga belum ditentukan" diartikan sebagai "gratis"? yang akan sepenuhnya salah. Juga, bagaimana jika kami memiliki masalah ini di situs tempat pengguna dapat memesan secara online? (Tidak mungkin untuk real estat, tetapi saya yakin Anda telah melihatnya dilakukan untuk banyak produk lain.) Apakah kita benar-benar ingin "harga belum ditentukan" diartikan sebagai "gratis"?

RE memiliki dua fungsi terpisah, "apakah ada?" dan "jika demikian, apa itu?" Ya, Anda tentu bisa melakukan itu, tetapi mengapa Anda mau? Sekarang program panggilan harus membuat dua panggilan, bukan satu. Apa yang terjadi jika seorang programmer gagal memanggil "any?" dan langsung menuju ke "apa itu?" ? Apakah program akan mengembalikan nol salah arah? Melempar pengecualian? Kembalikan nilai yang tidak ditentukan? Ini menciptakan lebih banyak kode, lebih banyak pekerjaan, dan lebih banyak kesalahan potensial.

Satu-satunya manfaat yang saya lihat adalah memungkinkan Anda untuk mematuhi aturan arbitrer. Apakah ada manfaat dari aturan ini yang membuatnya sepadan dengan kesulitan mematuhinya? Jika tidak, mengapa repot-repot?


Balas ke Jammycakes:

Pertimbangkan seperti apa kode yang sebenarnya. Saya tahu pertanyaan kata C # tetapi maafkan saya jika saya menulis Java. C # saya tidak terlalu tajam dan prinsipnya sama.

Dengan pengembalian nol:

HospList list=patient.getHospitalizationList(patientId);
if (list==null)
{
   // ... handle missing list ...
}
else
{
  for (HospEntry entry : list)
   //  ... do whatever ...
}

Dengan fungsi terpisah:

if (patient.hasHospitalizationList(patientId))
{
   // ... handle missing list ...
}
else
{
  HospList=patient.getHospitalizationList(patientId))
  for (HospEntry entry : list)
   // ... do whatever ...
}

Ini sebenarnya satu atau dua kode lebih sedikit dengan pengembalian nol, jadi tidak lebih banyak beban pada pemanggil, itu lebih sedikit.

Saya tidak melihat bagaimana ini menciptakan masalah KERING. Bukannya kita harus melakukan panggilan dua kali. Jika kita selalu ingin melakukan hal yang sama ketika daftar tidak ada, mungkin kita bisa mendorong penanganan ke fungsi daftar daripada meminta penelepon melakukannya, dan dengan demikian menempatkan kode di penelepon akan menjadi pelanggaran KERING. Tapi kita hampir pasti tidak ingin selalu melakukan hal yang sama. Dalam fungsi di mana kita harus memiliki daftar untuk diproses, daftar yang hilang adalah kesalahan yang mungkin menghentikan pemrosesan. Tetapi pada layar edit, kami pasti tidak ingin menghentikan pemrosesan jika mereka belum memasukkan data: kami ingin mereka memasukkan data. Jadi penanganan "no list" harus dilakukan di level pemanggil dengan satu atau lain cara. Dan apakah kita melakukan itu dengan pengembalian nol atau fungsi terpisah tidak ada bedanya dengan prinsip yang lebih besar.

Tentu, jika penelepon tidak memeriksa null, program bisa gagal dengan pengecualian null-pointer. Tetapi jika ada fungsi "punya" yang terpisah dan penelepon tidak memanggil fungsi itu tetapi secara buta memanggil fungsi "dapatkan daftar", lalu apa yang terjadi? Jika ia melempar pengecualian atau gagal, yah, itu hampir sama dengan apa yang akan terjadi jika ia mengembalikan nol dan tidak memeriksanya. Jika mengembalikan daftar kosong, itu hanya salah. Anda gagal membedakan antara "Saya punya daftar dengan nol elemen" dan "Saya tidak punya daftar". Ini seperti mengembalikan nol untuk harga ketika pengguna tidak memasukkan harga apa pun: itu hanya salah.

Saya tidak melihat bagaimana melampirkan atribut tambahan ke koleksi membantu. Penelepon masih harus memeriksanya. Bagaimana itu lebih baik daripada memeriksa null? Sekali lagi, hal terburuk yang dapat terjadi adalah programmer lupa untuk memeriksanya, dan memberikan hasil yang salah.

Sebuah fungsi yang mengembalikan null bukanlah kejutan jika programmer akrab dengan konsep makna nol "tidak memiliki nilai", yang saya pikir setiap programmer yang kompeten seharusnya pernah mendengar, apakah dia pikir itu ide yang bagus atau tidak. Saya pikir memiliki fungsi terpisah lebih merupakan masalah "kejutan". Jika seorang programmer tidak terbiasa dengan API, ketika dia menjalankan tes tanpa data, dia akan dengan cepat menemukan bahwa kadang-kadang dia kembali nol. Tetapi bagaimana ia menemukan keberadaan fungsi lain kecuali jika terlintas dalam benaknya bahwa mungkin ada fungsi semacam itu dan ia memeriksa dokumentasinya, dan dokumentasinya lengkap dan dapat dipahami? Saya lebih suka memiliki satu fungsi yang selalu memberi saya respons yang bermakna, daripada dua fungsi yang harus saya ketahui dan ingat untuk memanggil keduanya.

Jay
sumber
4
Mengapa perlu menggabungkan respons "tidak ada jawaban" dan "nol" dalam nilai pengembalian yang sama? Alih-alih, sudahkah metode mengembalikan "setiap rawat inap sebelumnya dalam lima tahun terakhir", dan memiliki metode terpisah yang bertanya, "apakah daftar rawat inap sebelumnya pernah diisi?". Itu mengasumsikan ada perbedaan antara daftar diisi dengan tidak ada rawat inap sebelumnya, dan daftar tidak diisi.
John Saunders
2
Tetapi jika Anda mengembalikan nol Anda sudah menempatkan beban tambahan pada pemanggil pula! Penelepon harus memeriksa setiap nilai kembali untuk null - pelanggaran KERING yang mengerikan. Jika Anda ingin menunjukkan "penelepon tidak menjawab pertanyaan" secara terpisah, itu adalah sederhana untuk membuat panggilan metode ekstra untuk menunjukkan fakta. Entah itu, atau gunakan tipe koleksi turunan dengan properti tambahan dari DeclinedToAnswer. Aturan untuk tidak pernah mengembalikan nol tidak sewenang-wenang sama sekali, itu adalah Prinsip Kejutan Paling Sedikit. Juga, tipe pengembalian metode Anda harus berarti apa yang dikatakan namanya. Null hampir pasti tidak.
jammycakes
2
Anda mengasumsikan bahwa getHospitalizationList Anda hanya dipanggil dari satu tempat dan / atau bahwa semua peneleponnya ingin membedakan antara "tidak ada jawaban" dan "nol". Ada akan menjadi kasus (hampir pasti mayoritas) di mana penelepon Anda tidak perlu membuat perbedaan itu, dan sehingga Anda memaksa mereka untuk menambahkan nol cek di tempat-tempat ini seharusnya tidak diperlukan. Ini menambah risiko signifikan pada basis kode Anda karena orang bisa dengan mudah lupa melakukannya - dan karena ada sedikit alasan yang sah untuk mengembalikan nol alih-alih koleksi, ini akan jauh lebih mungkin.
jammycakes
3
RE the name: Tidak ada nama fungsi yang dapat sepenuhnya menggambarkan apa yang dilakukan fungsi kecuali itu sepanjang fungsinya, dan dengan demikian sangat tidak praktis. Tetapi bagaimanapun juga, jika fungsi mengembalikan daftar kosong ketika tidak ada jawaban yang diberikan, maka dengan alasan yang sama, bukankah seharusnya disebut "getHospitalizationListOrEmptyListIfNoAnswer"? Tetapi sungguh, akankah Anda bersikeras bahwa fungsi Java Reader.read harus diganti namanya readCharacterOrReturnMinusOneOnEEndOfStream? ResultSet.getInt itu harus benar-benar "getIntOrZeroIfValueWasNull"? Dll
Jay
4
RE setiap panggilan ingin dibedakan: Ya, saya berasumsi itu, atau paling tidak bahwa penulis seorang penelepon harus membuat keputusan sadar bahwa ia tidak peduli. Jika fungsi mengembalikan daftar kosong untuk "tidak tahu", dan penelepon secara buta memperlakukan ini "tidak ada", itu bisa memberikan hasil yang sangat tidak akurat. Bayangkan jika fungsinya adalah "getAllergicReactionToMedicationList". Sebuah program yang secara buta memperlakukan "daftar tidak dimasukkan" sebagai "pasien tidak memiliki reaksi alergi" dapat benar-benar berakibat membunuh seorang pasien. Anda akan mendapatkan hasil yang serupa, jika kurang dramatis, di banyak sistem lain. ...
Jay
10

Jika koleksi kosong masuk akal secara semantik, itulah yang saya inginkan untuk dikembalikan. Mengembalikan koleksi kosong untuk GetMessagesInMyInbox()berkomunikasi "Anda benar-benar tidak memiliki pesan di kotak masuk Anda", sedangkan pengembalian nullmungkin berguna untuk berkomunikasi bahwa data yang tidak memadai tersedia untuk mengatakan seperti apa daftar yang mungkin dikembalikan seharusnya terlihat.

David Hedlund
sumber
6
Dalam contoh yang Anda berikan, sepertinya metode ini mungkin harus mengeluarkan pengecualian jika tidak dapat memenuhi permintaan, bukan hanya mengembalikan nol. Pengecualian jauh lebih berguna untuk mendiagnosis masalah daripada nol.
Greg Beech
baik ya, dalam contoh inbox nullnilai pasti tidak tampak masuk akal, saya berpikir secara umum tentang hal itu. Pengecualian juga bagus untuk mengomunikasikan fakta bahwa ada yang tidak beres, tetapi jika "data tidak mencukupi" yang dimaksud sesuai dengan yang diharapkan, maka melemparkan pengecualian akan ada desain yang buruk. Saya agak memikirkan skenario di mana itu sangat mungkin dan tidak ada kesalahan sama sekali untuk metode untuk kadang-kadang tidak dapat menghitung respons.
David Hedlund
6

Mengembalikan null bisa lebih efisien, karena tidak ada objek baru yang dibuat. Namun, itu juga sering membutuhkannull pemeriksaan (atau penanganan pengecualian).

Secara semantik, null dan daftar kosong tidak berarti hal yang sama. Perbedaannya halus dan satu pilihan mungkin lebih baik daripada yang lain dalam hal tertentu.

Apa pun pilihan Anda, dokumentasikan untuk menghindari kebingungan.

Karmic Coder
sumber
8
Efisiensi seharusnya hampir tidak pernah menjadi faktor ketika mempertimbangkan kebenaran desain API. Dalam beberapa kasus yang sangat spesifik seperti grafis primitif maka mungkin begitu, tetapi ketika berhadapan dengan daftar dan sebagian besar hal-hal tingkat tinggi lainnya maka saya sangat meragukannya.
Greg Beech
Setuju dengan Greg, terutama mengingat bahwa kode yang harus ditulis pengguna API untuk mengkompensasi "optimasi" ini mungkin lebih tidak efisien daripada jika desain yang lebih baik digunakan di tempat pertama.
Craig Stuntz
Setuju, dan dalam kebanyakan kasus itu tidak sebanding dengan optimasi. Daftar kosong praktis gratis dengan manajemen memori modern.
Jason Baker
6

Orang dapat berargumen bahwa alasan di balik Pola Objek Null mirip dengan yang mendukung pengembalian koleksi kosong.

Dan
sumber
4

Tergantung situasinya. Jika ini adalah kasus khusus, maka kembalikan nol. Jika fungsi hanya mengembalikan koleksi kosong, maka jelas mengembalikan itu ok. Namun, mengembalikan koleksi kosong sebagai kasus khusus karena parameter yang tidak valid atau alasan lain BUKAN ide yang baik, karena menutupi kondisi kasus khusus.

Sebenarnya, dalam hal ini saya biasanya lebih suka melempar pengecualian untuk memastikan itu BENAR-BENAR tidak diabaikan :)

Mengatakan itu membuat kode lebih kuat (dengan mengembalikan koleksi kosong) karena mereka tidak harus menangani kondisi nol adalah buruk, karena hanya menutupi masalah yang harus ditangani oleh kode panggilan.

Larry Watanabe
sumber
4

Saya berpendapat bahwa nullitu tidak sama dengan koleksi kosong dan Anda harus memilih yang terbaik mewakili apa yang Anda kembalikan. Dalam kebanyakan kasus nulltidak ada apa-apa (kecuali dalam SQL). Koleksi kosong adalah sesuatu, meskipun sesuatu yang kosong.

Jika Anda harus memilih satu atau yang lain, saya akan mengatakan bahwa Anda harus cenderung ke koleksi kosong daripada nol. Tetapi ada kalanya koleksi kosong tidak sama dengan nilai nol.

Jason Baker
sumber
4

Pikirkan selalu dalam mendukung klien Anda (yang menggunakan api Anda):

Mengembalikan 'null' sangat sering membuat masalah dengan klien yang tidak menangani pemeriksaan nol dengan benar, yang menyebabkan NullPointerException selama runtime. Saya telah melihat kasus-kasus dimana pemeriksaan nol yang hilang memaksa masalah produksi prioritas (klien menggunakan foreach (...) pada nilai nol). Selama pengujian masalah tidak terjadi, karena data yang dioperasikan sedikit berbeda.

manuel aldana
sumber
3

Saya ingin memberi penjelasan di sini, dengan contoh yang sesuai.

Pertimbangkan sebuah kasus di sini ..

int totalValue = MySession.ListCustomerAccounts()
                          .FindAll(ac => ac.AccountHead.AccountHeadID 
                                         == accountHead.AccountHeadID)
                          .Sum(account => account.AccountValue);

Di sini Pertimbangkan fungsi yang saya gunakan ..

1. ListCustomerAccounts() // User Defined
2. FindAll()              // Pre-defined Library Function

Saya dapat dengan mudah menggunakan ListCustomerAccountdan FindAllbukannya.,

int totalValue = 0; 
List<CustomerAccounts> custAccounts = ListCustomerAccounts();
if(custAccounts !=null ){
  List<CustomerAccounts> custAccountsFiltered = 
        custAccounts.FindAll(ac => ac.AccountHead.AccountHeadID 
                                   == accountHead.AccountHeadID );
   if(custAccountsFiltered != null)
      totalValue = custAccountsFiltered.Sum(account => 
                                            account.AccountValue).ToString();
}

CATATAN: Karena AccountValue tidak null, fungsi Sum () tidak akan kembali null., Karena itu saya dapat menggunakannya secara langsung.

Muthu Ganapathy Nathan
sumber
2

Kami melakukan diskusi ini di antara tim pengembangan di tempat kerja sekitar seminggu yang lalu, dan kami hampir dengan suara bulat pergi untuk pengumpulan kosong. Satu orang ingin mengembalikan nol untuk alasan yang sama yang disebutkan Mike di atas.

Henric
sumber
2

Koleksi Kosong. Jika Anda menggunakan C #, asumsinya adalah memaksimalkan sumber daya sistem tidak penting. Meskipun kurang efisien, pengembalian Empty Collection jauh lebih nyaman bagi para programmer yang terlibat (untuk alasan yang akan diuraikan di atas).

mothis
sumber
2

Mengembalikan koleksi kosong lebih baik dalam banyak kasus.

Alasannya adalah kemudahan implementasi penelepon, kontrak yang konsisten, dan implementasi yang lebih mudah.

Jika metode mengembalikan nol untuk menunjukkan hasil kosong, pemanggil harus menerapkan adaptor pemeriksaan nol selain enumerasi. Kode ini kemudian digandakan dalam berbagai penelepon, jadi mengapa tidak memasukkan adaptor ini ke dalam metode sehingga dapat digunakan kembali.

Penggunaan null yang valid untuk IEnumerable mungkin merupakan indikasi tidak adanya hasil, atau kegagalan operasi, tetapi dalam hal ini teknik lain harus dipertimbangkan, seperti melempar pengecualian.

using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;

namespace StackOverflow.EmptyCollectionUsageTests.Tests
{
    /// <summary>
    /// Demonstrates different approaches for empty collection results.
    /// </summary>
    class Container
    {
        /// <summary>
        /// Elements list.
        /// Not initialized to an empty collection here for the purpose of demonstration of usage along with <see cref="Populate"/> method.
        /// </summary>
        private List<Element> elements;

        /// <summary>
        /// Gets elements if any
        /// </summary>
        /// <returns>Returns elements or empty collection.</returns>
        public IEnumerable<Element> GetElements()
        {
            return elements ?? Enumerable.Empty<Element>();
        }

        /// <summary>
        /// Initializes the container with some results, if any.
        /// </summary>
        public void Populate()
        {
            elements = new List<Element>();
        }

        /// <summary>
        /// Gets elements. Throws <see cref="InvalidOperationException"/> if not populated.
        /// </summary>
        /// <returns>Returns <see cref="IEnumerable{T}"/> of <see cref="Element"/>.</returns>
        public IEnumerable<Element> GetElementsStrict()
        {
            if (elements == null)
            {
                throw new InvalidOperationException("You must call Populate before calling this method.");
            }

            return elements;
        }

        /// <summary>
        /// Gets elements, empty collection or nothing.
        /// </summary>
        /// <returns>Returns <see cref="IEnumerable{T}"/> of <see cref="Element"/>, with zero or more elements, or null in some cases.</returns>
        public IEnumerable<Element> GetElementsInconvenientCareless()
        {
            return elements;
        }

        /// <summary>
        /// Gets elements or nothing.
        /// </summary>
        /// <returns>Returns <see cref="IEnumerable{T}"/> of <see cref="Element"/>, with elements, or null in case of empty collection.</returns>
        /// <remarks>We are lucky that elements is a List, otherwise enumeration would be needed.</remarks>
        public IEnumerable<Element> GetElementsInconvenientCarefull()
        {
            if (elements == null || elements.Count == 0)
            {
                return null;
            }
            return elements;
        }
    }

    class Element
    {
    }

    /// <summary>
    /// http://stackoverflow.com/questions/1969993/is-it-better-to-return-null-or-empty-collection/
    /// </summary>
    class EmptyCollectionTests
    {
        private Container container;

        [SetUp]
        public void SetUp()
        {
            container = new Container();
        }

        /// <summary>
        /// Forgiving contract - caller does not have to implement null check in addition to enumeration.
        /// </summary>
        [Test]
        public void UseGetElements()
        {
            Assert.AreEqual(0, container.GetElements().Count());
        }

        /// <summary>
        /// Forget to <see cref="Container.Populate"/> and use strict method.
        /// </summary>
        [Test]
        [ExpectedException(typeof(InvalidOperationException))]
        public void WrongUseOfStrictContract()
        {
            container.GetElementsStrict().Count();
        }

        /// <summary>
        /// Call <see cref="Container.Populate"/> and use strict method.
        /// </summary>
        [Test]
        public void CorrectUsaOfStrictContract()
        {
            container.Populate();
            Assert.AreEqual(0, container.GetElementsStrict().Count());
        }

        /// <summary>
        /// Inconvenient contract - needs a local variable.
        /// </summary>
        [Test]
        public void CarefulUseOfCarelessMethod()
        {
            var elements = container.GetElementsInconvenientCareless();
            Assert.AreEqual(0, elements == null ? 0 : elements.Count());
        }

        /// <summary>
        /// Inconvenient contract - duplicate call in order to use in context of an single expression.
        /// </summary>
        [Test]
        public void LameCarefulUseOfCarelessMethod()
        {
            Assert.AreEqual(0, container.GetElementsInconvenientCareless() == null ? 0 : container.GetElementsInconvenientCareless().Count());
        }

        [Test]
        public void LuckyCarelessUseOfCarelessMethod()
        {
            // INIT
            var praySomeoneCalledPopulateBefore = (Action)(()=>container.Populate());
            praySomeoneCalledPopulateBefore();

            // ACT //ASSERT
            Assert.AreEqual(0, container.GetElementsInconvenientCareless().Count());
        }

        /// <summary>
        /// Excercise <see cref="ArgumentNullException"/> because of null passed to <see cref="Enumerable.Count{TSource}(System.Collections.Generic.IEnumerable{TSource})"/>
        /// </summary>
        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public void UnfortunateCarelessUseOfCarelessMethod()
        {
            Assert.AreEqual(0, container.GetElementsInconvenientCareless().Count());
        }

        /// <summary>
        /// Demonstrates the client code flow relying on returning null for empty collection.
        /// Exception is due to <see cref="Enumerable.First{TSource}(System.Collections.Generic.IEnumerable{TSource})"/> on an empty collection.
        /// </summary>
        [Test]
        [ExpectedException(typeof(InvalidOperationException))]
        public void UnfortunateEducatedUseOfCarelessMethod()
        {
            container.Populate();
            var elements = container.GetElementsInconvenientCareless();
            if (elements == null)
            {
                Assert.Inconclusive();
            }
            Assert.IsNotNull(elements.First());
        }

        /// <summary>
        /// Demonstrates the client code is bloated a bit, to compensate for implementation 'cleverness'.
        /// We can throw away the nullness result, because we don't know if the operation succeeded or not anyway.
        /// We are unfortunate to create a new instance of an empty collection.
        /// We might have already had one inside the implementation,
        /// but it have been discarded then in an effort to return null for empty collection.
        /// </summary>
        [Test]
        public void EducatedUseOfCarefullMethod()
        {
            Assert.AreEqual(0, (container.GetElementsInconvenientCarefull() ?? Enumerable.Empty<Element>()).Count());
        }
    }
}
George Polevoy
sumber
2

Saya menyebutnya kesalahan miliaran dolar saya ... Pada waktu itu, saya sedang merancang sistem tipe komprehensif pertama untuk referensi dalam bahasa berorientasi objek. Tujuan saya adalah untuk memastikan bahwa semua penggunaan referensi harus benar-benar aman, dengan pengecekan dilakukan secara otomatis oleh kompiler. Tapi saya tidak bisa menahan godaan untuk memasukkan referensi nol, hanya karena itu sangat mudah diimplementasikan. Ini telah menyebabkan kesalahan yang tak terhitung banyaknya, kerentanan, dan crash sistem, yang mungkin telah menyebabkan satu miliar dolar rasa sakit dan kerusakan dalam empat puluh tahun terakhir. - Tony Hoare, penemu ALGOL W.

Lihat di sini untuk badai kotoran rumit tentang nullsecara umum. Saya tidak setuju dengan pernyataan yang undefinedlain null, tetapi masih layak dibaca. Dan itu menjelaskan, mengapa Anda harus menghindari nullsama sekali dan tidak hanya dalam kasus yang Anda tanyakan. Intinya, nulldalam bahasa apa pun ada kasus khusus. Anda harus memikirkannya nullsebagai pengecualian. undefinedberbeda dalam hal itu, bahwa kode yang berurusan dengan perilaku tidak terdefinisi dalam kebanyakan kasus hanya bug. C dan sebagian besar bahasa lainnya juga memiliki perilaku yang tidak terdefinisi tetapi kebanyakan tidak memiliki pengidentifikasi untuk itu dalam bahasa tersebut.

ceving
sumber
1

Dari perspektif mengelola kompleksitas, tujuan rekayasa perangkat lunak utama, kami ingin menghindari penyebaran kompleksitas siklomatik yang tidak perlu ke klien API. Mengembalikan nol ke klien sama seperti mengembalikan mereka biaya kompleksitas siklomatik dari cabang kode lain.

(Ini sesuai dengan beban pengujian unit. Anda harus menulis tes untuk kasus pengembalian nol, selain kasus pengembalian koleksi kosong.)

dthal
sumber