구현 옆에 로깅이 SRP 위반입니까?


19

민첩한 소프트웨어 개발과 모든 원칙 (SRP, OCP 등)을 생각할 때 로깅 처리 방법을 스스로에게 묻습니다.

구현 옆에 로깅이 SRP 위반입니까?

yes로깅없이 구현을 실행할 수도 있기 때문에 말할 것 입니다. 어떻게 로깅을 더 나은 방법으로 구현할 수 있습니까? 몇 가지 패턴을 확인하고 사용자 정의 방식으로 원칙을 위반하지 않는 가장 좋은 방법은 원칙을 위반하는 것으로 알려진 패턴을 사용하는 것이 데코레이터 패턴을 사용하는 것입니다.

SRP 위반없이 많은 구성 요소가 있고 로깅을 추가하려고한다고 가정하겠습니다.

  • 성분 A
  • 성분 B는 A를 사용합니다.

A에 대한 로깅을 원하므로 인터페이스 I을 구현하는 A로 장식 된 다른 구성 요소 D를 만듭니다.

  • 인터페이스 I
  • 구성 요소 L (시스템의 로깅 구성 요소)
  • 컴포넌트 A는 I를 구현
  • 컴포넌트 D는 I를 구현하고 A를 장식 / 사용하며 로깅에 L을 사용합니다.
  • 성분 B는 I를 사용합니다.

장점 :-로깅없이 A를 사용할 수 있습니다-테스트 A는 로깅 모의가 필요하지 않음을 의미합니다-테스트가 더 간단합니다.

단점 :-더 많은 구성 요소와 더 많은 테스트

나는 이것이 또 다른 공개 토론 질문 인 것으로 알고 있지만 실제로 누군가가 데코레이터 또는 SRP 위반보다 더 나은 로깅 전략을 사용하고 있는지 알고 싶습니다. 기본 NullLogger 인 정적 싱글 톤 로거는 어떻습니까? syslog-logging을 원한다면 런타임에 구현 객체를 변경하십시오.



이미 읽었으며 답변이 만족스럽지 않습니다. 죄송합니다.
Aitch


@MarkRogers는 흥미로운 기사를 공유해 주셔서 감사합니다. Bob 아저씨는 'Clean Code'에서 멋진 SRP 구성 요소가 동일한 추상화 수준의 다른 구성 요소를 처리한다고 말합니다. 저에게는 상황이 너무 클 수 있으므로 설명을 이해하기가 더 쉽습니다. 로거의 맥락이나 추상화 수준이 무엇입니까?
Aitch

3
"나에게 답이 아니다"또는 "답이 만족스럽지 않다"는 다소 무시할 만하다. 당신은 명상하며 무엇을 특히 불만족 (당신이 특별히 귀하의 질문에 대한 독특한 무엇인지 그 해답?에 의해 충족되지 않은 않는 것을 요구?),이 요구 사항 / 독특한 측면은 명확하게 설명되어 있는지 확인하기 위해 질문을 편집 할 수 있습니다. 목적은 당신이 질문이 다르거 나 정당한 이유없이 닫혀서는 안된다는 것을 주장하는 상용구를 요구하지 않고, 명확하고 집중적으로 개선하기 위해 질문을 편집하도록하는 것입니다. (다른 답변에 대해서도 의견을 제시 할 수 있습니다.)
DW

답변:


-1

그렇습니다. 로깅은 교차 문제이므로 SRP위반 한 것입니다 .

올바른 방법은 로깅을 로거 클래스 (인터 셉션) 에 위임 하는 것입니다. 로그 작성은 SRP를 준수하는 것입니다.

좋은 예는 다음 링크를 참조하십시오. https://msdn.microsoft.com/en-us/library/dn178467%28v=pandp.30%29.aspx

다음은 간단한 예입니다 .

public interface ITenantStore
{
    Tenant GetTenant(string tenant);
    void SaveTenant(Tenant tenant);
}

public class TenantStore : ITenantStore
{
    public Tenant GetTenant(string tenant)
    {....}

    public void SaveTenant(Tenant tenant)
    {....}
} 

public class TenantStoreLogger : ITenantStore
{
    private readonly ILogger _logger; //dep inj
    private readonly ITenantStore _tenantStore;

    public TenantStoreLogger(ITenantStore tenantStore)
    {
        _tenantStore = tenantStore;
    }

    public Tenant GetTenant(string tenant)
    {
        _logger.Log("reading tenant " + tenant.id);
        return _tenantStore.GetTenant(tenant);
    }

    public void SaveTenant(Tenant tenant)
    {
        _tenantStore.SaveTenant(tenant);
        _logger.Log("saving tenant " + tenant.id);
    }
}

