Haruskah jenis pengecualian yang digunakan kembali disukai daripada yang digunakan tunggal?

8

Katakanlah saya memiliki Doors yang dikelola oleh DoorService. The DoorServicebertanggung jawab atas pembukaan, menutup dan mengunci pintu yang disimpan di database.

public interface DoorService
{
    void open(Door door) throws DoorLockedException, DoorAlreadyOpenedException;

    void close(Door door) throws DoorAlreadyClosedException;

    /**
     * Closes the door if open
     */
    void lock(Door door) throws DoorAlreadyLockedException;
}

Untuk metode kunci, ada a DoorAlreadyLockedExceptiondan untuk metode terbuka ada a DoorLockedException. Ini adalah opsi tetapi ada opsi lain yang memungkinkan:

1) Gunakan DoorLockedExceptionuntuk semuanya, agak canggung ketika menangkap pengecualian pada lock()panggilan

try
{
    doorService.lock(myDoor);
}
catch(DoorLockedException ex) // door ALREADY locked
{
    //error handling...
}

2) Memiliki 2 jenis pengecualian DoorLockedExceptiondanDoorAlreadyLockedException

3) Memiliki 2 tipe pengecualian tetapi biarkan DoorAlreadyLockedException extends DoorLockedException

Solusi mana yang terbaik dan mengapa?

Musim dingin
sumber
1
Bagaimana Anda mendefinisikan "yang terbaik?" Dalam C # kami bahkan tidak memeriksa pengecualian. Kami percaya bahwa ini membuat pengembangan kami lebih sederhana dan lebih dapat diandalkan.
Robert Harvey
2
Seberapa berbeda resolusi untuk AlreadyLocked dan Locked?
whatsisname
@ RobertTarvey Saya menganggap pengecualian sebagai bagian dari logika metode ini. Solusi terbaik adalah yang paling eksplisit dan paling logis. Tidak seperti di C #, kami mempromosikan penjelasan tentang kenyamanan dengan memaksa menangkap kasus luar biasa dan memastikan tidak ada yang memanggil metode tanpa memiliki pemahaman penuh tentang apa yang terjadi.
Musim dingin
1
Metode-metode ini seharusnya hanya mengembalikan kode status. Anda dapat memberi tahu pengguna Anda berdasarkan kode yang Anda dapatkan kembali, dan mendapatkan peningkatan kinerja secara gratis (pengecualian mahal, sekitar 1000 hingga 10.000 kali lebih mahal daripada mengembalikan kode status).
Robert Harvey
1
@RobertHarvey "pengecualian itu mahal, sekitar 1.000 hingga 10.000 kali lebih mahal daripada mengembalikan kode status" Saya sudah melakukan beberapa analisis tentang ini di Jawa dan biaya pengecualian sering kali terlalu banyak dinyatakan. Mereka 'bisa' mahal jika Anda memiliki tumpukan yang sangat dalam. IIRC, saya harus meningkatkan ukuran max stack secara signifikan untuk dapat membuat tumpukan yang cukup besar untuk membuat pengecualian yang cukup lambat untuk dikhawatirkan. Bahkan tumpukan babi seperti Spring tidak melakukan itu. Selama Anda tidak melempar dan menangkap dalam loop yang ketat, itu optimasi prematur untuk menghindari pengecualian.
JimmyJames

Jawaban:

21

Saya tahu orang-orang membuat masalah besar menggunakan pengecualian untuk kontrol aliran tapi itu bukan masalah terbesar di sini. Saya melihat pelanggaran pemisahan permintaan perintah besar . Tetapi itu pun bukan yang terburuk.

Tidak, yang terburuk ada di sini:

/**
 * Closes the door if open
 */

Kenapa segalanya meledak jika asumsi Anda tentang keadaan pintu salah tetapi lock()hanya memperbaikinya untuk Anda? Lupa bahwa ini membuat tidak mungkin untuk mengunci pintu terbuka, yang sangat mungkin dan sesekali berguna. Tidak masalah di sini adalah Anda telah mencampur dua filosofi yang berbeda untuk berurusan dengan asumsi yang salah. Itu membingungkan. Jangan lakukan itu. Tidak pada tingkat abstraksi yang sama dengan gaya penamaan yang sama. Ow! Bawa salah satu ide itu ke luar. Metode layanan pintu semua harus bekerja dengan cara yang sama.

