예외를 잡아서 다시 던지는 모범 사례는 무엇입니까?


156

포착 된 예외를 직접 다시 발생시켜야합니까, 아니면 새로운 예외를 둘러싸 야합니까?

즉, 내가해야한다면 :

try {
  $connect = new CONNECT($db, $user, $password, $driver, $host);
} catch (Exception $e) {
  throw $e;
}

아니면 이거:

try {
  $connect = new CONNECT($db, $user, $password, $driver, $host);
} catch (Exception $e) {
  throw new Exception("Exception Message", 1, $e);
}

귀하의 답변이 직접 던지 려면 예외 체인 사용을 제안하십시오 . 예외 체인을 사용하는 실제 시나리오를 이해할 수 없습니다.

답변:


287

의미있는 일을하려고하지 않는 한 예외를 포착해서는 안됩니다 .

"의미있는 것"은 다음 중 하나 일 수 있습니다.

예외 처리

가장 명백한 의미있는 조치는 예를 들어 오류 메시지를 표시하고 작업을 중단하여 예외를 처리하는 것입니다.

try {
    $connect = new CONNECT($db, $user, $password, $driver, $host);
}
catch (Exception $e) {
    echo "Error while connecting to database!";
    die;
}

로깅 또는 부분 정리

때로는 특정 컨텍스트 내에서 예외를 올바르게 처리하는 방법을 모릅니다. 아마도 "큰 그림"에 대한 정보가 부족하지만 가능한 한 실패한 지점까지 실패를 기록하려고합니다. 이 경우 다음을 포착, 기록 및 다시 던지기를 원할 수 있습니다.

try {
    $connect = new CONNECT($db, $user, $password, $driver, $host);
}
catch (Exception $e) {
    logException($e); // does something
    throw $e;
}

관련 시나리오는 실패한 작업에 대한 정리를 수행 할 수있는 적절한 위치에 있지만 실패를 최상위 수준에서 처리하는 방법을 결정하지는 않는 것입니다. 이전 PHP 버전에서는 다음과 같이 구현되었습니다.

$connect = new CONNECT($db, $user, $password, $driver, $host);
try {
    $connect->insertSomeRecord();
}
catch (Exception $e) {
    $connect->disconnect(); // we don't want to keep the connection open anymore
    throw $e; // but we also don't know how to respond to the failure
}

PHP 5.5에는 finally키워드 가 도입 되었으므로 정리 시나리오의 경우 다른 접근 방법이 있습니다. 어떤 일이 발생했는지 (예 : 오류와 성공 여부에 관계없이) 정리 코드를 실행해야하는 경우 이제 예외를 전달하면서 투명하게 허용하면서이를 수행 할 수 있습니다.

$connect = new CONNECT($db, $user, $password, $driver, $host);
try {
    $connect->insertSomeRecord();
}
finally {
    $connect->disconnect(); // no matter what
}

오류 추상화 (예외 체인 포함)

세 번째 경우는 가능한 많은 실패를 논리적으로 더 큰 그룹으로 그룹화하려는 경우입니다. 논리 그룹화의 예 :

class ComponentInitException extends Exception {
    // public constructors etc as in Exception
}

class Component {
    public function __construct() {
        try {
            $connect = new CONNECT($db, $user, $password, $driver, $host);
        }
        catch (Exception $e) {
            throw new ComponentInitException($e->getMessage(), $e->getCode(), $e);
        }
    }
}

이 경우 Component데이터베이스 연결을 사용하여 구현되었음을 사용자 가 알지 못하게 할 수 있습니다 (향후 옵션을 열어두고 파일 기반 스토리지를 사용하려고 할 수도 있음). 따라서 귀하의 사양은 Component"초기화 실패의 경우 ComponentInitException발생합니다"라고 말합니다. 이를 통해 소비자는 Component예상되는 유형의 예외를 포착 하는 동시에 디버깅 코드가 모든 (구현 종속) 세부 정보에 액세스 할 수 있습니다 .

보다 풍부한 컨텍스트 제공 (예외 연결)

마지막으로 예외에 대한 더 많은 컨텍스트를 제공하려는 경우가 있습니다. 이 경우 오류가 발생했을 때 수행하려는 작업에 대한 자세한 정보가 포함 된 다른 예외로 예외를 래핑하는 것이 좋습니다. 예를 들면 다음과 같습니다.

class FileOperation {
    public static function copyFiles() {
        try {
            $copier = new FileCopier(); // the constructor may throw

            // this may throw if the files do no not exist
            $copier->ensureSourceFilesExist();

            // this may throw if the directory cannot be created
            $copier->createTargetDirectory();

            // this may throw if copying a file fails
            $copier->performCopy();
        }
        catch (Exception $e) {
            throw new Exception("Could not perform copy operation.", 0, $e);
        }
    }
}

