Yuvalanmış try-catch blokları anti-pattern kullanıyor mu?


95

Bu bir antipattern mi? Bu kabul edilebilir bir uygulamadır?

    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?
            }
        }
    }

Bize bunun için dava açabilir misiniz? Neden her hata tipini üst seviye yakalamada ele alamıyorsunuz?
Morons

2
Son zamanlarda bu tür bir kod gördüm, deneysel bloklar içinde ne dediklerini gerçekten bilmeyen ve denemeyi engellemek istemeyen tecrübesiz programcılar tarafından yapıldı. Gördüğüm kod örneğinde, aynı işlem yapıldı ancak her seferinde geri dönüş parametreleriyle gerçekleştirildi.
Bay Smith,

@LokiAstari - Örneğiniz Son Bölümde bir denemedir. Catch olmayan bir yerde. Bu, Dene bölümünde bulunan bir yuvadır. Farklı.
Morons

4
Neden anti-patern olmalı?

2
"Daha fazla yakalamayı dene" için +1
JoelFan

Yanıtlar:


85

Bu bazen kaçınılmazdır, özellikle kurtarma kodunuz bir istisna atabiliyorsa.

Güzel değil, ama bazen alternatifler yok.


17
@MisterSmith - Her zaman değil.
Oded

4
Evet, elde etmeye çalıştığım şey bu. Tabii ki, yuvalanmış try / catch ifadelerinizde yeterli olduğunu söylemek zorunda olduğunuz bir nokta var. Sıralı deneme / yakalamanın aksine iç içe geçme davası açıyordum, ilk deneme düşerse yalnızca ikinci deneme içindeki kodun çalıştırılmasını istediğiniz durumlar olduğunu söylüyordum.
AndrewC

5
@MisterSmith: İç içe geçmiş yakalamaları, kısmen bayrak değişkenleriyle kontrol edilen sıralı deneme yakalamalarına tercih ederim (eğer işlevsel olarak aynı olsaydı).
SinirliFormsDesigner ile

31
{trading.commit () 'ı deneyin; } catch {try {jest.rollback (); } catch {seriouslogging ()} notsoseriouslogging (); } gerekli bir iç içe geçmiş deneme yakalamasının bir örneği
Thanos Papathanasiou

3
En azından yakalama bloğunu bir yönteme çıkarın, millet! En azından bunu okunabilir hale getirelim.
Bay Cochese,

43

Ben bir antipattern olduğunu düşünüyorum, sadece yaygın olarak kötüye.

Çoğu iç içe geçmiş deneme avı gerçekten önlenebilir ve çirkin biz cehennem, genellikle küçük geliştiricilerin ürünü.

Ama yardım edemeyeceğin zamanlar vardır.

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

Ayrıca, başarısız geri dönüşü belirtmek için bir yerde fazladan bir bool'a ihtiyacınız olacak ...


19

Mantık gayet iyi - bazı durumlarda istisnai olayları deneyimleyebilecek bir geri dönüş yaklaşımı denemek mükemmel bir fikir olabilir… bu nedenle bu kalıp hemen hemen kaçınılmazdır.

Ancak kodu daha iyi hale getirmek için aşağıdakileri öneririm:

  • Yeniden dengeleyici iç deney ... blokları ayrı fonksiyonlara ayırır, örneğin attemptFallbackMethodve attemptMinimalRecovery.
  • Yakalanmakta olan özel istisna türleri hakkında daha spesifik olun. Gerçekten herhangi bir Exception alt sınıfını bekliyor musunuz ve öyleyse gerçekten de hepsini aynı şekilde ele almak istiyor musunuz?
  • Bir finallybloğun daha anlamlı olabileceğini düşünün - bu genellikle "kaynak temizleme kodu" gibi hissettiren bir durum için geçerlidir.

14

Sorun değil. Göz önünde bulundurulması gereken bir yeniden düzenleme, kodu kendi yöntemine itmek ve başarı için erken çıkışları kullanmak, aynı düzeyde bir şeyler yapmak için farklı girişimler yazmanıza izin vermek:

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?

Bu şekilde parçalandığınızda, bir Strateji düzeninde silmeyi düşünebilirsiniz.

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");
    }
}

O zaman sadece TrySeveralThingsStrategybir tür bileşik strateji olan birini kullanın (birinin fiyatı için iki örnek!).

Büyük bir uyarı: stratejileriniz yeterince karmaşık olmadıkça veya bunları esnek şekillerde kullanmak istemediğiniz sürece yapmayın. Aksi takdirde, gereksiz bir nesne yönelimi büyük bir yığın ile birkaç basit kod satır büyüyor.


7

Otomatik olarak bir anti-patern olduğunu düşünmüyorum, ancak aynı şeyi yapmanın daha kolay ve daha temiz bir yolunu bulabilirsem kaçınırım. Çalıştığınız programlama dili bir finallyyapıya sahipse , bu bazı durumlarda bunun temizlenmesine yardımcı olabilir.


6

Tek başına bir kalıp değil, yeniden canlandırmanız gerektiğini söyleyen bir kod deseni.

Ve oldukça kolay, sadece aynı yöntemde bir deneme bloğundan başka bir şey yazmayan bir kural tanıması bilmek zorundasınız. İlgili kodu birlikte yazmayı iyi bilirseniz, genellikle her bir deneme bloğunu catch bloklarıyla kopyalayıp yapıştırmak ve yeni bir yöntemin içine yapıştırmak ve ardından orijinal bloğu bu yönteme yapılan bir çağrı ile değiştirmek olur.

