귀찮은 스타일 / 위험한 후 작업을 수행하기 위해 finally 절을 사용합니까?


30

Iterator를 작성하는 과정에서 다음 코드를 작성하는 것을 발견했습니다 (오류 처리 중단).

public T next() {
  try {
    return next;
  } finally {
    next = fetcher.fetchNext(next);
  }
}

읽는 것보다 약간 더 읽기 쉽다

public T next() {
  T tmp = next;
  next = fetcher.fetchNext(next);
  return tmp;
}

나는 가독성의 차이가 압도적이지 않을 수있는 간단한 예라는 것을 알고 있지만 예외가없는 경우에 최종 시도를 사용하는 것이 좋지 않은지에 대한 일반적인 견해에 관심이 있습니다. 코드를 단순화 할 때 실제로 선호되는 경우

나쁜 경우 : 왜? 스타일, 성능, 함정, ...?

결론 모든 답변에 감사드립니다! 결론은 (적어도 나를 위해) 첫 번째 예제가 일반적인 패턴이라면 더 읽기 쉽지만 그렇지 않다는 결론입니다. 따라서 난독 화 된 예외 흐름과 함께 목적 이외의 구문을 사용하여 도입 된 혼란은 단순화보다 중요합니다.


10
나는 개인적으로 두 번째 것을 더 잘 이해할 수 있었지만 finally블록을 거의 사용하지 않습니다 .
Christian Mann

2
반환 전류 = fetcher.fetchNext (현재); // 어떻습니까? 사전 프리 페칭이 필요합니까?
scarfridge

1
@scarfridge 예는 Iterator실제로 구현 하기 위해 일종의 프리 페치가 필요한 구현 에 관한 hasNext()것입니다. 직접 해보십시오.
maaartinus

1
@maaartinus 알고 있습니다. 때때로 hasNext ()는 단순히 우박 스톤 시퀀스를 반환하고 때로는 다음 요소를 가져 오는 것이 엄청나게 비쌉니다. 후자의 경우 지연 초기화와 유사한 요청시에만 요소를 가져와야합니다.
scarfridge

1
@scarfridge 일반적으로 사용하는 문제 Iterator는 값을 가져와야한다는 hasNext()것입니다 next().
maaartinus

답변:


18

개인적으로, 나는 명령 쿼리 분리 에 대한 아이디어를 줄 것 입니다. 바로 아래로 가면 next ()에는 두 가지 목적이 있습니다. 다음 요소를 검색하는 광고 목적과 next의 내부 상태를 변경하는 숨겨진 부작용. 그래서, 당신이하고있는 일은 방법의 본문에서 광고 된 목적을 수행 한 다음 finally 절에서 숨겨진 부작용을 시도하는 것입니다. '정확하지는 않지만'정확하게 보이지는 않습니다.

이 코드가 실제로 이해하기 쉬운 것은 코드가 얼마나 이해하기 쉬운 지에 대한 것입니다.이 경우 대답은 "meh"입니다. 간단한 return 문을 "시도"한 다음 오류 방지 오류 복구를위한 코드 블록에서 숨겨진 부작용을 실행합니다. 당신이하고있는 일은 '영리한'이고, '영리한'코드는 종종 관리자들이 "무엇을 ... 코드를 읽는 사람들이 "아, 허, 말이 되겠 군."

그렇다면 상태 돌연변이를 접근 자 호출과 분리하면 어떻게 될까요? 나는 당신이 걱정하는 가독성 문제가 무질서하다고 생각하지만, 그것이 당신의 더 넓은 추상화에 어떤 영향을 미치는지 모르겠습니다. 어쨌든 고려해야 할 것.


1
"영리한"댓글에 +1 그것은이 예제를 매우 정확하게 포착합니다.

2
next ()의 'return and advance'액션은 다루기 어려운 Java iterator 인터페이스에 의해 OP에 강제로 적용됩니다.
kevin cline

37

순전히 스타일 관점에서, 나는이 세 줄을 생각합니다 :

T current = next;
next = fetcher.getNext();
return current;

... try / finally 블록보다 더 명확하고 짧습니다. 예외가 발생할 것으로 예상하지 않기 때문에 try블록을 사용하면 사람들을 혼란스럽게 할 것입니다.


2
try / catch / finally 블록은 추가 오버 헤드를 추가 할 수 있습니다 .javac 또는 JIT 컴파일러가 예외를 throw하지 않더라도이를 제거할지 확실하지 않습니다.
Martijn Verburg

2
@MartijnVerburg 예, javac는 여전히 예외 테이블에 항목을 추가하고 try 블록이 비어 있어도 finally 코드를 복제합니다.
Daniel Lubarov

@Daniel - 덕분에 나는은 javap :-) 실행하기에 너무 게으른
마티 Verburg

@Martijn Verburg : 실제로 예외가 발생하지 않는 한 try-catch-finallt 블록 인 AFAIK는 기본적으로 무료입니다. 여기서 javac는 JIT가 처리하므로 아무것도 최적화하지 않아도됩니다.
maaartinus 님이

@maaartinus - 메이크업 감각을한다 감사 - :-) 다시 오픈 JDK 소스 코드를 읽을 필요가
마티 Verburg

6

