Apakah menggunakan blok try-catch bersarang merupakan anti-pola?

95

Apakah ini antipattern? Itu praktik yang bisa diterima?

    try {
        //do something
    } catch (Exception e) { 
        try {
            //do something in the same line, but being less ambitious
        } catch (Exception ex) {
            try {
                //Do the minimum acceptable
            } catch (Exception e1) {
                //More try catches?
            }
        }
    }
Tuan Smith
sumber
Bisakah Anda memberi kami kasing untuk ini? Mengapa Anda tidak bisa menangani setiap jenis kesalahan di tangkapan tingkat atas?
Moron
2
Saya telah melihat kode semacam ini baru-baru ini, dilakukan oleh programmer yang tidak berpengalaman yang tidak benar-benar tahu apa yang mereka panggil di dalam blok try, dan mereka tidak ingin repot-repot menguji kode. Dalam contoh kode yang saya lihat, itu adalah operasi yang sama tetapi dilakukan setiap kali dengan parameter fallback.
Tuan Smith
@LokiAstari -Contoh Anda adalah percobaan di Bagian Akhirnya .. Di mana tidak ada Tangkapan. Ini bersarang di bagian Coba .. Ini berbeda.
Moron
4
Mengapa harus anti-pola?
2
+1 untuk "tangkapan percobaan lagi?"
JoelFan

Jawaban:

85

Ini kadang-kadang tidak dapat dihindari, terutama jika kode pemulihan Anda mungkin mengeluarkan pengecualian.

Tidak cantik, tapi terkadang tidak ada alternatif.

Oded
sumber
17
@ PakSmith - Tidak selalu.
Oded
4
Ya ini semacam apa yang saya coba dapatkan. Tentu saja ada satu titik dalam pernyataan try / catch bersarang Anda di mana Anda hanya perlu mengatakan sudah cukup. Saya sedang membuat kasus untuk bersarang yang bertentangan dengan mencoba / menangkap berurutan, mengatakan bahwa ada situasi di mana Anda hanya ingin kode di dalam percobaan kedua dieksekusi jika percobaan pertama gagal.
AndrewC
5
@ MasterSmith: Saya lebih suka try-catch bersarang ke try-catch berurutan yang sebagian dikendalikan dengan variabel flag (jika secara fungsional sama).
FrustratedWithFormsDesigner
31
coba {transaction.commit (); } catch {try {transaction.rollback (); } catch {seriouslogging ()} notsoseriouslogging (); } adalah contoh tangkapan yang perlu dicoba
Thanos Papathanasiou
3
Setidaknya ekstrak catch block ke suatu metode, guys! Setidaknya mari kita buat ini bisa dibaca.
Mr Cochese
43

Saya tidak berpikir itu adalah antipattern, hanya banyak disalahgunakan.

Kebanyakan mencoba menangkap bersarang memang dapat dihindari dan jelek kita, biasanya produk pengembang junior.

Tetapi ada saatnya Anda tidak bisa menahannya.

try{
     transaction.commit();
   }catch{
     logerror();
     try{
         transaction.rollback(); 
        }catch{
         seriousLogging();
        }
   }

Selain itu, Anda akan memerlukan bool tambahan di suatu tempat untuk menandakan rollback yang gagal ...

Thanos Papathanasiou
sumber
19

Logikanya baik-baik saja - dalam beberapa situasi masuk akal untuk mencoba pendekatan fallback, yang dengan sendirinya dapat mengalami peristiwa luar biasa .... maka pola ini cukup banyak tidak dapat dihindari.

Namun saya akan menyarankan yang berikut ini untuk membuat kode lebih baik:

  • Refactor the inner try ... catch blocks out ke fungsi terpisah, mis . attemptFallbackMethodDan attemptMinimalRecovery.
  • Lebih spesifik tentang jenis pengecualian tertentu yang ditangkap. Apakah Anda benar-benar berharap setiap Exception subclass dan jika demikian Anda benar-benar ingin menangani mereka semua dengan cara yang sama?
  • Pertimbangkan apakah finallyblok mungkin lebih masuk akal - ini biasanya terjadi untuk apa pun yang terasa seperti "kode pembersihan sumber daya"
