Fungsi mengembalikan true / false vs. void ketika berhasil dan melempar pengecualian ketika gagal

22

Saya sedang membangun API, fungsi yang mengunggah file. Fungsi ini tidak akan mengembalikan apa-apa / batal jika file diunggah dengan benar dan memberikan pengecualian ketika ada masalah.

Mengapa pengecualian dan bukan hanya salah? Karena di dalam pengecualian saya dapat menentukan alasan kegagalan (tidak ada koneksi, nama file hilang, kata sandi salah, deskripsi file hilang, dll). Saya ingin membuat pengecualian khusus (dengan beberapa enum untuk membantu pengguna API untuk menangani semua kesalahan).

Apakah ini praktik yang baik atau lebih baik mengembalikan objek (dengan boolean di dalamnya, pesan kesalahan opsional dan enum untuk kesalahan)?

Accollativo
sumber
59
Benar dan salah berjalan seiring. Void dan exception berjalan seiring. Benar dan pengecualian berjalan bersama seperti kucing dan pajak. Suatu hari saya mungkin sedang membaca kode Anda. Tolong jangan lakukan ini padaku.
candied_orange
4
Saya sangat suka pola di mana objek dikembalikan yang merangkum keberhasilan atau kegagalan. Sebagai contoh, Rust memiliki di Result<T, E>mana Thasilnya jika berhasil dan Ekesalahan jika tidak berhasil. Melempar pengecualian (bisa - tidak begitu yakin di Jawa) bisa mahal karena melibatkan melepas tumpukan panggilan, tetapi membuat objek Pengecualian itu murah. Jelas, tetap berpegang pada pola bahasa yang Anda gunakan, tetapi jangan menggabungkan boolean dan pengecualian seperti ini.
Dan Pantry
4
Hati-hati di sini. Anda mungkin menuju ke lubang kelinci. Ketika program Anda mampu menangani masalah, biasanya lebih mudah untuk mendeteksi dan mengatasinya dengan memeriksa prasyarat. Anda akan jarang menangkap pengecualian, memeriksa datanya dalam kode, dan coba lagi setelah memperbaiki masalah. Karena itu, sangat jarang memiliki banyak tipe pengecualian. Jika Anda mencoba untuk mencatat masalah, ini jauh lebih masuk akal, tetapi jangan berharap untuk menulis banyak blok tangkap dengan sejumlah besar kode di dalamnya.
jpmc26
4
@DanPantry Di Jawa, tumpukan panggilan diperiksa saat membuat pengecualian, bukan saat melemparkannya. fillInStackTrace();disebut dalam konstruktor super, di kelasThrowable
Simon Forsberg

Jawaban:

35

Melempar pengecualian hanyalah cara tambahan untuk membuat metode mengembalikan nilai. Penelepon dapat memeriksa nilai kembali semudah menangkap pengecualian dan memeriksanya. Karena itu, memutuskan antara throwdan returnmembutuhkan kriteria lain.

Melempar pengecualian harus sering dihindari jika membahayakan efisiensi program Anda (membangun objek pengecualian dan melepaskan tumpukan panggilan jauh lebih berfungsi untuk komputer daripada hanya mendorong nilai ke dalamnya). Tetapi jika tujuan metode Anda adalah untuk mengunggah file, maka bottleneck akan selalu menjadi jaringan dan sistem file I / O, jadi tidak ada gunanya untuk mengoptimalkan metode pengembalian.

Itu juga ide yang buruk untuk membuang pengecualian untuk apa yang seharusnya menjadi aliran kontrol sederhana (misalnya ketika pencarian berhasil dengan menemukan nilainya), karena itu melanggar harapan pengguna API. Tetapi metode gagal memenuhi tujuannya adalah kasus luar biasa (atau setidaknya seharusnya), jadi saya tidak melihat alasan untuk tidak melempar pengecualian. Dan jika Anda melakukan itu, Anda bisa menjadikannya pengecualian khusus, lebih informatif (tapi itu ide yang baik untuk membuatnya menjadi subkelas standar, seperti pengecualian yang lebih umum seperti IOException).

