템플릿 혼란에서 클린 클래스 아키텍처(C++)로 전환하는 가장 좋은 방법은 무엇입니까?

StackOverflow https://stackoverflow.com/questions/324043

  •  11-07-2019
  •  | 
  •  

문제

전체 코드 줄이 200,000개가 넘는 약 100개의 템플릿을 포함하는 약 100개의 파일이 있는 대규모 템플릿 라이브러리를 가정합니다.일부 템플릿은 다중 상속을 사용하여 라이브러리 자체의 사용을 다소 단순하게 만듭니다(예:일부 기본 템플릿에서 상속되며 특정 비즈니스 규칙만 구현하면 됩니다.)

존재하는 모든 것(수년에 걸쳐 성장)은 "작동"하며 프로젝트에 사용됩니다.

그러나 해당 라이브러리를 사용하여 프로젝트를 컴파일하면 시간이 점점 더 많이 소모되고 특정 버그의 소스를 찾는 데 상당한 시간이 걸립니다.일부 상호 의존적인 템플릿을 변경해야 하기 때문에 수정은 종종 예상치 못한 부작용을 일으키거나 매우 어렵습니다.함수의 양이 너무 많아서 테스트가 거의 불가능합니다.

이제는 더 적은 템플릿과 더 전문화된 더 작은 클래스를 사용하도록 아키텍처를 단순화하고 싶습니다.

해당 작업을 수행하는 입증된 방법이 있습니까?어디에서 시작하는 것이 좋을까요?

도움이 되었습니까?

해결책

템플릿이 문제가되는 방법/이유를 알지 못하고 왜 비 모전 클래스가 개선되는지 확실하지 않습니다. 그것은 단지 심지어 의미가 아닙니다 클래스, 유형 안전성이 적고 버그에 대한 더 큰 잠재력?

다양한 클래스와 템플릿 간의 종속성을 단순화하고, 리팩토링 및 제거하는 것을 이해할 수 있지만, "템플릿이 적을수록 아키텍처가 더 좋을 것"이라고 자동으로 가정 할 수 있습니다.

나는 그 템플릿이라고 말하고 싶다 잠재적으로 당신이 얻는 것보다 훨씬 깨끗한 아키텍처를 만들 수 있습니다. 단순히 별도의 수업을 만들 수 있기 때문입니다 완전히 독립적인. 템플릿이 없으면 다른 클래스로 호출하는 클래스 기능은 클래스에 대해 알아야하거나 인터페이스가 미리 상속되는 인터페이스를 알아야합니다. 템플릿을 사용하면이 커플 링이 필요하지 않습니다.

템플릿을 제거하면 만 연결됩니다 의존성은 적습니다. 추가 된 유형의 템플릿은 컴파일 타임에 많은 버그를 감지하는 데 사용될 수 있습니다 (이 목적을 위해 STATIC_ASSERT의 코드를 자유롭게 뿌립니다).

물론 추가 된 컴파일 타임은 경우에 따라 템플릿을 피하는 데 유효한 이유 일 수 있으며, "전통적인"OOP 용어로 생각하는 데 익숙한 Java 프로그래머 만있는 경우 템플릿이 혼동 될 수 있습니다. 템플릿을 피하는 또 다른 유효한 이유가 되십시오.

그러나 아키텍처의 관점에서 볼 때 템플릿을 피하는 것이 잘못된 방향의 단계라고 생각합니다.

응용 프로그램을 리팩터링하십시오. 물론 필요한 것 같습니다. 그러나 앱의 원래 버전이 오용했기 때문에 확장 가능하고 강력한 코드를 생성하는 데 가장 유용한 도구 중 하나를 버리지 마십시오. 특히 이미 코드 금액에 관심이있는 경우 템플릿을 제거 할 가능성이 높습니다. 코드 줄.

다른 팁

당신은 당신의 서기가 코드를 리팩터링 할 수있는 것과 동일한 문제 (아마도 라이브러리의 사용을 단순화 할 것이라고 생각하기 때문에 더 많은 템플릿을 추가해야 함)와 같은 문제가있을 때 10 년 안에 자동 테스트가 필요하며 여전히 모든 테스트 사례를 충족한다는 것을 알고 있습니다. 마찬가지로 모든 사소한 버그 수정의 부작용은 즉시 눈에 띄게됩니다 (테스트 사례가 양호하다고 가정).

그 외에는 "나누기와 conqueor"

단위 테스트를 작성하십시오.

새 코드가 이전 코드와 동일하게 수행 해야하는 경우.

그것은 적어도 하나의 팁입니다.

편집하다:

새 기능으로 대체 한 이전 코드를 사용하지 않으면 새 코드로 조금씩 위상을 넘어갈 수 있습니다.

문제는 템플릿 사고 방식이 객체 지향 상속 기반 방식과 매우 다르다는 것입니다. "모든 것을 재 설계하고 처음부터 시작"이외의 다른 것에 대답하기는 어렵습니다.