mikera
sumber
14

Tidak masalah. Refactoring yang perlu dipertimbangkan adalah mendorong kode ke metode sendiri, dan menggunakan pintu keluar awal untuk sukses, membiarkan Anda menulis berbagai upaya untuk melakukan sesuatu di tingkat yang sama:

try {
    // do something
    return;
} catch (Exception e) {
    // fall through; you probably want to log this
}
try {
    // do something in the same line, but being less ambitious
    return;
} catch (Exception e) {
    // fall through again; you probably want to log this too
}
try {
    // Do the minimum acceptable
    return;
} catch (Exception e) {
    // if you don't have any more fallbacks, then throw an exception here
}
//More try catches?

Setelah Anda pecah seperti itu, Anda bisa berpikir untuk membungkusnya dalam pola Strategi.

interface DoSomethingStrategy {
    public void doSomething() throws Exception;
}

class NormalStrategy implements DoSomethingStrategy {
    public void doSomething() throws Exception {
        // do something
    }
}

class FirstFallbackStrategy implements DoSomethingStrategy {
    public void doSomething() throws Exception {
        // do something in the same line, but being less ambitious
    }
}

class TrySeveralThingsStrategy implements DoSomethingStrategy {
    private DoSomethingStrategy[] strategies = {new NormalStrategy(), new FirstFallbackStrategy()};
    public void doSomething() throws Exception {
        for (DoSomethingStrategy strategy: strategies) {
            try {
                strategy.doSomething();
                return;
            }
            catch (Exception e) {
                // log and continue
            }
        }
        throw new Exception("all strategies failed");
    }
}

Kemudian gunakan saja TrySeveralThingsStrategy, yang merupakan semacam strategi gabungan (dua pola untuk harga satu!).

Satu peringatan besar: jangan lakukan ini kecuali strategi Anda sendiri cukup kompleks, atau Anda ingin dapat menggunakannya dengan cara yang fleksibel. Kalau tidak, Anda akan membawa beberapa baris kode sederhana dengan tumpukan besar orientasi objek yang tidak perlu.

Tom Anderson
sumber
7

Saya tidak berpikir itu secara otomatis anti-pola, tetapi saya akan menghindarinya jika saya dapat menemukan cara yang lebih mudah dan lebih bersih untuk melakukan hal yang sama. Jika bahasa pemrograman yang Anda gunakan memiliki finallykonstruksi, itu mungkin membantu membersihkannya, dalam beberapa kasus.

FrustratedWithFormsDesigner
sumber
6

Bukan anti-pola per se, tetapi pola kode yang memberi tahu Anda perlu melakukan refactor.

Dan itu sangat mudah, Anda hanya perlu tahu aturan praktis yang menulis tidak lebih dari blok percobaan dalam metode yang sama. Jika Anda tahu benar untuk menulis kode terkait bersama-sama, biasanya hanya menyalin dan menempel setiap blok coba dengan blok tangkap dan menempelkannya di dalam metode baru, lalu ganti blok asli dengan panggilan ke metode ini.

Aturan praktis ini didasarkan pada saran Robert C. Martin dari bukunya 'Kode Bersih':

jika kata kunci 'coba' ada dalam suatu fungsi, itu harus menjadi kata pertama dalam fungsi dan bahwa seharusnya tidak ada apa-apa setelah tangkapan / akhirnya blok.

Contoh singkat tentang "pseudo-java". Misalkan kita memiliki sesuatu seperti ini:

try {
    FileInputStream is = new FileInputStream(PATH_ONE);
    String configData = InputStreamUtils.readString(is);
    return configData;
} catch (FileNotFoundException e) {
    try {
        FileInputStream is = new FileInputStream(PATH_TWO);
        String configData = InputStreamUtils.readString(is);
        return configData;
    } catch (FileNotFoundException e) {
        try {
            FileInputStream is = new FileInputStream(PATH_THREE);
            String configData = InputStreamUtils.readString(is);
            return configData;
        } catch (FileNotFoundException e) {
            return null;
        }
    }
}

