SOLID로 전환 한 후 엄청나게 증가한 클래스 수 관리 및 구성?


49

지난 몇 년 동안, 우리는 한 번에 몇 단계 씩 단계적으로 개선 된 코드로 점진적으로 전환 해 왔습니다. 우리는 마침내 적어도 SOLID와 비슷한 것으로 전환하기 시작했지만 아직 멀지 않았습니다. 전환 이후 개발자의 가장 큰 불만 중 하나는 이전에 모든 작업에서 5-10 개의 파일을 다루는 개발자 만 필요했던 수십 및 수십 개의 파일을 동료 검토하고 통과 할 수 없다는 것입니다.

전환을 시작하기 전에 아키텍처는 다음과 같이 구성되었습니다 (1 ~ 2 배 더 많은 파일이 부여됨).

Solution
- Business
-- AccountLogic
-- DocumentLogic
-- UsersLogic
- Entities (Database entities)
- Models (Domain Models)
- Repositories
-- AccountRepo
-- DocumentRepo
-- UserRepo
- ViewModels
-- AccountViewModel
-- DocumentViewModel
-- UserViewModel
- UI

현명하게, 모든 것이 엄청나게 선형적이고 컴팩트했습니다. 분명히 코드 중복, 긴밀한 결합 및 두통이 많았지 만 모두가 그것을 통과하고 알아낼 수있었습니다. Visual Studio를 오픈 한 적이없는 완전한 초보자는 단 몇 주 만에 알아낼 수있었습니다. 전체적인 파일 복잡성의 부족으로 초보자 개발자와 신입 사원이 너무 많은 램프 업 시간없이 기여하기 시작하는 것이 비교적 간단합니다. 그러나 이것은 코드 스타일의 이점이 거의없는 곳입니다.

코드베이스를 개선하려는 모든 시도를 진심으로지지하지만, 이와 같은 대규모 패러다임 전환에 대해 팀의 나머지 부분에서 약간의 피드백을 얻는 것이 일반적입니다. 현재 가장 큰 두 가지 문제는 다음과 같습니다.

  • 단위 테스트
  • 수업 수
  • 동료 검토 복잡성

단위 테스트는 모두 시간 낭비라고 생각하고 각 조각보다 개별적으로 코드 전체를 훨씬 빠르게 처리 테스트 할 수 있다는 점에서 팀에게 엄청나게 판매되었습니다. SOLID에 대한 승인으로 단위 테스트를 사용하는 것은 무의미했으며 현재로서는 농담이되었습니다.

클래스 수는 아마도 극복해야 할 가장 큰 장애물입니다. 5-10 개의 파일을 가져 오는 데 사용 된 작업에 이제 70-100이 걸릴 수 있습니다! 이러한 각 파일은 고유 한 용도로 사용되지만 파일의 양은 압도적 일 수 있습니다. 팀의 반응은 대부분 신음과 머리 긁힘이었습니다. 이전에는 작업에 하나 또는 두 개의 리포지토리 (모델 또는 둘, 논리 계층 및 컨트롤러 방법)가 필요했을 수 있습니다.

이제 간단한 파일 저장 응용 프로그램을 만들려면 파일이 이미 있는지 확인하는 클래스, 메타 데이터를 작성하는 클래스, 추상화 할 클래스가 DateTime.Now있으므로 단위 테스트 시간, 로직이 포함 된 모든 파일의 인터페이스, 파일을 주입 할 수 있습니다. 각 클래스에 대한 단위 테스트와 DI 컨테이너에 모든 것을 추가하는 하나 이상의 파일을 포함합니다.

중소형 응용 분야의 경우 SOLID는 매우 쉽게 판매 할 수 있습니다. 누구나 유지 보수 용이성과 이점을 봅니다. 그러나 대규모 응용 프로그램에서 SOLID에 대한 가치 제안은 보이지 않습니다. 따라서 조직과 관리를 개선하여 점점 커지는 고통을 극복 할 수있는 방법을 찾고 있습니다.


최근에 완료 한 작업을 기반으로 파일 볼륨의 예를 조금 더 강력하게 생각했습니다. 파일 동기화 요청을 받기 위해 최신 마이크로 서비스 중 하나에서 일부 기능을 구현하는 작업을 받았습니다. 요청이 수신되면 서비스는 일련의 조회 및 검사를 수행하고 문서를 네트워크 드라이브와 2 개의 개별 데이터베이스 테이블에 저장합니다.

문서를 네트워크 드라이브에 저장하려면 몇 가지 특정 클래스가 필요했습니다.

- IBasePathProvider 
-- string GetBasePath() // returns the network path to store files
-- string GetPatientFolderName() // gets the name of the folder where patient files are stored
- BasePathProvider // provides an implementation of IBasePathProvider
- BasePathProviderTests // ensures we're getting what we expect

- IUniqueFilenameProvider
-- string GetFilename(string path, string fileType);
- UniqueFilenameProvider // performs some filesystem lookups to get a unique filename
- UniqueFilenameProviderTests

- INewGuidProvider // allows me to inject guids to simulate collisions during unit tests
-- Guid NewGuid()
- NewGuidProvider 
- NewGuidProviderTests

- IFileExtensionCombiner // requests may come in a variety of ways, need to ensure extensions are properly appended.
- FileExtensionCombiner
- FileExtensionCombinerTests

- IPatientFileWriter
-- Task SaveFileAsync(string path, byte[] file, string fileType)
-- Task SaveFileAsync(FilePushRequest request) 
- PatientFileWriter
- PatientFileWriterTests