이 경우는 위와 비슷하지만 (예제로는 아마도 가장 좋은 것은 아닙니다) 더 많은 컨텍스트를 제공하는 요점을 보여줍니다. 예외가 발생하면 파일 복사가 실패했음을 알려줍니다. 그러나 실패 했습니까? 이 정보는 랩핑 된 예외에서 제공됩니다 (이 예제가 훨씬 복잡한 경우 둘 이상의 레벨이있을 수 있음).

예를 들어 UserProfile, 사용자 프로파일이 파일에 저장되고 트랜잭션 시맨틱을 지원하기 때문에 오브젝트 작성으로 인해 파일이 복사 되는 시나리오를 생각하면이 값을 보여줍니다 . 커밋 할 때까지 프로필 사본.

이 경우에

try {
    $profile = UserProfile::getInstance();
}

결과적으로 "대상 디렉토리를 만들 수 없습니다"예외 오류가 발생하면 혼란 스러울 수 있습니다. 컨텍스트를 제공하는 다른 예외 계층으로이 "핵심"예외를 랩하면 오류를 훨씬 쉽게 처리 할 수 ​​있습니다 ( "프로파일 복사 작성 실패"-> "파일 복사 조작 실패"-> "대상 디렉토리를 작성할 수 없음").


나는 마지막 2 가지 이유에 동의 : 1 / 예외 처리 : 마지막으로 사용과 사용자의 데이터 영역 위의 예외 로그 :이 레벨 2 / 로깅 또는 정리에 그것을하지 말아야
레미 bourgarel

1
@remi : PHP가 finally적어도 아직은 구문을 지원하지 않는다는 점을 제외하고는 ... 이제는 나갔습니다. 즉, 우리는 이와 같은 더러운 것들에 의지해야합니다 ...
ircmaxell

@ remibourgarel : 1 : 그것은 단지 예입니다. 물론이 수준에서는하지 말아야하지만 정답은 충분히 길다. 2 : @ircmaxell이 말했듯이 finallyPHP 에는 없습니다 .
Jon

3
마지막으로 PHP 5.5가 마침내 구현됩니다.
OCDev

12
여기에서 목록을 놓쳤다 고 생각하는 이유가 있습니다. 예외를 발견하고 검사 할 기회가있을 때까지 예외를 처리 할 수 ​​있는지 여부를 알 수 없을 수도 있습니다. 예를 들어, 오류 코드를 사용하고 오류가 많은 하위 수준 API의 래퍼에는 error_code기본 오류를 확인하기 위해 확인할 수 있는 속성 과 함께 오류에 대한 인스턴스를 throw하는 단일 예외 클래스가있을 수 있습니다. 암호. 이러한 오류 중 일부만 의미있게 처리 할 수있는 경우, 캐치, 검사 및 오류를 처리 할 수없는 경우 다시 던지기를 원할 수 있습니다.
Mark Amery

37

글쎄, 그것은 추상화를 유지하는 것입니다. 따라서 예외 체인을 사용하여 직접 던지는 것이 좋습니다. 왜 그런지, 누설 추상화 의 개념을 설명하겠습니다

모델을 작성한다고 가정 해 봅시다. 이 모델은 나머지 응용 프로그램에서 모든 데이터 지속성 및 유효성 검사를 추상화해야합니다. 이제 데이터베이스 오류가 발생하면 어떻게됩니까? 를 다시 던지면 DatabaseQueryException추상화가 유출됩니다. 이유를 이해하려면 추상화에 대해 잠시 생각하십시오. 당신은 상관하지 않는 방법 은 않습니다 단지, 데이터 모델을 저장합니다. 마찬가지로 모델의 기본 시스템에서 무엇이 잘못되었는지 정확히 신경 쓰지 않고 무언가 잘못되었다는 사실과 거의 무엇이 잘못되었는지 알고 있습니다.

따라서 DatabaseQueryException을 다시 발생 시키면 추상화가 유출되고 모델에서 진행되는 의미를 이해하기 위해 호출 코드가 필요합니다. 대신 generic을 만들고 그 안에 ModelStorageException걸린 부분을 감싸십시오 DatabaseQueryException. 이런 식으로, 호출 코드는 여전히 오류를 의미 적으로 처리하려고 시도 할 수 있지만, 추상화 계층에서 오류를 노출시키기 때문에 모델의 기본 기술은 중요하지 않습니다. 더 좋은 방법은 예외를 래핑했기 때문에 예외가 발생하여 기록해야하는 경우, 던져진 루트 예외를 추적 할 수 있으므로 (체인을 따라 가십시오) 그래야 필요한 모든 디버깅 정보를 얻을 수 있습니다!

