Saya sering menemukan metode / fungsi yang memiliki parameter boolean tambahan yang mengontrol apakah pengecualian dilemparkan pada kegagalan, atau null dikembalikan.
Sudah ada diskusi tentang yang mana yang merupakan pilihan yang lebih baik dalam hal ini, jadi jangan fokus pada hal ini di sini. Lihat mis. Mengembalikan nilai sulap, melemparkan pengecualian atau mengembalikan false pada kegagalan?
Sebagai gantinya, mari kita asumsikan bahwa ada alasan bagus mengapa kita ingin mendukung keduanya.
Secara pribadi saya pikir metode seperti itu lebih baik harus dibagi menjadi dua: Satu yang melempar pengecualian pada kegagalan, yang lain mengembalikan nol pada kegagalan.
Jadi, mana yang lebih baik?
A: Satu metode dengan $exception_on_failure
parameter.
/**
* @param int $id
* @param bool $exception_on_failure
*
* @return Item|null
* The item, or null if not found and $exception_on_failure is false.
* @throws NoSuchItemException
* Thrown if item not found, and $exception_on_failure is true.
*/
function loadItem(int $id, bool $exception_on_failure): ?Item;
B: Dua metode yang berbeda.
/**
* @param int $id
*
* @return Item|null
* The item, or null if not found.
*/
function loadItemOrNull(int $id): ?Item;
/**
* @param int $id
*
* @return Item
* The item, if found (exception otherwise).
*
* @throws NoSuchItemException
* Thrown if item not found.
*/
function loadItem(int $id): Item;
EDIT: C: Ada yang lain?
Banyak orang menyarankan opsi lain, atau mengklaim bahwa A dan B cacat. Saran atau pendapat semacam itu disambut baik, relevan dan bermanfaat. Jawaban lengkap dapat berisi informasi tambahan seperti itu, tetapi juga akan menjawab pertanyaan utama apakah parameter untuk mengubah tanda tangan / perilaku adalah ide yang bagus.
Catatan
Jika seseorang bertanya-tanya: Contohnya dalam PHP. Tapi saya pikir pertanyaannya berlaku lintas bahasa asalkan mirip dengan PHP atau Java.
sumber
loadItemOrNull(id)
, pertimbangkan apakahloadItemOr(id, defaultItem)
masuk akal untuk Anda. Jika item adalah String atau angka, itu sering terjadi.Jawaban:
Anda benar: dua metode jauh lebih baik untuk itu, karena beberapa alasan:
Di Jawa, tanda tangan dari metode yang berpotensi melempar pengecualian akan mencakup pengecualian ini; metode lain tidak. Itu membuatnya sangat jelas apa yang diharapkan dari yang satu dan yang lain.
Dalam bahasa seperti C # di mana tanda tangan metode tidak memberi tahu apa pun tentang pengecualian, metode publik masih harus didokumentasikan, dan dokumentasi tersebut mencakup pengecualian. Mendokumentasikan metode tunggal tidak akan mudah.
Contoh Anda sempurna: komentar di bagian kedua kode terlihat jauh lebih jelas, dan saya bahkan akan menyingkat "Item, jika ditemukan (pengecualian sebaliknya)." Ke bawah ke "Item." - adanya pengecualian potensial dan deskripsi yang Anda berikan padanya cukup jelas.
Dalam kasus metode tunggal, ada beberapa kasus di mana Anda ingin beralih nilai parameter boolean saat runtime , dan jika Anda melakukannya, itu berarti pemanggil harus menangani kedua kasus (
null
respons dan pengecualian). ), membuat kode jauh lebih sulit daripada yang seharusnya. Karena pilihan dibuat bukan pada saat runtime, tetapi ketika menulis kode, dua metode masuk akal.Beberapa kerangka kerja, seperti .NET Framework, telah menetapkan konvensi untuk situasi ini, dan mereka menyelesaikannya dengan dua metode, seperti yang Anda sarankan. Satu-satunya perbedaan adalah bahwa mereka menggunakan pola yang dijelaskan oleh Ewan dalam jawabannya , jadi berikan
int.Parse(string): int
pengecualian, sementaraint.TryParse(string, out int): bool
tidak. Konvensi penamaan ini sangat kuat di komunitas .NET dan harus diikuti setiap kali kode cocok dengan situasi yang Anda jelaskan.sumber
int.Parse()
dianggap sebagai keputusan desain yang sangat buruk, itulah sebabnya tim .NET terus maju dan mengimplementasikanTryParse
.->itemExists($id)
sebelum menelepon->loadItem($id)
. Atau mungkin itu hanya pilihan yang dibuat oleh orang lain di masa lalu, dan kita harus menerima begitu saja.data Maybe a = Just a | Nothing
).Variasi yang menarik adalah bahasa Swift. Di Swift, Anda mendeklarasikan fungsi sebagai "melempar", yaitu diizinkan untuk melempar kesalahan (serupa tetapi tidak persis sama dengan pengecualian Java). Pemanggil dapat memanggil fungsi ini dalam empat cara:
Sebuah. Dalam pernyataan coba-coba menangkap dan menangani pengecualian.
b. Jika penelepon itu sendiri dinyatakan sebagai melempar, panggil saja, dan setiap kesalahan yang dilemparkan berubah menjadi kesalahan yang dilemparkan oleh penelepon.
c. Menandai panggilan fungsi dengan
try!
yang berarti "Saya yakin panggilan ini tidak akan membuang". Jika fungsi yang disebut melakukan lemparan, aplikasi dijamin kecelakaan.d. Menandai pemanggilan fungsi
try?
yang artinya "Saya tahu fungsi itu bisa melempar tapi saya tidak peduli kesalahan mana yang sebenarnya dilemparkan". Ini mengubah nilai kembali fungsi dari tipe "T
" ke tipe "optional<T>
", dan pengecualian yang dilemparkan diubah menjadi pengembalian nihil.(a) dan (d) adalah kasus yang Anda tanyakan. Bagaimana jika fungsi tersebut dapat mengembalikan nol dan dapat melempar pengecualian? Dalam kasus itu, tipe nilai pengembalian akan menjadi "
optional<R>
" untuk beberapa tipe R, dan (d) akan mengubahnya menjadi "optional<optional<R>>
" yang agak samar dan sulit dimengerti tetapi sepenuhnya baik - baik saja. Dan fungsi Swift dapat kembalioptional<Void>
, jadi (d) dapat digunakan untuk melempar fungsi yang mengembalikan Void.sumber
Saya suka
bool TryMethodName(out returnValue)
pendekatannya.Ini memberi Anda indikasi konklusif apakah metode ini berhasil atau tidak dan penamaan cocok dengan membungkus metode pengecualian melempar di blok try catch.
Jika Anda hanya mengembalikan nol, Anda tidak tahu apakah itu gagal atau nilai pengembaliannya nol.
misalnya:
contoh penggunaan (berarti keluar melalui referensi tanpa diinisialisasi)
sumber
bool TryMethodName(out returnValue)
pendekatan" Anda terlihat dalam contoh asli?Biasanya parameter boolean menunjukkan fungsi dapat dipecah menjadi dua, dan sebagai aturan Anda harus selalu membaginya daripada melewati dalam boolean.
sumber
b
: Alih-alih memanggilfoo(…, b);
Anda harus menulisif (b) then foo_x(…) else foo_y(…);
dan mengulangi argumen lain, atau sesuatu seperti((b) ? foo_x : foo_y)(…)
jika bahasa memiliki operator ternary dan fungsi / metode kelas satu. Dan ini bahkan mungkin diulang dari beberapa tempat yang berbeda dalam program ini.if (myGrandmaMadeCookies) eatThem() else makeFood()
yang ya saya mungkin bungkus dengan fungsi lain seperti inicheckIfShouldCook(myGrandmaMadeCookies)
. Saya bertanya-tanya apakah nuansa yang Anda sebutkan berkaitan dengan apakah kita memberikan boolean tentang bagaimana kita ingin fungsi berperilaku, seperti dalam pertanyaan awal, vs. kita menyampaikan beberapa info tentang model kita, seperti pada Contoh nenek saya baru saja memberi.Clean Code menyarankan untuk menghindari argumen bendera boolean, karena artinya buram di situs panggilan:
sedangkan metode terpisah dapat menggunakan nama ekspresif:
sumber
Jika saya harus menggunakan A atau B maka saya akan menggunakan B, dua metode terpisah, untuk alasan yang disebutkan dalam jawaban Arseni Mourzenkos .
Tetapi ada metode lain 1 : Di Jawa disebut
Optional
, tetapi Anda dapat menerapkan konsep yang sama untuk bahasa lain di mana Anda dapat menentukan jenis Anda sendiri, jika bahasa tersebut belum menyediakan kelas yang sama.Anda mendefinisikan metode Anda seperti ini:
Dalam metode Anda, Anda akan kembali
Optional.of(item)
jika Anda menemukan item atau Anda kembaliOptional.empty()
jika Anda tidak. Anda tidak pernah membuang 2 dan tidak pernah kembalinull
.Untuk klien dari metode Anda, itu adalah sesuatu seperti
null
, tetapi dengan perbedaan besar yang memaksa untuk berpikir tentang kasus barang yang hilang. Pengguna tidak bisa begitu saja mengabaikan fakta bahwa mungkin tidak ada hasil.Optional
Diperlukan untuk mengeluarkan item dari tindakan eksplisit:loadItem(1).get()
akan melemparNoSuchElementException
atau mengembalikan barang yang ditemukan.loadItem(1).orElseThrow(() -> new MyException("No item 1"))
menggunakan pengecualian khusus jika yang dikembalikanOptional
kosong.loadItem(1).orElse(defaultItem)
mengembalikan item yang ditemukan atau yang berlaludefaultItem
(yang mungkin juganull
) tanpa melemparkan pengecualian.loadItem(1).map(Item::getName)
akan kembali lagiOptional
dengan nama item, jika ada.Optional
.Keuntungannya adalah sekarang tergantung pada kode klien apa yang harus terjadi jika tidak ada item dan Anda hanya perlu menyediakan metode tunggal .
1 Saya kira itu adalah sesuatu seperti
try?
di jawaban gnasher729s tetapi tidak sepenuhnya jelas bagi saya karena saya tidak tahu Swift dan tampaknya menjadi fitur bahasa jadi itu khusus untuk Swift.2 Anda dapat melempar pengecualian untuk alasan lain, misalnya jika Anda tidak tahu apakah ada item atau tidak karena Anda memiliki masalah berkomunikasi dengan database.
sumber
Sebagai hal lain yang setuju menggunakan dua metode, ini bisa sangat mudah diimplementasikan. Saya akan menulis contoh saya dalam C #, karena saya tidak tahu sintaks PHP.
sumber
Saya lebih suka dua metode, dengan cara ini, Anda tidak hanya akan memberi tahu saya Anda mengembalikan nilai, tetapi nilai mana yang tepat.
TryDoSomething () juga bukan pola yang buruk.
Saya lebih terbiasa melihat yang pertama dalam interaksi basis data sementara yang terakhir lebih banyak digunakan dalam hal-hal parse.
Seperti yang telah diberitahukan sebelumnya kepada Anda, parameter flag biasanya adalah bau kode (bau kode tidak berarti Anda melakukan sesuatu yang salah, hanya saja ada kemungkinan Anda salah melakukannya). Meskipun Anda menyebutkannya dengan tepat, kadang-kadang ada nuansa yang membuat sulit untuk mengetahui cara menggunakan bendera itu dan Anda akhirnya melihat ke dalam tubuh metode.
sumber
Sementara orang lain menyarankan sebaliknya, saya akan menyarankan bahwa memiliki metode dengan parameter untuk menunjukkan bagaimana kesalahan harus ditangani lebih baik daripada membutuhkan penggunaan metode terpisah untuk dua skenario penggunaan. Sementara itu mungkin diinginkan untuk juga memiliki entry point yang berbeda untuk "kesalahan melempar" versus "indikasi kesalahan kembali kesalahan" penggunaan, memiliki titik masuk yang dapat melayani kedua pola penggunaan akan menghindari duplikasi kode dalam metode wrapper. Sebagai contoh sederhana, jika seseorang memiliki fungsi:
dan satu menginginkan fungsi yang berperilaku sama tetapi dengan x dibungkus tanda kurung, akan diperlukan untuk menulis dua set fungsi pembungkus:
Jika ada argumen untuk cara menangani kesalahan, hanya satu bungkus yang akan dibutuhkan:
Jika pembungkusnya cukup sederhana, duplikasi kode mungkin tidak terlalu buruk, tetapi jika mungkin perlu diubah, menduplikasi kode pada gilirannya akan membutuhkan duplikasi perubahan apa pun. Bahkan jika seseorang memilih untuk menambahkan titik masuk yang tidak menggunakan argumen tambahan:
seseorang dapat menyimpan semua "logika bisnis" dalam satu metode - metode yang menerima argumen yang menunjukkan apa yang harus dilakukan jika terjadi kegagalan.
sumber
Saya pikir semua jawaban yang menjelaskan bahwa Anda seharusnya tidak memiliki bendera yang mengontrol apakah pengecualian dilemparkan atau tidak baik. Nasihat mereka akan menjadi saran saya untuk siapa saja yang merasa perlu untuk mengajukan pertanyaan itu.
Namun, saya ingin menunjukkan bahwa ada pengecualian yang berarti untuk aturan ini. Setelah Anda cukup maju untuk mulai mengembangkan API yang akan digunakan oleh ratusan orang lainnya, ada beberapa kasus di mana Anda mungkin ingin memberikan tanda seperti itu. Saat Anda menulis API, Anda tidak hanya menulis ke pembuat puritan. Anda menulis kepada pelanggan nyata yang memiliki keinginan nyata. Bagian dari pekerjaan Anda adalah membuatnya senang dengan API Anda. Itu menjadi masalah sosial, bukan masalah pemrograman, dan terkadang masalah sosial menentukan solusi yang kurang ideal sebagai solusi pemrograman sendiri.
Anda mungkin menemukan bahwa Anda memiliki dua pengguna berbeda dari API Anda, satu di antaranya menginginkan pengecualian dan satu lagi tidak. Contoh kehidupan nyata di mana ini bisa terjadi adalah dengan perpustakaan yang digunakan baik dalam pengembangan dan pada sistem tertanam. Dalam lingkungan pengembangan, pengguna mungkin ingin memiliki pengecualian di mana-mana. Pengecualian sangat populer untuk menangani situasi yang tidak terduga. Namun, mereka dilarang dalam banyak situasi tertanam karena mereka terlalu sulit untuk menganalisis kendala waktu nyata di sekitar. Ketika Anda benar-benar peduli bukan hanya waktu rata - rata yang diperlukan untuk menjalankan fungsi Anda, tetapi waktu yang diperlukan untuk kesenangan satu orang, gagasan melepaskan tumpukan di sembarang tempat sangat tidak diinginkan.
Contoh kehidupan nyata bagi saya: perpustakaan matematika. Jika perpustakaan matematika Anda memiliki
Vector
kelas yang mendukung mendapatkan vektor satuan dalam arah yang sama, Anda harus dapat membagi dengan besarnya. Jika besarnya 0, Anda memiliki situasi bagi dengan nol. Dalam pengembangan Anda ingin menangkap ini . Anda benar-benar tidak ingin pembagian mengejutkan dengan 0 berkeliaran. Melempar pengecualian adalah solusi yang sangat populer untuk ini. Ini hampir tidak pernah terjadi (itu benar-benar perilaku yang luar biasa), tetapi ketika itu terjadi, Anda ingin mengetahuinya.Pada platform tertanam, Anda tidak ingin pengecualian itu. Akan lebih masuk akal untuk melakukan pemeriksaan
if (this->mag() == 0) return Vector(0, 0, 0);
. Sebenarnya, saya melihat ini dalam kode nyata.Sekarang pikirkan dari perspektif bisnis. Anda dapat mencoba mengajarkan dua cara berbeda menggunakan API:
Ini memuaskan pendapat sebagian besar jawaban di sini, tetapi dari perspektif perusahaan ini tidak diinginkan. Pengembang yang disematkan harus mempelajarinya gaya pengkodean, dan pengembang pengembangan harus mempelajari yang lain. Ini bisa agak sial, dan memaksa orang ke satu cara berpikir. Berpotensi lebih buruk: bagaimana Anda membuktikan bahwa versi yang disematkan tidak menyertakan pengecualian melemparkan kode? Versi melempar dapat dengan mudah berbaur, bersembunyi di suatu tempat dalam kode Anda sampai Papan Tinjauan Kegagalan yang mahal menemukannya.
Sebaliknya, jika Anda memiliki bendera yang mengaktifkan atau menonaktifkan penanganan pengecualian, kedua grup dapat mempelajari gaya pengkodean yang sama persis. Bahkan, dalam banyak kasus, Anda bahkan dapat menggunakan kode satu grup dalam proyek grup lain. Dalam kasus menggunakan kode ini
unit()
, jika Anda benar-benar peduli tentang apa yang terjadi dalam kasus dibagi dengan 0, Anda harus menulis tes sendiri. Jadi, jika Anda memindahkannya ke sistem tertanam, di mana Anda mendapatkan hasil yang buruk, perilaku itu "waras." Sekarang, dari perspektif bisnis, saya melatih coders saya sekali, dan mereka menggunakan API yang sama dan gaya yang sama di semua bagian bisnis saya.Dalam sebagian besar kasus, Anda ingin mengikuti saran orang lain: gunakan idiom like
tryGetUnit()
atau mirip untuk fungsi yang tidak melempar pengecualian, dan sebaliknya mengembalikan nilai sentinel seperti null. Jika menurut Anda pengguna mungkin ingin menggunakan kode penanganan perkecualian dan kode penanganan nonkecualian berdampingan dalam satu program, tetap dengantryGetUnit()
notasi. Namun, ada kasus sudut di mana realitas bisnis dapat membuatnya lebih baik menggunakan bendera. Jika Anda menemukan diri Anda dalam sudut itu, jangan takut untuk melemparkan "aturan" di pinggir jalan. Untuk itulah aturannya!sumber