물론 특정 사례에 대한 간단한 방법이있을 수 있습니다. 우리는 당신이 가진 것에 대해 더 많이 알지 못하고 말할 수 없습니다.

템플릿 솔루션이 유지하기가 너무 어렵다는 사실은 어쨌든 디자인이 열악하다는 표시입니다.

몇 가지 요점 (그러나 참고 : 이것들입니다 ~ 아니다 참으로 악. 비 테일 플레이트 코드로 변경하려면 도움이 될 수 있습니다.


정적 인터페이스를 조회하십시오. 템플릿은 어떤 기능이 존재하는지 어디에 의존합니까? 그들은 어디에서 typedefs가 필요합니까?

일반적인 부분을 추상적 인 기본 클래스에 넣으십시오. 좋은 예는 CRTP 관용구를 우연히 발견 할 때입니다. 가상 함수가있는 추상적 인 기본 클래스로 바꿀 수 있습니다.

조회 정수 목록. 코드를 찾으면 통합 목록을 사용합니다 list<1, 3, 3, 1, 3>, 당신은 그것들을 대체 할 수 있습니다 std::vector, 그들을 사용하는 모든 코드가 일정한 표현식 대신 런타임 값으로 작동하는 경우에도 살 수 있다면.

조회 유형 특성. 일부 typedef가 존재하는지 또는 일부 메소드가 일반적인 템플릿 코드에 존재하는지 확인하는 많은 코드가 있습니다. 초록베이스 클래스는 순수한 가상 방법을 사용하고 typedefs를 기본에 상속 함으로써이 두 가지 문제를 해결합니다. 종종 Typedef는 sfinae, 그러면 불필요 할 것입니다.

조회 표현식 템플릿. 코드가 표현식 템플릿을 사용하여 임시 템플릿을 사용하지 않으면 임시 템플릿을 제거하고 관련 운영자에게 임시를 반환 / 통과하는 전통적인 방법을 사용해야합니다.

조회 함수 객체. 코드가 함수 객체를 사용하는 경우 추상 기본 클래스를 사용하도록 변경하고 다음과 같은 것을 가질 수 있습니다. void run(); 전화 (또는 계속 사용하려는 경우 operator(), 더 나은! 가상 일 수도 있습니다).

내가 이해하는 바로는 빌드 시간과 라이브러리의 유지 관리 가능성에 가장 관심이 있으신가요?

첫째, 한꺼번에 모든 것을 "수정"하려고 하지 마십시오.

둘째, 무엇을 고칠지 이해하십시오.템플릿 복잡성은 종종 다음과 같은 이유 때문에 발생합니다.특정 사용을 강제하고 컴파일러가 실수하지 않도록 도와줍니다.그 이유는 때로는 멀리까지 갈 수도 있지만, "자신이 무엇을 하는지 아무도 모른다"는 이유로 100줄을 버리는 것을 가볍게 여겨서는 안 됩니다.여기서 제가 제안하는 모든 것은 정말 불쾌한 버그를 유발할 수 있으므로 경고를 받았습니다.

셋째, 더 저렴한 수정 사항을 먼저 고려하십시오.예를 들어더 빠른 머신 또는 분산된 빌드 도구.최소한 보드가 차지할 모든 RAM을 투입하고 오래된 디스크를 폐기하십시오.그것은 차이를 만들어냅니다.OS용 드라이브 하나, 빌드용 드라이브 하나는 저렴한 RAID입니다.

도서관에 문서화가 잘 되어 있나요?그것이 그것을 만들 수 있는 가장 좋은 기회입니다. 그러한 문서를 만드는 데 도움이 되는 doxygen과 같은 도구를 살펴보십시오.

모두 고려되었나요?좋습니다. 이제 빌드 시간에 대한 몇 가지 제안을 드립니다. ;)


C++ 이해 모델 구축:모든 .cpp는 개별적으로 컴파일됩니다.이는 헤더가 많은 .cpp 파일이 많다는 것을 의미합니다. = 거대한 빌드입니다.하지만 모든 것을 하나의 .cpp 파일에 넣으라는 조언은 아닙니다!그러나 빌드 속도를 엄청나게 높일 수 있는 한 가지 방법(!)은 여러 개의 .cpp 파일을 포함하는 단일 .cpp 파일을 만들고 해당 "마스터" 파일만 컴파일러에 공급하는 것입니다.하지만 맹목적으로 그렇게 할 수는 없습니다. 이로 인해 발생할 수 있는 오류 유형을 이해해야 합니다.

아직 가지고 있지 않다면, 별도의 빌드 머신을 확보하세요 원격으로 접속할 수 있습니다.일부 포함이 중단되었는지 확인하려면 거의 전체 빌드를 많이 수행해야 합니다.다른 작업을 방해하지 않는 다른 컴퓨터에서 이 작업을 실행하고 싶을 것입니다.장기적으로는 어쨌든 일일 통합 빌드에 필요할 것입니다. ;)