finally 블록 내부의 코드 목적 에 따라 다릅니다 . 정식 예제는 스트림을 읽거나 쓴 후 스트림을 닫는 것입니다. 항상 "정리"해야합니다. 객체 (이 경우 이터레이터)를 유효한 상태로 복원하면 IMHO도 정리로 계산되므로 여기서 아무런 문제가 없습니다. return반환 값을 찾 자마자 OTOH를 사용 하고 finally 블록에 많은 관련없는 코드를 추가하면 목적이 모호 해지고 이해하기 어려워집니다.

"예외가없는 경우"사용시 아무런 문제가 없습니다. 코드를 던질 수만 있고 처리 할 계획이없는 경우 를 사용 try...finally하지 않고 사용 하는 것이 매우 일반적 입니다. 때때로, 마지막은 단지 보호 책일 뿐이며, 프로그램의 논리에 대해 예외가 발생하지 않는다는 것을 알고 있습니다 (클래식 "이것은 절대로 발생해서는 안됩니다"조건).catchRuntimeException

함정 : try블록 내에서 발생한 예외 는 finally블록을 실행합니다. 일관성이없는 상태가 될 수 있습니다. 따라서 귀하의 반환 진술이 다음과 같은 경우 :

return preProcess(next);

그리고이 코드는 예외 fetchNext가 발생했지만 여전히 실행됩니다. 다음과 같이 코딩하면 OTOH :

T ret = preProcess(next);
next = fetcher.fetchNext(next);
return ret;

그런 다음 실행되지 않습니다. try 코드가 예외를 제기 할 수 없다고 가정하고 있지만 이보다 복잡한 경우 어떻게 확신 할 수 있습니까? 반복자가 오래 지속되는 개체 인 경우 현재 스레드에서 복구 할 수없는 오류가 발생하더라도 항상 유효한 상태로 유지하는 것보다 기존 개체가 계속 유지됩니다. 그렇지 않으면 실제로별로 중요하지 않습니다 ...

성능 : 그러한 코드가 어떻게 작동하는지보기 위해 그러한 코드를 디 컴파일하는 것은 흥미로울 것입니다. 그러나 JVM을 충분히 추측하지 못해서 추측 할 수도 있습니다 ... 예외는 일반적으로 "예외적"이므로 처리하는 코드는 그것들은 속도에 대해 최적화 될 필요는 없습니다 (따라서 프로그램의 정상적인 제어 흐름에서 예외를 사용하지 않는 충고) finally.


실제로 이런 식으로 사용 하지 않는 꽤 좋은 이유를 제공 하지finally 않습니까? 말했듯이, 코드는 원하지 않는 결과를 초래합니다. 더 나쁜 것은 아마도 예외에 대해 생각조차하지 않는 것입니다.
Andres F.

2
일반적인 제어 흐름 속도는 예외 처리 구성의 영향을 크게받지 않습니다. 성능 저하는 실제로 예외를 던지고 잡을 때만 지불되며, try 블록에 들어가거나 일반 작업에서 finally 블록에 들어갈 때는 아닙니다.
yfeldblum

1
@AndresF. 사실이지만 모든 정리 코드 에도 유효 합니다. 예를 들어, 열린 스트림에 대한 참조가 null버그가있는 코드로 설정되어 있다고 가정합니다 . 읽기를 시도하면 예외가 발생하고 finally블록 에서 예외를 닫으려고하면 다른 예외가 발생합니다. 게다가, 이것은 계산 상태가 중요하지 않은 상황입니다. 예외 발생 여부에 관계없이 스트림을 닫으려고합니다. 이와 같은 다른 상황도있을 수 있습니다. 이런 이유로 나는 그것을 사용하지 않는 것이 아니라 유효한 용도의 함정으로 취급하고 있습니다.
mgibsonbr

try와 finally 블록이 모두 예외를 던질 때 try의 예외는 아무도 볼 수 없지만 문제의 원인 일 수 있습니다.
Hans-Peter Störr

3

제목 : "... 반환 후 작업을 수행하기위한 마지막 절 ..."은 거짓입니다. finally 블록은 함수가 반환되기 전에 발생합니다. 그것이 사실의 요점입니다.

여기서 남용하는 것은 평가 순서이며, 다음 값은 변경하기 전에 반환하기 위해 저장됩니다. 비 연속적으로 코드를 작성하기 때문에 따르기가 훨씬 어렵 기 때문에 일반적인 관행은 아니며 제 생각에는 잘못된 것입니다.

finally 블록의 의도 된 사용은 예외 처리에 관계없이 일부 리소스 정리, DB 연결 종료, 파일 / 소켓 닫기 등과 같은 결과가 발생해야하는 예외 처리에 사용됩니다.


+1, 언어 사양에 따라 값이 반환되기 전에 값이 저장된다고 보장하더라도 잠재적으로 독자의 기대에 위배되므로 합리적으로 가능한 경우 피해야합니다. 디버깅은 코딩보다 두 배 어렵습니다 ...
jmoreno

0

try의 (일반적으로, 귀하의 예제가 아닌) 부분은 예외를 던질 수 있기 때문에 매우 조심스럽게 생각할 것입니다. 원하지 않을 때 실행하십시오.

그리고 다른 웜 캔은 일반적으로 일부 유용한 조치가 마침내 예외를 던질 수 있으므로 실제로는 지저분한 방법으로 끝날 수 있습니다.

이 때문에, 읽는 사람은 이러한 문제에 대해 생각해야하므로 이해하기가 더 어렵습니다.

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