Bu kural, Robert C. Martin'in 'Temiz Kod' adlı kitabındaki önerisine dayanmaktadır:

'try' anahtar kelimesi bir fonksiyonda mevcutsa, fonksiyondaki ilk kelime olmalı ve catch / finally bloklarından sonra hiçbir şey olmamalıdır.

"Sözde-java" hakkında hızlı bir örnek. Diyelim ki böyle bir şeyimiz var:

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;
        }
    }
}

Sonra her bir deneme yakalayıcısını yeniden düzenleyebiliriz ve bu durumda her bir deneme yakalama bloğu aynı şeyi dener ancak farklı konumlarda (ne kadar uygun: D), yalnızca deneme yakalama bloklarından birini yapıştırarak kopyalamamız gerekir. .

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

Şimdi bunu daha önce olduğu gibi kullanıyoruz.

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

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

Umarım bu yardımcı olur :)


iyi örnek. bu örnek gerçekten refactor için yapmamız gereken kod türüdür. ancak bazı diğer zamanlarda, yuvalanmış deneme yakalama gereklidir.
linehrr

4

Kesinlikle kod okunabilirliğini azaltıyor. Ben söyleyebilirim, şansı varsa , o zaman yuvalama deneyin-yakalar kaçının.

Denemeleri yakalamanız gerekiyorsa, daima bir dakika durun ve şöyle düşünün:

  • onları birleştirme şansım var mı?

    try {  
      ... code  
    } catch (FirstKindOfException e) {  
      ... do something  
    } catch (SecondKindOfException e) {  
      ... do something else    
    }
    
  • Sadece iç içe kısmı yeni bir yönteme çıkartmalı mıyım? Kod çok daha temiz olacak.

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

Tek bir yöntemle üç veya daha fazla deneme yakalama düzeyi iç içe geçirmeniz gerekirse, bu, refactor için kesin bir zaman işaretidir.


3

Bu modeli ağ kodunda gördüm ve aslında mantıklı geliyor. İşte sözde kodda temel fikir:

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

Temel olarak bir buluşsal. Bağlanma başarısız bir girişimde bir ağ arızası olabilir, ancak iki kez gerçekleşirse, muhtemelen bağlanmaya çalıştığınız makineye erişilemez demektir. Muhtemelen bu kavramı uygulamanın başka yolları da var, ama büyük olasılıkla iç içe geçmiş denemelerden bile daha çirkinlerdi.


2

Bu durumu bu şekilde çözdüm (geri dönüşle tekrar yakala):

$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();

2

SetUp () yönteminin bir istisna atan bir kurucuda geçersiz yapıcı parametreleriyle nesneler oluşturması gereken bir tesadüf eseri (JUnit) bir test sınıfında bunu yapmak zorunda kaldım.

Örneğin, 3 geçersiz nesnenin yapılmasının başarısız olmasına neden olsaydım, iç içe geçmiş 3 try-catch bloğuna ihtiyacım olacaktı. Bunun yerine, yakalanan istisnaların ve dönüş değerinin, başarılı olduğunda test ettiğim sınıfın yeni bir örneği olduğu yeni bir yöntem yarattım.

Tabii ki, sadece 1 yönteme ihtiyacım vardı çünkü aynı 3 şeyi yaptım. Tamamen farklı şeyler yapan iç içe bloklar için bu kadar iyi bir çözüm olmayabilir, ancak en azından kodunuz çoğu durumda daha okunabilir hale gelirdi.


0

Aslında bunun bir antipattern olduğunu düşünüyorum.

Bazı durumlarda birden fazla deneme yakalamak isteyebilirsiniz, ancak yalnızca ne tür bir hata aradığınızı BİLMİYORSANIZ, örneğin:

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)
        {
        }
    }
}

Ne aradığınızı bilmiyorsanız, IMHO olan çirkin ve işlevsel olmayan ilk şeklini kullanmak zorundasınız. Sanırım ikincisi daha iyi.

Öyleyse, aradığınız hata türünü biliyorsanız, belirli olun . Aynı yöntem içinde iç içe geçmiş veya çoklu deneme yakalamalarına gerek yoktur.


2
Gösterdiğiniz gibi bir kod, çoğu durumda olmasa da çoğu zaman bir anlam ifade etmiyor. Bununla birlikte OP, art arda yapılan çoklu ifadelerden oldukça farklı bir soru olan iç içe geçmiş alıcılara atıfta bulunuyordu .
JimmyB

0

Bazı durumlarda iç içe geçmiş bir Try-Catch kaçınılmazdır. Örneğin, hata kurtarma kodunun kendisi atıp istisna olabildiğinde. Ancak, kodun okunabilirliğini artırmak için her zaman iç içe bloğu kendi yöntemine ayıklayabilirsiniz. Yuvalanmış Try-Catch-finally blokları hakkında daha fazla örnek için bu blog gönderisine göz atın .


0

Java'nın hiçbir yerinde Anti Pattern diye bahsetmemiş hiçbir şey yok. Evet birkaç şeyi iyi uygulama ve kötü uygulama olarak adlandırırız.

Bir toplama bloğunun içinde bir deneme / yakalama bloğu gerekiyorsa, ona yardımcı olamazsınız. Ve alternatif yok. Kural dışı durum atılırsa, bir catch bloğu deneme kısmı olarak çalışamaz.

Örneğin :

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

Burada yukarıdaki örnek yöntem istisna atar ancak doMethod (yöntem istisna işleme için kullanılır) bile istisna atar. Bu durumda, try catch içindeki try catch'i kullanmak zorundayız.

yapmaması önerilen bir şey ..

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)
{
}

bu önceki 12
cevapta
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.