Saya sedang mengembangkan perpustakaan yang ditujukan untuk rilis publik. Ini berisi berbagai metode untuk beroperasi pada set objek - menghasilkan, memeriksa, mempartisi dan memproyeksikan set ke dalam bentuk baru. Dalam hal ini relevan, ini adalah perpustakaan kelas C # yang berisi ekstensi gaya LINQ aktif IEnumerable
, yang akan dirilis sebagai paket NuGet.
Beberapa metode di pustaka ini dapat diberikan parameter input yang tidak memuaskan. Sebagai contoh, dalam metode kombinatorik, ada metode untuk menghasilkan semua set n item yang dapat dibangun dari set sumber item m . Misalnya, diberikan set:
1, 2, 3, 4, 5
dan meminta kombinasi 2 akan menghasilkan:
1, 2
1, 3
1, 4
dll ...
5, 3
5, 4
Sekarang, sangat mungkin untuk meminta sesuatu yang tidak dapat dilakukan, seperti memberikannya satu set 3 item dan kemudian meminta kombinasi 4 item sambil mengatur opsi yang mengatakan itu hanya dapat menggunakan setiap item sekali.
Dalam skenario ini, setiap parameter valid secara individual :
- Pengumpulan sumber tidak nol, dan memang mengandung item
- Ukuran kombinasi yang diminta adalah bilangan nol bukan positif
- Mode yang diminta (gunakan setiap item hanya sekali) adalah pilihan yang valid
Namun, keadaan parameter saat diambil bersama-sama menyebabkan masalah.
Dalam skenario ini, apakah Anda mengharapkan metode untuk melempar pengecualian (mis. InvalidOperationException
), Atau mengembalikan koleksi kosong? Tampaknya valid bagi saya:
- Anda tidak dapat menghasilkan kombinasi n item dari set item m di mana n> m jika Anda hanya diperbolehkan menggunakan setiap item sekali, sehingga operasi ini dapat dianggap mustahil, karenanya
InvalidOperationException
. - Himpunan kombinasi ukuran n yang dapat diproduksi dari item m ketika n> m adalah himpunan kosong; tidak ada kombinasi yang dapat diproduksi.
Argumen untuk set kosong
Kekhawatiran pertama saya adalah bahwa pengecualian mencegah chaining metode LINQ gaya idiomatik ketika Anda berurusan dengan dataset yang mungkin memiliki ukuran yang tidak diketahui. Dengan kata lain, Anda mungkin ingin melakukan sesuatu seperti ini:
var result = someInputSet
.CombinationsOf(4, CombinationsGenerationMode.Distinct)
.Select(combo => /* do some operation to a combination */)
.ToList();
Jika set input Anda berukuran variabel, perilaku kode ini tidak dapat diprediksi. Jika .CombinationsOf()
melempar pengecualian ketika someInputSet
memiliki kurang dari 4 elemen, maka kode ini kadang - kadang akan gagal saat runtime tanpa beberapa pemeriksaan awal. Dalam contoh di atas pengecekan ini sepele, tetapi jika Anda menyebutnya setengah dari rantai yang lebih panjang dari LINQ maka ini mungkin membosankan. Jika mengembalikan set kosong, maka result
akan kosong, yang mungkin Anda senangi.
Argumen untuk pengecualian
Kekhawatiran kedua saya adalah bahwa mengembalikan set kosong mungkin menyembunyikan masalah - jika Anda memanggil metode ini di tengah-tengah rantai LINQ dan secara diam-diam mengembalikan set kosong, maka Anda dapat mengalami masalah beberapa langkah kemudian, atau menemukan diri Anda dengan yang kosong set hasil, dan mungkin tidak jelas bagaimana itu terjadi mengingat Anda pasti memiliki sesuatu di set input.
Apa yang Anda harapkan, dan apa argumen Anda untuk itu?
sumber
Jawaban:
Kembalikan Set Kosong
Saya mengharapkan set kosong karena:
Ada 0 kombinasi 4 angka dari himpunan 3 ketika saya hanya dapat menggunakan setiap angka satu kali
sumber
Jika ragu, tanyakan orang lain.
Fungsi contoh Anda memiliki salah satu yang sangat mirip dengan Python:
itertools.combinations
. Mari kita lihat cara kerjanya:Dan itu terasa sangat baik bagi saya. Saya mengharapkan hasil yang saya bisa beralih dan saya mendapatkannya.
Tapi, jelas, jika Anda bertanya sesuatu yang bodoh:
Jadi saya akan mengatakan, jika semua parameter Anda memvalidasi tetapi hasilnya adalah set kosong mengembalikan set kosong, Anda bukan satu-satunya yang melakukannya.
Seperti yang dikatakan oleh @Bakuriu di komentar, ini juga sama untuk
SQL
permintaan sepertiSELECT <columns> FROM <table> WHERE <conditions>
. Selama<columns>
,<table>
,<conditions>
baik terbentuk terbentuk dan mengacu pada nama-nama yang ada, Anda dapat membangun serangkaian kondisi yang mengecualikan satu sama lain. Kueri yang dihasilkan hanya akan menghasilkan tanpa baris alih-alih melemparkanInvalidConditionsError
.sumber
n
elemen dalam koleksi untuk menghitung jumlah cara kami dapat mengambilnya. Jika pada titik tertentu tidak ada elemen yang tersisa saat mengambilnya, maka tidak ada (0) cara memilihn
elemen dari koleksi tersebut.1.0 / 0
, Python tidak mau.SELECT
atas tabel menghasilkan pengecualian saat kumpulan hasil kosong? (spoiler: tidak!). "Well-formedness" dari kueri hanya bergantung pada kueri itu sendiri dan beberapa metadata (misalnya apakah tabel ada dan memiliki bidang ini (dengan tipe ini)?) Setelah kueri terbentuk dengan baik dan Anda menjalankannya, Anda mengharapkan salah satu (mungkin kosong) hasil yang valid atau pengecualian karena "server" memiliki masalah (mis. server db turun atau sesuatu).Dalam istilah awam:
sumber
throw
atau mengembalikan set kosong ...Saya setuju dengan jawaban Ewan tetapi ingin menambahkan alasan tertentu.
Anda berhadapan dengan operasi matematika, jadi mungkin saran yang bagus untuk tetap menggunakan definisi matematika yang sama. Dari sudut pandang matematika, jumlah r -set dari n -set (yaitu nCr ) didefinisikan dengan baik untuk semua r> n> = 0. Ini adalah nol. Oleh karena itu mengembalikan set kosong akan menjadi kasus yang diharapkan dari sudut pandang matematika.
sumber
Saya menemukan cara yang baik untuk menentukan apakah akan menggunakan pengecualian, adalah membayangkan orang terlibat dalam transaksi.
Mengambil mengambil konten file sebagai contoh:
Tolong ambilkan saya isi file, "not exist.txt"
Sebuah. "Ini isinya: koleksi karakter kosong"
b. "Erm, ada masalah, file itu tidak ada. Aku tidak tahu harus berbuat apa!"
Tolong ambilkan saya isi file, "ada tetapi kosong.txt"
Sebuah. "Ini isinya: koleksi karakter kosong"
b. "Erm, ada masalah, tidak ada dalam file ini. Aku tidak tahu harus berbuat apa!"
Tidak diragukan lagi beberapa akan tidak setuju, tetapi bagi kebanyakan orang, "Erm, ada masalah" masuk akal ketika file tidak ada dan mengembalikan "koleksi karakter kosong" ketika file kosong.
Jadi terapkan pendekatan yang sama pada contoh Anda:
Tolong beri saya semua kombinasi 4 item untuk
{1, 2, 3}
Sebuah. Tidak ada, ini satu set kosong.
b. Ada masalah, saya tidak tahu harus berbuat apa.
Sekali lagi, "Ada masalah" akan masuk akal jika misalnya
null
ditawarkan sebagai set item, tetapi "ini set kosong" tampaknya respons yang masuk akal untuk permintaan di atas.Jika mengembalikan nilai kosong menutupi masalah (misalnya file yang hilang, a
null
), maka pengecualian yang biasanya digunakan sebagai gantinya (kecuali bahasa yang Anda pilih mendukungoption/maybe
jenis, maka terkadang hal itu lebih masuk akal). Kalau tidak, mengembalikan nilai kosong kemungkinan akan menyederhanakan biaya dan lebih sesuai dengan prinsip paling tidak heran.sumber
Karena itu untuk perpustakaan tujuan umum, insting saya adalah Biarkan pengguna akhir memilih.
Sama seperti yang kita miliki
Parse()
danTryParse()
tersedia bagi kita, kita dapat memiliki opsi yang kita gunakan tergantung pada output apa yang kita butuhkan dari fungsi. Anda akan menghabiskan lebih sedikit waktu untuk menulis dan mengelola pembungkus fungsi untuk membuang pengecualian daripada berdebat memilih satu versi fungsi.sumber
Single()
danSingleOrDefault()
segera muncul dalam pikiran. Single melempar pengecualian jika hasil nol atau> 1, sedangkan SingleOrDefault tidak akan melempar, dan sebagai gantinya kembalidefault(T)
. Mungkin OP bisa menggunakanCombinationsOf()
danCombinationsOfOrThrow()
.Single
danSingleOrDefault
(atauFirst
danFirstOrDefault
) cerita yang sama sekali berbeda, @RubberDuck. Mengambil contoh saya dari komentar saya sebelumnya. Ini baik-baik saja untuk anser tidak untuk query "Apa yang bahkan bilangan prima yang lebih besar dari dua ada?" , karena ini adalah jawaban yang logis dan masuk akal secara matematis. Lagi pula, jika Anda bertanya, "Apa yang pertama bahkan lebih besar dari dua?" tidak ada jawaban alami. Anda tidak dapat menjawab pertanyaan itu, karena Anda meminta satu nomor. Ini bukan tidak ada (itu bukan angka tunggal, tetapi satu set), bukan 0. Oleh karena itu kami melemparkan pengecualian.Anda perlu memvalidasi argumen yang disediakan saat fungsi Anda dipanggil. Dan faktanya, Anda ingin tahu cara menangani argumen yang tidak valid. Fakta bahwa banyak argumen saling bergantung, tidak menebus fakta bahwa Anda memvalidasi argumen.
Jadi saya akan memilih ArgumentException yang menyediakan informasi yang diperlukan bagi pengguna untuk memahami apa yang salah.
Sebagai contoh, periksa
public static TSource ElementAt<TSource>(this IEnumerable<TSource>, Int32)
fungsi di Linq. Yang melempar ArgumentOutOfRangeException jika indeks kurang dari 0 atau lebih besar dari atau sama dengan jumlah elemen dalam sumber. Dengan demikian indeks divalidasi sehubungan dengan enumerable yang disediakan oleh penelepon.sumber
new[] { 1, 2, 3 }.Skip(4).ToList();
Anda mendapatkan set kosong, yang membuat saya berpikir bahwa mengembalikan set kosong mungkin adalah pilihan yang lebih baik di sini.Anda harus melakukan salah satu dari yang berikut (meskipun terus secara konsisten menimbulkan masalah mendasar seperti jumlah kombinasi negatif):
Berikan dua implementasi, satu yang mengembalikan set kosong ketika input bersama tidak masuk akal, dan satu yang melempar. Coba panggil mereka
CombinationsOf
danCombinationsOfWithInputCheck
. Atau apa pun yang Anda suka. Anda dapat membalikkan ini sehingga yang memeriksa input adalah nama yang lebih pendek dan daftar adalahCombinationsOfAllowInconsistentParameters
.Untuk metode Linq, kembalikan yang kosong
IEnumerable
pada premis yang telah Anda jelaskan. Kemudian, tambahkan metode Linq ini ke perpustakaan Anda:Akhirnya, gunakan seperti itu:
atau seperti ini:
Harap dicatat bahwa metode pribadi yang berbeda dari yang umum diperlukan agar perilaku melempar atau tindakan terjadi ketika rantai linq dibuat alih-alih beberapa waktu kemudian ketika dihitung. Anda ingin membuangnya segera.
Namun, perlu diketahui bahwa setidaknya harus menyebutkan item pertama untuk menentukan apakah ada item. Ini adalah kelemahan potensial yang saya pikir sebagian besar dikurangi oleh fakta bahwa setiap pemirsa masa depan dapat dengan mudah alasan bahwa
ThrowIfEmpty
metode memiliki menghitung setidaknya satu item, jadi harus tidak terkejut dengan hal itu melakukannya. Tapi kamu tidak pernah tahu. Anda bisa menjadikan ini lebih eksplisitThrowIfEmptyByEnumeratingAndReEmittingFirstItem
. Tapi itu sepertinya berlebihan berlebihan.Saya pikir # 2 cukup, baik, luar biasa! Sekarang kekuatannya ada dalam kode panggilan, dan pembaca kode selanjutnya akan mengerti apa yang dilakukannya, dan tidak perlu berurusan dengan pengecualian yang tidak terduga.
sumber
Saya bisa melihat argumen untuk kedua kasus penggunaan - pengecualian sangat bagus jika kode hilir mengharapkan set yang berisi data. Di sisi lain, hanya satu set kosong sangat bagus jika jika ini diharapkan.
Saya pikir itu tergantung pada harapan penelepon jika ini adalah kesalahan, atau hasil yang dapat diterima - jadi saya akan mentransfer pilihan ke penelepon. Mungkin memperkenalkan opsi?
.CombinationsOf(4, CombinationsGenerationMode.Distinct, Options.AllowEmptySets)
sumber
Ada dua pendekatan untuk memutuskan apakah tidak ada jawaban yang jelas:
Tulis kode dengan asumsi opsi pertama, lalu opsi lainnya. Pertimbangkan mana yang akan bekerja paling baik dalam praktik.
Tambahkan parameter boolean "ketat" untuk menunjukkan apakah Anda ingin parameter diverifikasi secara ketat atau tidak. Misalnya, Java
SimpleDateFormat
memilikisetLenient
metode untuk mencoba mengurai input yang tidak sepenuhnya cocok dengan format. Tentu saja, Anda harus memutuskan apa defaultnya.sumber
Berdasarkan analisis Anda sendiri, mengembalikan set kosong tampaknya jelas benar - Anda bahkan telah mengidentifikasinya sebagai sesuatu yang mungkin diinginkan beberapa pengguna dan belum jatuh ke dalam perangkap melarang penggunaan karena Anda tidak dapat membayangkan pengguna pernah ingin menggunakannya seperti itu.
Jika Anda benar-benar merasa bahwa beberapa pengguna mungkin ingin memaksakan pengembalian yang tidak kosong, maka beri mereka cara untuk meminta perilaku itu daripada memaksanya pada semua orang. Misalnya, Anda dapat:
AssertNonempty
cek yang bisa mereka masukkan ke rantai mereka.sumber
Ini benar-benar tergantung pada apa yang diharapkan pengguna Anda dapatkan. Untuk contoh (yang agak tidak terkait) jika kode Anda melakukan pembagian, Anda bisa melempar pengecualian atau kembali
Inf
atauNaN
ketika Anda membagi dengan nol. Namun, tidak ada yang benar atau salah:Inf
di pustaka Python, orang akan menyerang Anda karena menyembunyikan kesalahanDalam kasus Anda, saya akan memilih solusi yang paling mencengangkan bagi pengguna akhir. Karena Anda sedang mengembangkan perpustakaan yang berurusan dengan set, set kosong sepertinya sesuatu yang diharapkan pengguna Anda untuk berurusan dengan, jadi mengembalikannya terdengar seperti hal yang masuk akal untuk dilakukan. Tapi saya mungkin keliru: Anda memiliki pemahaman yang jauh lebih baik tentang konteks daripada orang lain di sini, jadi jika Anda mengharapkan pengguna Anda mengandalkan set yang selalu kosong, Anda harus segera melemparkan pengecualian.
Solusi yang memungkinkan pengguna memilih (seperti menambahkan parameter "ketat") tidak definitif, karena mereka mengganti pertanyaan asli dengan yang setara baru: "Nilai mana yang
strict
seharusnya menjadi default?"sumber
Ini adalah konsepsi umum (dalam matematika) bahwa ketika Anda memilih elemen dari set Anda tidak dapat menemukan elemen dan karenanya Anda mendapatkan set kosong . Tentu saja Anda harus konsisten dengan matematika jika menggunakan cara ini:
Aturan umum yang ditetapkan:
Pertanyaan Anda sangat halus:
Bisa jadi input fungsi Anda harus menghormati kontrak : Dalam hal input yang tidak valid harus menimbulkan pengecualian, itu saja, fungsi tersebut tidak berfungsi di bawah parameter reguler.
Bisa jadi input fungsi Anda harus berperilaku persis seperti set , dan karenanya harus dapat mengembalikan set kosong.
Sekarang jika saya berada di Anda, saya akan pergi dengan cara "Set", tetapi dengan "TAPI" besar.
Asumsikan Anda memiliki koleksi yang "dengan hipotesis" hanya boleh dimiliki siswa perempuan:
Sekarang koleksi Anda tidak lagi merupakan "set murni", karena Anda memiliki kontrak di atasnya, dan karenanya Anda harus menegakkan kontrak Anda dengan pengecualian.
Ketika Anda menggunakan fungsi "set" Anda dengan cara yang murni, Anda tidak boleh melempar pengecualian jika set kosong, tetapi jika Anda memiliki koleksi yang tidak lebih "set murni" maka Anda harus membuang pengecualian di mana yang tepat .
Anda harus selalu melakukan apa yang terasa lebih alami dan konsisten: bagi saya satu set harus mematuhi aturan, sementara hal-hal yang tidak ditetapkan harus memiliki aturan yang dipikirkan dengan baik.
Dalam kasus Anda, sepertinya ide yang baik untuk dilakukan:
Tapi itu bukan ide yang baik, kita sekarang memiliki keadaan tidak valid yang dapat terjadi saat runtime pada saat-saat acak, namun itu tersirat dalam masalah sehingga kita tidak bisa menghapusnya, yakin kita bisa memindahkan pengecualian di dalam beberapa metode utilitas (tepatnya kode yang sama, pindah di tempat yang berbeda), tetapi itu salah dan pada dasarnya hal terbaik yang dapat Anda lakukan adalah tetap berpegang pada aturan yang ditetapkan .
Infact menambahkan kompleksitas baru hanya untuk menunjukkan Anda dapat menulis metode kueri linq tampaknya tidak layak untuk masalah Anda , saya cukup yakin jika OP dapat memberi tahu kami lebih banyak tentang domainnya, mungkin kami bisa menemukan tempat di mana pengecualian benar-benar diperlukan (jika sama sekali, ada kemungkinan masalahnya tidak memerlukan pengecualian sama sekali).
sumber
FemaleClass
kecuali jika hanya memiliki betina (itu tidak bisa dibuat secara publik, hanya kelas Builder yang bisa membagikan contoh), dan mungkin memiliki setidaknya satu betina di dalamnya . Kemudian kode Anda di semua tempat yangFemaleClass
dianggap sebagai argumen tidak harus menangani pengecualian karena objeknya dalam kondisi yang salah.