마지막으로 예외를 던지기


27

Fortify와 같은 정적 코드 분석기는 finally블록 안에 예외가 발생했을 때 "불만" 이라고 말합니다 Using a throw statement inside a finally block breaks the logical progression through the try-catch-finally. 일반적으로 나는 이것에 동의합니다. 그러나 최근 에이 코드를 보았습니다.

SomeFileWriter writer = null; 
try { 
     //init the writer
     //write into the file
} catch (...) {
     //exception handling
} finally {
     if (writer!= null) writer.close();  
}

이제 writer올바르게 닫을 수 없으면 writer.close()메소드에서 예외가 발생합니다. 쓰기 후 파일이 저장되지 않았기 때문에 예외가 발생해야합니다.

추가 변수를 선언하고, 닫는 중에 오류가 발생했을 때 설정 writer하고 finally 블록 다음에 예외를 throw 할 수 있습니다. 그러나이 코드는 정상적으로 작동하며 변경할 것인지 확실하지 않습니다.

finally블록 내부에 예외를 던지는 단점은 무엇입니까 ?


4
이것이 Java이고 Java 7을 사용할 수있는 경우 ARM 블록이 문제를 해결할 수 있는지 확인하십시오.
Landei

@ 랜디,이 문제를 해결하지만 불행히도 우리는 Java 7을 사용하지 않습니다.
superM

나는 당신이 보여준 코드가 "finally 블록 안에 throw 문을 사용하는"것이 아니며 논리적 진행은 괜찮습니다.
Mike

@Mike, Fortify가 보여주는 표준 요약을 사용했지만 직접 또는 간접적으로 예외가 발생했습니다.
superM

불행히도, try-with-resources 블록은 Fortify에 의해 마침내 내부에 던져진 예외로 감지됩니다. 자원은 마침내 Fortify에 의해 보안 위협으로보고되고 있습니다.
mmona 2011

답변:


18

기본적으로 finally절은 적절한 자원 해제를 보장하기 위해 존재합니다. 그러나 finally 블록 안에 예외가 발생하면 보증이 사라집니다. 더군다나, 기본 코드 블록에서 예외가 발생하면 finally블록 에서 발생한 예외로 인해 예외 가 숨겨집니다. close실제 이유가 아닌에 대한 호출로 인해 오류가 발생한 것으로 보입니다 .

어떤 사람들은 finally블록에 던져진 예외를 삼키면 서 엉뚱한 예외 처리기 패턴을 따릅니다 .

SomeFileWriter writer = null; 
try { 
     //init the writer
     //write into the file
} finally {
    if (writer!= null) {
        try {
            writer.close();
        } catch (...) {
        }
    }
}

이전 버전의 Java에서는이 "안전한"정리 작업을 수행하는 클래스에 리소스를 배치하여이 코드를 "단순화"할 수 있습니다. 내 친한 친구는 익명 유형의 목록을 작성합니다. 각 유형은 자원 정리를위한 논리를 제공합니다. 그런 다음 코드는 단순히 목록을 반복하고 finally블록 내에서 dispose 메서드를 호출합니다 .


1
+1 나는 그 불쾌한 코드를 사용하는 사람들 중에 있지만. 그러나 정당화를 위해 예외가 중요하지 않은 경우에만 항상이 작업을 수행하지는 않는다고 말할 것입니다.
superM

2
나 자신도 그렇게하고 있습니다. 때로는 전체 자원 관리자 (익명 여부에 관계없이)를 작성하는 것이 코드의 목적을 방해합니다.
트래비스 파크

나도 +1; 당신은 기본적으로 내가하려고했던 것이지만 더 좋았습니다. 주목할만한 점은 Java의 일부 스트림 구현은 실제로 close ()에서 예외를 throw 할 수 없지만 인터페이스는 일부를 수행하기 때문에 예외를 선언한다는 것입니다. 따라서 어떤 상황에서는 실제로 필요하지 않은 catch 블록을 추가 할 수 있습니다.
vaughandroid

