2011-08-31 4 views
21

내가 부딪친 기술에 대한 조언을 듣고 싶습니다. 코드 스 니펫을 보면 쉽게 이해할 수 있지만 다음 단락에서 좀 더 자세히 설명합니다.중첩 된 Java try/finally 코드 샌드위치에 대한 조언


"코드 샌드위치"관용구를 사용하면 자원 관리를 처리하는 것이 일반적입니다. C++의 RAII 관용구에 익숙해지면서 Java로 전환하여 예외적 인 자원 관리를 발견하여 심하게 중첩 된 코드를 만들었습니다. 제어 흐름을 이해하는 데 어려움을 겪고 있습니다.

분명히 (java data access: is this good style of java data access code, or is it too much try finally?, Java io ugly try-finally block 및 그 이상) 나는 혼자가 아닙니다.

  1. 명시 적으로 프로그램 상태를 유지 : resource1aquired, fileopened ..., 및 정리 조건 :

    나는이에 대처하기 위해 다른 솔루션을 시도 if (resource1acquired) resource1.cleanup() ...하지만 명시 적으로 프로그램 상태를 복제 오히려 피해야 변수 - 런타임은 상태를 알고 있으므로 신경 쓰지 않아도됩니다.

  2. 랩 기능의 모든 중첩 된 블록 - 훨씬 더 힘들어에서 결과를 제어 흐름에 따라, 정말 어색 함수 이름있게하기 : runResource1Acquired(r1), runFileOpened(r1, file), ...

그리고 마지막으로 나는 관용구에 도착 대신이의


:

,321도 개념 상 일부 research paper on code sandwiches의 지원 0

도우미 구성을 사용하면 보상 코드가 송신자 옆에있는보다 선형 구조에 도달 할 수 있습니다.

class Compensation { 
    public void compensate(){}; 
} 
compensations = new Stack<Compensation>(); 

그리고 중첩 된 코드는 선형이된다 :

