web-dev-qa-db-ja.com

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

これはアンチパターンですか?それは許容できる慣行ですか?

    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?
            }
        }
    }
105
Mister Smith

これは、特にリカバリコードが例外をスローする可能性がある場合は、避けられないことがあります。

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

94
Oded

私はそれをアンチパターンとは考えていません。広く誤用されているだけです。

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

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

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

また、失敗したロールバックを示すために、どこかに余分なブール値が必要です...

46

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

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

  • 内部のtry ... catchブロックを個別の関数にリファクタリングします。 attemptFallbackMethodおよびattemptMinimalRecovery
  • キャッチされている特定の例外タイプについてより具体的にします。 any Exceptionサブクラスを本当に期待していますか?そうであれば、本当に同じように処理しますか?
  • finallyブロックがより意味をなすかどうかを検討します。これは通常、「リソースクリーンアップコード」のようなものに当てはまります。
20
mikera

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

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つの大きな注意点:戦略自体が十分に複雑であるか、柔軟な方法でそれらを使用できるようにしたい場合を除き、これを行わないでください。それ以外の場合は、不要なオブジェクト指向の膨大な山を含む数行の単純なコードをラード化しています。

17
Tom Anderson

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

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

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

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

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

「疑似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;
    }
}

それが役に立てば幸いです:)

6
Adrián Pérez

それは確かにコードの可読性を低下させています。 機会があればと言って、try-catchesをネストしないでください。

トライキャッチをネストする必要がある場合は、常に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    
      }  
    }
    

1つのメソッドで3つ以上のレベルのトライキャッチをネストする必要があるかどうかは明らかです。これは、リファクタリングの時間の確実な兆候です。

4
CsBalazsHungary

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

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

基本的にそれはヒューリスティックです。接続に1回失敗すると、ネットワークに問題が発生する可能性がありますが、2回発生した場合は、接続しようとしているマシンに実際に到達できないことを意味します。この概念を実装する方法は他にもあると思われますが、ネストされた試行よりも醜い場合がほとんどです。

3
Mason Wheeler

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

$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
Adrian

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

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

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

2
MarioDS

実はそれはアンチパターンだと思います。

場合によっては、複数のトライキャッチが必要になることがありますが、どのようなエラーが表示されているかがわからない場合のみです。

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

あなたが探しているものがわからない場合は、最初の方法を使用する必要があります。後者の方がはるかに良いと思います。

したがって、探しているエラーの種類がわかっている場合は、具体的です。同じメソッド内で入れ子にしたり、複数の試行を行う必要はありません。

0
George Silva

場合によっては、入れ子になったTry-Catchが避けられないことがあります。たとえば、エラー回復コード自体がスローして例外が発生する可能性がある場合。ただし、コードを読みやすくするために、ネストされたブロックを独自のメソッドにいつでも抽出できます。ネストされたTry-Catch-Finallyブロックのその他の例については、 this ブログ投稿を確認してください。

0
codelion

Java=どこでもAnti Patternとして言及されているものはありません。はい、私たちはいくつかのことを良い習慣と悪い習慣と呼んでいます。

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