Adapun pelanggaran Pemisahan Permintaan Perintah saya tidak harus mencoba untuk menutup pintu untuk mencari tahu apakah itu ditutup atau terbuka. Aku seharusnya bisa bertanya. Layanan pintu tidak menyediakan cara untuk melakukan itu tanpa mungkin mengubah keadaan pintu. Itu membuat ini jauh lebih buruk daripada perintah yang juga terjadi untuk mengembalikan nilai (kesalahpahaman umum tentang apa CQS tentang). Di sini, perintah perubahan negara adalah satu-satunya cara untuk melakukan kueri! Ow!

Adapun pengecualian lebih mahal daripada kode status, itu pembicaraan optimasi. Cukup cepat cukup cepat. Tidak masalah sebenarnya adalah bahwa manusia tidak mengharapkan pengecualian untuk kasus tipikal. Anda dapat berdebat tentang apa yang paling Anda sukai. Bagi saya pertanyaan besarnya adalah seberapa mudah Anda membuat kode menggunakan.

ensureClosed(DoorService service, Door door){
  // Need door closed and unlocked. No idea of its state. What to do?
  try {
    service.open(door)
    service.close(door)
  } 
  catch( DoorLockedException e ){
    //Have no way to unlock the door so give up and die
    log(e);
    throw new NoOneGaveMeAKeyException(e);      
  }
  catch( DoorAlreadyOpenedException e ){
    try { 
      service.close(door);
    }
    catch( DoorAlreadyClosedException e ){
      //Some multithreaded goof has been messing with our door.
      //Oh well, this is what we wanted anyway.
      //Hope they didn't lock it.
    }
  }
}

Tolong jangan buat saya menulis kode seperti ini. Tolong beri kami isLocked()dan isClosed()metode. Dengan itu saya bisa menulis sendiri ensureClosed()dan ensureUnlocked()metode yang mudah dibaca. Yang hanya membuang jika kondisi pos mereka dilanggar. Saya lebih suka menemukan Anda sudah menulis dan mengujinya tentu saja. Hanya saja, jangan mencampurnya dengan yang melempar ketika mereka tidak dapat mengubah negara. Paling tidak beri mereka nama yang berbeda.

Apa pun yang Anda lakukan, tolong jangan panggil apa pun tryClose(). Itu nama yang mengerikan.

Adapun DoorLockedExceptionsendirian vs juga DoorAlreadyLockedExceptionsakit mengatakan ini: ini semua tentang menggunakan kode. Tolong jangan merancang layanan seperti ini tanpa menulis kode menggunakan dan melihat kekacauan yang Anda buat. Refactor dan mendesain ulang sampai menggunakan kode setidaknya dapat dibaca. Bahkan, pertimbangkan untuk menulis menggunakan kode terlebih dahulu.

ensureClosed(DoorService service, Door door){
  if( !service.isClosed(door) ){
    try{
      service.close(door);
    }
    catch( DoorAlreadyClosedException e ){
      //Some multithreaded goof has been messing with our door.
      //Oh well, this is what we wanted anyway.
      //Hope they didn't lock it.
    }
  } else {
    //This is what you wanted, so quietly do nothing. 
    //Why are you even here? Who bothers to write empty else conditions?
  }
}