장점은 다음과 같습니다

  • 로깅없이이를 테스트 할 수 있습니다-실제 단위 테스트
  • 심지어 런타임에서도 쉽게 로그 온 / 오프를 토글 할 수 있습니다
  • TenantStore 파일을 변경하지 않고도 다른 형태의 로깅 대신 로깅을 대체 할 수 있습니다.

좋은 링크 주셔서 감사합니다. 해당 페이지의 그림 1은 실제로 내가 가장 좋아하는 솔루션이라고합니다. 교차 절단 문제 (로깅, 캐싱 등) 및 데코레이터 패턴의 목록이 가장 일반적인 솔루션이며 더 큰 커뮤니티가 추상화와 인라인 로깅을 삭제하고 싶지만 내 생각에 완전히 틀리지 않는 것이 행복합니다. .
Aitch

2
어디서나 _logger 변수를 할당하는 것을 보지 못했습니다. 생성자 주입 사용을 계획하고 있었습니까? 그렇다면 컴파일러 경고가 표시 될 수 있습니다.
user2023861

27
N + 1 클래스가 필요한 일반 목적의 로거로 TenantStore를 DIP하는 대신 (LandlordStore, FooStore, BarStore 등을 추가 할 때) TenantStore로 DIP되는 TenantStoreLogger, FooStore로 DIP 된 FooStoreLogger가 있습니다. 등 2N 클래스가 필요합니다. 내가 알 수있는 한, 제로 이익을 위해. 로깅하지 않는 단위 테스트를 수행하려면 NullLogger를 구성하는 대신 N 클래스를 다시 트리거해야합니다. IMO, 이것은 매우 열악한 접근법입니다.
user949300

6
로깅이 필요한 모든 단일 클래스에 대해이 작업을 수행하면 코드 기반의 복잡성이 크게 증가합니다 (더 이상 클래스에 로깅이 없어서 더 이상 크로스 커팅 문제가되지 않음). 궁극적으로 유지 관리 할 인터페이스가 많기 때문에 코드를 유지 관리하기가 쉽지 않습니다 . 이는 단일 책임 원칙이 만들어진 모든 것과 상충됩니다.
jpmc26

9
공감. 테넌트 클래스에서 로깅 문제를 제거했지만 이제 TenantStoreLogger변경 될 때마다 TenantStore변경됩니다. 초기 솔루션보다 더 이상 우려를 분리하지 않습니다.
Laurent LA RIZZA

61

SRP를 너무 진지하게 받아들이고 있다고 말할 수 있습니다. 로깅이 SRP의 유일한 "위반"이 될 정도로 코드가 깔끔하다면 다른 모든 프로그래머의 99 % 이상을 수행하는 것입니다.

SRP의 요점은 다른 일을하는 코드가 모두 혼합 된 끔찍한 스파게티 코드를 피하는 것입니다. 로깅과 기능 코드를 혼합해도 알람 벨이 울리지 않습니다.


19
@Aitch : 당신의 선택은 당신의 클래스에 로깅을 배선하거나, 로거에게 핸들을 넘기거나 아무것도 기록하지 않는 것입니다. 다른 모든 것을 희생하면서 SRP에 대해 매우 엄격하려면 아무것도 기록하지 않는 것이 좋습니다. 소프트웨어가하는 일에 대해 알아야 할 것은 디버거를 사용하여 해결할 수 있습니다. SRP의 P는 "절대해서는 안되는 물리 법칙"이 아니라 "원칙"을 의미합니다.
Blrfl

3
@Aitch : 클래스의 로깅을 몇 가지 요구 사항으로 추적 할 수 있어야합니다. 그렇지 않으면 YAGNI를 위반하는 것입니다. 로깅이 테이블에있는 경우 클래스가 필요로하는 것과 같이 유효한 로거 핸들을 제공하는 것이 좋습니다. 이미 테스트를 통과 한 클래스의 것이 좋습니다. 실제 로그 항목을 생성하거나 비트 버킷에 덤프하는 것이 클래스의 인스턴스를 인스턴스화하는 문제입니다. 수업 자체는 신경 쓰지 않아야합니다.
Blrfl