따라서 총 15 개의 클래스 (POCO 및 스캐 폴딩 제외)는 상당히 간단한 저장을 수행합니다. 이 수치는 소수의 시스템에서 엔티티를 나타 내기 위해 POCO를 생성하고 다른 ORM과 호환되지 않는 타사 시스템과 통신 할 수있는 리포지토리를 구축하고 특정 작업의 복잡성을 처리하는 논리 방법을 구축해야 할 때 크게 증가했습니다.


52
"5-10 개의 파일을 가져 오는 데 사용 된 작업은 이제 70-100을 차지할 수 있습니다!" 이것은 결코 정상이 아닙니다. 많은 파일을 변경해야하는 어떤 종류의 변경이 있습니까?
행복감

43
작업 당 더 많은 파일 을 변경해야한다는 사실 (대단히 더 많이!)은 SOLID가 잘못되었음을 의미합니다. 요점은 관찰 된 변경 패턴을 반영하는 방식으로 코드를 (시간이 지남에 따라) 구성하여 변경을 간단하게하는 것입니다. SOLID의 모든 원칙에는 그 뒤에 특정한 추론이 있습니다 (어떻게 적용해야하는지). 이 상황에서 맹목적으로 적용함으로써 자신을 얻은 것처럼 보입니다. 단위 테스트 (TDD)와 동일합니다. 디자인 / 아키텍처를 수행하는 방법을 잘 이해하지 않고서하는 경우, 자신을 구멍에 파고들 것입니다.
Filip Milovanović

60
업무를 수행하는 데 도움이되는 실용적인 도구가 아닌 SOLID를 종교로 분명히 채택했습니다. SOLID의 어떤 것이 더 많은 일을하거나 더 힘들게 만들면 하지 마십시오.
Whatsisname

25
@Euphoric :이 문제는 두 가지 방식으로 발생할 수 있습니다. 70 ~ 100 개의 학급이 과잉이 될 가능성에 응답하고있는 것 같습니다. 그러나 이것이 단지 5-10 개의 파일로 압축 된 대규모 프로젝트 일 수는 없으며 (이전에 20KLOC 파일에서 작업 한 적이 있습니다 ...) 70-100은 실제로 올바른 양의 파일입니다.
8

18
내가 "객체 행복 질병"이라고 부르는 생각의 장애가 있는데, 이것은 큰 코드베이스에서 작업하는 비용을 줄이는 많은 가능한 기술 중 하나가 아니라 OO 기술이 그 자체로 끝났다는 신념입니다. "SOLID 행복 병"이라는 특별한 형태가 있습니다. SOLID는 목표가 아닙니다. 코드베이스 유지 관리 비용을 낮추는 것이 목표입니다. 교리 학자 SOLID가 아닌 해당 맥락에서 제안서를 평가하십시오. (여러분의 제안도 실제로 교리 학자가 아닐 수도 있습니다. SOLID도 고려해야합니다.)
Eric Lippert

답변:


104

이제 간단한 파일 저장 응용 프로그램을 만들려면 파일이 이미 있는지 확인하는 클래스, 메타 데이터를 작성하는 클래스, DateTime을 추상화하는 클래스가 있습니다. 이제 단위 테스트 시간, 포함하는 모든 파일의 인터페이스를 주입 할 수 있습니다. 로직, 각 클래스에 대한 단위 테스트를 포함하는 파일 및 DI 컨테이너에 모든 것을 추가하는 하나 이상의 파일.

나는 당신이 하나의 책임이라는 생각을 잘못 이해했다고 생각합니다. 수업의 단일 책임은 "파일 저장"일 수 있습니다. 그렇게하기 위해, 그 책임을 파일이 존재하는지 확인하는 방법, 메타 데이터를 쓰는 방법 등으로 분류 할 수 있습니다. 그런 다음 각 방법은 단일 책임을 가지며, 이는 클래스의 전반적인 책임의 일부입니다.

멀리 추상화하는 클래스 DateTime.Now가 좋습니다. 그러나 그중 하나만 필요하며 환경 기능 추상화를 담당하는 다른 환경 기능으로 단일 클래스로 묶을 수 있습니다. 여러 하위 책임이있는 단일 책임.

"논리를 포함하는 모든 파일에 대한 인터페이스"가 필요하지 않으며 부작용이있는 클래스 (예 : 파일 또는 데이터베이스에 읽고 쓰는 클래스)에 대한 인터페이스가 필요합니다. 그런 경우에도 해당 기능의 공개 부분에만 필요합니다. 예를 들어에서 AccountRepo인터페이스가 필요하지 않을 수 있습니다. 해당 리포지토리에 주입 된 실제 데이터베이스 액세스를위한 인터페이스 만 필요할 수 있습니다.

단위 테스트는 모두 시간 낭비라고 생각하고 각 조각보다 개별적으로 코드 전체를 훨씬 빠르게 처리 테스트 할 수 있다는 점에서 팀에게 엄청나게 판매되었습니다. SOLID에 대한 승인으로 단위 테스트를 사용하는 것은 무의미했으며 현재로서는 농담이되었습니다.

이것은 단위 테스트를 잘못 이해했음을 나타냅니다. 단위 테스트의 "단위"는 코드 단위가 아닙니다. 코드 단위조차 무엇입니까? 수업? 방법? 변수? 단일 기계 명령? 아니요, "단위"는 분리 단위, 즉 코드의 다른 부분과 분리하여 실행할 수있는 코드를 나타냅니다. 자동화 된 테스트가 단위 테스트인지 여부에 대한 간단한 테스트는 결과에 영향을주지 않고 다른 모든 단위 테스트와 병렬로 실행할 수 있는지 여부입니다. 단위 테스트에는 몇 가지 규칙이 있지만 이것이 핵심 측정입니다.

