Apakah rethrowing pengecualian membocorkan abstraksi?

12

Saya memiliki metode antarmuka yang menyatakan dalam dokumentasi itu akan membuang jenis pengecualian tertentu. Implementasi metode itu menggunakan sesuatu yang melempar pengecualian. Pengecualian internal ditangkap dan pengecualian yang dinyatakan oleh kontrak antarmuka dilemparkan. Berikut adalah sedikit contoh kode untuk menjelaskan dengan lebih baik. Itu ditulis dalam PHP tetapi cukup sederhana untuk diikuti.

// in the interface

/**
 * @return This method returns a doohickey to use when you need to foo
 * @throws DoohickeyDisasterException
 */
public function getThatDoohickey();

// in the implementation

public function getThatDoohickey() {

    try {
        $SomethingInTheClass->doSomethingThatThrowsAnException();
    } catch (Exception $Exc) {
        throw new DoohickeyDisasterException('Message about doohickey failure');
    }

    // other code may return the doohickey

}

Saya menggunakan metode ini dalam upaya untuk mencegah abstraksi bocor.

Pertanyaan saya adalah: Apakah melewatkan pengecualian internal yang dilemparkan sebagai pengecualian sebelumnya akan membocorkan abstraksi? Jika tidak, apakah cocok menggunakan kembali pesan pengecualian sebelumnya? Jika itu akan bocor abstraksi dapatkah Anda memberikan beberapa petunjuk mengapa Anda berpikir demikian?

Hanya untuk memperjelas, pertanyaan saya akan melibatkan perubahan ke baris kode berikut

throw new DoohickeyDisasterException($Exc->getMessage(), null, $Exc);
Charles Sprayberry
sumber
Pengecualian harus tentang penyebab bukan tentang siapa yang melemparnya . Jika kode Anda dapat memiliki file_not_found, maka ia harus membuang a file_not_found_exception. Pengecualian tidak boleh khusus perpustakaan.
Mooing Duck

Jawaban:

11

Pertanyaan saya adalah: Apakah melewatkan pengecualian internal yang dilemparkan sebagai pengecualian sebelumnya akan membocorkan abstraksi? Jika tidak, apakah cocok menggunakan kembali pesan pengecualian sebelumnya?

Jawabannya adalah, tergantung".

Secara khusus, itu tergantung pada pengecualian, dan apa artinya. Intinya, rethrowing itu berarti Anda menambahkan itu ke antarmuka Anda. Apakah Anda akan melakukannya jika Anda menulis kode yang Anda panggil sendiri, atau ini hanya untuk menghindari rebranding pengecualian?

Jika pengecualian Anda menyertakan jejak ke akar penyebab, dalam kode yang disebut, yang mungkin bocor data di luar abstraksi - tetapi, secara umum, saya pikir itu dapat diterima karena pengecualian ditangani oleh penelepon (dan tidak ada yang peduli di mana itu) berasal dari) atau ditampilkan kepada pengguna (dan Anda ingin tahu penyebab utama, untuk debugging.)

Namun, seringkali, hanya membiarkan pengecualian melalui adalah pilihan implementasi yang sangat masuk akal, dan bukan masalah abstraksi. Tanyakan saja "apakah saya akan membuang ini jika kode yang saya panggil tidak?"

Daniel Pittman
sumber
8

Sebenarnya, jika 'batin' pengecualian mengungkapkan apa-apa tentang cara kerja di dalam sebuah implementasi, Anda sedang bocor. Jadi secara teknis, Anda harus selalu menangkap semuanya dan membuang tipe pengecualian Anda sendiri.

TAPI.

Cukup banyak argumen penting yang dapat diajukan untuk menentang hal ini. Terutama:

  • Pengecualian adalah hal luar biasa ; mereka telah melanggar aturan 'operasi normal' dalam banyak hal, jadi mereka mungkin juga melanggar enkapsulasi dan abstraksi tingkat antarmuka.
  • Manfaat memiliki jejak tumpukan yang bermakna dan informasi kesalahan to-the-point sering melebihi kebenaran OOP, selama Anda tidak membocorkan informasi program internal melewati penghalang UI (yang akan menjadi pengungkapan informasi).
  • Membungkus setiap pengecualian dalam dalam tipe pengecualian luar yang sesuai dapat menyebabkan banyak kode boilerplate yang tidak berguna , yang mengalahkan seluruh tujuan pengecualian (yaitu, menerapkan penanganan kesalahan tanpa mencemari aliran kode biasa dengan boilerplate penanganan kesalahan).