3
@Aitch 단위 테스트에 대한 귀하의 질문에 답변하려면 : Do you mock the logger?, 그것이 바로 귀하가하는 일입니다. ILogger로거의 기능을 정의 하는 인터페이스 가 있어야합니다 . 테스트중인 코드에는 지정한 코드가 삽입됩니다 ILogger. 테스트를 위해 class TestLogger : ILogger. 이것에 대한 가장 좋은 점은 TestLogger마지막 문자열 또는 기록 된 오류와 같은 것을 노출시킬 수 있다는 것입니다. 테스트는 테스트중인 코드가 올바르게 로깅되고 있는지 확인할 수 있습니다. 예를 들어, UserSignInTimeGetsLogged()테스트는 TestLogger로 기록 될 수있는 테스트 일 수 있습니다 .
CurtisHx

5
99 %가 약간 낮은 것 같습니다. 아마도 모든 프로그래머의 100 %보다 낫습니다.
Paul Draper

2
정신력 +1 우리는 이런 종류의 사고가 더 필요합니다 : 단어와 추상적 원칙에 대한 집중력이 떨어지고 유지 보수 가능한 코드 기반갖는 데 더 집중 합니다 .
jpmc26

15

아니요, SRP 위반이 아닙니다.

로그로 보내는 메시지는 주변 코드와 같은 이유로 변경되어야합니다.

SRP를 위반하는 것은 코드에 직접 로깅하기 위해 특정 라이브러리를 사용하고 있습니다. 로깅 방법을 변경하기로 결정한 경우 SRP는 비즈니스 코드에 영향을 미치지 않아야한다고 명시합니다.

어떤 종류의 개요 Logger는 구현 코드에 액세스 할 수 있어야하며 구현시해야 할 말은 "이 메시지를 로그에 보내기"뿐입니다. 정확한 로깅 방법 (타임 스탬프조차도)을 결정하는 것은 구현의 책임이 아닙니다.

그런 다음 구현시 메시지를 보내는 로거가인지 여부도 알 수 없습니다 NullLogger.

그렇습니다.

나는 너무 빨리 크로스 커팅 문제로 로깅을 솔질하지 않을 것 입니다. 구현 코드에서 발생하는 특정 이벤트를 추적하기 위해 로그를 내보내는 것은 구현 코드에 속합니다.

교차 절단 문제인 OTOH는 실행 추적입니다 . 로깅은 각 방법마다 들어오고 나갑니다. 이 작업을 수행하는 것이 가장 좋습니다.


로거 메시지가 '로그인 사용자 xyz'라고 가정 해 봅시다. 타임 스탬프 앞에 붙는 로거에 전송됩니다. '로그인'이 구현에 어떤 의미가 있는지 알고 있습니까? 쿠키 또는 다른 메커니즘으로 세션을 시작합니까? 나는 로그인을 구현하는 많은 다른 방법이 있다고 생각하므로 구현 변경은 사용자가 로그인한다는 사실과 논리적으로 무관합니다. 이는 동일한 일을하는 다른 구성 요소 (예 : OAuthLogin, SessionLogin, BasicAuthorizationLogin)를 장식하는 또 다른 좋은 예입니다. A와 Login같은 로거로 장식 -interface.
Aitch

"로그인 사용자 xyz"메시지의 의미에 따라 다릅니다. 로그인이 성공했다는 사실을 표시하면 메시지를 로그로 보내는 것이 로그인 사용 사례에 속합니다. 로그인 정보를 문자열 (OAuth, Session, LDAP, NTLM, 지문, 햄스터 휠)로 나타내는 특정 방법은 자격 증명 또는 로그인 전략을 나타내는 특정 클래스에 속합니다. 제거 할 필요가 없습니다. 이 놀라운 사례는 교차 문제가 아닙니다. 로그인 사용 사례에 따라 다릅니다.
Laurent LA RIZZA

7

로깅은 종종 교차 문제로 간주되므로 구현에서 로깅을 분리하기 위해 AOP를 사용하는 것이 좋습니다.

언어에 따라 인터셉터 또는 일부 AOP 프레임 워크 (예 : Java의 AspectJ)를 사용하여이를 수행합니다.

문제는 이것이 실제로 번거로운 가치가 있는지입니다. 이러한 분리는 프로젝트의 복잡성을 증가시키면서 거의 이점을 제공하지 않습니다.


2
내가 본 대부분의 AOP 코드는 모든 메소드의 모든 입력 및 종료 단계를 기록하는 것에 관한 것입니다. 비즈니스 로직 부분 만 기록하고 싶습니다. 어노테이션이있는 메소드 만 기록 할 수 있지만 AOP는 스크립팅 언어 및 가상 머신 환경에만 존재할 수 있습니다. 예를 들어 C ++에서는 불가능합니다. 나는 AOP 접근 방식에별로 만족하지 않지만 더 확실한 해결책이 없다는 것을 인정합니다.
Aitch

