오류 억제에 대한 인수


35

우리 프로젝트 중 하나에서 다음과 같은 코드를 발견했습니다.

    SomeClass QueryServer(string args)
    {
        try
        {
            return SomeClass.Parse(_server.Query(args));
        }
        catch (Exception)
        {
            return null;
        }
    }

내가 아는 한, 이와 같은 오류를 억제하는 것은 원래 서버의 예외에서 유용한 정보를 파괴하고 실제로 종료해야 할 때 코드를 계속하게하므로 나쁜 습관입니다.

이 같은 모든 오류를 완전히 억제하는 것이 언제 적절한가요?


13
Eric Lippert는 vexing exceptions 라는 훌륭한 블로그 게시물을 작성하여 예외를 4 가지 범주로 분류하고 각각에 적합한 접근 방식을 제안합니다. C # 관점에서 작성되었지만 언어에 적용 가능하다고 생각합니다.
Damien_The_Unbeliever

3
코드가 "실패"원칙을 위반한다고 생각합니다. 실제 버그가 숨겨 질 가능성이 높습니다.
Euphoric


4
당신은 많은 것을 잡고 있습니다. NRE, 범위를 벗어난 배열 색인, 구성 오류 등과 같은 버그도 발견 할 수 있습니다. 버그를 발견하지 못하게되어 지속적으로 손상을 일으킬 수 있습니다.
usr

답변:


52

많은 라이브러리를 사용하여 수천 개의 파일이있는 코드를 상상해보십시오. 그들 모두가 이렇게 코딩되어 있다고 상상해보십시오.

예를 들어 서버 업데이트로 인해 하나의 구성 파일이 사라진다고 상상해보십시오. 이제 클래스를 사용하려고 할 때 스택 추적은 null 포인터 예외입니다. 어떻게 해결할 수 있습니까? 발견되지 않은 파일의 원시 스택 추적을 기록하는 것만으로도 [파일 경로] 시간이 걸릴 수 있습니다.

또는 더 나쁜 것은 나중에 코드 충돌을 일으키는 업데이트 후 사용하는 라이브러리 중 하나에서 오류가 발생하는 것입니다. 이것을 라이브러리로 어떻게 다시 추적 할 수 있습니까?

강력한 오류 처리 없이도

throw new IllegalStateException("THIS SHOULD NOT HAPPENING")

또는

LOGGER.error("[method name]/[arguments] should not be there")

시간을 절약 할 수 있습니다.

그러나 실제로 예외를 무시하고 이와 같이 null을 반환하거나 아무것도하지 않는 경우가 있습니다. 특히 잘못 설계된 레거시 코드와 통합 할 경우이 예외는 일반적인 경우로 예상됩니다.

실제로이 작업을 수행 할 때 예외를 무시하거나 필요에 따라 "적절하게 처리"하는지 궁금해합니다. 주어진 경우에 null을 반환하는 것이 예외를 "적절하게 처리"하는 경우 처리하십시오. 왜 이것이 적절한 지 설명을 추가하십시오.

모범 사례는 대부분 80 %, 99 %에 따라 수행해야하지만 적용되지 않는 경우는 항상 하나입니다. 이 경우 몇 달 후에 코드를 읽을 다른 사람들 (또는 자신)을 위해 연습을 따르지 않는 이유에 대해 의견을 남겨주십시오.


7
댓글에이를 수행해야하는 이유를 설명해 주신 +1 설명이 없으면 예외 삼키는 것이 항상 내 머리에 알람 벨을 올릴 것입니다.
jpmc26

내 문제는 주석이 해당 코드 안에 붙어 있다는 것입니다. 함수의 소비자로서 나는 그 의견을 결코 보지 못할 것입니다.
corsiKa

@ jpmc26 예외가 처리되면 (예 : 특수 값 반환) 예외 삼키지 않습니다.
중복 제거기

2
@corsiKa 소비자는 기능이 제대로 작동하는 경우 구현 세부 사항을 알 필요가 없습니다. 아마도 그들은 아파치 라이브러리 또는 봄에 이것 중 일부 일지 모르지만 그것을 알지 못할 것입니다.
Walfrat

@Deduplicator 네,하지만 알고리즘의 일부로 캐치를 사용하는 것도 좋은 연습이 아닙니다 :)
Walfrat

14

이 패턴이 유용한 경우가 있지만 일반적으로 예외가 발생하지 않아야 할 때 (예 : 예외가 정상적인 동작에 사용되는 경우)에 사용됩니다.