try { 
    connection = DBusConnection.SessionBus(); // may throw, needs cleanup 
    compensations.push(new Compensation(){ public void compensate() { 
     connection.disconnect(); 
    }); 

    connection.export("/MyObject", myObject); // may throw, needs cleanup 
    compensations.push(new Compensation(){ public void compensate() { 
     connection.unExport("/MyObject"); 
    }); 

    // unfolded try{}finally{} code 

} finally { 
    while(!compensations.empty()) 
     compensations.pop().compensate(); 
} 

내가 기뻐 없었다 : 상관없이, 제어 흐름은 선형을 유지하고 정리 코드가 원래 코드를 시각적으로 옆에 얼마나 많은 뛰어난 경로. 게다가, 인위적으로 제한된 closeQuietly 메서드가 필요하지 않으므로 더 유연하게 사용할 수 있습니다 (즉, Closeable 개체 일뿐만 아니라 Disconnectable, Rollbackable 및 기타 항목).

그러나 ...

다른 곳에서는이 기술에 대해 언급하지 않았습니다. 여기에 질문이 있습니다 :


이 기술이 유효합니까? 그것에 어떤 버그가 있습니까?

고마워요.

+1

당신이 경우 어디에서 취급하지 않는 것 compensate() 메서드 내에서 예외가 발생할 수 있습니다. 그러면 후속 보상이 실행되지 않습니다. –

+0

@ 케빈 : 참으로 - 너무 많은 C++ 관용구 : 소멸자가 던져서는 안되며, 여기에 그 관용구를 고집합니다. 예외를 처리하는 것은 구현을 보상하는 것입니다. – xtofl

+1

@xtofl - 당신이'try-finally'를 사용하는 것과는 확연히 다른 것 같습니다. 코드 자체로 그 관용구를 채택 할 수 있고,'finally' 블록을 신경 쓰지 않아도됩니다. 게다가, 많은'Compensators'는 자연스럽게 예외를 던질 것입니다 (예를 들어, DB 리소스를 닫는'SQLException'); ** 모든 구현은 모든 예외를 개별적으로 잡아서 삼키기 위해 ** finally 블록을 일관되게 한 곳에서 처리합니다 **. 이러한 작업을 수행하는 것은 선택 사항이 아니며 (런타임 예외는 사양을 위반할 수 있으며 언제든지 발생할 수 있음) 복사 붙여 넣기를 권장합니다. –

답변

2

좋네요.

없음 주요 불만, 내 머리 위로 떨어져 사소한 것들 :

  • 약간의 성능 부담
  • 당신이 그들을 볼 수있는 보상에 대한 몇 가지 final 확인해야합니다. 어쩌면 이것은 일부 유스 케이스를 막을 수 있습니다.
  • 프로그래밍 오류로 인해 실행시 보상 대기열을 실수로 비울 수 있으므로 보상 중에 예외를 catch하고 계속 실행해야합니다. 무엇이든간에
  • (비트가 멀리 가져 오기) 어쨌든 OTOH 프로그래밍 오류가 발생할 수 있습니다. 보상 대기열을 사용하면 "조건부 최종 차단"을 얻을 수 있습니다.
  • 너무 멀리 밀어 넣지 마십시오. 하나의 메소드 내에서는 괜찮은 것처럼 보일 것입니다 (그렇지만 어쨌든 try/finally 블록이 너무 많을 필요는 없습니다).하지만 보상 스택을 호출 스택에서 위아래로 통과시키지 마십시오.
  • 초기에 정리해야 할 문제가 될 수있는 "가장 바깥 쪽"에 대한 보정이 지연됩니다.
  • finally 블록에만 try 블록이 필요할 때만 의미가 있습니다. 어쨌든 catch 블록이있는 경우 마지막으로 추가 할 수 있습니다.
+0

감사합니다. "조건부 최종 블록"이란 무엇을 의미합니까? – xtofl

+0

글쎄, 당신은 'if (something) componensation.push (extraCompensation)'이라고 말할 수 있습니다. – Thilo

0

정말 복잡합니다. 내보낼 수없는 것을 내 보내려하면 어떻게됩니까?

// one try finally to rule them all. 
try { 
    connection = DBusConnection.SessionBus(); // may throw, needs cleanup 
    connection.export("/MyObject", myObject); // may throw, needs cleanup 
    // more things which could throw an exception 

} finally { 
    // unwind more things which could have thrown an exception 

    try { connection.unExport("/MyObject"); } catch(Exception ignored) { } 
    if (connection != null) connection.disconnect(); 
} 

사용 헬퍼 메소드 당신은 내가 당신이 연결이 사용하는 자원을 export 할 필요가 없습니다 의미 분리 생각했을 것이다

unExport(connection, "/MyObject"); 
disconnect(connection); 

할 수 있습니다.

+1

그런 다음 내보내기가 실행되지 않은 경우에도 unexport를 실행할 수 있습니다. 위의 간단한 예제가 아니라 아이디어를 얻습니다. (많은 불리언에 의해 처리 될 수 있음). – Thilo

+0

사실 :이 솔루션은 추가 된 복잡성으로 인해 실제로 코드 가독성이 추가되는 경우에만 유용합니다. 어쩌면이 예제는 예외적이지 않을 수 있습니다 :). – xtofl

+0

@ Thilo, "그렇다면 수출이 실행되지 않았더라도 수출을 취소 할 수 있습니다." 이것이 심지어 문제가된다는 것을 알고 있습니까? –

3

나는 접근 방식을 좋아하지만 몇 가지 제한 사항을 참조하십시오.

첫 번째는 원래의 finally 블록에있는 throw가 나중 블록에 영향을 미치지 않는다는 것입니다. 데모에서 unexport 동작을 실행하면 연결 해제 보상이 중지됩니다.

두 번째는 보정기가 볼 수 있도록 '최종'변수의 힙을 도입 할 필요성을 포함하여 Java 익명 클래스의 추악함으로 인해 언어가 복잡하다는 것입니다. 이것은 당신의 잘못이 아니지만 치료가 질병보다 더 나쁜지 궁금합니다.

하지만 전반적으로 나는 접근법이 좋아 매우 귀엽다.

1

Java 7에서 처음 소개 된 try-with-resources를 살펴 봐야합니다. 그러면 필요한 중첩이 줄어들 것입니다.

+1

나는했다. 너무 제한적인 imho 인'Closeable' 구현 자에게 자원을 제한합니다. (그리고 아직 Java 7을 사용하지는 않지만 사소한 문제입니다 :) – xtofl

+0

DBus의 버그 추적기에 버그를 신고 할 수 있습니다. Imho는이 클래스가 Closeable을 구현할 이유가 없습니다. – soc

3

내가 보는 방식대로, 당신이 원하는 것은 거래입니다. 보상은 약간 다르게 구현 된 트랜잭션입니다. 나는 당신이 JPA 리소스 나 트랜잭션과 롤백을 지원하는 다른 리소스로 작업하지 않는다고 가정한다. 왜냐하면 단순히 JTA (Java Transaction API)을 사용하기가 쉽기 때문이다. 또한, 여러분의 리소스가 여러분에 의해 개발되지 않았다고 가정합니다. JTA로부터 올바른 인터페이스를 구현하고 트랜잭션을 사용하게 할 수 있기 때문입니다.

그래서 나는 당신의 접근 방식을 좋아하지만, 나는 클라이언트로부터 터지기와 보상의 복잡성을 숨기고 있습니다. 또한 트랜잭션을 투명하게 처리 할 수 ​​있습니다.

따라서 (앞서, 추한 코드를 조심) :

public class Transaction { 
    private Stack<Compensation> compensations = new Stack<Compensation>(); 

    public Transaction addCompensation(Compensation compensation) { 
     this.compensations.add(compensation); 
    } 

    public void rollback() { 
     while(!compensations.empty()) 
     compensations.pop().compensate(); 
    } 
} 
+0

... 못생긴 코드? 나는 못 생기게 할 수있다 :) 이것은 재사용 가능한 클래스에 기술을 래핑하는 좋은 방법이다. – xtofl

