예외 발생 생성자에 오류를 기록해야합니까?


15

몇 달 동안 응용 프로그램을 작성하고 있었고 다음과 같은 패턴을 깨달았습니다.

logger.error(ERROR_MSG);
throw new Exception(ERROR_MSG);

또는 잡을 때 :

try { 
    // ...block that can throw something
} catch (Exception e) {
    logger.error(ERROR_MSG, e);
    throw new MyException(ERROR_MSG, e);
}

따라서 예외를 던지거나 잡을 때마다 기록합니다. 사실, 그것은 응용 프로그램에서 수행 한 거의 모든 로깅이었습니다 (응용 프로그램 초기화를위한 것 외에도).

프로그래머로서 저는 반복을 피합니다. 그래서 로거 호출을 예외 구성으로 옮기기로 결정했기 때문에 예외를 만들 때마다 상황이 기록됩니다. 또한 예외를 던지는 ExceptionHelper를 만들 수는 있지만 코드를 해석하기가 더 어려워지고 컴파일러가 코드를 잘 처리하지 못하여 해당 멤버에 대한 호출이 즉시 던지십시오.

이것이 반 패턴입니까? 그렇다면 왜 그렇습니까?


예외를 직렬화하고 역 직렬화하면 어떻게 되나요? 오류가 기록됩니까?
세상의 종말

답변:


18

그것이 안티 패턴으로 자격이 있는지 확실하지 않지만 IMO는 나쁜 생각입니다.

주어진 예외의 모든 인스턴스를 항상 기록하고 싶지는 않을 수도 있습니다 (입력 유효성 검사 중에 발생할 수 있습니다. 로깅은 방대하거나 흥미롭지 않을 수 있습니다).

둘째, 로깅 수준이 서로 다른 여러 가지 오류 발생을 기록하기로 결정할 수 있습니다.이 시점에서 예외를 구성 할 때이를 지정해야합니다. 이는 다시 로깅 동작으로 예외 작성을 버리는 것을 나타냅니다.

마지막으로 다른 예외를 기록하는 동안 예외가 발생하면 어떻게됩니까? 그걸 기록하겠습니까? 지저분해진다 ...

선택은 기본적으로 다음과 같습니다.

  • 예를 들어 설명대로 캐치, 기록 및 (재) 투척
  • ExceptionHelper 클래스를 만들어 두 가지를 모두 수행하지만 도우미에게는 코드 냄새가 나므로 권장하지 않습니다.
  • 포괄 예외 처리를 더 높은 수준으로 이동
  • 로깅 및 예외 처리와 같은 교차 절단 문제에 대한보다 정교한 솔루션에 대해서는 AOP 를 고려하십시오 (그러나 catch 블록에 두 줄을 갖는 것보다 훨씬 더 복잡합니다).

"볼륨이 많고 흥미롭지 않은 경우"+1 AOP 란 무엇입니까?
Tulains Córdova

@ user61852 Aspect 지향 프로그래밍 (링크를 추가했습니다). / R / t AOP와 자바 로깅 w이 질문에 쇼 하나 예 : stackoverflow.com/questions/15746676/logging-with-aop-in-spring
Dan1701

11

프로그래머로서 나는 반복을 피한다 ...]

개념 "don't repeat yourself"이 냄새가 나는 지점까지 너무 심각하게 받아 들여질 때마다 여기에 위험이 있습니다 .


2
이제 어떻게 모든 것이 좋을 때 정답을 고르고 서로를 기반으로해야합니까? DRY가 광신적으로 접근 할 때 DRY가 어떻게 문제가 될 수 있는지에 대한 훌륭한 설명.
Bruno Brant

1
그것은 DRY에서 정말 좋은 찌르기입니다. 나는 DRYholic이라고 고백해야합니다. 이제 DRY를 위해 5 줄의 코드를 다른 곳으로 옮길 생각을 두 번 고려할 것입니다.
SZT

@ SZaman 나는 원래 매우 유사했습니다. 밝은면에서는 중복을 제거하는 데 너무 멀리 의존하는 사람들이 복사 및 붙여 넣기를 사용하여 500 줄 함수를 작성하고 리팩토링을 생각하지 않는 사람들보다 훨씬 더 희망이 있다고 생각합니다. IMO를 염두에 두어야 할 중요한 것은 작은 중복을 제거 할 때마다 코드를 분산시키고 다른 곳으로 종속성을 리디렉션한다는 것입니다. 그것은 좋은 일이거나 나쁜 일일 수 있습니다. 그것은 당신에게 행동을 변화시키는 중앙 통제를 제공하지만 그 행동을 공유하는 것도 당신을 물기 시작할 수 있습니다 ...