예를 들어, 파일을 열고 반환 된 객체에 저장하는 클래스가 있다고 가정합니다. 파일이 존재하지 않으면 오류가 아닌 것으로 간주하여 null을 반환하고 대신 사용자가 새 파일을 만들도록합니다. 파일 열기 메소드는 파일이 없음을 나타 내기 위해 여기에서 예외를 발생시킬 수 있으므로 자동으로 파일을 잡는 것이 올바른 상황 일 수 있습니다.

그러나 종종이 작업을 수행하고 싶지는 않으며 코드에 이러한 패턴이 흩어져 있다면 지금 처리하고 싶습니다. 적어도 나는 이런 일이 발생했다는 것을 알려주는 로그 라인을 작성할 것으로 예상된다.


2
"예외가 발생하지 않아야 할 때 사용 된"그런 일은 없습니다. 예외의 요점은 무언가 잘못되었다는 신호를 보내는 것입니다.
Euphoric

3
@Euphoric 그러나 때때로 도서관의 작가는 그 도서관의 최종 사용자의 의도를 모릅니다. 한 사람의 "끔찍한 잘못"은 다른 사람이 쉽게 회복 할 수있는 상태 일 수 있습니다.
Simon B

2
@Euphoric, 그것은 당신의 언어가 "끔찍하게 잘못한"것으로 간주합니다 : 파이썬 사람들은 C ++에서 흐름 제어에 매우 가까운 것들에 대한 예외를 좋아합니다. 어떤 상황에서는 null을 반환하는 것이 방어적일 수 있습니다. 예를 들어, 항목이 갑자기 예측할 수없는 캐시에서 읽는 경우.
Matt Krause

10
@Euphoric, "파일을 찾을 수 없음은 일반적인 예외가 아닙니다. 파일이 존재하지 않을 가능성이 있는지 먼저 확인해야합니다." 아니! 아니! 아니! 아니! 그것은 원자 연산에 대한 고전적인 잘못된 생각입니다. 파일이 있는지 확인하고 액세스하는 사이에 파일이 삭제 될 수 있습니다. 이러한 상황을 처리하는 유일한 올바른 방법은 상황에 액세스하여 예외가 발생하는 경우 (예 : null선택한 언어가 제공 할 수있는 최상의 언어인지 반환) 예외를 처리 하는 것입니다.
David Arno

3
@ 유포 릭 : 그렇다면 파일에 적어도 두 번 액세스 할 수 없도록 처리하는 코드를 작성 하시겠습니까? 그것은 DRY를 위반하고, 또한 연습되지 않은 코드는 깨진 ​​코드입니다.
중복 제거기

7

이것은 100 % 상황에 따라 다릅니다. 그러한 함수의 호출자가 어딘가에 오류를 표시하거나 기록해야 할 필요가있는 경우에는 분명히 말이되지 않습니다. 호출자가 모든 오류나 예외 메시지를 무시한 경우 예외 나 메시지를 반환하는 것은 의미가 없습니다. 요구 사항이 이유를 표시하지 않고 코드 만 종료하도록하는 경우 호출

   if(QueryServer(args)==null)
      TerminateProgram();

아마 충분할 것입니다. 사용자가 오류의 원인을 찾기 어려울 지 고려해야합니다.이 경우 잘못된 형식의 오류 처리입니다. 그러나 호출 코드가 다음과 같은 경우

  result = QueryServer(args);
  if(result!=null)
      DoSomethingMeaningful(result);
  // else ??? Mask the error and let the user run into trouble later

그런 다음 코드 검토 중에 개발자와 논쟁해야합니다. 누군가가 올바른 요구 사항을 찾기에 너무 게으 르기 때문에 이러한 형태의 오류가 아닌 처리를 구현하는 경우이 코드는 생산에 들어 가지 않아야하며 코드 작성자는 그러한 게으름의 가능한 결과에 대해 가르쳐야합니다 .


5

시스템을 프로그래밍하고 개발하는 데 몇 년 동안 문제의 패턴이 유용한 것으로 판단되는 두 가지 상황이 있습니다 (두 가지 경우 모두 던진 예외의 로깅도 포함 null됩니다). ).

두 가지 상황은 다음과 같습니다.

1. 예외가 예외적 인 상태로 간주되지 않은 경우

이것은 일부 데이터에 대한 작업을 수행 할 때 발생합니다.이 경우 처리 될 수있는 데이터가 필요하지 않기 때문에 데이터가 throw 될 수 있지만 애플리케이션이 계속 실행되기를 원합니다. 당신이 그들을받을 경우, 좋은, 당신이하지 않은 경우도 좋습니다.