ensureUnlocked(DoorService service, Door door){
  if( service.islocked(door) ){
    throw new NoOneGaveMeAKeyException(); 
  }
}
candied_orange
sumber
The lock()metode yang memanggil close()itu hanya aku yang malas saat menulis pertanyaan, tapi terima kasih untuk membuat saya sadar bahwa! Saya tidak akan jatuh cinta ketika menulis kode nyata. Dalam kasus saya, 3 metode tidak pernah benar-benar dipanggil secara berturut-turut. Hanya saja: panggil satu, jika tidak ada pengecualian yang baik, jika tidak, tampilkan dialog yang diterjemahkan dan mungkin keluar (jika InvalidTokenException). Saya punya isLocked()dan isOpen()metode dan mereka digunakan dalam aliran kontrol server tetapi mereka tidak boleh digunakan sebagai cara untuk memeriksa apakah dialog kesalahan harus ditampilkan, itu pekerjaan untuk pengecualian.
Musim dingin
Untuk poin terakhir Anda dengan baik, ya menggunakan kode terlihat sedikit lebih baik dengan DoorAlreadyLockedExceptiontetapi membuat kelas kembar.
Musim dingin
2
Jika dengan kelas kembar Anda berarti pengecualian sendiri saya menunjukkan saya gunakan favorit warisan: public class DoorAlreadyLockedException inherits DoorLockedException {}. Tentukan kelas dalam satu baris hanya demi memberinya nama yang bagus. Pilih dari mana Anda mewarisi dengan bijak. Saya lebih suka mewarisi dari tempat setinggi yang saya bisa, meski tidak berlebihan, untuk menghindari rantai pewarisan yang lama.
candied_orange
Saya ingin mengganti nama NoOneGaveMeAKeymenjadi NoOneGaveMeAKeyExceptionhanya untuk menjadi mewah. Kalau tidak, saya setuju dengan poin utama yaitu sebelum diproses, kita harus bisa mengetahui keadaan objek.
Walfrat
2
Pengacara iblis - hanya memiliki isOpen/ isClosedtidak cukup baik. Pikirkan tentang sistem file. Anda dapat memeriksa file yang ada / tidak ada, tetapi itu masih bisa berubah pada saat Anda mencoba membaca atau menulisnya, dan IOExceptionhasilnya akan. Mungkin dalam konteks ini benar-benar ada peluang negara tidak valid ketika Anda membuka atau menutup pintu.
Tom G
8

Membuat perbedaan antara DoorLockedExceptiondan DoorAlreadyLockedExceptionmenyarankan bahwa berbagai jenis pengecualian sedang digunakan untuk kontrol aliran, praktik yang sudah secara luas dianggap sebagai antipattern.

Memiliki beberapa tipe pengecualian untuk sesuatu yang tidak berbahaya ini gratis. Kompleksitas tambahan tidak sebanding dengan manfaat tambahan.

Cukup gunakan DoorLockedExceptionuntuk semuanya.

Lebih baik lagi, lakukan saja. Jika pintu sudah terkunci, maka masih akan dalam kondisi yang benar ketika lock()metode selesai. Jangan melempar pengecualian untuk kondisi yang biasa-biasa saja.

Jika Anda masih merasa tidak nyaman dengan itu, ubah nama metode Anda ensureDoorLocked().

Robert Harvey
sumber
4
Tolong jangan terlalu sering menggunakan meme "pengecualian untuk kontrol aliran", jika kedua situasi, untuk alasan apa pun, memerlukan tindakan yang berbeda secara dramatis untuk diselesaikan, maka memiliki pengecualian yang berbeda masuk akal. Tidak jelas dari contoh jika memang begitu.
whatsisname
1
@whatsisname: Jika itu adalah meme yang sebenarnya tidak memiliki dasar, maka posting jawaban Anda sendiri untuk efek itu.
Robert Harvey
1
@RobertHarvey Pengecualian digunakan untuk "aliran kontrol" dari dialog kesalahan dan kesalahan log. Aplikasi logika utama jelas tidak dijalankan meskipun klausa menangkap. Saya tidak berpikir untuk membuat perbedaan antara DoorLockedExceptiondan DoorAlreadyLockedExceptionmenyarankan bahwa, itu hanya tentang memiliki penangan kesalahan yang jauh lebih signifikan untuk lock()metode ini dengan biaya memiliki kelas yang hampir digandakan.
Musim dingin
1
Jika Anda memanggil kunci () dan pintunya terkunci, Anda sudah tahu bahwa pintunya sudah dikunci. Anda tidak perlu diberi tahu apa yang sudah Anda ketahui.
Robert Harvey
2
@ewan: Oh, ffs. Sebut saja apa pun yang Anda inginkan; enumerasi, hasil, apa pun. Itu pendekatan yang tepat.
Robert Harvey
3

Subjek dari pertanyaan ini "Haruskah jenis pengecualian daur ulang lebih disukai daripada yang digunakan sekali pakai?" tampaknya telah diabaikan, tidak hanya dalam jawaban, tetapi dalam perincian untuk pertanyaan (jawabannya adalah untuk perincian pertanyaan).