Kilian Foth
sumber
7
Jika itu adalah hasil yang diharapkan untuk permintaan unggah file gagal, saya akan terkejut dengan pengecualian yang dilemparkan ke wajah saya (terutama jika ada nilai balik dalam tanda tangan metode).
hoffmale
1
Perhatikan bahwa alasan untuk membuatnya memperpanjang pengecualian standar adalah karena banyak (mungkin bahkan sebagian besar) penelepon tidak akan menulis kode untuk mencoba memperbaiki masalah. Akibatnya, sebagian besar penelepon akan menginginkan yang sederhana, "tangkap kelompok besar pengecualian ini," tanpa harus mendaftar semuanya secara eksplisit.
jpmc26
4
@ gbjbaanb Itu sebabnya pengecualian harus digunakan ketika memang ada beberapa situasi yang tidak dapat diperbaiki. Ketika Anda mencoba untuk membuka file dan file tidak dapat dibaca, ini merupakan kasus yang bagus untuk pengecualian. Ketika Anda memeriksa keberadaan file, boolean perlu digunakan karena diharapkan file tersebut tidak ada di sana.
Malcolm
21
Kilian mengatakan, dalam slogan rekursif "pengecualian untuk situasi luar biasa", situasi luar biasa adalah ketika fungsi tidak dapat memenuhi tujuannya. Jika fungsi ini seharusnya membaca file, dan file tidak dapat dibaca itu luar biasa . Jika fungsi seharusnya memberi tahu Anda apakah ada file atau tidak (apalagi potensi TOCTOU), maka file yang tidak ada tidak luar biasa karena fungsi tersebut masih dapat melakukan apa yang seharusnya dilakukan. Jika fungsi seharusnya menyatakan bahwa file itu ada, maka itu tidak ada adalah luar biasa karena itulah yang dimaksud "menegaskan".
Steve Jessop
10
Perhatikan bahwa satu-satunya perbedaan antara fungsi yang memberi tahu Anda apakah ada file, dan fungsi yang menyatakan bahwa ada file, adalah apakah kasus file tidak ada adalah luar biasa. Orang-orang kadang berbicara seolah-olah itu adalah properti dari kondisi kesalahan apakah itu "luar biasa" atau tidak. Ini bukan properti dari kondisi kesalahan, ini adalah properti gabungan dari kondisi kesalahan dan tujuan dari fungsi itu terjadi. Kalau tidak, kita tidak akan pernah menangkap pengecualian.
Steve Jessop
31

Sama sekali tidak ada alasan untuk kembali truesukses jika Anda tidak kembali falsepada kegagalan. Seperti apa seharusnya kode klien?

if (result = tryMyAPICall()) {
    // business logic
}
else {
    // this will *never* happen anyways
}

Dalam hal ini, penelepon membutuhkan blok coba-tangkap, tetapi kemudian ia dapat menulis:

try {
    result = tryMyAPICall();
    // business logic
    // will only reach this line when no exception
    // no reason to use an if-condition
} catch (SomeException se) { }

Jadi nilai truekembali sama sekali tidak relevan untuk penelepon. Jadi simpan saja metodenya void.


Secara umum, ada tiga cara untuk merancang mode failre.

  1. Kembalikan benar / salah
  2. Gunakan void, lempar (dicentang) pengecualian
  3. mengembalikan objek hasil antara .

Kembali true/false

Ini digunakan di beberapa API yang lebih tua, kebanyakan c-style. Kerugiannya adalah obviuos, Anda tidak tahu apa yang salah. PHP cukup sering melakukan ini, mengarah ke kode seperti ini:

if (xyz_parse($data) === FALSE)
   $error = xyz_last_error();

Dalam konteks multi-utas, ini bahkan lebih buruk.

Melempar (dicentang) pengecualian

Ini cara yang bagus untuk melakukannya. Di beberapa titik, Anda dapat mengharapkan kegagalan. Java melakukan ini dengan soket. Asumsi dasarnya adalah bahwa panggilan harus berhasil, tetapi semua orang tahu bahwa operasi tertentu mungkin gagal. Koneksi soket ada di antara mereka. Jadi penelepon terpaksa menangani kegagalan. itu desain yang bagus, karena memastikan penelepon benar-benar menangani kegagalan, dan memberi penelepon cara yang elegan untuk menangani kegagalan.

Kembalikan objek hasil

Ini adalah cara lain yang bagus untuk menangani ini. Ini sering digunakan untuk parsing atau hanya hal-hal yang perlu divalidasi.

ValidationResult result = parser.validate(data);
if (result.isValid())
    // business logic
else
    error = result.getvalidationError();

Bagus, logika bersih untuk penelepon juga.