1
@Aitch. "C ++ 불가능합니다." : "aop c ++"에 대해 Google을 검색하면 관련 기사를 찾을 수 있습니다. "... 필요한 AOP 코드는 모든 방법의 모든 입력 및 종료 단계를 기록하는 것입니다. 일부 비즈니스 논리 부분 만 기록하고 싶습니다." Aop에서는 패턴을 정의하여 수정할 방법을 찾을 수 있습니다. 즉, 네임 스페이스 "my.busininess. *"의 모든 메소드
k3b

1
특히 로그에 흥미로운 정보, 예를 들어 예외 스택 추적에 포함 된 것보다 많은 정보를 포함시키려는 경우 로깅은 교차 문제가되지 않습니다.
Laurent LA RIZZA

5

잘 들립니다. 상당히 표준적인 로깅 데코레이터를 설명하고 있습니다. 당신은 :

구성 요소 L (시스템의 로깅 구성 요소)

여기에는 하나의 책임이 있습니다 : 정보를 전달하는 로깅.

컴포넌트 A는 I를 구현

인터페이스 I의 구현 제공 (I가 SRP를 올바르게 준수한다고 가정)은 하나의 책임이 있습니다.

이것은 중요한 부분입니다.

컴포넌트 D는 I를 구현하고 A를 장식 / 사용하며 로깅에 L을 사용합니다.

그렇게 말하면 복잡하게 들리지만 다음과 같이보십시오. 컴포넌트 D는 가지를 수행합니다. A와 L을 함께 가져옵니다.

  • 구성 요소 D는 기록하지 않습니다. L에게 위임
  • 컴포넌트 D는 I 자체를 구현하지 않습니다. 그것을 A에게 위임

구성 요소 D가 가지고있는 책임은 모르겠 사용하는 경우 L 통지되었는지 확인하는 것입니다. A와 L의 구현은 모두 다른 곳에 있습니다. 이것은 완전히 SRP를 준수하며 OCP의 깔끔한 예이며 데코레이터의 일반적인 사용입니다.

중요한주의해야 할 점은 : D는 로깅 구성 요소 L을 사용하는 경우, 당신이 변경할 수있는 방법을 사용해야한다 방법 당신에게있는 거 로깅을. 이를 수행하는 가장 간단한 방법은 L로 구현 된 인터페이스 IL을 갖는 것입니다.

  • 구성 요소 D는 IL을 사용하여 기록합니다. L의 인스턴스가 제공됩니다
  • 구성 요소 D는 I를 사용하여 기능을 제공합니다. A의 인스턴스가 제공됩니다
  • 성분 B는 I를 사용합니다. D의 인스턴스가 제공됩니다

그렇게하면 다른 것에 직접 의존하는 것이 없으므로 쉽게 교체 할 수 있습니다. 이를 통해 변경에 쉽게 적응하고 시스템의 일부를 조롱하여 단위 테스트를 수행 할 수 있습니다.


실제로 네이티브 위임 지원이있는 C # 만 알고 있습니다. 내가 쓴 이유 D implements I입니다. 답변 주셔서 감사합니다.
Aitch

1

물론 교차 절단 문제가 있으므로 SRP를 위반합니다. 그러나 조치를 실행하여 로깅을 작성하는 클래스를 작성할 수 있습니다.

예:

class Logger {
   ActuallLogger logger;
   public Action ComposeLog(string msg, Action action) {
      return () => {
          logger.debug(msg);
          action();
      };
   }
}

2
공감. 로깅은 실제로 교차 문제입니다. 코드에서 시퀀싱 메소드 호출도 마찬가지입니다. 그것은 SRP 위반을 주장하기에 충분한 이유가 아닙니다. 응용 프로그램에서 특정 이벤트 발생을 기록하는 것은 중요한 문제가 아닙니다. 이러한 메시지가 관심있는 사용자에게 전달되는 방법은 실제로 별도의 문제이며 구현 코드에서 설명하는 것은 SRP 위반입니다.
Laurent LA RIZZA

"시퀀싱 방법 호출"또는 기능 구성은 교차 절단 문제가 아니라 구현 세부 사항입니다. 내가 만든 함수의 책임은 작업과 함께 로그 문을 작성하는 것입니다. 이 함수의 기능을 설명하기 위해 "and"라는 단어를 사용할 필요는 없습니다.
Paul Nikonowicz

구현 세부 사항이 아닙니다. 코드 모양에 큰 영향을 미칩니다.
Laurent LA RIZZA

"이 기능을 수행하는 방법"의 관점에서 SRP를보고 있다고 생각합니다. "이 기능을 수행하는 방법"의 관점에서 SRP를 볼 때.
Paul Nikonowicz
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.