클래스의 일부 선택적 속성을 생각할 수 있습니다.

2. 응용 프로그램에서 이미 사용 된 인터페이스를 사용하여 라이브러리의 새로운 (더 좋고 더 빠른) 구현을 제공 할 때

오래된 라이브러리를 사용하는 응용 프로그램이 있다고 가정합니다.이 라이브러리는 예외를 throw하지 않지만 null오류가 발생하면 반환 됩니다. 따라서이 라이브러리에 대한 어댑터를 작성하여 라이브러리의 원래 API를 거의 복사하고 응용 프로그램 에서이 새로운 (아직 던지지 않음) 인터페이스를 사용하고 null직접 검사를 처리합니다 .

새로운 버전의 라이브러리가 제공되거나 동일한 기능을 제공하는 완전히 다른 라이브러리가 제공됩니다.이 함수는 nulls 를 반환하는 대신 예외를 발생시키고 사용하려고합니다.

기본 응용 프로그램에 예외를 누설하지 않으려면이 새 종속성을 랩핑하기 위해 작성한 어댑터에 예외를 억제하고 로그하십시오.


첫 번째 경우는 문제가 아니며 원하는 코드 동작입니다. 그러나 두 번째 상황에서, null라이브러리 어댑터 의 리턴 값이 실제로 오류를 의미하는 경우 예외를 발생시키기 위해 API를 리팩토링하고 예외를 확인하는 대신이를 포착하는 것이 좋습니다 null(그리고 일반적으로 코드 방식이 좋습니다).

개인적으로 첫 번째 경우에만 예외 억제를 사용합니다. 우리는 나머지 응용 프로그램을 nulls 대신 예외로 작동시킬 예산이없는 두 번째 경우에만 사용했습니다 .


-1

프로그램이 예외를 포착해야한다고 말하는 것이 논리적이지만, 프로그래머가 예상하지 않은 예외를 처리하는 방법을 알지 못하는 경우가 많으므로 그러한 주장은 많은 작업이 실패 할 수 있다는 사실을 무시합니다. 부작용이없는 방법은 거의 무한하며, 대부분의 경우 이러한 실패에 대한 적절한 처리 방법은 동일합니다. 실패에 대한 정확한 세부 사항은 관련이 없으므로 프로그래머가이를 예상했는지 여부는 중요하지 않습니다.

예를 들어, 함수의 목적이 문서 파일을 적합한 개체로 읽고 새 문서 창을 만들어 해당 개체를 표시하거나 파일을 읽을 수없는 것으로 사용자에게보고하는 경우 유효하지 않은 문서 파일은 응용 프로그램을 중단시키지 않아야합니다. 대신 문제를 나타내는 메시지를 표시해야하지만 어떤 이유로 문서를로드하려는 시도가 시스템의 다른 상태를 손상시키지 않는 한 나머지 응용 프로그램은 계속 정상적으로 실행되도록해야합니다. .

기본적으로 예외를 올바르게 처리하는 것은 예외가 발생한 위치보다 예외 유형에 덜 의존하는 경우가 많습니다. 읽기-쓰기 잠금으로 리소스가 보호되고 읽기 위해 잠금을 획득 한 메서드 내에서 예외가 발생하는 경우 메서드가 리소스에 대해 아무것도 수행 할 수 없으므로 일반적으로 잠금을 해제해야합니다. . 쓰기를 위해 잠금을 획득하는 동안 예외가 발생하면 보호 된 자원이 유효하지 않은 상태 일 수 있으므로 잠금이 종종 무효화되어야합니다. 잠금 프리미티브가 "유효하지 않은"상태가 아닌 경우, 그러한 무효화를 추적하는 플래그를 추가해야합니다. 다른 코드가 보호 된 객체를 잘못된 상태로 볼 수 있기 때문에 잠금을 해제하지 않고 잠금을 해제하는 것은 좋지 않습니다. 그러나 매달린 자물쇠를 떠나는 것은 적절한 솔루션 중 하나입니다. 올바른 해결책은 잠금을 무효화하여 보류 중이거나 이후에 획득하려는 시도가 즉시 실패하도록하는 것입니다.

무효화 리소스를 사용하기 전에 포기한 것으로 판명되면 응용 프로그램을 중단해야 할 이유가 없습니다. 무효화 된 리소스가 응용 프로그램의 지속적인 작동에 중요한 경우, 응용 프로그램을 중단해야하지만 리소스를 무효화하면 문제가 발생할 수 있습니다. 원래 예외를 수신 한 코드는 어떤 상황이 적용되는지 알 수있는 방법이 없지만 자원을 무효화하는 경우 두 경우 모두 올바른 조치가 수행되도록 할 수 있습니다.


