동료들은 "로깅 / 캐싱 / 등은 교차하는 문제"라고 말하고 어디에서나 해당하는 싱글 톤을 사용하여 진행합니다. 그러나 그들은 IoC와 DI를 좋아합니다.
SOLI D 원칙 을 어기는 것이 실제로 유효한 변명 입니까?
동료들은 "로깅 / 캐싱 / 등은 교차하는 문제"라고 말하고 어디에서나 해당하는 싱글 톤을 사용하여 진행합니다. 그러나 그들은 IoC와 DI를 좋아합니다.
SOLI D 원칙 을 어기는 것이 실제로 유효한 변명 입니까?
답변:
아니.
SOLID는 불가피한 변경을 설명하기위한 지침으로 존재합니다. 당신이 있습니까 정말 ... 로깅 라이브러리, 또는 대상을 변경하려고하지 않거나 필터링 및 포맷, 또는 결코? 당신이 있습니까 정말 ... 당신의 캐싱 라이브러리, 또는 대상, 또는 전략, 또는 범위 지정 또는 변경하지 않을?
물론입니다. 상기 적어도 , 당신은 테스트를 위해 그 (것)들을 분리하기 위해 제정신 방식으로 이러한 것들을 조롱 할 것입니다. 테스트를 위해 격리하려는 경우 실제 이유로 격리하려는 비즈니스 이유가 발생할 수 있습니다.
그러면 로거 자체 가 변경을 처리 할 것이라는 주장을 얻게 됩니다. "아, 대상 / 필터링 / 포맷 / 전략이 바뀌면 설정 만 변경하면됩니다!" 그것은 쓰레기입니다. 이제 이러한 모든 것을 처리하는 God Object가있을뿐 아니라 정적 분석을 얻지 않고 컴파일 시간 오류가 발생하지 않는 XML (또는 이와 유사한)로 코드를 작성하고 있습니다. 실제로 효과적인 단위 테스트를받을 수 없습니다.
SOLID 지침을 위반하는 경우가 있습니까? 물론. 때로는 상황 이 바뀌지 않을 수도 있습니다 (완전히 다시 작성하지 않아도 됨). 때때로 LSP를 약간 위반하는 것이 가장 깨끗한 솔루션입니다. 때때로 고립 된 인터페이스를 만드는 것은 가치가 없습니다.
그러나 로깅 및 캐싱 (및 기타 유비쿼터스 간 절단 문제)은 그렇지 않습니다. 일반적으로 지침을 무시할 때 발생하는 커플 링 및 설계 문제의 훌륭한 예입니다.
String
하거나 모듈에서 벗어나지 Int32
않을 List
것입니다. 변화를 계획 하는 것이 합리적 이고 제정신 인 정도 입니다. 그리고 대부분 명백한 "핵심"유형을 넘어서서, 변화 할 가능성을 분별하는 것은 실제로 경험과 판단의 문제입니다.
예
이것이 "크로스 컷팅 문제"라는 용어의 요점입니다. 이는 SOLID 원칙에 맞지 않는 것을 의미합니다.
이상주의가 현실과 만나는 곳입니다.
SOLID를 처음 접하는 사람들과 교차 절단은 종종 이러한 정신적 도전에 부딪칩니다. 괜찮아, 놀라지 마 모든 것을 SOLID에 적용하려고 노력하지만 SOLID가 의미가없는 로깅 및 캐싱과 같은 몇 가지 장소가 있습니다. 크로스 컷팅은 SOLID의 형제입니다.
HttpContextBase
있지 않은 상태에서 단위 테스트를하는 것이 어떤 것인지 상상해보십시오 . 나는이 수업이 없다면 나의 현실이 실제로 신맛이 나는 것을 확신한다.
로깅을 위해 나는 생각합니다. 로깅은 광범위 하며 일반적으로 서비스 기능과 관련이 없습니다. 로깅 프레임 워크 싱글 톤 패턴을 사용하는 것이 일반적이며 잘 이해되고 있습니다. 당신이하지 않으면, 당신은 어디에서나 로거를 작성하고 주입 하고 당신은 그것을 원하지 않습니다.
위의 한 가지 문제는 누군가가 '그러나 로깅을 어떻게 테스트 할 수 있습니까?' 라고 말할 것입니다. . 내 생각은 실제로 로그 파일을 읽고 이해할 수 있다고 주장하는 것 외에도 일반적으로 로깅을 테스트하지 않는다는 것입니다. 로깅 테스트를 보았을 때 일반적으로 누군가 클래스가 실제로 무언가를 수행 했다고 주장해야하며 피드백을 얻기 위해 로그 메시지를 사용하고 있기 때문입니다. 나는 오히려 그 클래스에 리스너 / 옵저버를 등록하고 호출되는 테스트를 주장합니다. 그런 다음 해당 관찰자 내에 이벤트 로깅을 넣을 수 있습니다.
그러나 캐싱은 완전히 다른 시나리오라고 생각합니다.
내 2 센트 ...
예, 아니오
당신이 채택한 원칙을 실제로 위반 해서는 안됩니다 . 그러나 원칙은 항상 더 높은 목표를 달성하기 위해 미묘한 차이를 가져야합니다. 그래서, 적절하게 조절 이해와 함께, 몇 가지 명백한 위반하지 않을 수 있습니다 실제 은 "정신"또는 위반 "전체 원칙의 몸."
특히 SOLID 원칙은 많은 뉘앙스를 요구할뿐만 아니라 "작업 가능하고 유지 관리 가능한 소프트웨어 제공"이라는 목표에 종속적입니다. 따라서 특정 SOLID 원칙을 준수하는 것은 SOLID의 목표와 상충 될 때 자멸적이고 모순적입니다. 그리고 여기서는 종종 유지 보수성을 능가 하는 것으로 나타났습니다 .
SOLID 의 D 는 어떻습니까? 재사용 가능한 모듈을 해당 컨텍스트에 상대적으로 무관하게 만들어 유지 보수성을 향상시키는 데 기여합니다. 그리고 "재사용 가능한 모듈"을 "다른 별개의 컨텍스트에서 사용하려는 코드 모음"으로 정의 할 수 있습니다. 그리고 이것은 단일 함수, 클래스, 클래스 세트 및 프로그램에 적용됩니다.
로거 구현을 변경하면 모듈이 "또 다른 고유 한 컨텍스트"가 될 수 있습니다.
그래서 두 가지 큰 경고를 드리겠습니다 .
첫째 , "재사용 가능한 모듈"을 구성하는 코드 블록 주위에 선을 그리는 것은 전문적인 판단의 문제입니다. 그리고 당신의 판단은 반드시 당신의 경험으로 제한됩니다.
당신이하지 않으면 현재 다른 맥락에서 모듈을 사용하여 계획, 그것은이다 아마 그것에 무력 의존하는 OK. 경고에 대한주의 사항 : 계획이 잘못되었을 수도 있지만 괜찮습니다. 모듈 후에 모듈을 더 오래 쓸수록 "언젠가 다시 필요할지"에 대한 감각이 더욱 직관적이고 정확 해집니다. 그러나, 당신은 아마도 회고 적으로 "나는 모든 것을 가능한 한 많이 모듈화하고 분리 했지만 과도하게하지는 않았다 "고 말하지 못할 것이다 .
판단의 잘못에 대해 죄책감을 느끼면 자백하고 계속하십시오 ...
둘째 : 반전 제어 는 주입 의존성을 같지 않습니다 .
의존성 ad nauseam 주입 을 시작할 때 특히 그렇습니다 . 의존성 주입은 중요한 IoC 전략에 유용한 전술입니다. 그러나 DI는 인터페이스 및 어댑터 사용과 같은 다른 전술보다 모듈 내에서 컨텍스트에 대한 단일 노출 지점 보다 효과 가 적다고 주장합니다 .
그리고 잠시 이것에 초점을 맞추겠습니다. 왜냐하면 경우에도 당신이 분사 Logger
광고 싫증 , 당신은에 대한 코드를 작성에 필요한 Logger
인터페이스를 제공합니다. Logger
다른 순서로 매개 변수를 취하는 다른 공급 업체 의 새 제품 을 사용할 수 없었습니다 . 이러한 능력은 모듈 내에 존재하고 인터페이스 내에 단일 서브 모듈 (어댑터)을 갖는 인터페이스에 대한 코딩으로부터 비롯되어 종속성을 관리하는 데있다.
어댑터에 대해 코딩하는 경우 어댑터에 Logger
삽입 되는지 어댑터에 의해 감지 되는지 는 일반적으로 전반적인 유지 관리 목표에 크게 영향을 미치지 않습니다. 그리고 더 중요한 것은 모듈 수준의 어댑터가 있다면 아무 것도 넣지 않는 것입니다. 모듈 용으로 작성 되었습니다 .
tl; dr-원칙을 사용하는 이유를 고려하지 않고 원칙에 대한 소란을 멈추십시오. 그리고, 더 실제적으로, 단지를 구축 Adapter
각 모듈에 대해. "모듈"경계를 그릴 위치를 결정할 때 판단하십시오. 각 모듈 내에서 계속해서를 참조하십시오 Adapter
. 그리고 물론, 주입 실제 로거으로 Adapter
하지만 - 하지 를해야 할 수도 있습니다 모든 작은 일에 있습니다.
로깅을 항상 싱글 톤으로 구현 해야한다는 생각 은 너무나 자주 견인되고있는 거짓말 중 하나입니다.
최신 운영 체제가 사용 되는 한 출력 의 특성에 따라 여러 위치에 로그를 기록 할 수있는 것으로 인식되었습니다 .
시스템 설계자는 새로운 솔루션에 맹목적으로 포함하기 전에 과거 솔루션의 효능에 대해 끊임없이 의문을 제기해야합니다. 그들이 그러한 근면을 수행하지 않는다면, 그들은 일을하지 않는 것입니다.
진정으로 로깅하는 것은 특별한 경우입니다.
@Telastyn의 글을 참고하세요 :
로깅 라이브러리 또는 대상을 변경하거나 필터링 또는 형식을 변경하지 않습니까?
로깅 라이브러리를 변경해야 할 것으로 예상되면 파사드를 사용해야합니다. Java 세계에있는 경우 SLF4J
나머지의 경우, 적절한 로깅 라이브러리는 로깅이 진행되는 위치, 필터링되는 이벤트, 로거 구성 파일을 사용하여 로그 이벤트의 형식을 지정하는 방법 및 사용자 정의 플러그인 클래스를 변경합니다. 다양한 상용 대안이 있습니다.
요컨대, 로깅에 대한 해결 된 문제이므로 문제를 해결하기 위해 Dependency Injection을 사용할 필요가 없습니다.
DI가 표준 로깅 방식보다 유리한 유일한 경우는 응용 프로그램의 로깅을 단위 테스트에 적용하려는 경우입니다. 그러나 대부분의 개발자는 로깅이 클래스 기능의 일부가 아니며 테스트 해야 할 것이 아니라고 말할 것입니다 .
@Telastyn의 글을 참고하세요 :
그러면 로거 자체가 변경을 처리 할 것이라는 주장을 얻게됩니다. "아, 대상 / 필터링 / 포맷 / 전략이 바뀌면 설정 만 변경하면됩니다!" 그것은 쓰레기입니다. 이제 이러한 모든 것을 처리하는 신 객체가있을뿐만 아니라 정적 분석을 얻지 않고 컴파일 시간 오류가 발생하지 않는 XML 또는 유사한 코드로 코드를 작성하고 있습니다. 실제로 효과적인 단위 테스트를받을 수 없습니다.
이것이 매우 이론적 인 소문입니다. 실제로 대부분의 개발자와 시스템 통합자는 구성 파일을 통해 로깅을 구성 할 수 있다는 사실을 좋아합니다. 그리고 그들은 모듈의 로깅을 단위 테스트하지 않을 것이라는 사실을 좋아합니다.
물론 로깅 구성을 채우면 문제가 발생할 수 있지만 시작하는 동안 응용 프로그램이 실패하거나 너무 많거나 너무 적은 로깅으로 나타납니다. 1) 구성 파일의 실수를 수정하여 이러한 문제를 쉽게 해결할 수 있습니다. 2) 대안은 로깅 레벨을 변경할 때마다 완전한 빌드 / 분석 / 테스트 / 배포주기입니다. 허용되지 않습니다.
예 , 아니오 !
예 : 다른 서브 시스템 (또는 시맨틱 레이어 또는 라이브러리 또는 모듈 식 번들링의 다른 개념) 이 동일한 공통 공유 싱글 톤을 사용하는 모든 서브 시스템보다는 초기화 중에 동일한 로거 또는 다른 로거를 수용하는 것이 합리적이라고 생각합니다 .
하나,
아니요 : 동시에 모든 작은 객체에서 로깅을 매개 변수화하는 것은 불합리합니다 (생성자 또는 인스턴스 방법으로). 불필요하고 무의미한 부풀림을 피하려면 소규모 엔티티는 엔 클로징 컨텍스트의 싱글 톤 로거를 사용해야합니다.
이것은 모듈화 수준을 생각해야하는 많은 이유 중 하나입니다. 메소드는 클래스로 묶이고 클래스는 서브 시스템 및 / 또는 의미 계층으로 묶습니다. 이 더 큰 번들은 귀중한 추상화 도구입니다. 모듈 형 경계 내에서 교차 할 때와 다른 고려 사항을 제공해야합니다.
먼저 강력한 싱글 톤 캐시로 시작합니다. 다음으로 볼 수있는 다음은 전역 상태, 설명이 class
없는 es 및 테스트 할 수없는 코드의 API를 도입하는 데이터베이스 계층을위한 강력한 싱글 톤입니다 .
데이터베이스에 싱글 톤을 갖지 않기로 결정했다면, 아마도 캐시에 싱글 톤을 갖는 것은 좋은 생각이 아닐 수 있습니다. 결국 그것들은 다른 메커니즘을 사용하는 매우 유사한 개념 인 데이터 스토리지를 나타냅니다.
클래스에서 싱글 톤을 사용 하면 정적 메서드 뒤에 숨겨진 것이 무엇인지 알 수 없기 때문에 이론적 으로 무한한 수의 클래스에 특정 양의 종속성이있는 클래스가 됩니다.
지난 10 년 동안 나는 프로그래밍을 보냈지 만 로깅 논리를 변경하려는 노력을 보인 사례는 단 한 번 뿐이었다 (단일로 작성 됨). 따라서 의존성 주입을 좋아하지만 로깅은 실제로 큰 관심사가 아닙니다. 반면 캐시는 항상 의존성으로 만들 것입니다.
예, 아니오, 그러나 대부분 아니오
나는 대부분의 대화가 정적 대 주입 인스턴스를 기반으로한다고 가정합니다. 아무도 로깅이 SRP를 깨뜨릴 것을 제안하지 않습니다. 우리는 주로 "종속성 역전 원리"에 대해 이야기하고 있습니다. Tbh 나는 대부분 Telastyn의 대답이 없다고 동의합니다.
스태틱을 사용하는 것이 언제 괜찮습니까? 분명히 때로는 괜찮습니다. 그렇습니다. 추상화의 혜택에 대한 답은 "아니오"라는 대답은 그들이 당신이 지불하는 것임을 지적합니다. 직업이 어려운 이유 중 하나는 모든 상황에 대해 적어두고 적용 할 수있는 답변이 없기 때문입니다.
갖다:
Convert.ToInt32("1")
나는 이것을 선호한다 :
private readonly IConverter _converter;
public MyClass(IConverter converter)
{
Guard.NotNull(converter)
_converter = conveter
}
....
var foo = _converter.ToInt32("1");
왜? 전환 코드를 교체 할 수있는 유연성이 필요한 경우 코드를 리팩터링해야한다는 점에 동의합니다. 나는 이것을 조롱 할 수 없다는 것을 받아들이고 있습니다. 나는 단순성과 간결함이이 거래의 가치가 있다고 믿는다.
경우 스펙트럼의 다른 쪽 끝을 보면, IConverter
를했다 SqlConnection
, 나는 상당히 정적 호출로 그를보고 충격을 것입니다. 이유가 분명하지 않은 이유. 나는 SQLConnection
applciation에서 상당히 "cross-cutting"할 수 있다고 지적 하므로 정확한 단어를 사용하지 않았을 것입니다.
더 같은 로깅 SQLConnection
또는 Convert.ToInt32
? 나는 'SQLConnection`과 더 비슷하다고 말하고 싶습니다.
로깅을 조롱해야합니다 . 외부 세계와 대화합니다. 를 사용하여 메소드를 작성할 때 Convert.ToIn32
클래스의 별도로 테스트 가능한 출력을 계산하는 도구로 사용하고 있습니다. Convert
"1"+ "2"== "3" 을 확인할 때 제대로 호출 할 필요가 없습니다 . 로깅은 다르며, 클래스의 전적으로 독립적 인 출력입니다. 나는 그것이 당신, 지원 팀 및 비즈니스에 가치가있는 출력이라고 가정합니다. 로깅이 정확하지 않으면 클래스가 작동하지 않으므로 단위 테스트를 통과해서는 안됩니다. 클래스 로그를 테스트해야합니다. 나는 이것이 살인자라고 생각한다. 나는 정말로 여기서 멈출 수있다.
또한 변경 될 가능성이 높다고 생각합니다. 좋은 로깅은 문자열을 인쇄하는 것이 아니라 응용 프로그램이 수행하는 작업을 보여줍니다 (이벤트 기반 로깅의 팬입니다). 기본 로깅이 매우 정교한보고 UI로 바뀌는 것을 보았습니다. 벌목 모양이 비슷 _logger.Log(new ApplicationStartingEvent())
하거나 적 으면이 방향으로 향하는 것이 훨씬 쉽습니다 Logger.Log("Application has started")
. 아마도 이것이 결코 일어날 수없는 미래를위한 재고를 창출하고 있다고 주장 할 수도 있습니다. 이것은 판단 요청이며 가치가 있다고 생각합니다.
실제로 내 개인 프로젝트 _logger
에서 응용 프로그램이 수행하는 작업을 파악하기 위해 순수하게 로깅을 사용하지 않는 UI를 만들었습니다 . 이것은 애플리케이션이 무엇을하고 있는지 알아 내기 위해 코드를 작성할 필요가 없다는 것을 의미했으며, 결국 견고한 로깅으로 끝났습니다. 로깅에 대한 나의 태도가 단순하고 변하지 않았다고 생각한다면, 그 아이디어는 나에게 일어나지 않았을 것입니다.
로깅의 경우 Telastyn에 동의합니다.
Semantic Logging Application Block
있습니다. MS "patterns and pratice"팀에 의해 생성 된 대부분의 코드처럼, 아이러니하게도 반 패턴입니다.
첫 번째 교차 절단 문제는 주요 빌딩 블록이 아니며 시스템의 종속성으로 취급되어서는 안됩니다. 예를 들어 로거가 초기화되지 않았거나 캐시가 작동하지 않으면 시스템이 작동해야합니다. 어떻게 시스템을 덜 결합하고 응집력있게 만들 것입니까? OOID 시스템 설계에서 SOLID가 등장했습니다.
객체를 싱글 톤으로 유지하는 것은 SOLID와 관련이 없습니다. 그것은 객체가 메모리에 얼마나 오래 살기를 원하는지 당신의 객체 수명주기입니다.
초기화하기 위해 의존성이 필요한 클래스는 제공된 클래스 인스턴스가 싱글 톤인지 일시적인지 알 수 없습니다. 그러나 tldr; 모든 메소드 또는 클래스에서 Logger.Instance.Log ()를 작성하는 경우 문제가있는 코드 (코드 냄새 / 하드 커플 링)가 정말 지저분합니다. 사람들이 SOLID를 학대하기 시작하는 순간입니다. 그리고 OP와 같은 동료 개발자는 이와 같은 진정한 질문을하기 시작합니다.