Saya punya metode di mana semua logika dilakukan di dalam foreach loop yang berulang di atas parameter metode:
public IEnumerable<TransformedNode> TransformNodes(IEnumerable<Node> nodes)
{
foreach(var node in nodes)
{
// yadda yadda yadda
yield return transformedNode;
}
}
Dalam hal ini, mengirimkan koleksi kosong menghasilkan koleksi kosong, tapi saya bertanya-tanya apakah itu tidak bijaksana.
Logika saya di sini adalah jika seseorang memanggil metode ini, maka mereka bermaksud untuk mengirimkan data, dan hanya akan mengirimkan koleksi kosong ke metode saya dalam keadaan yang salah.
Haruskah saya menangkap perilaku ini dan melemparkan pengecualian untuk itu, atau apakah itu praktik terbaik untuk mengembalikan koleksi kosong?
c#
exceptions
collections
parameters
Nick Udell
sumber
sumber
null
tetapi tidak jika itu kosong.Jawaban:
Metode utilitas tidak boleh membuang koleksi kosong. Klien API Anda akan membenci Anda karenanya.
Koleksi bisa kosong; "pengumpulan-yang-tidak-boleh-kosong" secara konseptual adalah hal yang jauh lebih sulit untuk dikerjakan.
Mengubah koleksi kosong memiliki hasil yang jelas: koleksi kosong. (Anda bahkan dapat menghemat beberapa sampah dengan mengembalikan parameter itu sendiri.)
Ada banyak keadaan di mana modul menyimpan daftar barang yang mungkin atau mungkin belum diisi dengan sesuatu. Harus memeriksa kekosongan sebelum setiap panggilan untuk
transform
menjengkelkan dan memiliki potensi untuk mengubah algoritma yang sederhana dan elegan menjadi kekacauan yang jelek.Metode utilitas harus selalu berusaha untuk menjadi liberal dalam input mereka dan konservatif dalam output mereka.
Demi semua alasan ini, demi Tuhan, tangani koleksi kosong dengan benar. Tidak ada yang lebih menjengkelkan daripada modul pembantu yang berpikir itu tahu apa yang Anda inginkan lebih baik daripada yang Anda lakukan.
sumber
null
ke dalam koleksi kosong. Fungsi dengan keterbatasan implisit adalah rasa sakit.Populate1(input); output1 = TransformNodes(input); Populate2(input); output2 = TransformNodes(input);
Jika Populate1 meninggalkan koleksi kosong dan Anda mengembalikannya untuk panggilan TransformNodes pertama, output1 dan input akan menjadi koleksi yang sama, dan ketika Populaten2 dipanggil jika ia menempatkan node dalam koleksi, Anda akan berakhir dengan yang kedua set input dalam output2.Saya melihat dua pertanyaan penting yang menentukan jawaban untuk ini:
1. Pengembalian yang berarti
Intinya, jika Anda bisa mengembalikan sesuatu yang bermakna, maka jangan melemparkan pengecualian. Biarkan penelepon berurusan dengan hasilnya. Jadi jika fungsi Anda ...
Secara umum, bias FP saya memberi tahu saya - mengembalikan sesuatu yang bermakna - dan nol dapat memiliki arti yang valid dalam kasus ini.
2. Gaya
Apakah kode umum Anda (atau kode proyek atau kode tim) mendukung gaya fungsional? Jika tidak, pengecualian akan diharapkan dan ditangani. Jika ya, maka pertimbangkan untuk mengembalikan Tipe Opsi . Dengan jenis opsi, Anda bisa mengembalikan jawaban yang bermakna atau Tidak Ada / Tidak Ada . Dalam contoh ketiga saya dari atas, Tidak ada yang akan menjadi jawaban yang baik dalam gaya FP. Fakta bahwa fungsi mengembalikan sinyal Tipe opsi dengan jelas kepada penelepon daripada jawaban yang bermakna mungkin tidak mungkin dan penelepon harus siap untuk menghadapinya. Saya merasa itu memberi penelepon lebih banyak pilihan (jika Anda akan memaafkan permainan kata-kata).
F # adalah di mana semua keren Net anak-anak melakukan hal semacam ini tetapi C # tidak mendukung gaya ini.
tl; dr
Simpan pengecualian untuk kesalahan tak terduga di jalur kode Anda sendiri, tidak sepenuhnya input (dan legal) yang dapat diprediksi dari orang lain.
sumber
Seperti biasa, itu tergantung.
Apakah penting bahwa koleksinya kosong?
Sebagian besar kode penanganan pengumpulan mungkin akan mengatakan "tidak"; koleksi dapat memiliki sejumlah item di dalamnya, termasuk nol.
Sekarang, jika Anda memiliki semacam koleksi di mana itu "tidak valid" untuk tidak memiliki item di dalamnya, maka itu adalah persyaratan baru dan Anda harus memutuskan apa yang harus dilakukan tentang itu.
Pinjam beberapa logika pengujian dari dunia Database: uji untuk nol item, satu item dan dua item. Itu melayani kasus-kasus yang paling penting (menyiram kondisi dalam atau gabungan kartesian yang buruk).
sumber
java.util.Collection
, tetapicom.foo.util.NonEmptyCollection
kelas khusus , yang secara konsisten dapat mempertahankan invarian ini, dan mencegah Anda masuk ke keadaan yang tidak valid untuk memulai.Sebagai soal desain yang bagus, terima sebanyak mungkin variasi input Anda sepraktis mungkin. Pengecualian hanya boleh dilontarkan ketika (input yang tidak dapat diterima disajikan ATAU kesalahan tak terduga terjadi saat memproses) DAN sebagai hasilnya program tidak dapat dilanjutkan dengan cara yang dapat diprediksi .
Dalam hal ini, diharapkan koleksi kosong akan disajikan, dan kode Anda harus menanganinya (yang sudah ada). Ini akan menjadi pelanggaran terhadap semua yang baik jika kode Anda melemparkan pengecualian di sini. Ini akan mirip dengan mengalikan 0 dengan 0 dalam matematika. Itu mubazir, tetapi mutlak diperlukan agar bisa bekerja sebagaimana mestinya.
Sekarang, lanjutkan ke argumen koleksi nol. Dalam hal ini, koleksi nol adalah kesalahan pemrograman: programmer lupa untuk menetapkan variabel. Ini adalah kasus di mana pengecualian dapat dilemparkan, karena Anda tidak dapat memprosesnya secara berarti menjadi sebuah output, dan mencoba melakukannya akan memperkenalkan perilaku yang tidak terduga. Ini akan mirip dengan membagi dengan nol dalam matematika - itu sama sekali tidak berarti.
sumber
Solusi yang tepat jauh lebih sulit untuk dilihat ketika Anda hanya melihat fungsi Anda secara terpisah. Pertimbangkan fungsi Anda sebagai salah satu bagian dari masalah yang lebih besar . Salah satu solusi yang mungkin untuk contoh itu terlihat seperti ini (dalam Scala):
Pertama, Anda membagi string menjadi non-digit, memfilter string kosong, mengubah string menjadi bilangan bulat, lalu memfilter agar hanya menyimpan angka empat digit. Fungsi Anda mungkin ada
map (_.toInt)
di dalam pipa.Kode ini cukup mudah karena setiap tahap dalam pipa hanya menangani string kosong atau koleksi kosong. Jika Anda meletakkan string kosong di awal, Anda mendapatkan daftar kosong di akhir. Anda tidak harus berhenti dan memeriksa
null
atau pengecualian setelah setiap panggilan.Tentu saja, itu mengira daftar output kosong tidak memiliki lebih dari satu makna. Jika Anda perlu membedakan antara output kosong yang disebabkan oleh input kosong dan yang disebabkan oleh transformasi itu sendiri, itu benar-benar mengubah banyak hal.
sumber
Pertanyaan ini sebenarnya tentang pengecualian. Jika Anda melihatnya seperti itu dan mengabaikan koleksi kosong sebagai detail implementasi, jawabannya langsung:
1) Suatu metode harus mengeluarkan pengecualian ketika itu tidak dapat melanjutkan: baik tidak dapat melakukan tugas yang ditunjuk, atau mengembalikan nilai yang sesuai.
2) Suatu metode harus menangkap pengecualian ketika ia dapat melanjutkan meskipun gagal.
Jadi, metode pembantu Anda tidak boleh "membantu" dan membuat pengecualian kecuali itu tidak dapat melakukan pekerjaannya dengan koleksi kosong. Biarkan penelepon menentukan apakah hasilnya dapat ditangani.
Apakah itu mengembalikan koleksi kosong atau nol, sedikit lebih sulit tetapi tidak banyak: koleksi nullable harus dihindari jika memungkinkan. Tujuan dari kumpulan yang dapat dibatalkan adalah untuk menunjukkan (seperti dalam SQL) bahwa Anda tidak memiliki informasi - kumpulan anak-anak misalnya mungkin nol jika Anda tidak tahu apakah seseorang memiliki, tetapi Anda tidak tahu bahwa mereka tidak melakukannya. Tetapi jika itu penting untuk beberapa alasan, mungkin perlu variabel tambahan untuk melacaknya.
sumber
Metode ini dinamai
TransformNodes
. Dalam hal koleksi kosong sebagai input, mendapatkan kembali koleksi kosong adalah alami dan intuitif, dan masuk akal secara matematika.Jika metode ini dinamai
Max
dan dirancang untuk mengembalikan elemen maksimum, maka akan wajar untuk membuangNoSuchElementException
koleksi kosong, karena maksimum tidak ada yang masuk akal secara matematis.Jika metode ini dinamai
JoinSqlColumnNames
dan dirancang untuk mengembalikan string di mana elemen-elemen bergabung dengan koma untuk digunakan dalam query SQL, maka masuk akal untuk membuangIllegalArgumentException
koleksi kosong, karena pemanggil pada akhirnya akan mendapatkan kesalahan SQL pula jika ia menggunakan string dalam query SQL secara langsung tanpa pemeriksaan lebih lanjut, dan alih-alih memeriksa string kosong yang dikembalikan, ia seharusnya memeriksa koleksi kosong sebagai gantinya.sumber
Max
tidak ada yang sering negatif tak terbatas.Mari kita mundur dan menggunakan contoh berbeda yang menghitung rata-rata aritmatika dari suatu array nilai.
Jika array input kosong (atau nol), dapatkah Anda memenuhi permintaan pemanggil? Tidak. Apa pilihan Anda? Anda bisa:
Saya katakan beri mereka kesalahan jika mereka memberi Anda input yang tidak valid dan permintaan tidak dapat diselesaikan. Maksud saya kesalahan sejak hari pertama sehingga mereka memahami persyaratan program Anda. Lagi pula, fungsi Anda tidak dalam posisi untuk merespons. Jika operasi bisa gagal (misalnya menyalin file), maka API Anda harus memberi mereka kesalahan yang bisa mereka tangani.
Sehingga dapat menentukan bagaimana perpustakaan Anda menangani permintaan dan permintaan yang salah yang mungkin gagal.
Sangat penting bagi kode Anda untuk konsisten dalam cara menangani kelas kesalahan ini.
Kategori berikutnya adalah memutuskan bagaimana perpustakaan Anda menangani permintaan yang tidak masuk akal. Kembali ke contoh serupa dengan Anda - mari kita gunakan fungsi yang menentukan apakah file yang ada di jalan:
bool FileExistsAtPath(String)
. Jika klien melewati string kosong, bagaimana Anda menangani skenario ini? Bagaimana dengan array kosong atau null yang diteruskanvoid SaveDocuments(Array<Document>)
? Putuskan untuk perpustakaan / basis kode Anda, dan konsistenlah. Saya kebetulan menganggap kesalahan kasus ini, dan melarang klien membuat permintaan omong kosong dengan menandainya sebagai kesalahan (melalui pernyataan). Beberapa orang akan sangat menentang gagasan / tindakan itu. Saya menemukan deteksi kesalahan ini sangat membantu. Ini sangat baik untuk menemukan masalah dalam program - dengan lokasi yang baik untuk program yang menyinggung. Program jauh lebih jelas dan benar (pertimbangkan evolusi basis kode Anda), dan jangan membakar siklus dalam fungsi yang tidak melakukan apa pun. Kode lebih kecil / bersih dengan cara ini, dan pemeriksaan umumnya didorong ke lokasi di mana masalah dapat diperkenalkan.sumber
if (!FileExists(path)) { ...create it!!!... }
bug - banyak yang akan ditangkap sebelum melakukan, apakah garis kebenaran tidak kabur.Sebagai aturan praktis, fungsi standar harus dapat menerima daftar input terluas dan memberikan umpan balik padanya, ada banyak contoh di mana programmer menggunakan fungsi dengan cara yang tidak direncanakan oleh perancang, dengan pemikiran ini saya percaya fungsi tersebut harus dapat menerima tidak hanya koleksi kosong tetapi berbagai jenis input dan mengembalikan umpan balik dengan anggun, baik itu objek kesalahan dari operasi apa pun yang dilakukan pada input ...
sumber
writePaycheckToEmployee
seharusnya tidak menerima angka negatif sebagai masukan ... tetapi jika "umpan balik" berarti "melakukan apa pun yang mungkin terjadi selanjutnya," maka ya setiap fungsi akan melakukan sesuatu yang dilakukannya selanjutnya.