Lalu kita bisa refactor setiap try catch dan dalam hal ini setiap try-catch block mencoba hal yang sama tetapi di lokasi yang berbeda (betapa mudahnya: D), kita hanya perlu menyalin paste salah satu blok try-catch dan membuat metode untuk itu. .

public String loadConfigFile(String path) {
    try {
        FileInputStream is = new FileInputStream(path);
        String configData = InputStreamUtils.readString(is);
        return configData;
    } catch (FileNotFoundException e) {
        return null;
    }
}

Sekarang kami menggunakan ini dengan tujuan yang sama seperti sebelumnya.

String[] paths = new String[] {PATH_ONE, PATH_TWO, PATH_THREE};

String configData;
for(String path : paths) {
    configData = loadConfigFile(path);
    if (configData != null) {
        break;
    }
}

Saya harap itu membantu :)

Adrián Pérez
sumber
contoh yang baik. contoh ini benar-benar jenis kode yang harus kita refactor. Namun beberapa kali lain, try-catch bersarang diperlukan.
linehrr
4

Ini tentu mengurangi pembacaan kode. Saya akan mengatakan, jika Anda memiliki kesempatan , maka hindari bersarang mencoba-menangkap.

Jika Anda harus mencoba tangkapan, selalu berhenti sebentar dan pikirkan:

  • apakah saya memiliki kesempatan untuk menggabungkannya?

    try {  
      ... code  
    } catch (FirstKindOfException e) {  
      ... do something  
    } catch (SecondKindOfException e) {  
      ... do something else    
    }
    
  • haruskah saya cukup mengekstrak bagian bersarang ke dalam metode baru? Kode akan jauh lebih bersih.

    ...  
    try {  
      ... code  
    } catch (FirstKindOfException e) {  
       panicMethod();  
    }   
    ...
    
    private void panicMethod(){   
    try{  
    ... do the nested things  
    catch (SecondKindOfException e) {  
      ... do something else    
      }  
    }
    

Jelas jika Anda harus membuat tiga atau lebih level tangkapan percobaan, dalam satu metode, itu adalah tanda pasti waktu untuk refactor.

CsBalazsHungary
sumber
3

Saya telah melihat pola ini dalam kode jaringan, dan itu sebenarnya masuk akal. Inilah ide dasarnya, dalam kodesemu:

try
   connect;
catch (ConnectionFailure)
   try
      sleep(500);
      connect;
   catch(ConnectionFailure)
      return CANT_CONNECT;
   end try;
end try;

Pada dasarnya ini adalah heuristik. Satu upaya gagal untuk terhubung bisa saja merupakan kesalahan jaringan, tetapi jika itu terjadi dua kali, itu mungkin berarti mesin yang Anda coba sambungkan benar-benar tidak dapat dijangkau. Mungkin ada cara lain untuk menerapkan konsep ini, tetapi mereka kemungkinan besar akan lebih jelek daripada percobaan yang disarangkan.

Mason Wheeler
sumber
2

Saya memecahkan situasi ini seperti ini (coba-coba dengan mundur):

$variableForWhichINeedFallback = null;
$fallbackOptions = array('Option1', 'Option2', 'Option3');
while (!$variableForWhichINeedFallback && $fallbackOptions){
    $fallbackOption = array_pop($fallbackOptions);
    try{
        $variableForWhichINeedFallback = doSomethingExceptionalWith($fallbackOption);
    }
    catch{
        continue;
    }
}
if (!$variableForWhichINeedFallback)
    raise new ExceptionalException();
Adrian
sumber
2

Saya "harus" melakukan ini di kelas uji secara kebetulan (JUnit), di mana metode setUp () harus membuat objek dengan parameter konstruktor yang tidak valid dalam konstruktor yang melemparkan pengecualian.

Jika saya harus membuat konstruksi 3 objek tidak valid gagal, misalnya, saya perlu 3 blok try-catch, bersarang. Saya menciptakan metode baru sebagai gantinya, di mana pengecualian di mana tertangkap, dan nilai kembali adalah contoh baru dari kelas yang saya uji ketika berhasil.

Tentu saja, saya hanya perlu 1 metode karena saya melakukan 3 kali yang sama. Ini mungkin bukan solusi yang bagus untuk blok bersarang yang melakukan hal-hal yang sama sekali berbeda, tetapi setidaknya kode Anda akan menjadi lebih mudah dibaca dalam banyak kasus.

MarioDS
sumber
0

Saya benar-benar berpikir itu adalah antipattern.

Dalam beberapa kasus, Anda mungkin ingin mencoba beberapa tangkapan, tetapi hanya jika Anda TIDAK TAHU jenis kesalahan apa yang Anda cari, misalnya:

public class Test
{
    public static void Test()
    {            
        try
        {
           DoOp1();
        }
        catch(Exception ex)
        {
            // treat
        }

        try
        {
           DoOp2();
        }
        catch(Exception ex)
        {
            // treat
        }

        try
        {
           DoOp3();
        }
        catch(Exception ex)
        {
            // treat
        }
    }

    public static void Test()
    {
        try
        {
            DoOp1();
            DoOp2();
            DoOp3();
        }
        catch (DoOp1Exception ex1)
        {
        }
        catch (DoOp2Exception ex2)
        {
        }
        catch (DoOp3Exception ex3)
        {
        }
    }
}

Jika Anda tidak tahu apa yang Anda cari, Anda HARUS menggunakan cara pertama, yaitu IMHO, jelek, dan tidak fungsional. Saya kira yang terakhir jauh lebih baik.

Jadi, jika Anda tahu jenis kesalahan apa yang Anda cari, lebih spesifik . Tidak perlu untuk bersarang atau mencoba-tangkap dalam metode yang sama.

George Silva
sumber
2
Kode seperti yang Anda tunjukkan memang tidak masuk akal di sebagian besar atau tidak semua kasus. Namun, OP mengacu pada try-catch bersarang , yang merupakan pertanyaan yang sangat berbeda dari itu untuk beberapa pernyataan berurutan.
JimmyB
0

Dalam beberapa kasus, Try-Catch yang bersarang tidak dapat dihindari. Misalnya ketika kode pemulihan kesalahan itu sendiri dapat melempar dan pengecualian. Tetapi untuk meningkatkan keterbacaan kode Anda selalu dapat mengekstrak blok bersarang ke dalam metode sendiri. Lihat posting blog ini untuk lebih banyak contoh di blok Try-Catch-Finally yang disarangkan.

codelion
sumber
0

Tidak ada yang disebutkan sebagai Anti Pattern di java di mana saja. Ya, kami menyebut beberapa hal praktik yang baik dan praktik yang buruk.

Jika blok coba / tangkap diperlukan di dalam blok tangkap yang diperlukan, Anda tidak dapat membantunya. Dan tidak ada alternatif. Sebagai blok penangkap tidak dapat berfungsi sebagai bagian mencoba jika pengecualian dilemparkan.

Sebagai contoh :

String str=null;
try{
   str = method(a);
}
catch(Exception)
{
try{
   str = doMethod(a);
}
catch(Exception ex)
{
  throw ex;
}

Di sini, dalam contoh di atas metode melempar pengecualian tetapi doMethod (digunakan untuk menangani metode pengecualian) bahkan melempar pengecualian. Dalam hal ini kita harus menggunakan try catch di dalam try catch.

beberapa hal yang disarankan untuk tidak dilakukan adalah ..

try 
{
  .....1
}
catch(Exception ex)
{
}
try 
{
  .....2
}
catch(Exception ex)
{
}
try 
{
  .....3
}
catch(Exception ex)
{
}
try 
{
  .....3
}
catch(Exception ex)
{
}
try 
{
  .....4
}
catch(Exception ex)
{
}
gauravprasad
sumber
ini sepertinya tidak menawarkan sesuatu yang substansial daripada 12 jawaban sebelumnya
agas