따라서 코드의 일부를 다른 부분에 영향을 미치지 않고 실제로 전체적으로 테스트 할 수 있다면 그렇게하십시오.

항상 실용적이며 모든 것이 타협임을 기억하십시오. DRY를 고수할수록 코드가 더 밀접하게 결합되어야합니다. 추상화를 많이 도입할수록 코드 테스트가 쉬워 지지만 이해하기가 더 어려워집니다. 이념을 피하고 이상과 단순성을 유지하는 것 사이의 균형을 찾으십시오. 개발 및 유지 보수 모두에 최대 효율의 장점이 있습니다.


27
나는 사람들이 "방법이 한 가지만해야한다"라는 너무 자주 반복되는 진언을 고수하려고 할 때 비슷한 두통이 발생한다고 덧붙이고 싶습니다. .
Logarr

8
Re "항상 실용적이며 모든 것이 타협 된 것을 기억하십시오" : Bob 삼촌의 제자들은 (원래의 의도와 상관없이) 이것에 대해 알려져 있지 않습니다.
Peter Mortensen

13
첫 번째 부분을 요약하면 일반적으로 플러그 인-여과기, 플립 스위치, 설탕이 필요없는 리필, 오픈 냉장고, 나가기 우유, 커피 세트가 아닌 커피 인턴이 있습니다. -스푼, 식 다운 컵, 타설 커피, 설탕 추가, 우유 추가, 저어 컵 및 컵 인도 인턴. ; P
저스틴 타임

12
OP 문제의 근본 원인은 단일 작업을 수행해야하는 기능 과 단일 책임
alephzero

6
"규칙은 현명한 사람들의지도와 바보의 순종을위한 것이다."
-Douglas

29

5-10 개의 파일을 가져 오는 데 사용 된 작업에 이제 70-100이 걸릴 수 있습니다!

이것은 단일 책임 원칙 (SRP) 의 반대 입니다. 그 시점에 도달하기 위해서는 기능을 매우 세분화 된 방식으로 나누었어야하지만 SRP의 핵심은 아닙니다 . 이는 응집력 의 핵심 아이디어를 무시하는 것입니다 .

SRP에 따르면 소프트웨어는 가능한 변경 이유에 따라 정의 된 라인을 따라 모듈로 나뉘어 야하므로 단일 설계 변경을 다른 곳에서 수정하지 않고도 하나의 모듈 에만 적용 할 수 있습니다 . 이러한 의미에서 단일 "모듈"은 둘 이상의 클래스에 해당 할 수 있지만 한 번의 변경으로 수십 개의 파일을 터치해야하는 경우 실제로는 여러 번 변경되거나 SRP가 잘못되었습니다.

원래 SRP를 공식화 한 Bob Martin 은 몇 년 전에 상황을 명확히하기 위해 블로그 게시물을 작성 했습니다 . SRP의 목적을 위해 "변경 이유"가 무엇인지에 대해 어느 정도 논의합니다. 전체적으로 읽을 가치가 있지만 특별한주의를 기울여야 할 것들 중 SRP의 다음과 같은 대안입니다.

같은 이유로 바뀌는 것들을 모아라 . 다른 이유로 바뀌는 것들을 분리하십시오.

(강조 광산). SRP는 사물을 가장 작은 조각으로 나누는 것이 아닙니다 . 그것은 좋은 디자인이 아니며, 당신의 팀은 저항 할 권리가 있습니다. 코드 기반을 업데이트하고 유지 관리하기가 어렵습니다. 단위 테스트 고려 사항에 따라 팀을 판매하려고하는 것처럼 들리지만 카트를 말보다 먼저 넣을 수 있습니다.

마찬가지로 인터페이스 분리 원칙을 절대적인 것으로 간주해서는 안됩니다. 더 이상 SRP보다 코드를 세분화해야 할 이유가 없으며 일반적으로 SRP와 잘 어울립니다. 인터페이스에 포함 된 몇 가지 방법 몇 가지 , 클라이언트를 깰 이유가없는 사용하지 않는합니다. 당신은 다시 응집력을 찾고 있습니다.

또한 공개 상속 원칙 또는 Liskov 대체 원칙을 깊은 상속 계층 구조를 선호하는 이유로 사용하지 말 것을 권한다. 수퍼 클래스가있는 서브 클래스보다 타이트한 커플 링이 없으며 타이트한 커플 링은 설계상의 문제입니다. 대신, 그것이 가능한 곳이면 상속보다 구성을 선호하십시오. 이렇게하면 커플 링이 줄어들어 특정 변경 사항을 처리해야하는 파일 수가 달라지며 종속성 반전에 잘 맞습니다.


1
나는 단지 선이 어디에 있는지 알아 내려고 노력하고 있다고 생각한다. 최근의 작업에서는 상당히 간단한 작업을 수행해야했지만 기존 스캐 폴딩이나 기능이 많이없는 코드베이스에있었습니다. 따라서 내가해야 할 일은 매우 간단했지만 모두 독특하고 공유 수업에 적합하지 않은 것 같습니다. 필자의 경우 문서를 네트워크 드라이브에 저장하고 두 개의 별도 데이터베이스 테이블에 기록해야했습니다. 각 단계를 둘러싼 규칙은 상당히 구체적이었습니다. 파일 이름 생성 (간단한 guid)조차도 테스트를보다 편리하게하기 위해 몇 가지 클래스가있었습니다.
JD Davis