@SZaman "이 기능 만 필요하면이 중심 기능을 사용하는 다른 사람은 필요하지 않습니다."와 같이 변경하려면 어쨌든, 그것은 내가보기에 균형 잡힌 행동입니다-완벽하게하기 어려운 것! 그러나 때로는 약간의 중복이 코드를 더 독립적이고 분리시키는 데 도움이 될 수 있습니다. 그리고 코드 조각을 테스트하고 실제로 작동하는 경우 여기저기서 기본적인 논리를 복제하더라도 변경해야 할 이유가 거의 없습니다. 한편 외부의 많은 것들에 의존하는 것은 변화해야 할 훨씬 더 많은 외부 이유를 발견합니다.

6

@ Dan1701를 에코하려면

이것은 문제의 분리에 관한 것입니다-로깅을 예외로 옮기면 예외와 로깅 사이에 긴밀한 연결이 발생하고 로깅 예외에 대한 추가 책임을 추가하여 예외 클래스에 대한 종속성을 만들 수 있음을 의미합니다 필요하지 않습니다.

유지 보수 관점에서 볼 때 (최소한 예제와 같은 컨텍스트에서) 예외가 관리자로부터 기록되고 있다는 사실을 숨기고 있다고 주장 할 수 있습니다. 아마 예외의 생성자에 예외 처리기의 위치)에서 하지 당신이 의도.

마지막으로 예외가 생성 / 발생 된 시점에서 정확히 동일한 방식으로 예외를 기록하려고한다는 가정을합니다. 매우 불쾌한 로깅 및 비 로깅 예외가 발생하지 않는 한.

따라서이 경우 "SRP"가 "DRY"보다 우선합니다.


1
[...]"SRP" trumps "DRY"-나는이 인용문이 그것을 완벽하게 요약한다고 생각합니다.

@Ike가 말했듯이 ... 이것은 내가 찾던 일종의 근거입니다.
Bruno Brant

로깅의 컨텍스트를 변경하면 예외 클래스가 오리진 또는 로그 항목 인 것처럼 로그되지 않습니다.
Tulains Córdova

2

오류는 처리 할 수없는 예외를 로깅하는 것이므로 로깅이 올바르게 처리하는지 여부를 알 수 없습니다.
오류의 일부를 처리 할 수 ​​있거나 처리해야하는 경우가 거의 없으며, 여기에는 로깅이 포함될 수 있지만 여전히 호출자에게 오류를 신호 해야 합니다. 예를 들어 가장 낮은 수준의 읽기를 수행하는 경우 수정 불가능한 읽기 오류 등이 있지만 일반적으로 호출자와 통신하는 정보가 심각하게 필터링되어 보안 및 유용성이 향상됩니다.

당신이 당신의 경우에 할 수 있고 어떤 이유로해야 할 일은 구현자가 호출자가 기대하는 예외를 변환하고, 컨텍스트를 위해 원본을 연결하고 다른 것을 잘 남겨 두는 것입니다.

요약하자면, 귀하의 코드는 예외를 부분적으로 처리 할 권리와 의무를 인정하여 SRP를 위반했습니다.
드라이는 들어오지 않습니다.


1

catch 블록을 사용하여 예외를 기록하기로 결정했기 때문에 예외를 다시 던지는 것은 (예외가 전혀 변경되지 않았다는 것을 의미) 나쁜 생각입니다.

우리가 예외, 예외 메시지 및 처리를 사용하는 이유 중 하나는 무엇이 잘못되었는지 알 수 있고 현명하게 작성된 예외는 버그를 훨씬 빨리 찾아 낼 수 있다는 것입니다.

또한 예외를 처리하는 데 비용이 더 많이 든다는 것을 기억 if하십시오. 따라서 느낌이 들기 때문에 모든 것을 자주 처리해서는 안됩니다. 응용 프로그램의 성능에 영향을 미칩니다.

그러나 오류가 나타난 응용 프로그램 계층을 표시하는 수단으로 예외를 사용하는 것이 좋습니다.

다음과 같은 세미 의사 코드를 고려하십시오.

interface ICache<T, U>
{
    T GetValueByKey(U key); // may throw an CacheException
}

class FileCache<T, U> : ICache<T, U>
{
    T GetValueByKey(U key)
    {
        throw new CacheException("Could not retrieve object from FileCache::getvalueByKey. The File could not be opened. Key: " + key);
    }
}

class RedisCache<T, U> : ICache<T, U>
{
    T GetValueByKey(U key)
    {
        throw new CacheException("Could not retrieve object from RedisCache::getvalueByKey. Failed connecting to Redis server. Redis server timed out. Key: " + key);
    }
}

class CacheableInt
{
    ICache<int, int> cache;
    ILogger logger;

    public CacheableInt(ICache<int, int> cache, ILogger logger)
    {
        this.cache = cache;
        this.logger = logger;
    }

    public int GetNumber(int key) // may throw service exception
    {
        int result;

        try {
            result = this.cache.GetValueByKey(key);
        } catch (Exception e) {
            this.logger.Error(e);
            throw new ServiceException("CacheableInt::GetNumber failed, because the cache layer could not respond to request. Key: " + key);
        }

        return result;
    }
}

