ネストされたtry-catchブロックの使用はアンチパターンですか?


95

これはアンチパターンですか?それは受け入れられる慣習ですか?

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

この件について教えていただけますか?なぜトップレベルキャッチですべてのエラータイプを処理できないのですか?
モロン

2
最近、この種のコードを見たことがあります。未経験のプログラマーは、tryブロック内で何を呼び出しているのか実際にはわからず、コードのテストに煩わされたくありません。私が見たコードサンプルでは、​​同じ操作でしたが、フォールバックパラメーターを使用して毎回実行されました。
ミスタースミス

@LokiAstari-あなたの例は、最後のセクションで試してみることです。これは、Tryセクションにネストされています。これは異なります。
モロン

4
なぜそれがアンチパターンである必要があるのですか?

2
「キャッチをもっと試してみませんか?」の+1
ジョエルファン

回答:


85

特に回復コードが例外をスローする場合は、これは避けられないことがあります。

きれいではありませんが、代替手段がない場合があります。


17
@MisterSmith-常にではありません。
オデッド

4
はい、これは私がやろうとしていたものの一種です。もちろん、入れ子になったtry / catchステートメントには、十分だと言うだけのポイントがあります。最初の試行が失敗した場合にのみ、2番目の試行内のコードを実行したい状況があると言って、シーケンシャルなtry / catchとは対照的にネストのケースを作成していました。
AndrewC

5
@MisterSmith:フラグ変数で部分的に制御されているシーケンシャルなトライキャッチよりも、ネストされたトライキャッチの方が望ましいです(機能的に同じ場合)。
FrustratedWithFormsDesigner

31
try {transaction.commit(); } catch {try {transaction.rollback(); } catch {seriouslogging()} notsoseriouslogging(); }必要なネストされたトライキャッチの一例である
サノスPapathanasiou

3
少なくとも、メソッドにcatchブロックを抽出してください!少なくともこれを読みやすくしましょう。
ミスターコチェッセ14年

43

私はそれがアンチパターンであるとは思わない、ただ広く誤用されている。

ネストされたtry catchのほとんどは、実際には回避可能でいものです。通常はジュニア開発者の製品です。

しかし、それを助けることができない場合があります。

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

また、失敗したロールバックを示すために、どこかに追加のブールが必要になります...


19

ロジックは問題ありません-フォールバックアプローチを試してみると状況によっては完全に理にかなっており、例外的なイベントが発生する可能性があります。したがって、このパターンはほとんど避けられません。

ただし、コードを改善するために次のことをお勧めします。

  • 内側のtry ... catchブロックをリファクタリングして、例えばattemptFallbackMethodやなどの個別の関数にしattemptMinimalRecoveryます。
  • キャッチされている特定の例外タイプについて、より具体的にしてください。あなたは本当に期待してください任意の例外のサブクラスをし、もしそうなら、あなたは本当にそれらをすべて同じように扱うようにしたいですか?
  • finallyブロックがより理にかなっているかどうかを検討してください-これは通常、「リソースクリーンアップコード」のように感じるものすべてに当てはまります

14

いいんだよ。考慮すべきリファクタリングは、コードを独自のメソッドにプッシュし、成功のために早期終了を使用して、同じレベルで何かをしようとするさまざまな試みを記述できるようにすることです。

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?

そのように分割したら、戦略パターンにまとめることを考えることができます。

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

次にTrySeveralThingsStrategy、を使用します。これは、一種の複合戦略です(1つの価格で2つのパターン!)。

1つの大きな注意点:戦略自体が十分に複雑であるか、柔軟な方法でそれらを使用できるようにしたい場合を除き、これを行わないでください。それ以外の場合は、不要なオブジェクト指向の巨大な山で簡単なコードの数行をたどっています。


7

私はそれが自動的にアンチパターンだとは思わないが、同じことをするより簡単でよりクリーンな方法を見つけることができればそれを避けるだろう。使用しているプログラミング言語にfinally構造がある場合、これは場合によってはクリーンアップに役立つ可能性があります。


6

それ自体はアンチパターンではなく、リファクタリングする必要があることを示すコードパターンです。

そして、それは非常に簡単です。同じメソッドでtryブロックだけを記述している経験則を知っている必要があります。関連するコードを一緒に書くことをよく知っている場合、通常は各tryブロックをcatchブロックでコピーして貼り付け、新しいメソッド内に貼り付けてから、元のブロックをこのメソッドの呼び出しで置き換えます。