3
다시 한 번, @JDDavis는 테스트 목적으로 순전히 단일 클래스를 통해 여러 클래스를 선택하는 것이 장바구니를 말보다 앞서고 있으며 SRP와 직접 연결되어 응집 기능을 그룹화해야합니다. 특정 사항에 대해서는 조언 할 수 없지만 개별 기능 변경으로 인해 많은 파일을 수정해야하는 문제는 정당화하려는 문제가 아니라 해결해야하는 문제입니다.
John Bollinger

동의합니다. Wikipedia를 인용하자면, "Martin은 책임을 변경 이유로 정의하고 클래스 또는 모듈에는 변경해야 할 이유가 하나만 있어야한다는 결론을 내립니다." "최근에"이 원칙은 사람에 관한 것 "이라고 언급했습니다. 사실 이것은 SRP의"책임 "이 기능이 아니라 이해 관계자를 의미한다고 생각합니다. 클래스는 한 명의 이해 관계자 (프로그램 변경을 요구하는 사람)만이 요구하는 변경에 대해 책임을 져야하며, 변경을 요구하는 다른 이해 관계자에 대한 응답으로 가능한 한 적은 변경을해야합니다.
Corrodias

12

5-10 개의 파일을 가져 오는 데 사용 된 작업에 이제 70-100이 걸릴 수 있습니다!

이건 거짓말이야 작업은 5-10 개의 파일 만 가져 가지 않았습니다.

파일이 10 개 미만인 작업을 해결하지 못했습니다. 왜? C #을 사용하고 있기 때문입니다. C #은 고급 언어입니다. hello world를 만들기 위해 10 개 이상의 파일을 사용하고 있습니다.

글을 쓰지 않았기 때문에 눈치 채지 못할 것입니다. 그래서 당신은 그들을 보지 않습니다. 당신은 그들을 믿습니다.

문제는 파일 수가 아닙니다. 그것은 당신이 지금 너무 많은 일을 믿지 않기 때문입니다.

따라서 테스트가 통과되면 .NET의 파일을 신뢰하는 방식으로 이러한 파일을 신뢰하는 지점까지 테스트를 수행하는 방법을 찾으십시오. 그렇게하는 것이 단위 테스트의 요점입니다. 아무도 파일 수에 신경 쓰지 않습니다. 그들은 믿을 수없는 것들의 수를 걱정합니다.

중소형 응용 분야의 경우 SOLID는 매우 쉽게 판매 할 수 있습니다. 누구나 유지 보수 용이성과 이점을 봅니다. 그러나 대규모 응용 프로그램에서 SOLID에 대한 가치 제안은 보이지 않습니다.

대규모 응용 프로그램에서는 아무런 변화가 없으므로 변경이 어렵습니다. 여기에 적용하는 가장 좋은 지혜는 밥 삼촌이 아닙니다. Michael Feathers의 저서 "레거시 코드로 효과적으로 작업하기"에서 비롯된 것입니다.

다시 쓰기 페스트를 시작하지 마십시오. 이전 코드는 어려운 지식을 나타냅니다. 문제가 있고 새롭고 개선 된 패러다임으로 표현되지 않기 때문에 그것을 던지기 X는 단지 새로운 문제를 요구하고 어려운 지식이 없습니다.

대신 테스트 할 수없는 오래된 코드를 테스트 할 수있는 방법을 찾으십시오 (Fathers의 레거시 코드는 말합니다). 이 은유 코드는 셔츠와 같습니다. 큰 부분은 이음새를 제거하는 방식으로 코드를 분리하기 위해 되돌릴 수있는 자연 이음새에 결합됩니다. 이렇게하면 나머지 코드를 분리 할 수있는 테스트 "슬리브"를 연결할 수 있습니다. 이제 테스트 슬리브를 만들 때 작업 셔츠로이 작업을 수행했기 때문에 슬리브에 대한 확신이 있습니다. (이 비유가 아프기 시작했습니다).

이 아이디어는 대부분의 상점과 마찬가지로 최신 요구 사항 만 작업 코드에 있다고 가정합니다. 이를 통해 검증 된 작업 상태의 모든 비트를 잃지 않고 검증 된 작업 코드를 변경할 수있는 테스트에서이를 잠글 수 있습니다. 이제이 첫 번째 테스트 단계를 거쳐 "레거시"(최소한) 코드를 테스트 할 수 있도록 변경을 시작할 수 있습니다. 이음새 테스트는 항상 수행 한 작업이고 새 테스트에서는 코드가 실제로 생각하는 작업을 수행한다고 말함으로써 백업을 수행하므로 대담 할 수 있습니다.

이것과 관련이있는 것은 :

SOLID로 전환 한 후 엄청나게 증가한 클래스 수 관리 및 구성?

추출.

나쁜 추상화로 코드베이스를 싫어하게 만들 수 있습니다. 잘못된 추상화는 내면을 들여다 보는 것입니다. 내부를 볼 때 놀라지 마십시오. 내가 기대했던 것과 거의 같아라.

인터페이스를 사용하는 방법을 보여주는 좋은 이름, 읽을 수있는 테스트 (예)를 제공하고 구성하여 찾을 수 있으며 10, 100 또는 1000 개의 파일을 사용했는지 신경 쓰지 않습니다.

설명이 좋은 이름을 가진 것을 찾도록 도와주십시오. 좋은 이름을 가진 것에 좋은 이름을 가진 것을 넣으십시오.

이 모든 작업을 올바르게 수행하면 3 ~ 5 개의 다른 파일에만 의존하여 작업을 마치는 위치로 파일을 추상화 할 수 있습니다. 70-100 파일은 여전히 ​​존재합니다. 그러나 그들은 3 대 5 뒤에 숨어 있습니다. 3 대 5를 올바르게 수행해야 신뢰할 수 있습니다.