Saya setuju dengan sebagian besar kritik terhadap kode yang ditawarkan responden.

Tetapi sebagai jawaban untuk pertanyaan utama, saya akan mengatakan bahwa Anda harus lebih suka menggunakan kembali 'pengecualian daur ulang' daripada 'penggunaan tunggal'.

Dalam penggunaan penanganan pengecualian yang lebih umum, Anda memiliki JARAK yang luar biasa dalam kode antara tempat pengecualian terdeteksi dan dibuang, dan di mana pengecualian ditangkap dan ditangani. Itu berarti menggunakan tipe pribadi (modular) dalam penanganan pengecualian pada umumnya tidak akan berfungsi dengan baik. Program tipikal dengan penanganan pengecualian - akan memiliki beberapa lokasi tingkat atas dalam kode di mana pengecualian ditangani. Dan karena itu adalah lokasi tingkat atas, sulit bagi mereka untuk memiliki pengetahuan terperinci tentang aspek-aspek sempit dari modul tertentu.

Sebaliknya, mereka kemungkinan akan memiliki penangan tingkat tinggi untuk beberapa masalah tingkat tinggi.

Satu-satunya alasan menggunakan kelas pengecualian kustom terperinci tampaknya masuk akal dalam sampel Anda, adalah karena (dan di sini saya membayangi responden lain) - Anda tidak boleh menggunakan penanganan pengecualian untuk aliran kontrol biasa.

Lewis Pringle
sumber
1
"Dalam penggunaan penanganan pengecualian yang lebih tipikal, Anda memiliki JARAK yang hebat" Kami tidak menganalisis apa yang khas. Kami sedang merancang cara agar bisa bekerja. Banyak desain bagus menangkap perkecualian mereka di tingkat berikutnya dalam tumpukan panggilan dan dalam ruang lingkup yang sangat terbatas. Itu tidak boleh diabaikan sebagai kontrol aliran ketika semua yang mereka lakukan adalah pulih dari masalah. Desain pengecualian terbaik dimulai dengan merencanakan cara memulihkan sebelum menentukan pengecualian. Maka nama pengecualian tidak harus tentang melaporkan masalah. Mereka tentang menemukan solusinya. Anda dapat menggunakan string msg untuk melaporkan masalah.
candied_orange
"Kami tidak menganalisis apa yang khas. Kami sedang merancang cara agar itu bekerja.", Setuju, itulah yang Anda lakukan. Bukan apa yang saya lakukan. Jika Anda melihat judulnya, ia menanyakan satu pertanyaan. Jika Anda melihat isi pesan, ia menanyakan tiga pertanyaan (terkait) yang berbeda. Anda menjawab pertanyaan tubuh (sampai taraf tertentu - benar-benar Anda mengkritik kode sampel). Saya menjawab pertanyaan utama.
Lewis Pringle
BTW, Anda mengklaim "Banyak desain bagus menangkap pengecualian mereka di tingkat berikutnya di tumpukan panggilan dan dalam cakupan yang sangat terbatas". Saya tidak yakin saya sepenuhnya tidak setuju dengan itu. Tetapi saya harus mengatakan, setelah 40 tahun pemrograman, saya kesulitan menemukan contoh untuk mendukung klaim itu. Saya menyadari ini sedikit spin-off (dan saya mungkin tidak menangani ini dengan benar - baru di stackoverflow) - tetapi apakah Anda keberatan menawarkan satu atau dua contoh di mana penanganan try / catch yang dilokalkan adalah solusi terbaik? Ini tentu bukan pola umum / umum.
Lewis Pringle
Contoh ya? Izinkan saya memperkenalkan Anda kepada Jon Skeet
candied_orange
OK, itu sama sekali bukan contoh dari apa yang Anda klaim. Anda berkata, "Banyak desain bagus menangkap pengecualian mereka di level berikutnya di tumpukan panggilan dan dalam ruang lingkup yang sangat terbatas". Contoh Anda baru saja menerjemahkan API yang hanya menghasilkan pengecualian kesalahan menjadi yang menghasilkan beberapa nilai sentinel. Mungkin ini hanya perbedaan kata antara kami berdua. Saya tidak akan menggambarkan itu sebagai "desain yang bagus" tetapi 'peretasan terlokalisasi untuk menyiasati pilihan desain yang tidak nyaman dalam alat yang Anda gunakan'.
Lewis Pringle
0

