개발 중 서로 다른 유형의 동작간에 전환하기 위해 #ifdef 사용


28

개발 중에 #ifdef를 사용하여 다른 유형의 동작을 전환하는 것이 좋은 방법입니까? 예를 들어 기존 코드의 동작을 변경하고 싶습니다. 동작을 변경하는 방법에 대한 몇 가지 아이디어가 있으며 다른 구현을 전환하여 다른 방법을 테스트하고 비교해야합니다. 일반적으로 코드 변경은 복잡하며 다른 파일의 다른 메소드에 영향을 미칩니다.

나는 보통 몇 가지 식별자를 소개하고 그런 식으로 뭔가를한다.

void foo()
{
    doSomething1();
#ifdef APPROACH1
    foo_approach1();
#endif
    doSomething2();
#ifdef APPROACH2
    foo_approach2();
#endif
}

void bar()
{
    doSomething3();
#ifndef APPROACH3
    doSomething4();
#endif
    doSomething5();
#ifdef APPROACH2
    bar_approach2();
#endif
}

int main()
{
    foo();
    bar();
    return 0;
}

이를 통해 서로 다른 접근 방식 사이를 빠르게 전환하고 하나의 소스 코드 사본으로 모든 것을 수행 할 수 있습니다. 개발에 대한 좋은 접근 방법입니까 아니면 더 나은 방법이 있습니까?



2
개발에 대해 이야기하고 있기 때문에 다른 구현으로 전환하고 실험하기 위해 쉽게 할 수있는 모든 것을해야한다고 생각합니다. 이는 특정 문제를 해결하기위한 모범 사례가 아니라 개발 과정에서 개인적인 선호와 비슷합니다.
Emerson Cardoso

1
전략 패턴 또는 우수한 다형성을 사용하는 것이 좋습니다. 전환 가능한 동작에 대한 단일 플러그인 지점을 유지하는 데 도움이되기 때문입니다.
pmf

4
#ifdef블록이 꺼져 있으면 일부 IDE는 블록의 아무것도 평가하지 않습니다 . 일상적으로 모든 경로를 작성하지 않으면 코드가 쉽게 고칠 수 있고 컴파일되지 않는 경우가 발생했습니다.
Berin Loritsch

다른 질문에 대한 이 답변을 살펴보십시오 . #ifdefs덜 성가신 많은 방법을 제시합니다 .
user1118321

답변:


9

이 사용 사례에 버전 관리 분기를 사용하는 것이 좋습니다. 이를 통해 구현 간 차이점을 파악하고 각각에 대한 개별 히스토리를 유지할 수 있으며 결정을 내리고 버전 중 하나를 제거해야 할 경우 오류가 발생하기 쉬운 편집을 수행하는 대신 해당 분기를 버릴 수 있습니다.


git이런 종류의 일에 특히 능숙합니다. 어쩌면 svn, hg또는 다른 사람 의 경우는 많지 않지만 여전히 할 수 있습니다.
twalberg

저의 첫 생각이기도했습니다. "다른 무언가로 혼란스러워하고 싶습니까?" git branch!
Wes Toleman

42

망치를 쥐고있을 때 모든 것이 못처럼 보입니다. #ifdef프로그램에서 사용자 정의 동작을 얻는 일종의 수단으로 사용 하는 방법을 알고 있다면 유혹 입니다. 같은 실수를했기 때문에 알고 있습니다.

#ifdef플랫폼 별 값을 정의하는 데 이미 사용 된 MFC C ++로 작성된 레거시 프로그램을 상속했습니다 . 즉, 특정 매크로 값을 정의하거나 정의하지 않으면 32 비트 플랫폼 또는 64 비트 플랫폼에서 사용되도록 프로그램을 컴파일 할 수 있습니다.

그런 다음 클라이언트에 대한 사용자 지정 동작을 작성해야하는 문제가 발생했습니다. 브랜치를 만들고 클라이언트를 위해 별도의 코드 기반을 만들 수 있었지만 유지 관리가 어려웠습니다. 또한 시작시 프로그램에서 읽을 구성 값을 정의하고 이러한 값을 사용하여 동작을 판별 할 수 있었지만 각 클라이언트의 구성 파일에 적절한 구성 값을 추가하려면 사용자 정의 설정을 작성해야합니다.

나는 유혹을 받고 포기했습니다. #ifdef다양한 행동을 구별하기 위해 코드에 섹션을 썼습니다 . 실수하지 마십시오. 처음에는 최고였습니다. 클라이언트에 프로그램 버전을 재배포 할 수있는 매우 작은 동작 변경이 있었으며 코드 기반 버전이 둘 이상일 필요는 없습니다.