그래서 당신이 정말로 필요한 것은 사람들이 신뢰하는 모든 것들과 테스트에 대한 좋은 이름을 만들어 내기 위해 어휘입니다. 그것 없이는 당신도 나를 미치게 할 것입니다.

@Delioth 통증 증가에 대해 좋은 지적을 합니다. 식기 세척기 위의 찬장에있는 음식에 익숙해지면 아침 식사 바 위에있는 음식에 익숙해지기까지합니다. 몇 가지를 더 어렵게 만듭니다. 몇 가지를 더 쉽게 만듭니다. 그러나 사람들이 설거지의 장소에 동의하지 않으면 모든 종류의 악몽이 발생합니다. 큰 코드 기반에서 문제는 한 번에 일부 요리 만 이동할 수 있다는 것입니다. 이제 두 곳에서 요리를했습니다. 혼란 스럽습니다. 설거지가 있어야 할 위치에 있다는 것을 믿기 어렵게 만듭니다. 당신이 이것을 지나고 싶지만 할 수있는 유일한 일은 설거지를 계속 움직이는 것입니다.

문제는 아침 식사 바에서 요리를하는 것이이 모든 말도 안되는 일을하기 전에 그만한 가치가 있는지 정말로 알고 싶습니다. 제가 추천 할 수있는 것은 캠핑을하는 것입니다.

새로운 패러다임을 처음 시도 할 때 마지막으로 적용해야 할 곳은 큰 코드 기반입니다. 이것은 팀의 모든 구성원에게 적용됩니다. SOLID가 작동하거나 OOP가 작동하거나 기능적 프로그래밍이 작동한다고 믿지 않아야합니다. 모든 팀원은 장난감 프로젝트에서 새로운 아이디어를 가지고 놀 수있는 기회를 가져야합니다. 최소한 그들이 어떻게 작동하는지 볼 수 있습니다. 그것은 그들이 잘하지 않는 것을 볼 수 있습니다. 그것은 그들이 큰 혼란을 일으키기 직전에 그것을 배우는 것을 허용합니다.

사람들에게 안전한 놀이 공간을 제공하면 새로운 아이디어를 채택하고 설거지가 실제로 새로운 집에서 작동 할 수 있다는 자신감을 갖게됩니다.


3
질문의 고통 중 일부는 고통도 커질 가능성이 있다고 언급 할 가치가 있습니다. 그렇습니다. 예를 들어 15 개의 파일을 만들어야 할 수도 있습니다 ... 이제 GUIDProvider 또는 BasePathProvider를 다시 작성할 필요가 없습니다. , 또는 ExtensionProvider 등입니다. 새로운 그린 필드 프로젝트를 시작할 때와 같은 종류의 장애물입니다. 대부분 사소하고 작성하기는 어리석지 만 여전히 작성해야하는 지원 기능이 많이 있습니다. 그들을 구축하기 위해 짜증,하지만 일단 거기에 당신은 그들에 대해 생각할 필요가 없습니다 .....
Delioth

@Delioth 나는 이것이 사실이라고 믿을 수 없을 정도로 기울어졌습니다. 이전에는 기능의 일부 하위 집합 (AppSettings에 포함 된 URL 만 원한다고 가정)이 필요한 경우 단순히 하나의 방대한 클래스가 전달되어 사용되었습니다. 새로운 접근 방식을 사용하면 AppSettingsURL 또는 파일 경로를 얻기 위해 전체를 전달할 이유가 없습니다 .
JD Davis

1
다시 쓰기 페스트를 시작하지 마십시오. 이전 코드는 어려운 지식을 나타냅니다. 문제가 있고 새롭고 개선 된 패러다임으로 표현되지 않기 때문에 그것을 던지기 X는 단지 새로운 문제 세트를 요구하고 어려운 지식이 없습니다. 이. 물론.
Flot2011

10

코드가 잘 분리되지 않았거나 작업 크기가 너무 큰 것처럼 들립니다.

codemod 또는 대규모 리팩토링을 수행하지 않는 한 코드 변경 5-10 개의 파일 이어야 합니다. 단일 변경 사항이 많은 파일에 닿으면 변경 사항이 캐스케이드됨을 의미합니다. 일부 개선 된 추상화 (더 많은 단일 책임, 인터페이스 분리, 종속성 반전)가 도움이 될 것입니다. 그것은 당신이 아마 갔다 할 수도있어 너무 짧고 얇은 형태의 계층 구조 - 하나의 책임과 좀 더 실용을 사용할 수 있습니다. 코드가 무엇을하고 있는지 알기 위해 수십 개의 파일을 이해할 필요가 없기 때문에 코드를 더 쉽게 이해할 수 있어야합니다.

또한 일이 너무 크다는 신호일 수도 있습니다. UI 변경 및 API 변경 및 데이터 액세스 변경 및 보안 변경 및 테스트 변경 등을 요구하는 "이 기능을 추가하십시오"대신 서비스 가능한 부분으로 분류하십시오. 비트간에 적절한 계약을 설정해야하므로 검토하기 쉽고 이해하기 쉽습니다.

물론 단위 테스트는이 모든 것을 도와줍니다. 그들은 당신이 알맞은 인터페이스를 만들도록 강요합니다. 테스트하는 데 필요한 비트를 주입 할 수있을 정도로 유연하게 코드를 작성해야합니다 (테스트하기 어려운 경우 재사용하기 어렵습니다). 또한 엔지니어가 많을수록 테스트해야 할 것이기 때문에 사람들을 과도하게 엔지니어링하지 못하게합니다.


