Saya sering menemukan diri saya mengembalikan boolean dari suatu metode, yang digunakan di banyak lokasi, untuk memuat semua logika di sekitar metode itu di satu tempat. Semua metode panggilan (internal) yang perlu diketahui adalah apakah operasi itu berhasil, atau tidak.
Saya menggunakan Python tetapi pertanyaannya tidak selalu spesifik untuk bahasa itu. Hanya ada dua opsi yang bisa saya pikirkan
- Naikkan pengecualian, meskipun situasinya tidak luar biasa, dan ingatlah untuk menangkap pengecualian itu di setiap tempat fungsi tersebut dipanggil
- Kembalikan boolean seperti yang saya lakukan.
Ini adalah contoh yang sangat sederhana yang menunjukkan apa yang saya bicarakan.
import os
class DoSomething(object):
def remove_file(self, filename):
try:
os.remove(filename)
except OSError:
return False
return True
def process_file(self, filename):
do_something()
if remove_file(filename):
do_something_else()
Meskipun fungsional, saya benar-benar tidak menyukai cara melakukan sesuatu, ini "berbau", dan kadang-kadang dapat menghasilkan banyak jika bersarang. Tapi, saya tidak bisa memikirkan cara yang lebih sederhana.
Saya bisa beralih ke filosofi yang lebih LBYL dan menggunakan os.path.exists(filename)
sebelum mencoba penghapusan tetapi tidak ada jaminan file tidak akan dikunci sementara itu (tidak mungkin tapi mungkin) dan saya masih harus menentukan apakah penghapusan telah berhasil atau tidak.
Apakah ini desain yang "dapat diterima" dan jika tidak, apa cara yang lebih baik untuk mendesain ini?
Intuisi Anda tentang hal ini benar, ada cara yang lebih baik untuk melakukan ini: Monad .
Apa itu Monads?
Monad adalah (untuk memparafrasekan Wikipedia) cara merantai operasi bersama sambil menyembunyikan mekanisme chaining; dalam kasus Anda mekanisme rantai adalah bersarang
if
. Sembunyikan itu dan kode Anda akan berbau jauh lebih baik.Ada beberapa monad yang akan melakukan hal itu ("Mungkin" dan "Baik") dan beruntung bagi Anda mereka adalah bagian dari perpustakaan python monad yang sangat bagus!
Apa yang dapat mereka lakukan untuk kode Anda
Berikut ini contoh menggunakan monad "Either" ("Failable" di perpustakaan yang ditautkan), di mana suatu fungsi dapat mengembalikan Sukses atau Kegagalan, tergantung pada apa yang terjadi:
Sekarang, ini mungkin tidak terlihat jauh berbeda dari yang Anda miliki sekarang, tetapi pertimbangkan bagaimana keadaannya jika Anda memiliki lebih banyak operasi yang dapat mengakibatkan Kegagalan:
Pada setiap
yield
s dalamprocess_file
fungsi, jika pemanggilan fungsi mengembalikan suatu Kegagalan makaprocess_file
fungsi tersebut akan keluar, pada saat itu , mengembalikan nilai Kegagalan dari fungsi yang gagal, alih-alih melanjutkan melalui sisanya dan mengembalikanSuccess("All ok.")
Sekarang, bayangkan melakukan hal di atas dengan bersarang
if
! (Bagaimana Anda menangani nilai pengembalian !?)Kesimpulan
Monad itu bagus :)
Catatan:
Saya bukan programmer Python - Saya menggunakan perpustakaan monad yang ditautkan di atas dalam skrip yang saya ninja'akan untuk beberapa otomatisasi proyek. Namun, saya berpendapat bahwa secara umum pendekatan idiomatis yang disukai adalah menggunakan pengecualian.
IIRC ada kesalahan ketik pada skrip lib pada halaman yang terhubung meskipun saya lupa di mana itu ATM. Saya akan memperbarui jika saya ingat.Saya mengubah versi saya terhadap halaman dan menemukan:def failable_monad_examle():
->def failable_monad_example():
-p
inexample
tidak ada.Untuk mendapatkan hasil dari fungsi yang didekorasi dengan Failable (seperti
process_file
), Anda harus menangkap hasilnyavariable
dan melakukanvariable.value
untuk mendapatkannya.sumber
Fungsi adalah kontrak, dan namanya harus menunjukkan kontrak apa yang akan dipenuhi. IMHO, jika Anda menamakannya
remove_file
maka itu harus menghapus file dan gagal melakukannya harus menyebabkan pengecualian. Di sisi lain, jika Andatry_remove_file
beri nama , itu harus "mencoba" untuk menghapus dan mengembalikan boolean untuk mengetahui apakah file tersebut telah dihapus atau tidak.Ini akan mengarah pada pertanyaan lain - apakah seharusnya
remove_file
atau tidaktry_remove_file
? Itu tergantung pada situs panggilan Anda. Sebenarnya, Anda dapat memiliki kedua metode dan menggunakannya dalam skenario yang berbeda, tapi saya pikir menghapus file per-se memiliki peluang sukses yang tinggi jadi saya lebih suka memilikiremove_file
pengecualian melempar saja ketika gagal.sumber
Dalam kasus khusus ini mungkin berguna untuk memikirkan mengapa Anda mungkin tidak dapat menghapus file. Katakanlah masalahnya adalah bahwa file tersebut mungkin ada atau tidak ada. Maka Anda harus memiliki fungsi
doesFileExist()
yang mengembalikan benar atau salah, dan fungsiremoveFile()
yang hanya menghapus file.Dalam kode Anda, Anda akan terlebih dahulu memeriksa apakah file tersebut ada. Jika ya, hubungi
removeFile
. Jika tidak, maka lakukan hal-hal lain.Dalam hal ini Anda mungkin masih ingin
removeFile
melemparkan pengecualian jika file tidak dapat dihapus karena alasan lain, seperti izin.Singkatnya, pengecualian harus dilemparkan untuk hal-hal yang, yah, luar biasa. Jadi, jika sangat normal bahwa file yang Anda coba hapus mungkin tidak ada, maka itu bukan pengecualian. Tuliskan predikat boolean untuk memeriksanya. Di sisi lain, jika Anda tidak memiliki izin menulis untuk file tersebut, atau jika file tersebut ada pada sistem file jarak jauh yang tiba-tiba tidak dapat diakses, itu mungkin kondisi yang luar biasa.
sumber