Ada sedikit perdebatan kapan harus menggunakan kasus kedua, dan kapan harus menggunakan yang ketiga. Beberapa orang percaya bahwa pengecualian harus luar biasa dan bahwa Anda tidak boleh merancang dengan kemungkinan pengecualian dalam pikiran, dan akan selalu menggunakan opsi ketiga. Tidak masalah. Tetapi kami telah memeriksa pengecualian di Jawa, jadi saya tidak melihat alasan untuk tidak menggunakannya. Saya menggunakan ekspektasi yang dicentang ketika asumsi dasarnya adalah bahwa panggilan harus berhasil (seperti menggunakan soket), tetapi kegagalan mungkin terjadi, dan saya menggunakan opsi ketiga ketika sangat tidak jelas apakah panggilan tersebut akan berhasil (seperti memvalidasi data). Tetapi ada pendapat berbeda tentang ini.

Dalam kasus Anda, saya akan menggunakan void+ Exception. Anda mengharapkan unggahan file berhasil, dan ketika tidak, itu luar biasa. Tetapi penelepon dipaksa untuk menangani mode kegagalan itu, dan Anda dapat mengembalikan Pengecualian yang dengan benar menjelaskan kesalahan apa yang terjadi.

Polygnome
sumber
5

Ini benar-benar bermuara pada apakah kegagalan itu luar biasa atau diharapkan .

Jika kesalahan bukan sesuatu yang Anda harapkan secara umum, maka kemungkinan besar pengguna API akan memanggil metode Anda tanpa penanganan kesalahan khusus. Melempar pengecualian memungkinkan untuk menggelembungkan tumpukan ke tempat di mana ia diperhatikan.

Jika di sisi lain kesalahan adalah hal biasa, Anda harus mengoptimalkan untuk pengembang yang memeriksa mereka, dan try-catchklausa sedikit lebih rumit daripada seri if/elifatau switch.

Selalu desain API dalam pola pikir seseorang yang menggunakannya.

Xiong Chiamiov
sumber
1
Dalam kasus saya, seperti yang ditunjukkan oleh seseorang, harus merupakan kegagalan luar biasa yang tidak dapat diunggah oleh pengguna API.
Accollativo
4

Jangan mengembalikan boolean jika Anda tidak pernah ingin kembali false. Cukup buat metode voiddan dokumentasikan bahwa ia akan melempar pengecualian ( IOExceptioncocok) pada kegagalan.

Alasan untuk ini adalah bahwa jika Anda mengembalikan boolean, pengguna API Anda dapat menyimpulkan bahwa ia dapat melakukan ini:

if (fileUpload(file) == false) {
    // Handle failure.
}

Tentu saja itu tidak akan berhasil; yaitu, ada ketidaksesuaian antara kontrak metode Anda dan perilakunya. Jika Anda melemparkan pengecualian yang dicentang, pengguna metode ini harus menangani kegagalan:

try {
    fileUpload(file);
} catch (IOException e) {
    // Handle failure.
}
JeroenHoek
sumber
3

Jika metode Anda memiliki nilai balik, melemparkan pengecualian mungkin mengejutkan penggunanya. Jika saya melihat metode mengembalikan boolean, saya cukup yakin itu akan mengembalikan true jika berhasil dan salah jika tidak, dan saya akan menyusun kode saya menggunakan klausa if-else. Jika pengecualian Anda akan didasarkan pada enum, Anda mungkin juga mengembalikan nilai enum, mirip dengan windows HResult, dan menyimpan nilai enum untuk saat metode berhasil.

Ini juga menjengkelkan untuk memiliki pengecualian metode melempar ketika itu bukan langkah terakhir dalam suatu prosedur. Harus menulis aliran coba-tangkap dan alihkan kendali ke blok tangkap adalah resep yang baik untuk spageti, dan harus dihindari.

Jika Anda maju dengan pengecualian, coba kembalikan void sebagai gantinya, pengguna akan mengira itu berhasil jika tidak ada pengecualian yang dilemparkan, dan tidak ada yang akan mencoba menggunakan klausa if-else untuk mengalihkan aliran kontrol.

ccControl
sumber
1
Enum hanyalah ide untuk membantu pengguna API untuk menangani berbagai jenis kesalahan tanpa mem-parsing pesan kesalahan. Jadi dari sudut pandang Anda dalam kasus saya lebih baik mengembalikan enum dan menambahkan enum untuk "OK".
Accollativo