중첩 된 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?
            }
        }
    }

우리에게 이것에 대한 사례를 줄 수 있습니까? 최상위 레벨 캐치에서 모든 오류 유형을 처리 할 수없는 이유는 무엇입니까?
Morons

2
try 블록 내부에서 무엇을 호출하는지 모르고 코드 테스트를 방해하고 싶지 않은 경험이없는 프로그래머가 수행 한이 종류의 코드를 최근에 보았습니다. 내가 본 코드 샘플에서는 동일한 작업이지만 폴백 매개 변수로 매번 수행했습니다.
Mister Smith

@LokiAstari-귀하의 예는 최종 섹션에서 시도입니다. 캐치가없는 곳. Try 섹션에 중첩되어 있습니다. 다릅니다.
Morons

4
왜 안티 패턴이어야합니까?

2
"더 많은 캐치 시도"에 +1
JoelFan

답변:


85

특히 복구 코드에서 예외가 발생하는 경우 불가피한 경우가 있습니다.

예쁘지 않지만 때로는 대안이 없습니다.


17
@MisterSmith-항상 그런 것은 아닙니다.
Oded

4
그렇습니다. 이것은 내가 얻으려고했던 것입니다. 물론 중첩 된 try / catch 문에 충분하다고 말해야 할 부분이 있습니다. 순차 try / catch와 달리 중첩을 시도하고 있는데, 첫 번째 시도가 실패하면 두 번째 시도 내부의 코드 만 실행하려는 상황이 있다고 말합니다.
AndrewC

5
@ MisterSmith : 중첩 된 try-catches를 플래그 변수로 부분적으로 제어하는 ​​순차적 try-catch보다 선호합니다 (기능이 동일한 경우).
FrustratedWithFormsDesigner

31
try {transaction.commit (); } catch {try {transaction.rollback (); } catch {seriouslogging ()} notsoseriouslogging (); } 필요한 중첩 된 시도 캐치의 예
Thanos Papathanasiou

3
적어도 catch 블록을 메소드로 추출하십시오! 적어도 이것을 읽을 수있게하십시오.
Mr Cochese

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일종의 복합 전략 인 하나를 사용하십시오 (하나의 가격에 두 가지 패턴!).

한 가지 큰 경고 : 전략 자체가 충분히 복잡하거나 유연한 방식으로 사용하려는 경우가 아니면이 작업을 수행하지 마십시오. 그렇지 않으면 불필요한 객체 지향의 거대한 더미로 몇 줄의 간단한 코드를 작성하고 있습니다.


7

나는 그것이 자동적으로 반 패턴이라고 생각하지 않지만, 같은 일을 더 쉽고 깨끗하게 할 수 있다면 그것을 피할 것이다. 작업중 인 프로그래밍 언어에 finally구문 이 있으면 경우에 따라 정리하는 데 도움이 될 수 있습니다.


6

안티 패턴 그 자체는 아니지만 리팩토링해야한다는 코드 패턴입니다.

그리고 그것은 매우 쉽습니다. 같은 방법으로 try 블록을 쓰는 법칙을 알아야합니다. 관련 코드를 함께 작성하는 것이 좋다면 일반적으로 각 try 블록을 catch 블록으로 복사하여 붙여 넣은 다음 새 메서드 안에 붙여 넣은 다음 원래 블록을이 메서드에 대한 호출로 바꿉니다.

이 경험 법칙은 Robert C. Martin의 책 'Clean Code'에서 제안한 내용을 기반으로합니다.

키워드 'try'가 함수에 존재하면, 키워드는 함수의 첫 번째 단어 여야하며 catch / finally 블록 뒤에 아무 것도 없어야합니다.

"의사 자바"에 대한 간단한 예. 다음과 같은 것이 있다고 가정하십시오.

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 블록 중 하나를 복사하여 붙여 넣기 만하면됩니다. .

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-catch를 중첩해야하는 경우 항상 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-catch를 중첩해야하는지 분명합니다.


3

네트워크 코드에서이 패턴을 보았으며 실제로 의미가 있습니다. 의사 코드의 기본 아이디어는 다음과 같습니다.

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

기본적으로 휴리스틱입니다. 연결 시도가 실패하면 네트워크 결함 일 수 있지만 두 번 발생하면 연결하려는 시스템에 실제로 연결할 수없는 것입니다. 이 개념을 구현하는 다른 방법이있을 수도 있지만 중첩 된 시도보다 훨씬 더 나쁠 수 있습니다.


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

나는 setUp () 메소드가 예외를 던지는 생성자에 잘못된 생성자 매개 변수를 가진 객체를 생성 해야하는 테스트 클래스 (JUnit) 에서이 작업을 수행해야했습니다.

예를 들어, 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)
        {
        }
    }
}

당신이 찾고있는 것을 모르는 경우 첫 번째 방식을 사용해야합니다 .IMHO는 추악하고 기능적이지 않습니다. 후자가 훨씬 낫습니다.

따라서 어떤 종류의 오류를 찾고 있는지 알고 있다면 구체적으로 지정하십시오 . 동일한 방법으로 중첩되거나 여러 번의 시도를 필요로하지 않습니다.


2
당신이 보여준 것과 같은 코드는 실제로 모든 경우에 의미가있는 것은 아닙니다. 그러나 OP는 중첩 된 try-catch를 참조했습니다 . 이는 여러 개의 연속 명령문에 대한 질문과는 상당히 다른 질문입니다.
JimmyB

0

경우에 따라 중첩 된 Try-Catch를 피할 수 없습니다. 예를 들어 오류 복구 코드 자체에서 예외가 발생할 수 있습니다. 그러나 코드의 가독성을 향상시키기 위해 항상 중첩 된 블록을 자체 메소드로 추출 할 수 있습니다. 중첩 된 Try-Catch-Finally 블록에 대한 자세한 예는 블로그 게시물을 확인하십시오 .


0

Java에는 안티 패턴으로 언급 된 곳이 없습니다. 그렇습니다. 우리는 좋은 습관과 나쁜 습관을 거의 요구하지 않습니다.

캐치 블록 내부에 try / catch 블록이 필요한 경우이를 지원할 수 없습니다. 그리고 대안이 없습니다. 예외가 발생하면 catch 블록이 시도 부분으로 작동 할 수 없습니다.

예를 들어 :

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.