사후 처리가 필요하지 않은 한 단순히 동일한 예외를 잡아서 다시 던지지 마십시오. 그러나 같은 블록 } catch (Exception $e) { throw $e; }은 무의미합니다. 그러나 상당한 추상화 이득을 위해 예외를 다시 랩핑 할 수 있습니다.


2
좋은 대답입니다. 스택 오버플로 주위의 많은 사람들이 (답변 등을 기반으로) 사람들이 잘못 사용하고있는 것 같습니다.
제임스

8

IMHO, 예외를 다시 던져서 다시 던지는 것은 쓸모없습니다 . 이 경우에는 그냥 잡지 말고 이전에 호출 한 메소드가 처리하도록하십시오 (일명 호출 스택에서 '상위'인 메소드) .

다시 던지면 잡은 예외를 새로운 예외에 연결하여 잡은 예외에 포함 된 정보를 유지할 수 있기 때문에 확실히 좋은 습관입니다. 그러나 다시 던지는 것은 일부 정보추가하거나 잡은 예외에 대해 무언가처리하는 경우에만 유용 하며 컨텍스트, 값, 로깅, 리소스 해제 등 무엇이든 가능합니다.

몇 가지 정보를 추가하는 방법은 확장하는 Exception클래스를 같은 예외를 가지고 NullParameterException, DatabaseException등 더 이상이이 developper는 자신이 처리 할 수있는 몇 가지 예외를 포착 할 수 있습니다. 예를 들어, 데이터를 다시 연결하는 등의 DatabaseException원인을 파악하고 해결하려고 할 수 있습니다 Exception.


2
쓸모없는 것은 아닙니다. 예외를 던지는 함수에서 예외를 처리 한 다음 다시 던져서 더 높은 캐치가 다른 것을 수행하도록해야 할 때가 있습니다. 내가 작업하고있는 프로젝트 중 하나에서 때로는 액션 메소드에서 예외를 포착하고 사용자에게 친근한 통지를 표시 한 다음 다시 던져서 코드에서 시도 catch 블록을 더 멀리 잡아서 오류를 기록 할 수 있습니다. 로그.
MitMaro April

1
내가 말했듯이 예외에 정보를 추가합니다 (통지 표시, 기록). OP의 예 에서처럼 다시 던지는 것은 아닙니다 .
Clement Herreman

2
리소스를 닫아야하지만 추가 할 정보가없는 경우 다시 던질 수 있습니다. 나는 세계에서 가장 깨끗한 것은 아니다 동의하지만, 그렇지 않아 끔찍한
ircmaxell

2
@ircmaxell 동의, 다시 던지는 것 외에는 아무것도하지 않는
Clement Herreman

1
중요한 점은 파일을 다시 던져서 예외가 원래 발생한 위치에 대한 파일 및 / 또는 행 정보를 잃어 버리는 것입니다. 따라서 질문의 두 번째 예와 같이 일반적으로 새 것을 던지고 오래된 것을 전달하는 것이 좋습니다. 그렇지 않으면 catch 블록을 가리키고 실제 문제가 무엇인지 추측하게됩니다.
DanMan

2

PHP 5.3의 Exception Best Practices를 살펴 봐야합니다.

PHP에서의 예외 처리는 새로운 기능이 아닙니다. 다음 링크에서는 예외를 기반으로하는 PHP 5.3의 두 가지 새로운 기능을 볼 수 있습니다. 첫 번째는 중첩 된 예외이고 두 번째는 SPL 확장 (현재 PHP 런타임의 핵심 확장)에서 제공하는 새로운 예외 유형 세트입니다. 이 두 가지 새로운 기능은 모범 사례에 포함되어 있으며 자세히 살펴볼 가치가 있습니다.

http://ralphschindler.com/2010/09/15/exception-best-practices-in-php-5-3


1

당신은 보통 이런 식으로 생각합니다.

클래스는 일치하지 않는 많은 유형의 예외를 throw 할 수 있습니다. 따라서 해당 클래스 또는 클래스 유형에 대한 예외 클래스를 작성하여 던집니다.

따라서 클래스를 사용하는 코드는 한 가지 유형의 예외 만 잡아야합니다.


1
이 방법에 대해 더 자세히 읽을 수있는 자세한 내용이나 링크를 제공 할 수 있습니다.
Rahul Prasad
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.