Yang mengatakan, penangkapan dan membungkus pengecualian sering tidak masuk akal, jika hanya untuk menambahkan informasi tambahan untuk log Anda, atau untuk mencegah membocorkan informasi internal untuk pengguna akhir. Penanganan kesalahan masih merupakan bentuk seni, dan sulit untuk mengubahnya menjadi beberapa aturan umum. Beberapa pengecualian harus digelembungkan, yang lain tidak boleh, dan keputusan juga tergantung pada konteks. Jika Anda mengode UI, Anda tidak ingin membiarkan apa pun sampai ke pengguna tanpa filter; tetapi jika Anda mengkode perpustakaan yang mengimplementasikan beberapa protokol jaringan, menggelegak pengecualian tertentu mungkin adalah hal yang benar untuk dilakukan (setelah semua, jika jaringan sedang down, ada sedikit yang dapat Anda lakukan untuk meningkatkan informasi atau memulihkan dan melanjutkan ; menerjemahkan NetworkDownExceptionke FoobarNetworkDownExceptionhanya cruft tidak berarti).

tammmer
sumber
6

Pertama-tama, apa yang Anda lakukan di sini bukanlah memikirkan kembali pengecualian. Rethrowing berarti membuang pengecualian yang sama, seperti ini:

try {
    $SomethingInTheClass->doSomethingThatThrowsAnException();
} catch (Exception $Exc) {
    throw $Exc;
}

Jelas, ini agak sia-sia. Rethrowing adalah untuk keadaan di mana Anda ingin melepaskan beberapa sumber daya atau mencatat pengecualian atau apa pun yang mungkin ingin Anda lakukan, tanpa menghentikan pengecualian dari menggelegak tumpukan.

Apa yang sudah Anda lakukan adalah mengganti semua pengecualian, apa pun penyebabnya, dengan DoohickeyDisasterException. Yang mungkin atau tidak mungkin apa yang ingin Anda lakukan. Tapi saya akan menyarankan bahwa setiap kali Anda melakukan ini, Anda juga harus melakukan seperti yang Anda sarankan dan lulus pengecualian asli sebagai pengecualian dalam, kalau-kalau itu berguna dalam jejak tumpukan.

Tapi ini bukan tentang abstraksi yang bocor, itu masalah lain.

Untuk menjawab pertanyaan apakah pengecualian rethrown (atau memang pengecualian tidak tertangkap) dapat menjadi abstraksi yang bocor, saya akan mengatakan bahwa itu tergantung pada kode panggilan Anda. Jika kode itu membutuhkan pengetahuan tentang kerangka yang diabstraksi maka itu adalah abstraksi yang bocor, karena jika Anda mengganti kerangka itu dengan yang lain maka Anda harus mengubah kode di luar abstraksi Anda.

Misalnya, jika DoohickeyDisasterException adalah kelas dari framework dan kode panggilan Anda terlihat seperti ini, maka Anda memiliki abstraksi yang bocor:

try {
    $wrapper->getThatDoohickey();
} catch (DoohickeyDisasterException $Exc) {
    echo "The Doohickey is all wrong!";
    echo $Exc->getMessage();
    gotoAHappyPlaceAndSingHappySongs();
}

Jika Anda mengganti kerangka kerja pihak ketiga Anda dengan yang lain, yang menggunakan nama pengecualian yang berbeda, dan Anda harus menemukan semua referensi ini dan mengubahnya. Lebih baik untuk membuat kelas Pengecualian Anda sendiri dan membungkus kerangka pengecualian di dalamnya.

Jika, di sisi lain, DoohickeyDisasterException adalah salah satu buatan Anda sendiri maka Anda tidak membocorkan abstraksi, Anda harus lebih mementingkan diri sendiri dengan poin pertama saya.

pdr
sumber
2

Aturan yang saya coba tempuh adalah: bungkus jika Anda menyebabkannya. Demikian pula, jika bug dibuat karena pengecualian runtime, itu harus semacam DoHickeyException atau MyApplicationException.

Apa yang tersirat adalah bahwa jika penyebab kesalahan adalah sesuatu di luar kode Anda, dan Anda tidak mengharapkannya gagal maka gagal dengan anggun dan rethrow itu. Demikian juga, jika ini merupakan masalah dengan kode Anda, maka bungkus dengan pengecualian (berpotensi) kustom.

Misalnya, jika file diharapkan ada di disk, atau layanan diharapkan tersedia, maka tangani dengan anggun dan ulangi kesalahan sehingga konsumen kode Anda dapat mengetahui mengapa sumber daya itu tidak tersedia. Jika Anda membuat terlalu banyak data untuk beberapa jenis, maka kesalahan Anda untuk membungkus dan melempar.

Steven Evers
sumber