2
예외 세부 정보를 모르고 예외를 처리하기 때문에 질문에 대답하지 못합니다! = (일반적인) 예외를 자동으로 소비합니다.
Taemyr

@Taemyr : 함수의 목적이 "가능한 경우 X를 수행하거나 그렇지 않다는 것을 표시"하고 X가 불가능한 경우, 함수가 아닌 모든 종류의 예외를 나열하는 것은 종종 비현실적입니다 발신자는 "X를 할 수 없었습니다"를 넘어서도 걱정하지 않을 것입니다. 포켓몬 예외 처리는 종종 그러한 경우에 일을 작동시키는 유일한 실용적인 방법이며, 예기치 않은 예외로 인해 손상된 것을 무효화하는 것에 대해 코드가 훈련되면 일부 사람들이 만드는 것만 큼 악할 필요는 없습니다.
supercat 2019

정확하게. 호출하는 메소드가 예외를 던지는 지 여부에 관계없이 목적을 완전히 채울 수 있다면 모든 메소드에는 목적이 있어야합니다. 오류 처리는 기본적으로 오류 후 작업을 완료하는 것입니다. 그것은 오류가 발생한 것이 아니라 중요한 것입니다.
jmoreno

@jmoreno : 상황을 어렵게 만드는 것은 많은 상황에서 많은 예외가있을 것입니다. 많은 예외는 프로그래머가 특별히 예상하지 않았으며, 문제를 나타내지 않을 것입니다. 특히 문제를 나타내는 것은 아니며 예측할 수있는 체계적인 방법은 없습니다. 내가 제대로 처리하지 못하는 유일한 방법은 예외로 인해 손상된 것을 무효화하는 것입니다. 따라서 문제가 발견되면 문제가 발생하고 중요하지 않으면 무시됩니다.
supercat

-2

이런 종류의 오류를 삼키는 것은 누구에게나 유용하지 않습니다. 그렇습니다. 원래 발신자는 예외에 신경 쓰지 않을 수 있지만 다른 사람 은 할 수 있습니다 .

그럼 그들은 무엇을합니까? 그들은 예외를 처리하는 코드를 추가하여 어떤 종류의 예외가 다시 발생하는지 확인합니다. 큰. 그러나 예외 처리기가 남아 있으면 원래 호출자가 더 이상 null을 반환하지 않고 다른 곳에서 문제가 발생합니다.

일부 업스트림 코드에서 오류가 발생하여 null을 반환 할 수 있음을 알고 있더라도 호출 코드 IMHO에서 예외를 회피하려고 시도하지 않으면 심각하지 않습니다.


"심각하게 불활성"- "심각하게 부적당하다"는 뜻입니까?
밀 교 화면 이름

@EsotericScreenName 하나를 선택 ... ;-)
로비 디

-3

제 3 자 라이브러리가 잠재적으로 유용한 방법을 가지고있는 사례를 보았습니다. 내가 무엇을 할 수 있을지?

  • 내가 필요한 것을 정확하게 구현하십시오. 마감일이 어려울 수 있습니다.
  • 타사 코드를 수정하십시오. 그러나 모든 업그레이드와의 병합 충돌을 찾아야합니다.
  • 예외를 일반 널 (null) 포인터, 부울 거짓 등으로 바꾸려면 랩퍼 메소드를 작성하십시오.

예를 들어 라이브러리 방법

public foo findFoo() {...} 

첫 번째 foo를 반환하지만 아무것도 없으면 예외를 throw합니다. 그러나 내가 필요한 것은 첫 번째 foo 또는 null을 반환하는 방법입니다. 그래서 나는 이것을 작성합니다 :

public foo myFindFoo() {
    try {
        return findFoo() 
    } 
    catch (NoFooException ex) {
        return null;
    }
}

깔끔하지 않습니다. 그러나 때로는 실용적인 솔루션입니다.


1
"당신을 위해 일해야 할 예외를 던져라"는 무슨 뜻입니까?
corsiKa

@corsiKa, 내 마음에 오는 예는 목록이나 유사한 데이터 구조를 통해 검색하는 메소드입니다. 그들은 마십시오 실패 가 아무것도 찾을 또는 그들이 널 (null) 값을 반환 할 때? 또 다른 예는 부울 false를 리턴하지 않고 시간 종료시 실패하는 waitUntil 메소드입니다.
om
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.