2
5-10 개의 파일에서 70-100 개의 파일은 가상적인 것 이상입니다. 마지막 작업은 최신 마이크로 서비스 중 하나에서 일부 기능을 만드는 것이 었습니다. 새로운 서비스는 요청을 받고 문서를 저장해야했습니다. 이를 위해 2 개의 개별 데이터베이스에서 사용자 엔터티를 나타내는 클래스와 각각에 대한 리포지토리가 필요했습니다. 쓸 다른 테이블을 나타내는 Repos. 파일 데이터 검사 및 이름 생성을 처리하기위한 전용 클래스 그리고 목록은 계속됩니다. 말할 것도없이, 로직을 포함하는 모든 클래스는 인터페이스로 표시되므로 단위 테스트를 위해 조롱 할 수 있습니다.
JD 데이비스

1
우리의 오래된 코드베이스에 이르기까지 그것들은 모두 밀접하게 결합되어 있으며 믿을 수 없을 정도로 모 놀리 식입니다. SOLID 방식을 사용하면 POCO의 경우 클래스 간의 유일한 커플 링이 이루어졌으며 다른 모든 것은 DI와 인터페이스를 통해 전달됩니다.
JD Davis

3
@JDDavis-하나의 마이크로 서비스가 여러 데이터베이스와 직접 작동하는 이유는 무엇입니까?
Telastyn

1
우리 dev 관리자와 타협했습니다. 그는 모 놀리 식 및 절차 적 소프트웨어를 매우 선호합니다. 따라서 마이크로 서비스는 원래보다 훨씬 더 많은 매크로입니다. 인프라가 향상됨에 따라 천천히 자체 마이크로 서비스로 이동합니다. 현재 우리는 특정 기능을 마이크로 서비스로 옮기기 위해 약간의 접근 방식을 따르고 있습니다. 여러 서비스가 특정 리소스에 액세스해야하므로 해당 서비스를 자체 마이크로 서비스로 이동하고 있습니다.
JD Davis

4

여기에 이미 언급 된 것들 중 일부에 대해 설명하고 싶지만 객체 경계가 그려지는 관점에서 더 자세히 설명하겠습니다. Domain-Driven Design과 유사한 것을 따르는 경우 객체가 비즈니스의 측면을 나타내는 것일 수 있습니다. Customer그리고 Order, 예를 들어, 객체가 될 것입니다. 이제 시작점으로 사용했던 클래스 이름을 기반으로 추측을하려면 클래스 AccountLogic에는 모든 계정에 대해 실행되는 코드가 있습니다. 그러나 OO에서 각 클래스는 맥락과 정체성을 갖도록되어 있습니다. Account객체를 가져 와서 AccountLogic클래스 로 전달 해서는 안되며 해당 클래스가 Account객체를 변경하도록해야 합니다. 이것이 빈혈 모델이며 OO를 잘 나타내지 않습니다. 대신에Account클래스에는 Account.Close()또는 과 같은 동작이 있어야 Account.UpdateEmail()하며 이러한 동작은 해당 계정 인스턴스에만 영향을 미칩니다.

이제 이러한 동작을 처리하는 방법은 추상화 (예 : 인터페이스)로 표시되는 종속성으로 오프로드 될 수 있습니다 (많은 경우). Account.UpdateEmail예를 들어 데이터베이스 나 파일을 업데이트하거나 서비스 버스 등에 메시지를 보내려고 할 수 있습니다. 나중에 변경 될 수 있습니다. 따라서 Account클래스는 예를 들어 객체에 IEmailUpdate의해 구현되는 많은 인터페이스 중 하나 일 수있는에 대한 종속성을 가질 수 있습니다 AccountRepository. 당신은 전체 통과 싶지 않을 것이다 IAccountRepository받는 인터페이스를 Account아마 너무 많이, 같은 검색 등을 수행하고 싶지 않을 수도 있습니다 (임의) 계정 찾기 때문에 개체 Account개체에 액세스 할 수 있도록,하지만 비록 AccountRepository모두를 구현할 수 IAccountRepositoryIEmailUpdate인터페이스의Account객체는 필요한 작은 부분에만 액세스 할 수 있습니다. 인터페이스 분리 원칙 을 유지하는 데 도움이됩니다 .

현실적으로 다른 사람들이 언급했듯이 급증하는 클래스를 다루는 경우 SOLID 원칙 (및 확장으로 OO)을 잘못 사용했을 가능성이 있습니다. SOLID는 복잡한 코드가 아니라 코드를 단순화하는 데 도움이됩니다. 그러나 SRP와 같은 것이 무엇을 의미하는지 실제로 이해하려면 시간이 걸립니다. 그러나 더 중요한 것은 SOLID의 작동 방식이 도메인과 제한된 컨텍스트 (또 다른 DDD 용어)에 크게 의존한다는 것입니다. 은 총알이나 모든 크기에 맞는 것은 없습니다.

내가 작업하는 사람들에게 강조하고 싶은 한 가지 더 : OOP 객체는 행동을 가져야하며, 실제로는 데이터가 아닌 행동으로 정의됩니다. 개체에 속성과 필드 만있는 경우 의도 한 동작이 아니지만 여전히 동작이 있습니다. 다른 집합 논리가없는 공개적으로 쓸 수있는 / 설정 가능한 속성은 포함하는 클래스의 동작이 이유와 관계없이 언제 어디서나 필요한 비즈니스 논리 나 유효성 검사없이 해당 속성의 값을 수정할 수 있음을 의미합니다. 그것은 일반적으로 사람들이 의도 한 행동이 아니지만, 빈혈 모델이있는 경우, 일반적으로 클래스를 사용하는 모든 사람에게 발표하는 행동입니다.


2