1
finally 블록 내에서 try / catch 블록을 사용하는 것이 왜 그렇게 나쁜가요? 오히려 모든 연결과 스트림 등을 조용히 닫아서 모든 코드를 부 풀리는 것보다는 조용히 닫을 수 있습니다. 또는 무엇이든) 예외를 일으 킵니다. 실제로 알고 싶거나 무언가를 할 수있는 일입니다.
ban-geoengineering

10

Travis Parks의 말에 따르면 finally블록의 예외는 반환 값이나 try...catch블록의 예외를 소비합니다 .

Java 7을 사용하는 경우 try-with-resources 블록 을 사용하여 문제를 해결할 수 있습니다 . 문서에 따르면 리소스가 구현되는 한 java.lang.AutoCloseable(대부분의 라이브러리 작성자 / 독자가 지금하는 경우) try-with-resources 블록이 닫힙니다. 여기서 추가 이점은 닫을 때 발생하는 예외가 억제되어 원래 반환 값 또는 예외가 전달 될 수 있다는 것입니다.

에서

FileWriter writer = null;
try {
  writer = new FileWriter("myFile.txt");
  writer.write("hello");
} catch(...) {
  // return/throw new exception
} finally {
  writer.close(); // an exception would consume the catch block's return/exception
}

try (FileWriter writer = new FileWriter("myFile.txt")) {
  writer.write("hello");
} catch(...) {
  // return/throw new exception, always gets returned even if writer fails to close
}

http://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html


1
때때로 도움이됩니다. 그러나 파일을 닫을 수 없을 때 실제로 예외를 throw 해야하는 경우이 접근법은 문제를 숨 깁니다.
superM

1
@superM 실제로 try-with-resources는에서 예외를 숨기지 않습니다 close(). "닫을 때 발생하는 예외는 억제되어 원래의 반환 값 또는 예외가 전달 될 수 있습니다"- 이것은 사실이 아닙니다 . close()try / catch 블록에서 다른 예외가 발생하는 경우에만 메소드 의 예외 가 억제됩니다. 따라서 try블록이 예외를 발생시키지 않으면 close()메소드 의 예외 가 발생하고 반환 값이 반환되지 않습니다. 심지어 현재 catch블록 에서 잡을 수도 있습니다 .
Ruslan Stelmachenko 님

0

이것이 사례별로 해결해야 할 문제라고 생각합니다. 어떤 경우에는 분석기가 말하는 코드가 정확하지 않으며 다시 생각해야한다는 점에서 정확합니다. 그러나 던지거나 다시 던지는 것이 가장 좋은 경우가 있습니다. 위임 할 수있는 것이 아닙니다.


0

자원에 대한 접근 방식으로도 이러한 경고가 존재하는 이유에 대한 개념적인 답변이 될 것입니다. 불행히도 달성하려는 쉬운 솔루션이 아닙니다.

오류 복구 실패

finally 트랜잭션의 성공 여부에 관계없이 실행되는 트랜잭션 후 제어 플로우를 모델링합니다.

실패한 경우, 오류가 완전히 복구되기 전에 ( 대상에 도달하기 전에) 오류 복구 도중에finally 실행되는 논리를 캡처합니다 .catch

오류 복구 오류가 발생하는 개념적 문제를 상상해보십시오 .

트랜잭션을 커밋하려고하는 데이터베이스 서버가 중간에 메모리 부족으로 인해 실패한다고 가정합니다. 이제 서버는 아무 일도 일어나지 않은 것처럼 트랜잭션을 특정 시점으로 롤백하려고합니다. 그러나 롤백 과정에서 또 다른 오류가 발생한다고 상상해보십시오. 이제 데이터베이스에 반 커밋 된 트랜잭션이 생겼습니다. 트랜잭션의 원자 성과 불가분의 특성이 이제 깨져서 데이터베이스의 무결성이 손상됩니다.

이 개념적 문제는 수동 오류 코드 전파를 사용하는 C인지, 예외 및 소멸자를 사용하는 C ++인지 또는 예외 및를 사용하는 Java인지에 관계없이 오류를 처리하는 모든 언어에 존재합니다 finally.

finally 예외가 발생하는 과정에서 소멸자가 C ++에서 실패 할 수없는 것과 같은 방식으로 언어를 제공 할 수 없습니다.

