Mengembalikan boolean ketika kesuksesan atau kegagalan adalah satu-satunya perhatian

15

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

  1. Naikkan pengecualian, meskipun situasinya tidak luar biasa, dan ingatlah untuk menangkap pengecualian itu di setiap tempat fungsi tersebut dipanggil
  2. 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?

Ben
sumber

Jawaban:

11

Anda harus kembali booleanketika metode / fungsi berguna dalam membuat keputusan logis.

Anda harus melempar exceptionketika metode / fungsi tidak mungkin digunakan dalam keputusan logis.

Anda harus membuat keputusan tentang seberapa penting kegagalan itu, dan apakah itu harus ditangani atau tidak. Jika Anda dapat mengklasifikasikan kegagalan sebagai peringatan, lalu kembali boolean. Jika objek memasuki kondisi buruk yang membuat panggilan di masa depan tidak stabil, maka lemparkan a exception.

Praktik lain adalah kembali objectsbukannya hasil. Jika Anda menelepon open, maka itu harus mengembalikan Fileobjek atau nulljika tidak dapat membuka. Ini memastikan pemrogram memiliki instance objek yang dalam keadaan valid yang dapat digunakan.

EDIT:

Ingatlah bahwa sebagian besar bahasa akan membuang hasil fungsi saat jenisnya boolean atau integer. Jadi mungkin untuk memanggil fungsi ketika tidak ada tugas tangan kiri untuk hasilnya. Ketika bekerja dengan hasil boolean, selalu berasumsi programmer mengabaikan nilai yang dikembalikan dan menggunakannya untuk memutuskan apakah itu lebih baik menjadi pengecualian.

Reactgular
sumber
Ini adalah validasi dari apa yang saya lakukan jadi saya suka jawabannya :-). Pada objek, meskipun saya mengerti dari mana Anda berasal, saya tidak melihat bagaimana ini membantu dalam sebagian besar kasus saya akan menggunakannya. Saya ingin menjadi KERING jadi saya akan mengembalikan objek ke metode tunggal karena saya hanya ingin melakukan satu hal untuk itu. Saya kemudian pergi dengan kode yang sama yang saya miliki sekarang, simpan dengan metode tambahan. (juga untuk contoh yang diberikan saya menghapus file sehingga objek file null tidak banyak bicara :-)
Ben
Hapus itu rumit, karena tidak dijamin. Saya belum pernah melihat metode penghapusan file melemparkan pengecualian, tetapi apa yang bisa dilakukan programmer jika gagal? Terus mengulangi coba lagi? Tidak, ini masalah OS. Kode harus mencatat hasilnya dan melanjutkan.
Reactgular
4

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:

import os

class DoSomething(object):

    def remove_file(self, filename):
        try:
            os.remove(filename)
            return Success(None)
        except OSError:
            return Failure("There was an OS Error.")

    @do(Failable)
    def process_file(self, filename):
        do_something()
        yield remove_file(filename)
        do_something_else()
        mreturn(Success("All ok."))

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:

    def action_that_might_fail_and_returns_something(self):
        # get some random value between 0 and 1 here
        if value < 0.5:
            return Success(value)
        else:
            return Failure("Bad value! Bad! Go to your room!")

    @do(Failable)
    def process_file(self, filename):
        do_something()
        yield remove_file(filename)
        yield action_that_might_fail(somearg)
        yield another_action_that_might_fail(someotherarg)
        some_val = yield action_that_might_fail_and_returns_something()
        yield something_that_used_the_return_value(some_val)
        do_something_else()
        mreturn(Success("All ok."))

Pada setiap yields dalam process_filefungsi, jika pemanggilan fungsi mengembalikan suatu Kegagalan maka process_filefungsi 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():- pin exampletidak ada.

Untuk mendapatkan hasil dari fungsi yang didekorasi dengan Failable (seperti process_file), Anda harus menangkap hasilnya variabledan melakukan variable.valueuntuk mendapatkannya.

paul
sumber
2

Fungsi adalah kontrak, dan namanya harus menunjukkan kontrak apa yang akan dipenuhi. IMHO, jika Anda menamakannya remove_filemaka itu harus menghapus file dan gagal melakukannya harus menyebabkan pengecualian. Di sisi lain, jika Anda try_remove_fileberi 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_fileatau tidak try_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 memiliki remove_filepengecualian melempar saja ketika gagal.

tia
sumber
0

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 fungsi removeFile()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 removeFilemelemparkan 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.

Dima
sumber
Itu sangat spesifik untuk contoh yang saya berikan, yang saya lebih suka hindari. Saya belum menulis ini, ini akan mengarsipkan file dan mencatat fakta bahwa ini telah terjadi dalam database. File dapat dimuat ulang kapan saja (meskipun begitu file yang dimuat jauh lebih kecil kemungkinannya untuk dimuat ulang) ada kemungkinan bahwa file dapat dikunci oleh proses lain antara pemeriksaan dan upaya penghapusan. Tidak ada yang luar biasa tentang kegagalan. Ini adalah standar Python untuk tidak repot-repot memeriksa dulu dan menangkap pengecualian ketika dinaikkan (jika diperlukan), saya tidak ingin melakukan apa pun dengannya kali ini.
Ben
Jika tidak ada yang luar biasa tentang kegagalan, maka memeriksa apakah Anda dapat menghapus file adalah bagian yang sah dari logika program Anda. Prinsip tanggung jawab tunggal menentukan bahwa Anda harus memiliki fungsi pemeriksaan dan fungsi removeFile.
Dima