Alternatif yang belum dijelajahi. Anda mungkin memodelkan hal yang salah dengan kelas Anda, bagaimana jika Anda memiliki ini?

public interface OpenDoor
{
    public ClosedDoor close();
    public LockedDoor lock();
}

public interface ClosedDoor
{
    public OpenDoor open();
    public LockedDoor lock();
}

public interface LockedDoor
{
    // ? unlock? open?
}

Sekarang tidak mungkin untuk mencoba sesuatu yang merupakan kesalahan. Anda tidak memerlukan pengecualian.

Caleth
sumber
Tetapi di sebagian besar aplikasi Anda tidak dapat menyimpan referensi ke objek-objek itu, karena database dapat diubah kapan saja. Upaya kreatif yang bagus
Musim Dingin
0

Saya akan menjawab pertanyaan asli, daripada mengkritik contohnya.

Sederhananya, jika Anda berharap bahwa klien mungkin perlu bereaksi berbeda untuk setiap kasus, itu menjamin jenis pengecualian baru.

Jika Anda memutuskan untuk menggunakan beberapa jenis pengecualian dan Anda masih berharap bahwa klien dalam beberapa kasus ingin membuat klausa tangkapan umum untuk mereka, Anda mungkin harus membuat superclass abstrak yang mereka berdua kembangkan.

Gudmundur Orn
sumber
0

Ini pseudocode tapi saya pikir Anda akan mendapatkan apa yang saya maksud:

public interface DoorService
{
    // ensures the door is open, uses key if locked, returns false if lock without a key or key does not fit
    bool Open(Door door, optional Key key) throws DoorStuckException, HouseOnFireException;

    // closes the door if open. already closed doors stay closed. Locked status does not change.
    void Close(Door door) throws throws DoorStuckException, HouseOnFireException;

    // closes the door if open and locks it
    void CloseAndLock(Door door, Key key) throws DoorStuckException, HouseOnFireException;
}

Ya, penggunaan kembali jenis pengecualian masuk akal, jika Anda menggunakan pengecualian untuk hal-hal yang benar-benar luar biasa . Karena hal-hal yang benar-benar luar biasa sebagian besar masalah lintas sektoral. Anda menggunakan Pengecualian pada dasarnya untuk aliran kontrol dan itu mengarah ke masalah tersebut.

tidak ada
sumber
Tetapi tipe-tipe pengecualian itu benar-benar tidak deskriptif. Apa artinya memiliki pintu "macet" ketika Anda hanya bisa membuka, menutup, atau mengunci pintu?
Musim Dingin
Ini hanya menelan pengecualian dan tidak melakukan apa pun dengannya. Ini tidak sesuai dalam konteks di mana Anda tidak ingin tahu apa yang terjadi. Ini terasa seperti cara C # untuk melakukan contoh Java saya. Ada alasan mengapa saya bekerja dengan Java.
Musim dingin
Nah, itu macet. Itu tidak bisa bergerak. Jadi Anda tidak bisa membukanya dan Anda tidak bisa menutupnya. Ini bukan keadaan logis yang perlu Anda perbaiki dengan memanggil metode lain, ini adalah keadaan luar biasa , yang tidak dapat Anda selesaikan melalui layanan pintu.
nvoigt
Dan itu tidak menelan apa pun , itu tidak menghasilkan pengecualian untuk keadaan program yang tidak biasa. Jika istri saya meminta saya untuk menutup pintu balkon dan saya bangun dan mengetahui bahwa itu sudah ditutup, saya tidak berlari dan berteriak dan panik. Saya baru saja kembali dan mengatakan "sudah tutup sekarang".
novigt
Tepat, dengan pengecualian yang dicentang, pengecualian tidak dapat kembali ke awal program harus ditangkap oleh penelepon. Pada panggilan itu Anda hanya mengabaikan pengecualian atau mencatat peringatan dan Anda baik-baik saja. Apa yang Anda sebut "pengecualian luar biasa" hanyalah RuntimeExceptions. DoorStuckExceptiondan HouseOnFireExceptiontidak harus diperiksa, itu tidak dimaksudkan untuk ditangkap pada semua panggilan.
Musim Dingin