사용 미리 컴파일된 헤더.(빠른 기계를 사용하면 더 잘 확장됩니다. 위 참조)

헤더 포함 정책을 확인하세요..모든 파일은 "독립적"이어야 합니다(예:다른 사람이 포함해야 하는 모든 것을 포함하세요), 자유롭게 포함하지 마세요.불행히도 불필요한 #incldue 문을 찾는 도구를 아직 찾지 못했지만 "핫스팟" 파일에서 사용하지 않는 헤더를 제거하는 데 시간을 투자하는 것이 도움이 될 수 있습니다.

전방 선언 생성 및 사용 당신이 사용하는 템플릿에 대해.종종 여러 위치에서 전방 선언이 포함된 헤더를 포함하고 일부 특정 항목에서만 전체 헤더를 사용할 수 있습니다.이는 컴파일 시간에 큰 도움이 될 수 있습니다.을 체크 해봐 <iosfwd> 표준 라이브러리가 I/O 스트림에 대해 이를 수행하는 방법 헤더.

몇 가지 유형의 템플릿에 대한 오버로드:다음과 같은 극소수 유형에만 유용한 복잡한 함수 템플릿이 있는 경우:

// .h
template <typename FLOAT> // float or double only
FLOAT CalcIt(int len, FLOAT * values) { ... }

헤더에서 오버로드를 선언하고 템플릿을 본문으로 이동할 수 있습니다.

// .h
float CalcIt(int len, float * values);
double CalcIt(int len, double * values);

// .cpp
template <typename FLOAT> // float or double only
FLOAT CalcItT(int len, FLOAT * values) { ... }

float CalcIt(int len, float * values) { return CalcItT(len, values); }
double CalcIt(int len, double * values) { return CalcItT(len, values); }

이렇게 하면 긴 템플릿이 단일 컴파일 단위로 이동됩니다.
불행히도 이것은 수업에만 제한적으로 사용됩니다.

다음 사항을 확인하세요. 그만큼 PIMPL 관용구 헤더의 코드를 .cpp 파일로 이동할 수 있습니다.

그 뒤에 숨어 있는 일반적인 규칙은 다음과 같습니다. 라이브러리의 인터페이스를 구현과 분리하세요.댓글을 활용하고, detail 네임스페이스 및 별도 .impl.h 헤더를 사용하여 외부에 알려야 하는 내용을 정신적, 육체적으로 분리합니다.이는 라이브러리의 실제 가치를 노출시키고(실제로 복잡성을 캡슐화합니까?) "쉬운 대상"을 먼저 대체할 수 있는 기회를 제공합니다.


보다 구체적인 조언과 주어진 조언이 얼마나 유용한지는 실제 라이브러리에 따라 크게 달라집니다.

행운을 빌어요!

언급했듯이 단위 테스트는 좋은 생각입니다. 실제로, 파열 될 수있는 "간단한"변경 사항을 도입하여 코드를 깨뜨리지 않고 테스트 제품군을 만들고 테스트에 대한 비준수를 고치는 데 집중하십시오. 버그가 밝혀 질 때 테스트를 업데이트하는 활동이 있습니다.

그 외에도 가능한 경우 템플릿 관련 문제를 디버깅하는 데 도움이되는 도구 업그레이드를 제안합니다.

나는 종종 거대하고 인스턴스화하기 위해 많은 시간과 기억이 필요한 레거시 템플릿을 발견했지만 필요하지 않았습니다. 이 경우 지방을 잘라내는 가장 쉬운 방법은 템플릿 인수에 의존하지 않은 모든 코드를 가져 와서 일반 번역 장치에 정의 된 별도의 기능으로 숨기는 것이 었습니다. 이것은 또한이 코드를 약간 수정하거나 문서화가 변경되어야 할 때 재 컴파일이 적을 수있는 긍정적 인 부작용을 가졌습니다. 다소 분명하게 들리지만 사람들이 클래스 템플릿을 작성하는 빈도는 놀랍습니다. 템플릿 정보가 필요한 코드가 아니라 헤더에서 모든 것이 헤더에 정의되어야한다고 생각하는 것은 정말 놀랍습니다.

당신이 고려하고 싶은 또 다른 것은 여러 상속의 집계 대신 템플릿 "믹스 인"스타일을 만들어 상속 계층을 얼마나 자주 정리하는지입니다. 템플릿 인수 중 하나를 기본 클래스의 이름으로 만들어서 도망 칠 수있는 곳을 확인하십시오 (The Way boost::enable_shared_from_this 공장). 물론 이것은 일반적으로 생성자가 논증을 취하지 않는 경우에만 잘 작동합니다. 올바르게 초기화하는 것에 대해 걱정할 필요가 없습니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top