class CacheableIntService
{
    CacheableInt cacheableInt;
    ILogger logger;

    CacheableInt(CacheableInt cacheableInt, ILogger logger)
    {
        this.cacheableInt = cacheableInt;
        this.logger = logger;
    }

    int GetNumberAndReturnCode(int key)
    {
        int number;

        try {
            number = this.cacheableInt.GetNumber(key);
        } catch (Exception e) {
            this.logger.Error(e);
            return 500; // error code
        }

        return 200; // ok code
    }
}

누군가가 코드를 호출 GetNumberAndReturnCode하고 500코드를 수신하여 오류를 표시 했다고 가정 해 봅시다 . 그는 지원 부서에 연락하여 로그 파일을 열고 다음을 확인합니다.

ERROR: 12:23:27 - Could not retrieve object from RedisCache::getvalueByKey. Failed connecting to Redis server. Redis server timed out. Key: 28
ERROR: 12:23:27 - CacheableInt::GetNumber failed, because the cache layer could not respond to request. Key: 28

그런 다음 개발자는 프로세스를 중단시킨 소프트웨어 계층을 즉시 알고 문제를 쉽게 식별 할 수 있습니다. 이 경우 Redis 시간 초과가 발생하지 않아야하므로 중요합니다.

다른 사용자가 동일한 메소드를 호출하고 500코드를 수신 할 수도 있지만 로그는 다음을 표시합니다.

INFO: 11:11:11- Could not retrieve object from RedisCache::getvalueByKey. Value does not exist for the key 28.
INFO: 11:11:11- CacheableInt::GetNumber failed, because the cache layer could not find any data for the key 28.

이 경우 지원은 존재하지 않는 ID 값을 요청하여 요청이 유효하지 않다고 사용자에게 간단히 응답 할 수 있습니다.


요약

예외를 처리하는 경우 올바른 방식으로 처리하십시오. 또한 아키텍처 계층에 따라 우선 올바른 데이터 / 메시지를 포함 시키십시오. 따라서 메시지는 발생할 수있는 문제를 식별하는 데 도움이됩니다.


1

문제가 더 기본적인 수준에 있다고 생각합니다. 오류를 기록하고 같은 장소에서 예외로 throw합니다. 이것이 반 패턴입니다. 이는 동일한 오류가 발생하면 여러 번 기록되고 다른 예외에 싸여 다시 throw 될 수 있음을 의미합니다.

이 대신 예외가 만들어 질 때가 아니라 잡힐 때 오류를 기록하는 것이 좋습니다. (물론,이를 위해서는 항상 어딘가에 잡히는 지 확인해야합니다.) 예외가 발견되면 스택 예외가 다시 발생하거나 다른 예외의 원인으로 랩핑 되지 않은 경우에만 스택 추적을 기록합니다 . 래핑 된 예외의 스택 추적 및 메시지는 어쨌든 "Caused by ..."로 스택 추적에 기록됩니다. 또한 포수는 예를 들어 첫 번째 오류에서 오류를 기록하지 않고 다시 시도하거나 오류 또는 경고로 처리하도록 결정할 수 있습니다.


1

나는 그것이 오래된 실이라는 것을 알고 있지만 비슷한 문제가 발생하여 비슷한 해결책을 찾아서 2 센트를 더할 것입니다.

나는 SRP 위반 주장을 사지 않습니다. 어쨌든 완전히 아닙니다. 다음 두 가지 사항을 가정 해 봅시다. 1. 예외가 발생할 때 예외를 기록하려고합니다 (프로그램 흐름을 다시 만들 수 있도록 추적 수준에서). 이것은 예외 처리와 관련이 없습니다. 2. AOP를 사용할 수 없거나 사용하지 않을 것입니다-최선의 방법이라고 생각하지만 불행히도 도구를 제공하지 않는 언어에 집착하고 있습니다.

내가 보는 방식에 따라 예외를 throw하려는 클래스는 로그를 인식해야하기 때문에 기본적으로 대규모 SRP 위반이 발생합니다. 예외 클래스로 로깅을 이동하면 실제로 SRP 위반이 크게 줄어 듭니다. 이제 예외 만 코드베이스의 모든 클래스가 아니라 예외를 위반하기 때문입니다.


0

이것은 안티 패턴입니다.

제 생각에는 예외의 생성자에서 로깅 호출을하는 것은 다음의 예입니다. 결함 : 생성자가 실제 작업 .

생성자가 외부 서비스 호출을 기대하지 않습니다. Miško Hevery가 지적했듯이 서브 클래스와 모의가 원치 않는 동작을 상속하도록 강요하는 매우 바람직하지 않은 부작용입니다.

따라서, 그것은 또한 가장 놀랍게도 되는 원칙을 위반할 것 입니다.

다른 사람과 응용 프로그램을 개발하는 경우이 부작용은 분명하지 않을 것입니다. 혼자 일하고 있더라도 잊어 버릴 수 있습니다.

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