시간이 지남에 따라 프로그램이 더 이상 일관되게 작동하지 않기 때문에 어쨌든 유지 관리가 어려워졌습니다 . 프로그램의 버전을 테스트하려면 클라이언트가 누구인지 알아야했습니다. 코드는 하나 또는 두 개의 헤더 파일로 줄이려고 시도했지만 매우 어수선하고 제공 된 빠른 수정 접근 방식 #ifdef은 악성 암과 같은 프로그램 전체에 그러한 솔루션이 확산되었음을 의미했습니다.

그 이후로 내 교훈을 배웠으며, 당신도 그래야합니다. 반드시 필요한 경우 사용하고 플랫폼 변경에 엄격하게 사용하십시오. 프로그램과 클라이언트 간의 동작 차이에 접근하는 가장 좋은 방법은 시작시로드 된 구성 만 변경하는 것입니다. 프로그램은 일관된 상태로 유지되며 읽기와 디버깅이 더 쉬워집니다.


"디버그 인 경우 변수 x 정의 ..."와 같은 디버그 버전은 로깅과 같은 경우에 유용 할 수있는 것처럼 보이지만 디버그가 활성화되고 활성화되지 않은 경우 프로그램 작동 방식을 완전히 변경할 수 있습니다. .
whn

8
@snb 나는 그것에 대해 생각했다. 나는 여전히 구성 파일을 변경하고 더 자세하게 기록하는 것을 선호합니다. 그렇지 않으면 프로덕션 환경에서 프로그램에 문제가 발생하여 실행 파일을 완전히 바꾸지 않고는 디버깅 할 수 없습니다. 이상적인 상황에서도 바람직하지 않습니다. ;)
Neil

예, 디버깅을 위해 다시 컴파일하지 않아도되는 것이 더 이상적입니다.
whn

9
설명하는 내용의 극단적 인 예를 보려면이 기사의 "유지 관리 문제"하위 제목 아래 두 번째 단락에서 MS가 몇 년 전 C 런타임 대부분을 처음부터 다시 작성해야하는 시점에 도달 한 이유를 살펴보십시오. . blogs.msdn.microsoft.com/vcblog/2014/06/10/…
Dan Neely

2
@snb 대부분의 로깅 라이브러리는 로깅 수준 메커니즘을 가정합니다. 디버깅 중에 특정 정보를 기록하려면 기록 수준을 낮게 기록하십시오 (일반적으로 "Debug"또는 "Verbose"). 그런 다음 응용 프로그램에는 로깅 할 수준을 알려주 는 구성 매개 변수가 있습니다. 답은 여전히이 문제에 대한 구성입니다. 이것은 또한 클라이언트 환경 에서이 낮은 로깅 수준을 켤 수 있다는 큰 이점을 가지고 있습니다 .
jpmc26

21

일시적으로 현재 수행중인 작업에 아무런 문제가 없습니다 (예 : 체크인 전). 다른 기술 조합을 테스트하거나 코드 섹션을 무시하는 좋은 방법입니다 (문제 자체를 말하지만).

그러나 경고의 한마디 : #ifdef 브랜치를 유지하지 마십시오 . 내가 읽는 중 하나를 알아 내기 위해 4 가지 다른 방법으로 구현 된 동일한 것을 읽는 데 시간을 낭비하는 것보다 조금 더 실망 스럽습니다 .

#ifdef를 읽으려면 실제로 건너 뛰는 것을 기억해야하므로 노력이 필요합니다! 절대적으로 필요 이상으로 힘들게 만들지 마십시오.

#ifdef를 최대한 적게 사용하십시오. 일반적으로 개발 환경 내에서 디버그 / 릴리스 빌드와 같은 영구적 인 차이나 다른 아키텍처에 대해 이를 수행 할 수있는 방법이 있습니다 .

포함 된 라이브러리 버전에 의존하는 #ifdef 분할이 필요한 라이브러리 기능을 작성했습니다. 따라서 때로는 유일한 방법 일 수도 있고 가장 쉬운 방법 일 수도 있지만 유지 관리에 대해 화를 내야합니다.


1

이와 같은 #ifdef를 사용하면 코드를 읽기가 매우 어렵습니다.

따라서, #ifdef를 사용하지 마십시오.

ifdef를 사용하지 않는 이유는 다양 할 수 있습니다. 나에게는 이것으로 충분합니다.

void foo()
{
    doSomething1();
#ifdef APPROACH1
    foo_approach1();
#endif
    doSomething2();
#ifdef APPROACH2
    foo_approach2();
#endif
}

할 수있는 많은 일을 할 수 있습니다 :

void foo()
{
    doSomething1();
    doSomething2();
}

void foo()
{
    doSomething1();
    foo_approach1();
    doSomething2();
}

void foo()
{
    doSomething1();
    doSomething2();
    foo_approach2();
}

void foo()
{
    doSomething1();
    foo_approach1();
    doSomething2();
    foo_approach2();
}

어떤 접근 방식이 정의되어 있는지에 따라 다릅니다. 그것이 첫눈에 분명하지 않습니다.

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