이 개념적이고 어려운 문제를 피할 수있는 유일한 방법은 트랜잭션을 롤백하고 중간에 리소스를 해제하는 과정에서 재귀 예외 / 오류가 발생하지 않도록하는 것입니다.

따라서 여기서 안전한 디자인은 writer.close()실패 할 수없는 디자인 입니다. 일반적으로 설계 중에 복구 도중 실패 할 수있는 시나리오를 피할 수있는 방법이 있으므로 불가능합니다.

불행히도 유일한 방법은 오류 복구에 실패 할 수 없습니다. 이를 보장하는 가장 쉬운 방법은 이러한 종류의 "리소스 릴리스"및 "역 부작용"기능이 실패 할 수 없도록하는 것입니다. 쉽지 않습니다. 적절한 오류 복구가 어렵고 불행히도 테스트하기가 어렵습니다. 그러나이를 달성하는 방법은 프로세스에서 "파기", "닫기", "종료", "롤백"등의 외부 오류가 발생할 수없는 기능이 있는지 확인하는 것입니다. 기존 오류에서 복구하는 동안 호출됩니다.

예 : 로깅

finally블록 안에 물건을 기록하고 싶다고 가정 해 봅시다 . 로깅이 실패 하지 않는 한 이것은 종종 큰 문제가 될 것 입니다. 파일에 더 많은 데이터를 추가 할 수 있기 때문에 거의 확실하게 로깅에 실패 할 수 있으며 실패 할 여러 가지 이유를 쉽게 찾을 수 있습니다.

따라서 여기서 해결책은 finally블록에 사용되는 로깅 기능 이 호출자에게 던질 수 없도록 만드는 것입니다 (실패 할 수는 있지만 던지지는 않습니다). 우리는 어떻게 할 수 있습니까? 중첩 된 try / catch 블록이있는 경우 최종 언어 내에서 언어를 던질 수있는 경우 예외를 삼키고 예외를 오류 코드로 전환하여 호출자를 던지는 것을 방지 할 수 있습니다. 기존 오류 복구 스택 외부에서 분리되어 실패 할 수있는 프로세스 또는 스레드가 풀립니다. 오류가 발생할 가능성없이 해당 프로세스와 통신 할 수있는 한, 동일한 스레드 내에서 재귀 적으로 발생하는 경우 안전 문제가이 시나리오에만 존재하므로 예외적으로 안전 합니다..

이 경우, 로그에 실패하고 아무것도하지 않는 것만으로도 실패하지 않으면 로깅 실패를 피할 수 있습니다 (예 : 리소스가 유출되거나 부작용을 롤백하지 않는 등).

어쨌든 소프트웨어 예외를 진정으로 만드는 것이 얼마나 어려운지 이미 상상할 수 있습니다. 가장 미션 크리티컬 한 소프트웨어를 제외하고는이를 최대한 활용하지 않아도됩니다. 그러나 매우 일반적인 라이브러리 작성자조차도 종종 여기에서 혼란스러워 라이브러리를 사용하여 응용 프로그램의 전체 예외 안전을 망칠 수 있기 때문에 예외 안전을 실제로 달성하는 방법에 주목할 가치가 있습니다.

SomeFileWriter

SomeFileWriterinside을 던질 수 있다면 close기존 예외에서 복구하는 컨텍스트에서 닫지 않는 한 일반적으로 예외 처리와 호환되지 않는다고 말하고 싶습니다. 코드가 통제 할 수없는 경우 SOL 일 수 있지만이 명백한 예외 안전 문제에 대해 작성자에게 알리는 것이 좋습니다. 그것이 당신의 통제 안에 있다면, 나의 주요 권장 사항은 그것을 닫는 것이 필요한 수단으로 실패하지 않도록하는 것입니다.

운영 체제가 실제로 파일을 닫지 못할 수 있다고 상상해보십시오. 이제 시스템 종료시 파일을 닫으려고하는 모든 프로그램이 종료 되지 않습니다 . 지금해야 할 일은 응용 프로그램을 열어 둔 상태로 유지하고 (아마 아니오) 파일 리소스를 유출하고 문제를 무시하는 것입니다 (중요하지 않은 경우에는 괜찮을 수 있음)? 가장 안전한 디자인 : 파일을 닫을 수 없도록합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.