この経験則は、彼の本「Clean Code」からのRobert C. Martinの提案に基づいています。

キーワード「try」が関数に存在する場合、それは関数の最初の単語である必要があり、catch / finallyブロックの後には何もないはずです。

「pseudo-java」の簡単な例。次のようなものがあるとします:

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

その後、各try catchをリファクタリングできます。この場合、各try-catchブロックは同じことを試みますが、異なる場所で(方法:D)、try-catchブロックの1つをコピーして貼り付け、そのメソッドを作成するだけです。 。

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

これで以前と同じ目的でこれを使用します。

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

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

私はそれが役立つことを願っています:)


良い例え。この例は、本当にリファクタリングする必要がある種類のコードです。ただし、他の場合には、ネストされたtry-catchが必要です。
-linehrr

4

確かにコードの可読性が低下しています。私は、言うあなたは機会があれば、その後の入れ子のtry-漁獲を避けます。

トライキャッチをネストする必要がある場合は、常に1分間停止して、次のことを考えてください。

  • それらを組み合わせる機会はありますか?

    try {  
      ... code  
    } catch (FirstKindOfException e) {  
      ... do something  
    } catch (SecondKindOfException e) {  
      ... do something else    
    }
    
  • 単にネストされた部分を新しいメソッドに抽出する必要がありますか?コードはずっときれいになります。

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

単一のメソッドで3つ以上のレベルのtry-catchesをネストする必要がある場合、それはリファクタリングの時間の確実な兆候です。


3

ネットワークコードでこのパターンを見てきましたが、実際には意味があります。擬似コードでの基本的な考え方は次のとおりです。

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

基本的にはヒューリスティックです。接続に失敗する1つの失敗は、単にネットワークの不具合である可能性がありますが、2回発生する場合は、おそらく、接続しようとしているマシンが実際に到達不能であることを意味します。この概念を実装する方法はおそらく他にもありますが、ネストされた試行よりもい可能性が高いでしょう。


2

私はこのような状況を次のように解決しました(フォールバックでキャッチしよう):

$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

偶然にテストクラス(JUnit)でこれを行う必要がありました。setUp()メソッドは、例外をスローしたコンストラクターで無効なコンストラクターパラメーターを持つオブジェクトを作成する必要がありました。

たとえば、3つの無効なオブジェクトの構築を失敗させる必要がある場合、ネストされた3つのtry-catchブロックが必要になります。代わりに、例外がキャッチされ、戻り値が成功したときにテストしていたクラスの新しいインスタンスである新しいメソッドを作成しました。

もちろん、同じ3回行ったので1つのメソッドだけが必要でした。まったく異なることを行うネストされたブロックにはあまり良いソリューションではないかもしれませんが、ほとんどの場合、少なくともコードは読みやすくなります。


0

実際にはアンチパターンだと思います。

場合によっては、複数の試行錯誤が必要になる場合がありますが、どの種類のエラーを探しているのかわからない場合のみです。例:

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

探しているものがわからない場合は、最初の方法を使用する必要があります。これは、見た目が悪く、機能的ではありません。後者のほうがずっと良いと思います。

したがって、探しているエラーの種類がわかっている場合は、具体的に説明してください。同じメソッド内にネストされた、または複数のトライキャッチの必要はありません。


2
示したようなコードは、すべてではないにしても、ほとんどの場合、実際には意味がありません。ただし、OPはネストされた try-catches を参照していまし。これは、複数の連続したステートメントの場合とはまったく異なる質問です。
JimmyB

0

ネストされたTry-Catchが避けられない場合があります。たとえば、エラー回復コード自体が例外をスローできる場合。ただし、コードの可読性を向上させるために、ネストされたブロックを独自のメソッドに常に抽出できます。チェックアウトこのネストされたのtry-catch-finallyブロックの詳細例については、ブログ記事を。


0

Javaのアンチパターンとして言及されているものはどこにもありません。はい、私たちはいくつかのことを良い習慣と悪い習慣と呼びます。

try / catchブロックがcatchブロック内で必要な場合、その必要はありません。そして、代替手段はありません。例外がスローされた場合、catchブロックはtry部分として機能しません。

例えば ​​:

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

上記の例では、メソッドは例外をスローしますが、doMethod(メソッド例外の処理に使用)は例外をスローします。この場合、try catch内でtry catchを使用する必要があります。

しないことが提案されているものは..

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

これは、以前の12の回答よりも実質的なものを提供していないようです
-gnat
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.