따라서 총 15 개의 클래스 (POCO 및 스캐 폴딩 제외)는 상당히 간단한 저장을 수행합니다.

미쳤어 요 ..하지만이 수업은 제가 직접 쓴 것 같아요. 그럼 그것들을 살펴 봅시다. 인터페이스와 테스트를 무시하자.

  • BasePathProvider-파일로 작업하는 사소한 프로젝트는 IMHO가 필요합니다. 따라서 이미 그런 것이 있다고 가정하고 그대로 사용할 수 있습니다.
  • UniqueFilenameProvider -물론 이죠, 그렇지 않나요?
  • NewGuidProvider -GUID를 사용하려고하지 않는 한 동일한 경우입니다.
  • FileExtensionCombiner -같은 경우입니다.
  • PatientFileWriter -이것은 현재 작업의 주요 클래스입니다.

나에게 그것은 좋아 보인다 : 네 개의 도우미 클래스가 필요한 새 클래스를 하나 작성해야합니다. 네 가지 도우미 클래스는 모두 재사용이 가능하기 때문에 이미 코드 기반 어딘가에 있습니다. 그렇지 않으면, 운이 좋지 않습니다 (팀원이 실제로 파일을 작성하고 GUID를 사용하는 사람입니까?) 또는 다른 문제입니다.


테스트 클래스에 대해서는 새 클래스를 만들거나 업데이트 할 때 테스트해야합니다. 따라서 5 개의 클래스를 작성한다는 것은 5 개의 테스트 클래스를 작성하는 것을 의미합니다. 그러나 이것이 디자인을 더 복잡하게 만들지는 않습니다.

  • 테스트 클래스는 자동으로 실행되므로 그 밖의 다른 곳에서는 테스트 클래스를 사용하지 않습니다.
  • 테스트중인 클래스를 업데이트하지 않거나 문서로 사용하지 않는 한 클래스를 다시 살펴보고 싶을 것입니다 (이상적으로 테스트는 클래스의 사용법을 명확하게 보여줍니다).

인터페이스와 관련하여 인터페이스는 DI 프레임 워크 또는 테스트 프레임 워크가 클래스를 처리 할 수없는 경우에만 필요합니다. 당신은 그것들을 불완전한 도구에 대한 통행료로 볼 수 있습니다. 또는 더 복잡한 것들이 있다는 것을 잊게 해주는 유용한 추상화로 볼 수 있습니다. 인터페이스의 소스를 읽는 것은 구현의 소스를 읽는 것보다 시간이 덜 걸립니다.


이 견해에 감사합니다. 이 특정한 경우에, 나는 기능을 상당히 새로운 마이크로 서비스에 쓰고있었습니다. 불행히도, 주요 코드베이스에서도 위의 내용 중 일부를 사용하고 있지만 실제로 원격으로 재사용 가능한 방법은 없습니다. 재사용이 필요한 모든 것은 일부 정적 클래스에서 끝나거나 코드를 복사하여 붙여 넣습니다. 나는 아직도 조금 멀리 가고 있다고 생각하지만 모든 것을 완전히 해체하고 분리 할 필요 는 없다는 것에 동의합니다 .
JD Davis

@ JDDavis 나는 다른 답변과는 다른 것을 쓰려고했습니다 (주로 동의하는). 무언가를 복사하여 붙여 넣을 때마다 무언가를 일반화하는 대신 재사용 할 수없는 또 다른 코드 조각을 생성하여 하루 더 복사하여 붙여 넣을 수 있으므로 재사용이 방지됩니다. IMHO 그것은 맹목적으로 규칙을 따른 직후에 두 번째로 큰 죄입니다. 규칙을 준수하면 생산성이 높아지고 (특히 미래의 변화를 초래할 수 있음) 노력이 부적절하지 않은 경우에 약간의 도움이되는 경우가 있습니다. 모두 친척입니다.
maaartinus

@JDDavis 그리고 모든 것은 도구의 품질에 달려 있습니다. 예 : DI가 기업적이고 복잡하다고 주장하는 사람들이 있지만, 대부분 DI라고 주장합니다 . +++규칙을 어기는 것에 대해 : 4 개의 클래스가 있는데, 장소에 필요한데, 주요 리팩토링 후에 만 ​​코드를 더보기 흉하게 만들 수 있습니다 (적어도 내 눈에는). 더 나은 방법을 찾을 수는 있지만 행복합니다.이 싱글 톤의 수는 나이 이후로 변경되지 않았습니다).
maaartinus

이 답변은 OP가 예제에 질문을 추가했을 때 내가 생각했던 것을 거의 표현합니다. @JDDavis 간단한 사례를위한 기능적 도구를 사용하여 상용구 코드 / 클래스를 절약 할 수 있다고 덧붙여 보겠습니다. 예를 들어 GUI 공급자-새로운 인터페이스에 새로운 클래스를 도입하는 대신 이것을 활용 하고 생성자에 Func<Guid>익명의 메소드를 삽입하는 것이 ()=>Guid.NewGuid()어떻습니까? 그리고이 .Net 프레임 워크 기능을 테스트 할 필요가 없습니다. 이것은 Microsoft가 귀하를 위해 한 일입니다. 총 4 개의 수업이 절약됩니다.
Doc Brown

... 그리고 당신이 제시 한 다른 사례들이 같은 방식으로 단순화 될 수 있는지 확인해야합니다 (아마도 전부는 아닙니다).
Doc Brown

2

추상화에 따라 단일 책임 클래스 작성 및 단위 테스트 작성은 정확한 과학이 아닙니다. 학습 할 때 한 방향으로 너무 멀리 스윙하고, 극단적으로 가고, 그에 맞는 표준을 찾는 것이 완전히 정상입니다. 진자가 너무 많이 흔들린 것처럼 들리거나 붙어있을 수도 있습니다.