+1

내가 여기 놓친 유일한 것은'compensations.pop() .compatate();'에 대한 try-catch 래핑이다. 이전의 몇 가지 질문에서 지적한 것처럼 예외를 처리한다. – gnat

+1

글쎄, 나는 또한 getters와 setter를 그리워한다. 적절한 생성자, null 매개 변수 검사 등 - 내 취향에 꽤 못 생겼습니다. :)하지만 롤백을 수행하는 코드는 무효 또는 비 기능적 보정을 확인하는 것과 같이 더 정교해야합니다. 보상이 실패 할 경우 당신이 원하는 것은 완전히 다른 이야기입니다 : 계속 하시겠습니까? 보상은 서로에 따라 좌우됩니까? 예외 (이 경우 응용 프로그램 상태가 정의되지 않았으므로 좋아하는 것)를 throw합니까? 귀하의 요구 사항은 아마도 당신에게 무엇을해야할지 알려주므로 자신을 노크하십시오! :) – LeChe

3

어휘 범위의 끝에서 호출되는 Java 용 장치 같은 소멸자, 흥미로운 주제입니다; 언어 수준에서 가장 잘 다루어 지지만, 언어 문화는 그다지 매력적이지 않습니다.

시공 조치 직후의 파괴 조치 지정은 논의되었습니다 (태양 아래에서는 새로운 내용이 아님). 자바 (8), 우리가 할 수 폐쇄를 추가하는 경우

{ 
    A 
    try 
    { 
     B 
    } 
    finally 
    { 
     F 
    } 
} 

{ 
    A 
    finally: 
     F 
    B 
} 

를 변환하여

{ 
    FileReader reader = new FileReader(source); 
    finally: reader.close(); // any statement 

    reader.read(); 
} 

이 작동 : 예는 개인 토론에서, http://projectlombok.org/features/Cleanup.html

또 다른 예입니다 이 기능을 종결시 간결하게 구현하십시오.

auto_scope 
#{ 
    A; 
    on_exit #{ F; } 
    B; 
} 

그러나, 클로저, 대부분의 리소스 라이브러리는 공급자가 자신의 자동 정리 장치 것, 고객이 정말 스스로 그것을 처리 할 필요가 없습니다

File.open(fileName) #{ 

    read... 

}; // auto close 
+0

롬복 ... 일부 표준처럼 보이 : : 자바에 대한 부스트 :) 힌트 주셔서 감사합니다! – xtofl