이것이 레일에서 벗어난 것으로 의심되는 곳입니다.

단위 테스트는 모두 시간 낭비라고 생각하고 각 조각보다 개별적으로 코드 전체를 훨씬 빠르게 처리 테스트 할 수 있다는 점에서 팀에게 엄청나게 판매되었습니다. SOLID에 대한 승인으로 단위 테스트를 사용하는 것은 무의미했으며 현재로서는 농담이되었습니다.

대부분의 SOLID 원칙에서 비롯된 이점 중 하나 (확실히 유일한 이점은 아님)는 코드에 대한 단위 테스트 작성이 더 쉽다는 것입니다. 클래스가 추상화에 의존하는 경우 추상화를 조롱 할 수 있습니다. 분리 된 추상화는 조롱하기가 더 쉽습니다. 클래스가 한 가지 작업을 수행하면 복잡성이 낮아질 수 있으므로 가능한 모든 경로를 쉽게 알고 테스트 할 수 있습니다.

팀에서 단위 테스트를 작성하지 않으면 두 가지 관련 문제가 발생합니다.

첫째, 그들은 모든 이점을 실현하지 않고 이러한 모든 인터페이스와 클래스를 만들기 위해 많은 추가 작업을 수행하고 있습니다. 단위 테스트 작성이 우리의 삶을 더 편하게 만드는 방법을 이해하려면 약간의 시간과 연습이 필요합니다. 단위 테스트 작성을 배우는 사람들이 그에 충실하는 데는 이유가 있지만, 스스로 테스트 할 수있을 정도로 오래 지속해야합니다. 당신의 팀이 그것을 시도하지 않는다면 그들은 그들이하고있는 나머지 여분의 일들이 쓸모없는 것처럼 느껴질 것입니다.

예를 들어 리팩토링해야 할 경우 어떻게됩니까? 백개의 작은 수업이 있지만 변경 사항이 적용되는지 여부를 테스트하는 테스트가 없다면 추가 수업과 인터페이스는 개선이 아닌 부담으로 보일 것입니다.

둘째, 단위 테스트를 작성하면 코드에 실제로 필요한 추상화의 양을 이해하는 데 도움이됩니다. 내가 말했듯이, 그것은 과학이 아닙니다. 우리는 나쁘게 시작하여 모든 곳을 향한 찬사를하며 더 나아집니다. 단위 테스트에는 SOLID를 보완하는 고유 한 방법이 있습니다. 추상화를 추가하거나 분리해야하는 시점을 어떻게 알 수 있습니까? 다른 말로하면, "SOLID 충분"상태를 어떻게 알 수 있습니까? 답은 무언가를 테스트 할 수없는 경우가 종종 있습니다.

어쩌면 작은 추상화와 클래스를 만들지 않고 코드를 테스트 할 수 있습니다. 그러나 테스트를 작성하지 않으면 어떻게 알 수 있습니까? 우리는 얼마나 멀리 가나 요? 우리는 점점 더 작은 것들을 깨뜨리는 것에 집착 할 수 있습니다. 토끼 구멍입니다. 코드에 대한 테스트를 작성하는 기능은 우리가 목적을 달성 한 시점을 파악하여 집착을 멈추고, 더 많은 코드를 작성하는 것을 즐겁게 할 수 있도록 도와줍니다.

단위 테스트는 모든 것을 해결하는 은색 총알이 아니지만 개발자의 삶을 더 좋게 만드는 정말 멋진 총알입니다. 우리는 완벽하지 않으며 테스트도 아닙니다. 그러나 테스트는 우리에게 자신감을줍니다. 우리는 코드가 올바르기를 기대하며 잘못되었을 때 놀랍니다. 우리는 완벽하지 않으며 테스트도 아닙니다. 그러나 코드를 테스트 할 때 확신이 있습니다. 우리는 코드가 배포 될 때 손톱을 물어 뜯을 가능성이 적고 이번에는 어떤 일이 일어날 지, 그것이 우리의 잘못이 될지 궁금해합니다.

또한 단위 테스트를 작성하면 코드 테스트 속도가 느려지지 않고 더 빠릅니다. 우리는 건초 더미의 바늘과 같은 문제를 찾기 위해 오래된 코드를 다시 보거나 디버깅하는 데 시간을 덜 소비합니다.

버그가 줄어들고 더 많은 일을 할 수 있으며 불안감을 자신감으로 대체합니다. 유행이나 뱀 기름이 아닙니다. 진짜야 많은 개발자들이 이것을 증명할 것입니다. 당신의 팀이 이것을 경험하지 않았다면, 그들은 그 학습 곡선을 뚫고 혹을 극복해야합니다. 그들이 즉시 결과를 얻지 못할 것이라는 것을 깨닫고 기회를주십시오. 그러나 그것이 일어날 때 그들은 기뻐하고 결코 뒤돌아 보지 않을 것입니다. 또는 격리 된 파리 아가되어 단위 테스트 및 기타 누적 된 프로그래밍 지식이 시간 낭비 인 방법에 대한 분노한 블로그 게시물을 작성합니다.

전환 이후 개발자의 가장 큰 불만 중 하나는 이전에 모든 작업에서 5-10 개의 파일을 다루는 개발자 만 필요했던 수십 및 수십 개의 파일을 동료 검토하고 통과 할 수 없다는 것입니다.

모든 단위 테스트를 통과하면 동료 검토가 훨씬 쉬워지고 검토의 상당 부분이 테스트가 의미가 있는지 확인하는 것입니다.

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