"if"체인을 피하는 방법?


266

이 의사 코드가 있다고 가정합니다.

bool conditionA = executeStepA();
if (conditionA){
    bool conditionB = executeStepB();
    if (conditionB){
        bool conditionC = executeStepC();
        if (conditionC){
            ...
        }
    }
}

executeThisFunctionInAnyCase();

executeStepX이전의 성공한 경우에만 함수 를 실행해야합니다. 어쨌든 executeThisFunctionInAnyCase함수는 마지막에 호출되어야합니다. 나는 프로그래밍의 초보자이기 때문에 매우 기본적인 질문에 대해 유감스럽게 생각합니다 if. 코드 가독성을 희생시키면서 그러한 종류의 "피라미드 코드"를 생성하는 긴 체인 을 피할 수있는 방법이 있습니까 (예 : C / C ++) ?

executeThisFunctionInAnyCase함수 호출을 건너 뛸 수 있으면 코드가 다음과 같이 단순화 될 수 있음을 알고 있습니다.

bool conditionA = executeStepA();
if (!conditionA) return;
bool conditionB = executeStepB();
if (!conditionB) return;
bool conditionC = executeStepC();
if (!conditionC) return;

그러나 제약은 executeThisFunctionInAnyCase함수 호출입니다. 그 break진술을 어떤 식 으로든 사용할 수 있습니까?


254
@ FrédéricHamidi 잘못 틀렸다! 예외없이 프로그램 흐름을 유도하는 것이 좋다고 말하지 마십시오! 너무 많은 이유로 예외는이 목적에 적합하지 않습니다.
Piotr Zierhoffer 2016 년

26
@ Piotr, 나는 파이썬에 의해 망쳐졌습니다 (실제로 이것을 장려합니다). C ++에서 흐름 제어에 예외를 사용하지 않아야하지만 실제로 흐름 제어입니까? 반환하는 함수 false가 예외적 인 상황과 유사한 것으로 간주 될 수 없습니까?
Frédéric Hamidi

13
프로그램의 의미에 따라 다릅니다. false반환 꽤 정상이 될 수 있습니다.
dornhege 2016 년

28
귀하의 질문을 첫 번째 개정으로 롤백했습니다. 특정 수의 질문 (> 0)을받은 후에는 급격히 질문을 변경해서는 안됩니다. 그 순간까지 주어진 모든 답변이 무효화되고 혼란을 야기 할 수 있기 때문입니다. 대신 새로운 질문을여십시오.
구두

13
모든 "초보 프로그래머"가 이와 같은 디자인 질문을하기를 바랍니다.
Jezen Thomas

답변:


486

&&(논리 AND)를 사용할 수 있습니다 .

if (executeStepA() && executeStepB() && executeStepC()){
    ...
}
executeThisFunctionInAnyCase();

이것은 두 가지 요구 사항을 모두 만족시킵니다.

  • executeStep<X>()이전에 성공한 경우에만 평가해야합니다 ( 단락 평가 라고 함 ).
  • executeThisFunctionInAnyCase() 어떤 경우에도 실행됩니다

29
이것은 의미 론적으로 정확하고 (실제로 모든 조건이 충족되기를 원함) 매우 우수한 프로그래밍 기술 (단락 평가)입니다. 또한 함수를 생성하면 코드가 엉망이되는 복잡한 상황에서도 사용할 수 있습니다.
Sanchises 2016 년

58
@RobAu : 그러면 주니어 프로그래머는 최종 평가에 의존하는 코드를 보게되어 결국이 주제를 다시 배우게되어 결국 수석 프로그래머가되는 데 도움이 될 것입니다. 따라서 분명히 윈-윈 : 괜찮은 코드와 누군가가 그것을 읽음으로써 무언가를 배웠습니다.
x4u

24
정답입니다
표시 이름

61
@RobAu : 이것은 모호한 구문 트릭을 사용하는 해킹이 아니며 거의 모든 프로그래밍 언어에서 관용적으로 표준 관행에 이르기까지 관용적입니다.
BlueRaja-대니 Pflughoeft

38
이 솔루션은 조건이 실제로 간단한 함수 호출 인 경우에만 적용됩니다. 실제 코드에서 이러한 조건은 길이가 2-5 줄이 될 수 있으며 (그리고 그 자체는 많은 다른 &&것과의 조합 일 ||수 있음) if가독성을 떨어 뜨리지 않고 단일 명령문 으로 결합하려는 방법은 없습니다 . 그리고 이러한 조건을 외부 함수로 옮기는 것이 항상 쉬운 것은 아닙니다. 이전에 계산 된 많은 로컬 변수에 의존 할 수 있기 때문에 각 인수를 개별 인수로 전달하려고하면 끔찍한 혼란이 생길 ​​수 있습니다.
hamstergene 2016 년

358

추가 기능을 사용하여 두 번째 버전이 작동하도록하십시오.

void foo()
{
  bool conditionA = executeStepA();
  if (!conditionA) return;

  bool conditionB = executeStepB();
  if (!conditionB) return;

  bool conditionC = executeStepC();
  if (!conditionC) return;
}

void bar()
{
  foo();
  executeThisFunctionInAnyCase();
}

깊이 중첩 된 if (첫 번째 변형)를 사용하거나 "함수의 일부"를 벗어나려는 욕구는 일반적으로 추가 기능이 필요하다는 것을 의미합니다.


51
+1 마침내 누군가 제정신의 답변을 게시했습니다. 이것은 제 생각에 가장 정확하고 안전하며 읽기 쉬운 방법입니다.
Lundin

31
+1 이것은 "단일 책임 원칙"을 잘 보여줍니다. 기능 foo은 일련의 관련 조건 및 동작을 통해 작동합니다. 기능 bar은 의사 결정과 완전히 분리됩니다. 우리가 조건과 행동의 세부 사항을 본다면 foo여전히 너무 많은 일을하고 있는 것으로 판명 되었지만 지금은 이것이 좋은 해결책입니다.
GraniteRobert

13
단점은 C에 중첩 함수가 없기 때문에 변수를 사용하는 데 필요한 3 단계가 매개 변수 bar로 수동 전달되어야한다는 것 foo입니다. 그 경우이고 foo한 번만 호출되면 매우 재사용 할 수없는 단단히 결합 된 두 개의 함수를 정의하지 않기 위해 goto 버전을 사용하려고합니다.
hugomg 2016 년

7
C의 단락 구문을 모르지만 C #에서 foo ()는 다음과 같이 작성할 수 있습니다.if (!executeStepA() || !executeStepB() || !executeStepC()) return
Travis

6
@ user1598390 Goto는 항상 많은 설정 코드를 풀어야하는 시스템 프로그래밍에서 항상 사용됩니다.
Scotty Bauer

166

goto이 경우에는 구식 C 프로그래머가 사용 합니다. gotoLinux 스타일 가이드에서 실제로 권장하는 사용법 중 하나 는 중앙 집중식 함수 종료라고합니다.

int foo() {
    int result = /*some error code*/;
    if(!executeStepA()) goto cleanup;
    if(!executeStepB()) goto cleanup;
    if(!executeStepC()) goto cleanup;

    result = 0;
cleanup:
    executeThisFunctionInAnyCase();
    return result;
}

어떤 사람들 goto은 몸을 고리에 싸서 그것을 사용하여 사용 하는 문제를 해결 하지만 효과적으로 두 가지 접근 방식이 동일합니다. goto만하면 다른 정리해야하는 경우 접근 방법은 더 나은 executeStepA()성공적인되었다 :

int foo() {
    int result = /*some error code*/;
    if(!executeStepA()) goto cleanupPart;
    if(!executeStepB()) goto cleanup;
    if(!executeStepC()) goto cleanup;

    result = 0;
cleanup:
    innerCleanup();
cleanupPart:
    executeThisFunctionInAnyCase();
    return result;
}

루프 접근 방식을 사용하면이 경우 두 가지 레벨의 루프가 생깁니다.


108
+1. 많은 사람들이이를보고 goto즉시 "이것은 끔찍한 코드입니다"라고 생각하지만 올바른 용도로 사용됩니다.
Graeme Perrow

46
이 점을 제외하면 실제로 유지 관리 관점에서 볼 때 매우 지저분한 코드입니다. 특히 레이블이 여러 개인 경우 코드가 읽기 어려워집니다. 이것을 달성하는 더 우아한 방법이 있습니다 : 함수를 사용하십시오.
Lundin

29
-1 나는 goto이것을 보았고 이것이 끔찍한 코드라고 생각할 필요조차 없습니다. 나는 한 번 그런 유지해야했다, 그것은 매우 불쾌합니다. OP는 질문 끝에 합리적인 C 언어 대안을 제안하며 내 대답에 포함했습니다.
건배와 hth. -Alf

56
이 제한된 자체 포함 goto 사용에는 아무런 문제가 없습니다. 그러나 goto 게이트웨이 약물이며 조심하지 않으면 언젠가는 아무도 발로 스파게티를 먹지 않는다는 것을 깨닫게 될 것입니다. 그렇지 않으면 레이블이있는 악몽 버그 ... "
Tim Post

66
나는 아마도이 스타일로 수십만 줄의 매우 명확하고 유지하기 쉬운 코드를 작성했다. 이해해야 할 두 가지 중요한 사항은 (1) 훈련입니다! 모든 기능의 레이아웃에 대한 명확한 지침을 수립하고 극도의 필요에 따라 위반하는 것 (2) 우리가하고있는 일은 예외가없는 언어로 예외를 시뮬레이션 하는 것임을 이해해야 합니다 . throw보다 여러 가지면에서 악화 goto에 있기 때문에 throw도 분명하지 않다 그것을 로컬 컨텍스트에서 당신이 끝날거야! Goto 스타일 제어 흐름에 예외와 동일한 디자인 고려 사항을 사용하십시오.
Eric Lippert

131

이것은 일반적인 상황이며 여러 가지 일반적인 방법으로 처리 할 수 ​​있습니다. 정식 답변에 대한 나의 시도는 다음과 같습니다. 누락 된 부분이 있으면 의견을 말하고이 게시물을 최신 상태로 유지합니다.

이것은 화살표입니다

당신이 논의하는 것을 화살표 안티 패턴이라고 합니다. 중첩 된 if 체인이 오른쪽으로 점점 더 먼쪽으로 확장 된 다음 왼쪽으로 다시 확장되는 코드 블록을 형성하므로 코드 편집기 창의 오른쪽을 "가리키는"시각적 화살표를 형성하기 때문에 화살표라고합니다.

가드로 화살을 평평하게하다

화살표를 피하는 몇 가지 일반적인 방법은 여기에서 설명 합니다 . 가장 일반적인 방법은 코드가 예외 흐름을 먼저 처리 한 다음 기본 흐름을 처리 하는 가드 패턴 을 사용하는 것입니다.

if (ok)
{
    DoSomething();
}
else
{
    _log.Error("oops");
    return;
}

... 사용할 것 ....

if (!ok)
{
    _log.Error("oops");
    return;
} 
DoSomething(); //notice how this is already farther to the left than the example above

일련의 가드가있는 경우 모든 가드가 왼쪽으로 완전히 나타나고 if가 중첩되지 않기 때문에 코드가 상당히 평평 해집니다. 또한 논리 조건과 관련 오류를 시각적으로 연결하여 진행 상황을 훨씬 쉽게 알 수 있습니다.

화살:

ok = DoSomething1();
if (ok)
{
    ok = DoSomething2();
    if (ok)
    {
        ok = DoSomething3();
        if (!ok)
        {
            _log.Error("oops");  //Tip of the Arrow
            return;
        }
    }
    else
    {
       _log.Error("oops");
       return;
    }
}
else
{
    _log.Error("oops");
    return;
}

가드:

ok = DoSomething1();
if (!ok)
{
    _log.Error("oops");
    return;
} 
ok = DoSomething2();
if (!ok)
{
    _log.Error("oops");
    return;
} 
ok = DoSomething3();
if (!ok)
{
    _log.Error("oops");
    return;
} 
ok = DoSomething4();
if (!ok)
{
    _log.Error("oops");
    return;
} 

이것은 객관적이고 정량적으로 읽기 쉽습니다.

  1. 주어진 논리 블록의 {및} 문자는 서로 더 가깝습니다.
  2. 특정 선을 이해하는 데 필요한 정신적 맥락의 양이 더 적습니다.
  3. if 조건과 관련된 전체 로직이 한 페이지에있을 가능성이 더 큼
  4. 코더가 페이지 / 아이 트랙을 스크롤 할 필요성이 크게 줄어 듭니다.

끝에 공통 코드를 추가하는 방법

가드 패턴의 문제점은 "기회 복귀"또는 "기회 종료"에 의존한다는 것입니다. 즉, 각 함수마다 정확히 하나의 종료 점이 있어야하는 패턴을 깨뜨립니다. 두 가지 이유로 문제가 있습니다.

  1. 파스칼을 코딩하는 법을 배운 사람들은 하나의 기능 = 하나의 출구 지점이라는 것을 알게되었습니다.
  2. 어떤 주제에 관계없이 종료시 실행되는 코드 섹션을 제공하지 않습니다 .

아래에서는 언어 기능을 사용하거나 문제를 완전히 피함으로써이 제한을 해결하기위한 몇 가지 옵션을 제공했습니다.

옵션 1.이 작업을 수행 할 수 없습니다 : 사용 finally

불행히도 C ++ 개발자는이 작업을 수행 할 수 없습니다. 그러나 이것은 finally 키워드를 포함하는 언어에 대한 최고의 대답입니다. 이것이 정확히 목적이기 때문입니다.

try
{
    if (!ok)
    {
        _log.Error("oops");
        return;
    } 
    DoSomething(); //notice how this is already farther to the left than the example above
}
finally
{
    DoSomethingNoMatterWhat();
}

옵션 2 : 문제 방지 : 기능 재구성

코드를 두 가지 기능으로 나누어 문제를 피할 수 있습니다. 이 솔루션은 모든 언어에 대해 작업 할 수있는 이점이 있으며 또한 결함 발생률을 낮추고 자동화 된 단위 테스트의 특수성을 향상시키는 입증 된 방법 인 순환 복잡성 을 줄일 수 있습니다.

예를 들면 다음과 같습니다.

void OuterFunction()
{
    DoSomethingIfPossible();
    DoSomethingNoMatterWhat();
}

void DoSomethingIfPossible()
{
    if (!ok)
    {
        _log.Error("Oops");
        return;
    }
    DoSomething();
}

옵션 3. 언어 트릭 : 가짜 루프 사용

내가 보는 또 다른 일반적인 트릭은 다른 답변에서 볼 수 있듯이 while (true) 및 break를 사용하는 것입니다.

while(true)
{
     if (!ok) break;
     DoSomething();
     break;  //important
}
DoSomethingNoMatterWhat();

이것은을 사용하는 것보다 덜 정직하지만 goto논리 범위의 경계를 명확하게 표시하기 때문에 리팩토링 할 때 엉망이되는 경향이 적습니다. 라벨이나 goto문장 을 잘라 붙여 넣는 순진한 코더가 큰 문제를 일으킬 수 있습니다! (그리고 솔직히 패턴이 너무 일반적이기 때문에 의도를 명확하게 전달한다고 생각하기 때문에 전혀 "정직하지 않다".

이 옵션에는 다른 변형이 있습니다. 예를 들어, switch대신에 사용할 수 있습니다 while. break키워드가 있는 모든 언어 구성이 작동 할 것입니다.

옵션 4. 객체 수명주기 활용

다른 접근 방식은 객체 수명주기를 활용합니다. 컨텍스트 객체를 사용하여 매개 변수 (우리의 순진한 예가 의심스러운 것)를 수행하고 완료되면 처분하십시오.

class MyContext
{
   ~MyContext()
   {
        DoSomethingNoMatterWhat();
   }
}

void MainMethod()
{
    MyContext myContext;
    ok = DoSomething(myContext);
    if (!ok)
    {
        _log.Error("Oops");
        return;
    }
    ok = DoSomethingElse(myContext);
    if (!ok)
    {
        _log.Error("Oops");
        return;
    }
    ok = DoSomethingMore(myContext);
    if (!ok)
    {
        _log.Error("Oops");
    }

    //DoSomethingNoMatterWhat will be called when myContext goes out of scope
}

참고 : 선택한 언어의 객체 수명주기를 이해해야합니다. 이것이 작동하기 위해서는 일종의 결정 론적 가비지 콜렉션이 필요합니다. 즉, 소멸자가 언제 호출되는지 알아야합니다. 일부 언어에서는 Dispose소멸자 대신 사용해야 합니다.

옵션 4.1. 객체 수명주기 활용 (래퍼 패턴)

객체 지향 접근 방식을 사용하려는 경우 올바르게 수행 할 수도 있습니다. 이 옵션은 클래스를 사용하여 정리가 필요한 자원과 기타 작업을 "랩핑"합니다.

class MyWrapper 
{
   bool DoSomething() {...};
   bool DoSomethingElse() {...}


   void ~MyWapper()
   {
        DoSomethingNoMatterWhat();
   }
}

void MainMethod()
{
    bool ok = myWrapper.DoSomething();
    if (!ok)
        _log.Error("Oops");
        return;
    }
    ok = myWrapper.DoSomethingElse();
    if (!ok)
       _log.Error("Oops");
        return;
    }
}
//DoSomethingNoMatterWhat will be called when myWrapper is destroyed

다시 한 번, 객체 수명주기를 이해해야합니다.

옵션 5. 언어 트릭 : 단락 평가 사용

다른 기술은 단락 평가를 이용하는 것입니다 .

if (DoSomething1() && DoSomething2() && DoSomething3())
{
    DoSomething4();
}
DoSomethingNoMatterWhat();

이 솔루션은 && 연산자의 작동 방식을 활용합니다. &&의 왼쪽이 false로 평가되면 오른쪽은 평가되지 않습니다.

이 트릭은 소형 코드가 필요한 경우와 코드에서 유지 보수가 많이 이루어지지 않는 경우에 가장 유용합니다 (예 : 잘 알려진 알고리즘 구현). 보다 일반적인 코딩의 경우이 코드의 구조가 너무 취약합니다. 논리를 약간만 변경해도 전체 다시 쓰기가 트리거 될 수 있습니다.


12
드디어? C ++에는 finally 절이 없습니다. 소멸자가있는 객체는 finally 절이 필요하다고 생각되는 상황에서 사용됩니다.
Bill Door

1
위의 두 의견을 수용하도록 편집되었습니다.
John Wu

2
사소한 코드 (예 : 내 예에서)를 사용하면 중첩 패턴이 더 이해하기 쉽습니다. 실제 코드 (여러 페이지에 걸쳐있을 수 있음)를 사용하면 가드 패턴은 스크롤이 적고 시선 추적이 적기 때문에 객관적으로 읽기가 쉽습니다 (예 : {에서}까지의 평균 거리가 더 짧음).
존 우

1
더 이상 1920 x 1080 화면에서 코드가 보이지 않는 중첩 패턴을 보았습니다 ... 세 번째 조치가 실패하면 어떤 오류 처리 코드가 수행되는지 알아보십시오 ... (0) 대신 최종 중단이 필요하지 않습니다. 반면에 (true) {...}는 "continue"가 다시 시작되도록합니다.
gnasher729 2016 년

2
옵션 4는 실제로 C ++에서 메모리 누수입니다 (사소한 구문 오류 무시). 이런 경우에는 "신규"를 사용하지 않고 "MyContext myContext;"라고 말하면됩니다.
Sumudu Fernando

60

그냥 해

if( executeStepA() && executeStepB() && executeStepC() )
{
    // ...
}
executeThisFunctionInAnyCase();

그렇게 간단합니다.


각각 근본적 으로 질문을 변경 한 3 가지 수정 사항 (버전 1로 개정판을 세면 4 번)으로 응답하는 코드 예제를 포함시킵니다.

bool conditionA = executeStepA();
if (conditionA){
    bool conditionB = executeStepB();
    if (conditionB){
        bool conditionC = executeStepC();
        if (conditionC){
            ...
        }
    }
}

executeThisFunctionInAnyCase();

14
나는 질문의 첫 번째 버전에 대한 응답으로 (더 간결하게) 대답했으며 궤도의 Darkness Races에 의한 논평과 편집 후에 Bill the Lizard에 의해 삭제되기 전에 20 개의 공감대를 얻었습니다.
건배와 hth. -Alf

1
@ Cheersandhth-Alf : 모드가 삭제되었다고 믿을 수 없습니다. 너무 나쁘다. (+1)
상자성 크로와상

2
용기 내 +1! (3 번은 중요하다 : D).
haccks 2016 년

초보자 프로그래머는 여러 부울 "and"를 사용한 순서 실행, 다른 언어의 차이점 등을 배우는 것이 중요합니다.이 답변은 그에 대한 훌륭한 포인터입니다. 그러나 일반적인 상용 프로그래밍에서는 "시작하지 않는"것입니다. 간단하지 않고 유지 관리 할 수 ​​없으며 수정할 수 없습니다. 물론, 자신을 위해 빠른 "카우보이 코드"를 작성하는 경우이 작업을 수행하십시오. 그러나 "오늘날 실시 된 일상적인 소프트웨어 엔지니어링과는 관련이 없습니다." BTW 당신이 여기서 참 아야했던 우스운 "편집 혼동"에 대해 죄송합니다, @cheers :)
Fattie

1
@ JoeBlow : 나는 이것에 Alf와 함께 있습니다-나는 &&목록이 훨씬 명확하다는 것을 알았습니다. 나는 일반적으로 조건을 구분하여 줄을 분리 // explanation하고 각각에 후행 을 추가합니다 .... 결국, 코드를 살펴보면 코드가 훨씬 적습니다 &&. 제 생각에는 대부분의 전문 C ++ 프로그래머가 이것에 익숙 할 것이지만, 다른 산업 / 프로젝트에서 말하듯이 초점과 경험이 다릅니다.
Tony Delroy

35

실제로 C ++에는 액션을 연기하는 방법이 있습니다 : 객체의 소멸자를 사용하는 것.

C ++ 11에 액세스 할 수 있다고 가정합니다.

class Defer {
public:
    Defer(std::function<void()> f): f_(std::move(f)) {}
    ~Defer() { if (f_) { f_(); } }

    void cancel() { f_ = std::function<void()>(); }

private:
    Defer(Defer const&) = delete;
    Defer& operator=(Defer const&) = delete;

    std::function<void()> f_;
}; // class Defer

그런 다음 해당 유틸리티를 사용하십시오.

int foo() {
    Defer const defer{&executeThisFunctionInAnyCase}; // or a lambda

    // ...

    if (!executeA()) { return 1; }

    // ...

    if (!executeB()) { return 2; }

    // ...

    if (!executeC()) { return 3; }

    // ...

    return 4;
} // foo

67
이것은 나에게 완전한 난독 화입니다. 실제로 많은 C ++ 프로그래머가 가능한 많은 언어 기능을 던져서 문제를 해결하기를 원하는 이유를 이해하지 못합니다. 이제 모든 이국적인 언어 기능을 사용하는 데 관심이 있습니다. 그리고 거기서부터 메타 코드를 작성하고 메타 코드를 유지 관리 한 다음 메타 코드를 처리하기위한 메타 코드를 작성하여 며칠 및 몇 주 동안 바쁘게 지낼 수 있습니다.
Lundin 2016 년

24
@ 룬딘 : 글쎄, 나는 조기 계속 / 중단 / 반환이 도입되거나 예외가 발생하자마자 깨지기 쉬운 취성 코드에 어떻게 만족할 수 있는지 이해하지 못한다. 다른 한편으로,이 솔루션은 향후 유지 관리에있어 탄력적이며 C ++의 가장 중요한 기능 중 하나 인 풀기 중에 소멸자가 실행된다는 사실에만 의존합니다. 이 자격을 갖춘 이국적인 가 힘을 모든 표준 컨테이너 즐겁게한다는 기본 원칙이있는 동안은, 적어도 말을합니다.
Matthieu M.

7
@Lundin : Matthieu 코드의 장점은 예외가 발생 executeThisFunctionInAnyCase();하더라도 실행 된다는 것 foo();입니다. 예외 안전 코드를 작성할 때는 이러한 모든 정리 함수를 소멸자에 배치하는 것이 좋습니다.
Brian

6
@Brian 그러면에서 예외를 throw하지 마십시오 foo(). 그리고 만약 그렇다면, 잡아라. 문제 해결됨. 해결 방법을 쓰지 않고 버그를 수정하여 버그를 수정하십시오.
Lundin

18
@Lundin :이 Defer클래스는 재사용 가능한 작은 코드 조각으로, 예외없이 안전한 방식으로 블록 끝 정리를 수행 할 수 있습니다. 보다 일반적으로 스코프 가드라고 합니다. 예 범위 가드의 사용은 어떤처럼, 다른 많은 수동 방식으로 표현 될 수 for루프 블록과 같이 표현 될 수있다 while차례로으로 표현 될 수 루프, if그리고 goto, 당신이 원하는 경우 어셈블리 언어로 표현 될 수있다, 또는 특별한 짧은 그런트와 성가의 나비 효과에 의해 지시되는 우주 광선을 통해 메모리의 비트를 변경함으로써 진정한 마스터 인 사람들을 위해. 그러나 왜 그렇게 하는가.
건배와 hth. -Alf

34

return 문 (Itjax가 처방 한 방법)과 함께 추가 래퍼 함수가 필요없는 멋진 기술이 있습니다. 그것은 DO를 사용하게 while(0)의사 루프를. 이는 while (0)실제로 루프가 아니라 한 번만 실행되도록합니다. 그러나 루프 구문에서는 break 문을 사용할 수 있습니다.

void foo()
{
  // ...
  do {
      if (!executeStepA())
          break;
      if (!executeStepB())
          break;
      if (!executeStepC())
          break;
  }
  while (0);
  // ...
}

11
여러 개의 리턴을 가진 함수를 사용하는 것은 비슷하지만 더 읽기 쉽습니다.
Lundin

4
예, 확실히 더 읽기 쉽습니다. 그러나 효율성 관점에서 do {} while (0) 구문을 사용하면 추가 함수 호출 (매개 변수 전달 및 반환) 오버 헤드를 피할 수 있습니다.
Debasis

5
당신은 여전히 ​​기능을 자유롭게 할 수 있습니다 inline. 어쨌든, 이것은이 문제 이상으로 도움이되기 때문에 알아두면 좋은 기술입니다.
Ruslan

2
@Lundin 코드 지역을 고려해야합니다. 코드를 너무 많은 곳에 퍼 뜨리면 문제가 있습니다.
API-Beast

3
내 경험상 이것은 매우 특이한 관용구입니다. 도대체 무슨 일이 일어나고 있는지 알아내는 데 시간이 걸리며 코드를 검토 할 때 나쁜 징조입니다. 다른 더 일반적이고 읽기 쉬운 접근 방식에 비해 이점이없는 것으로 보이므로 사인온 할 수 없었습니다.
코디 그레이

19

당신은 또한 이것을 할 수 있습니다 :

bool isOk = true;
std::vector<bool (*)(void)> funcs; //vector of function ptr

funcs.push_back(&executeStepA);
funcs.push_back(&executeStepB);
funcs.push_back(&executeStepC);
//...

//this will stop at the first false return
for (auto it = funcs.begin(); it != funcs.end() && isOk; ++it) 
    isOk = (*it)();
if (isOk)
 //doSomeStuff
executeThisFunctionInAnyCase();

이렇게하면 최소 선형 성장 크기, 통화 당 +1 회선이 있으며 쉽게 유지 관리 할 수 ​​있습니다.


편집 : (@Unda에게 감사) 가시성이 느슨하기 때문에 큰 팬이 아닙니다.

bool isOk = true;
auto funcs { //using c++11 initializer_list
    &executeStepA,
    &executeStepB,
    &executeStepC
};

for (auto it = funcs.begin(); it != funcs.end() && isOk; ++it) 
    isOk = (*it)();
if (isOk)
 //doSomeStuff
executeThisFunctionInAnyCase();

1
push_back () 내부의 우발적 인 함수 호출에 관한 것이지만 어쨌든
Quentin

4
왜 이것이 공감대를 얻었는지 알고 싶습니다. 실행 단계가 실제로 대칭 적이라고 가정하면 깨끗하고 유지 관리가 가능합니다.
ClickRick

1
이것은 분명히 더 깨끗해 보일 수 있습니다. 사람들에게는 이해하기 어려울 수 있으며 컴파일러에서는 이해하기가 더 어렵습니다!
Roy T.

1
함수를 데이터로 취급하는 것이 좋은 생각 인 경우가 많습니다. 일단 완료하면 후속 리팩토링도 볼 수 있습니다. 처리하는 각 부분이 함수 참조가 아닌 객체 참조 인 것이 더 좋습니다. 이렇게하면 코드를 더욱 향상시킬 수 있습니다.
Bill K

3
약간 사소한 사건에 대한 과잉 설계,하지만이 기술은 확실히 멋진 기능 다른 사람이하지 않는있다 : 당신은 실행 순서 및 런타임에 호출 된 함수 [수]를 변경할 수 있으며, 그 :) 좋은
Xocoatzin

18

이게 효과가 있을까요? 나는 이것이 당신의 코드와 동일하다고 생각합니다.

bool condition = true; // using only one boolean variable
if (condition) condition = executeStepA();
if (condition) condition = executeStepB();
if (condition) condition = executeStepC();
...
executeThisFunctionInAnyCase();

3
나는 보통 ok이와 같은 변수를 사용할 때 변수를 호출합니다 .
Macke

1
나는 왜 downvotes를 알고 있는지에 관심이 있습니다. 무슨 일이야?
ABCplus

1
순환 복잡성에 대한 단락 접근 방식과 응답을 비교합니다.
AlphaGoku

14

원하는 코드가 현재 표시된 것으로 가정합니다.

bool conditionA = executeStepA();
if (conditionA){
    bool conditionB = executeStepB();
    if (conditionB){
        bool conditionC = executeStepC();
        if (conditionC){
            ...
        }
    }
}    
executeThisFunctionInAnyCase();

올바른 접근 방식은 읽기가 가장 쉽고 유지 관리가 가장 쉽다는 점에서 들여 쓰기 수준이 적을 것입니다. 이는 현재 질문의 목적입니다.

// Pre-declare the variables for the conditions
bool conditionA = false;
bool conditionB = false;
bool conditionC = false;

// Execute each step only if the pre-conditions are met
conditionA = executeStepA();
if (conditionA)
    conditionB = executeStepB();
if (conditionB)
    conditionC = executeStepC();
if (conditionC) {
    ...
}

// Unconditionally execute the 'cleanup' part.
executeThisFunctionInAnyCase();

이를 통해 s, 예외, 더미 루프 또는 기타 어려운 구조 가 필요 하지 않으며 간단한 작업을 수행 할 수 있습니다.gotowhile


루프를 사용할 때 추가 "플래그"변수를 도입 할 필요없이 일반적으로 루프를 사용 return하거나 break루프에서 뛰어 넘을 수 있습니다. 이 경우 goto를 사용하는 것도 비슷합니다. 변경 가능한 변수 복잡성을 위해 추가 goto 복잡성을 거래하고 있음을 기억하십시오.
hugomg

2
@hugomg 변수는 원래 질문에있었습니다. 여기에는 추가적인 복잡성 이 없습니다 . 질문에 대한 가정이 있었으며 (예 : 변수가 개정 된 코드에 필요함) 유지되었습니다. 그것들이 필요하지 않으면 코드를 단순화 할 수 있지만 질문의 불완전한 성격을 감안할 때 유효하게 만들 수있는 다른 가정은 없습니다.
ClickRick

매우 유용한 접근 방식, 예를 들어. 자백자가 사용하기 newbie때문에 단점이없는보다 깨끗한 솔루션을 제공합니다. 나는 또한 steps동일한 서명을 가지고 있거나 블록이 아닌 기능 에 의존하지 않는다는 것을 알고 있습니다. 더 정교한 접근 방식이 유효한 경우에도 이것을 첫 번째 패스 리 팩터로 사용하는 것을 볼 수 있습니다.
Keith

12

어떤 식 으로든 진술을 사용할 수 있습니까?

아마도 가장 좋은 해결책은 아니지만 문을 do .. while (0)루프 에 넣고 break대신 문을 사용할 수 있습니다 return.


2
내가 그것을 공감 한 사람은 아니지만, 이것은 효과가 현재 원하는 것이지만 필연적으로 고통을 초래할 수있는 무언가에 대한 루핑 구조의 남용 일 것입니다. 다른 프로젝트로 옮긴 후 2 년 안에 유지 보수해야하는 다음 개발자에게 적합합니다.
ClickRick

3
do .. while (0)매크로 정의에 사용 하는 @ClickRick 도 루프를 남용하지만 괜찮은 것으로 간주됩니다.
ouah

1
아마도, 그러나 그것을 달성하는 더 깨끗한 방법이 있습니다.
ClickRick

4
@ClickRick 내 대답의 유일한 목적은 대답하는 것입니다. 문장을 어떤 식 으로든 사용할 수 있으며 대답은 예입니다. 내 대답의 첫 번째 단어는 이것이 사용하는 솔루션이 아닐 수도 있음을 나타냅니다.
ouah

2
이 답변은 단지 의견이어야합니다
msmucker0527

12

원하는 if조건으로 모든 조건을 자신의 함수에 넣을 수 있으며, 반환시 executeThisFunctionInAnyCase()함수를 실행합니다 .

OP의 기본 예제에서 조건 테스트와 실행을 분리 할 수 ​​있습니다.

void InitialSteps()
{
  bool conditionA = executeStepA();
  if (!conditionA)
    return;
  bool conditionB = executeStepB();
  if (!conditionB)
    return;
  bool conditionC = executeStepC();
  if (!conditionC)
    return;
}

그런 다음 그렇게 불렀습니다.

InitialSteps();
executeThisFunctionInAnyCase();

C ++ 11 람다를 사용할 수 있으면 (OP에 C ++ 11 태그가 없지만 여전히 옵션 일 수 있음) 별도의 함수를 포기하고 람다로 묶을 수 있습니다.

// Capture by reference (variable access may be required)
auto initialSteps = [&]() {
  // any additional code
  bool conditionA = executeStepA();
  if (!conditionA)
    return;
  // any additional code
  bool conditionB = executeStepB();
  if (!conditionB)
    return;
  // any additional code
  bool conditionC = executeStepC();
  if (!conditionC)
    return;
};

initialSteps();
executeThisFunctionInAnyCase();

10

루프 를 싫어 goto하고 싫어 do { } while (0);하고 C ++을 사용하고 싶다면 임시 람다를 사용하여 동일한 효과를 얻을 수도 있습니다.

[&]() { // create a capture all lambda
  if (!executeStepA()) { return; }
  if (!executeStepB()) { return; }
  if (!executeStepC()) { return; }
}(); // and immediately call it

executeThisFunctionInAnyCase();

1
if 고토 당신이 싫어하는 && 당신이 (0) 동안} {할 싫어하는 && 당신이 C처럼 ++ ... 죄송합니다, 저항 할 수 있지만, 마지막 조건이 실패하는 문제는 태그가 있기 때문에 C 뿐만 아니라 C ++
ClickRick

@ClickRick 모두를 기쁘게하는 것은 항상 어렵습니다. 에서 의견을 C / C ++ 당신이 그 중 하나는 다른의 사용에 일반적으로 코드가 찌푸리게 같은 것은 존재하지 않는다.
Alex

9

코드에서 IF / ELSE 체인은 언어 문제가 아니라 프로그램 디자인입니다. 프로그램을 리팩터링하거나 다시 작성할 수 있다면 Design Patterns ( http://sourcemaking.com/design_patterns)를 참조하십시오. 수 있다면 더 나은 솔루션을 찾기 위해 )을 .

일반적으로 코드에 IF가 많으면 전략 디자인 패턴 ( http://sourcemaking.com/design_patterns/strategy/c-sharp-dot-net) 을 구현할 수 있습니다 . ) 또는 조합 수 있습니다. 다른 패턴의.

if / else의 긴 목록을 작성하는 대안이 있다고 확신하지만 체인이 당신에게 아름답게 보일 것 외에는 아무것도 변경하지 않을 것입니다 (그러나 보는 사람의 눈에 아름다움은 여전히 ​​코드에 적용됩니다) 너무 :-))). (새 조건이 있고 6 개월 동안이 코드에 대해 아무것도 기억하지 못하면 쉽게 추가 할 수 있습니까? 또는 체인이 변경되면 어떻게 빠르고 오류가 없는지와 같은 것에 대해 걱정해야합니다. 나는 그것을 구현할 것인가?


9

당신은 그냥 이렇게 ..

coverConditions();
executeThisFunctionInAnyCase();

function coverConditions()
 {
 bool conditionA = executeStepA();
 if (!conditionA) return;
 bool conditionB = executeStepB();
 if (!conditionB) return;
 bool conditionC = executeStepC();
 if (!conditionC) return;
 }

99의 100 번, 이것이 유일한 방법입니다.

컴퓨터 코드에서 무언가 까다로운 것을 시도하지 마십시오.


그건 그렇고, 나는 다음이 확실하다고 확신합니다. 당신이 염두에두고 실제 솔루션 ...

계속 문 알고리즘 프로그래밍에서 매우 중요하다. (goto 문은 알고리즘 프로그래밍에서 중요합니다.)

많은 프로그래밍 언어에서 다음을 수행 할 수 있습니다.

-(void)_testKode
    {
    NSLog(@"code a");
    NSLog(@"code b");
    NSLog(@"code c\n");
    
    int x = 69;
    
    {
    
    if ( x == 13 )
        {
        NSLog(@"code d---\n");
        continue;
        }
    
    if ( x == 69 )
        {
        NSLog(@"code e---\n");
        continue;
        }
    
    if ( x == 13 )
        {
        NSLog(@"code f---\n");
        continue;
        }
    
    }
    
    NSLog(@"code g");
    }

(먼저 참고 :이 예제와 같은 나체 블록은 특히 "알고리즘"프로그래밍을 다루는 경우 아름다운 코드를 작성하는 데 중요하고 중요한 부분입니다.)

다시 말하지만, 그것은 당신이 정확히 머리 속에 가지고 있었던 것입니다. 그리고 그것은 그것을 작성하는 아름다운 방법이므로, 당신은 좋은 본능을 가지고 있습니다.

그러나 현재 버전의 objective-c (비록-Swift에 대해 모른다, 죄송합니다)에는 비극적으로 둘러싼 블록이 루프인지 확인하는 risible 기능이 있습니다.

여기에 이미지 설명을 입력하십시오

그 문제를 해결하는 방법은 다음과 같습니다.

-(void)_testKode
    {
    NSLog(@"code a");
    NSLog(@"code b");
    NSLog(@"code c\n");
    
    int x = 69;
    
    do{
    
    if ( x == 13 )
        {
        NSLog(@"code d---\n");
        continue;
        }
    
    if ( x == 69 )
        {
        NSLog(@"code e---\n");
        continue;
        }
    
    if ( x == 13 )
        {
        NSLog(@"code f---\n");
        continue;
        }
    
    }while(false);
    
    NSLog(@"code g");
    }

잊지 마세요 ..

{} while (false);

"이 블록을 한 번만 수행"을 의미합니다.

즉, 글쓰기 do{}while(false);와 글쓰기 사이에는 전혀 차이가 없습니다 {}.

이것은 이제 원하는대로 완벽하게 작동합니다 ... 출력은 여기 있습니다 ...

여기에 이미지 설명을 입력하십시오

그래서 그것은 당신이 당신의 머리에 알고리즘을 보는 방법 일 수 있습니다. 당신은 항상 당신의 머리 속에있는 것을 쓰려고 노력해야합니다. (특히 술을 마시지 않으면 예쁘게 나오기 때문입니다! :))

이런 일이 많이 발생하는 "알고리즘"프로젝트에서 objective-c에는 항상 다음과 같은 매크로가 있습니다.

#define RUNONCE while(false)

... 그래서 당신은 이것을 할 수 있습니다 ...

-(void)_testKode
    {
    NSLog(@"code a");
    int x = 69;
    
    do{
    if ( x == 13 )
        {
        NSLog(@"code d---\n");
        continue;
        }
    if ( x == 69 )
        {
        NSLog(@"code e---\n");
        continue;
        }
    if ( x == 13 )
        {
        NSLog(@"code f---\n");
        continue;
        }
    }RUNONCE
    
    NSLog(@"code g");
    }

두 가지 점이 있습니다.

비록 objective-c가 continue 문이있는 블록의 유형을 확인하는 것은 어리석은 일이지만 "그와 싸울"수없는 문제입니다. 따라서 어려운 결정입니다.

b, 예에서 그 블록을 들여 써야 할 질문이 있습니까? 그런 질문으로 잠을 잃었으므로 조언을 할 수 없습니다.

도움이 되길 바랍니다.


당신은 두 번째로 가장 투표 된 답변을 놓쳤습니다 .
Palec

현상금을 더 많이 받기 위해 현상금을 넣었습니까? :)
TMS

에 모든 주석을 넣는 대신 if보다 설명적인 함수 이름을 사용하고 주석을 함수에 넣을 수도 있습니다.
Thomas Ahle

왝. 나는이 혼란에 대해 단락 평가 (20 년 이상 언어로 알려져 있으며 잘 알려진)를 간결하게 읽는 1 라인 솔루션을 취할 것입니다. 우리 둘 다 서로 협력하지 않는 것이 행복하다고 생각할 수 있습니다.
M2tM

8

false를 리턴하는 대신 실패하면 execute 함수가 예외를 처리하도록하십시오. 그러면 호출 코드는 다음과 같습니다.

try {
    executeStepA();
    executeStepB();
    executeStepC();
}
catch (...)

물론 원래 예제에서 실행 단계가 단계 내에서 오류가 발생하는 경우에만 false를 반환한다고 가정합니다.


3
흐름을 제어하기 위해 예외를 사용하는 것은 종종 나쁜 습관과 냄새 나는 코드로 간주됩니다
user902383

8

많은 좋은 답변이 이미 있지만 대부분은 유연성 중 일부 (아주 거의 없음)와 상충되는 것으로 보입니다. 이 절충이 필요없는 일반적인 접근 방식은 상태 / 계속 변수를 추가하는 입니다. 물론 가격은 다음을 추적하기위한 하나의 추가 가치입니다.

bool ok = true;
bool conditionA = executeStepA();
// ... possibly edit conditionA, or just ok &= executeStepA();
ok &= conditionA;

if (ok) {
    bool conditionB = executeStepB();
    // ... possibly do more stuff
    ok &= conditionB;
}
if (ok) {
    bool conditionC = executeStepC();
    ok &= conditionC;
}
if (ok && additionalCondition) {
    // ...
}

executeThisFunctionInAnyCase();
// can now also:
return ok;

ok &= conditionX;간단하지 ok = conditionX;않습니까?
ABCplus

많은 경우에, 당신은 그렇게 할 수 있습니다. 이것은 개념적 데모입니다. 작업 코드에서 우리는 가능한 한 단순화하려고 노력했습니다
blgt

+1 질문에 명기 된대로 c 에서 작동하는 몇 가지 깨끗하고 유지 보수 가능한 답변 중 하나
ClickRick

6

C ++ (질문에는 C와 C ++로 태그 지정됨)에서 예외를 사용하도록 함수를 변경할 수없는 경우와 같은 작은 도우미 함수를 작성하면 여전히 예외 메커니즘을 사용할 수 있습니다

struct function_failed {};
void attempt(bool retval)
{
  if (!retval)
    throw function_failed(); // or a more specific exception class
}

그런 다음 코드는 다음과 같이 읽을 수 있습니다.

try
{
  attempt(executeStepA());
  attempt(executeStepB());
  attempt(executeStepC());
}
catch (function_failed)
{
  // -- this block intentionally left empty --
}

executeThisFunctionInAnyCase();

멋진 구문을 사용하는 경우 명시 적 캐스트를 통해 작동하도록 만들 수 있습니다.

struct function_failed {};
struct attempt
{
  attempt(bool retval)
  {
    if (!retval)
      throw function_failed();
  }
};

그런 다음 코드를 다음과 같이 작성할 수 있습니다

try
{
  (attempt) executeStepA();
  (attempt) executeStepB();
  (attempt) executeStepC();
}
catch (function_failed)
{
  // -- this block intentionally left empty --
}

executeThisFunctionInAnyCase();

예외에 대한 값 확인을 리팩토링하는 것이 반드시 좋은 방법은 아니지만 오버 헤드 해제 예외가 상당히 많습니다.
존 우

4
-1 C ++에서 정상적인 흐름에 예외를 사용하는 것은 프로그래밍 연습이 좋지 않습니다. C ++에서는 예외적 인 상황을 위해 예외를 예약해야합니다.
Jack Aidley

1
질문 텍스트 (나에 의해 강조)에서 : "함수 이전 단계가 성공할 경우에만 executeStepX 함수를 실행해야합니다 . "즉, 반환 값은 실패를 나타내는 데 사용됩니다. 즉, 이것은 오류 처리입니다 (그리고 실패 예외적 이기를 바랍니다 ). 오류 처리는 정확히 예외가 고안된 것입니다.
celtschk 2016 년

1
아니. 먼저, 오류 처리가 아니라 오류 전파 를 허용하는 예외가 작성되었습니다 . 둘째, "이전 함수가 성공한 경우에만 함수 executeStepX를 실행해야합니다." 이전 함수에 의해 반환 된 부울 거짓이 명백히 예외적이거나 잘못된 경우를 나타내는 것은 아닙니다. 따라서 귀하의 진술은 안전하지 않습니다 . 오류 처리 및 흐름 살균은 여러 가지 다른 방법으로 구현할 수 있으며 예외는 오류 전파위치 외부 오류 처리를 허용하는 도구이며 예외가 아닙니다.

6

코드가 예제처럼 단순하고 언어가 단락 평가를 지원하는 경우 다음을 시도해보십시오.

StepA() && StepB() && StepC() && StepD();
DoAlways();

함수에 인수를 전달하고 코드를 이전 방식으로 작성할 수 없도록 다른 결과를 가져 오는 경우 다른 많은 답변이 문제에 더 적합합니다.


실제로 주제를 더 잘 설명하기 위해 내 질문을 편집했지만 대부분의 답변을 무효화하지는 않았습니다. : \
ABCplus

나는 SO의 새로운 사용자이며 초보자 프로그래머입니다. 그러면 2 가지 질문이 있습니다.이 질문으로 인해 다른 질문이 중복되어 표시 될 위험이 있습니까? 또 다른 요점은 초보자 SO 사용자 / 프로그래머가 어떻게 모든 것 중에서 가장 좋은 대답을 선택할 수 있습니까?
ABCplus

6

C ++ 11 이상의 경우 D의 범위 (종료) 메커니즘 과 유사한 범위 종료 시스템 을 구현하는 것이 좋습니다 .

이를 구현하는 한 가지 가능한 방법은 C ++ 11 람다와 일부 도우미 매크로를 사용하는 것입니다.

template<typename F> struct ScopeExit 
{
    ScopeExit(F f) : fn(f) { }
    ~ScopeExit() 
    { 
         fn();
    }

    F fn;
};

template<typename F> ScopeExit<F> MakeScopeExit(F f) { return ScopeExit<F>(f); };

#define STR_APPEND2_HELPER(x, y) x##y
#define STR_APPEND2(x, y) STR_APPEND2_HELPER(x, y)

#define SCOPE_EXIT(code)\
    auto STR_APPEND2(scope_exit_, __LINE__) = MakeScopeExit([&](){ code })

이를 통해 함수에서 일찍 돌아와서 범위 종료시 정의한 정리 코드가 항상 실행되도록 할 수 있습니다.

SCOPE_EXIT(
    delete pointerA;
    delete pointerB;
    close(fileC); );

if (!executeStepA())
    return;

if (!executeStepB())
    return;

if (!executeStepC())
    return;

매크로는 실제로 장식 일뿐입니다. MakeScopeExit()직접 사용할 수 있습니다.


매크로가이 작업을 수행 할 필요가 없습니다. 그리고 [=]일반적으로 범위가있는 람다에는 잘못되었습니다.
Yakk-Adam Nevraumont

예, 매크로는 단지 장식용이며 버릴 수 있습니다. 그러나 가치에 의한 포착이 가장 안전한 "일반적인"접근 방식이라고 말하지 않습니까?
glampert

1
아니오 : 람다는 람다가 생성 된 현재 범위를 넘어 지속되지 않는 경우 사용하십시오 [&]: 안전하고 최소한의 놀라움입니다. 선언 시점에서 람다 (또는 사본)가 스코프보다 더 오래 생존 할 수있을 때만 가치로 포착합니다.
Yakk-Adam Nevraumont

그렇습니다. 변경하겠습니다. 감사!
glampert

6

아무도 가장 간단한 솔루션을 제공하지 않는 이유는 무엇입니까? :디

모든 함수의 서명이 동일하면 다음과 같이 할 수 있습니다 (C 언어의 경우).

bool (*step[])() = {
    &executeStepA,
    &executeStepB,
    &executeStepC,
    ... 
};

for (int i = 0; i < numberOfSteps; i++) {
    bool condition = step[i]();

    if (!condition) {
        break;
    }
}

executeThisFunctionInAnyCase();

깨끗한 C ++ 솔루션의 경우, execute 메소드 를 포함하고 단계를 오브젝트로 랩핑 하는 인터페이스 클래스를 작성해야 합니다.
그런 다음 위의 솔루션은 다음과 같습니다.

Step *steps[] = {
    stepA,
    stepB,
    stepC,
    ... 
};

for (int i = 0; i < numberOfSteps; i++) {
    Step *step = steps[i];

    if (!step->execute()) {
        break;
    }
}

executeThisFunctionInAnyCase();

5

개별 조건 변수가 필요하지 않다고 가정하고 테스트를 반전하고 else-falthrough를 "ok"경로로 사용하면보다 수직적 인 if / else 문 집합을 얻을 수 있습니다.

bool failed = false;

// keep going if we don't fail
if (failed = !executeStepA())      {}
else if (failed = !executeStepB()) {}
else if (failed = !executeStepC()) {}
else if (failed = !executeStepD()) {}

runThisFunctionInAnyCase();

실패한 변수를 생략하면 코드가 IMO를 너무 모호하게 만듭니다.

내부 변수를 선언해도 괜찮습니다. = vs ==에 대해 걱정할 필요가 없습니다.

// keep going if we don't fail
if (bool failA = !executeStepA())      {}
else if (bool failB = !executeStepB()) {}
else if (bool failC = !executeStepC()) {}
else if (bool failD = !executeStepD()) {}
else {
     // success !
}

runThisFunctionInAnyCase();

이것은 모호하지만 컴팩트합니다.

// keep going if we don't fail
if (!executeStepA())      {}
else if (!executeStepB()) {}
else if (!executeStepC()) {}
else if (!executeStepD()) {}
else { /* success */ }

runThisFunctionInAnyCase();

5

이것은 state machine처럼 보입니다 . state-pattern으로 쉽게 구현할 수 있기 때문에 편리합니다 .

Java에서는 다음과 같이 보일 것입니다.

interface StepState{
public StepState performStep();
}

구현은 다음과 같이 작동합니다.

class StepA implements StepState{ 
    public StepState performStep()
     {
         performAction();
         if(condition) return new StepB()
         else return null;
     }
}

등등. 그런 다음 big if 조건을 다음으로 대체 할 수 있습니다.

Step toDo = new StepA();
while(toDo != null)
      toDo = toDo.performStep();
executeThisFunctionInAnyCase();

5

Rommik이 언급했듯이 이에 대한 디자인 패턴을 적용 할 수는 있지만 통화를 연결하려면 전략 대신 데코레이터 패턴을 사용합니다. 코드가 단순하다면 중첩을 방지하기 위해 잘 구조화 된 답변 중 하나를 사용합니다. 그러나 복잡하거나 다이나믹 체인이 필요한 경우 데코레이터 패턴을 사용하는 것이 좋습니다. 다음은 yUML 클래스 다이어그램입니다 .

yUML 클래스 다이어그램

LinqPad C # 프로그램 샘플은 다음과 같습니다 .

void Main()
{
    IOperation step = new StepC();
    step = new StepB(step);
    step = new StepA(step);
    step.Next();
}

public interface IOperation 
{
    bool Next();
}

public class StepA : IOperation
{
    private IOperation _chain;
    public StepA(IOperation chain=null)
    {
        _chain = chain;
    }

    public bool Next() 
    {
        bool localResult = false;
        //do work
        //...
        // set localResult to success of this work
        // just for this example, hard coding to true
        localResult = true;
        Console.WriteLine("Step A success={0}", localResult);

        //then call next in chain and return
        return (localResult && _chain != null) 
            ? _chain.Next() 
            : true;
    }
}

public class StepB : IOperation
{
    private IOperation _chain;
    public StepB(IOperation chain=null)
    {
        _chain = chain;
    }

    public bool Next() 
    {   
        bool localResult = false;

        //do work
        //...
        // set localResult to success of this work
        // just for this example, hard coding to false, 
            // to show breaking out of the chain
        localResult = false;
        Console.WriteLine("Step B success={0}", localResult);

        //then call next in chain and return
        return (localResult && _chain != null) 
            ? _chain.Next() 
            : true;
    }
}

public class StepC : IOperation
{
    private IOperation _chain;
    public StepC(IOperation chain=null)
    {
        _chain = chain;
    }

    public bool Next() 
    {
        bool localResult = false;
        //do work
        //...
        // set localResult to success of this work
        // just for this example, hard coding to true
        localResult = true;
        Console.WriteLine("Step C success={0}", localResult);
        //then call next in chain and return
        return (localResult && _chain != null) 
            ? _chain.Next() 
            : true;
    }
}

IMHO 디자인 패턴에 대한 최고의 책은 Head First Design Patterns 입니다.


Jefffrey의 답변과 같은 것보다 이것의 이점은 무엇입니까?
Dason

요구 사항이 변경 될 때 도메인 변경 지식이 없어도 관리가 더 간단 해집니다. 특히 중첩 된 if의 일부 섹션이 얼마나 깊고 긴지 고려할 때. 그것은 모두 매우 깨지기 쉬우므로 작업 할 위험이 높습니다. 잘못 이해하지 마십시오. 최적화 시나리오로 인해이 문제를 해결하고 ifs로 돌아갈 수 있지만 99 %는 괜찮습니다. 그러나 요점은 그 수준에 도달하면 유지 관리에 신경 쓰지 않고 성능이 필요하다는 것입니다.
John Nicholas

4

몇 가지 답변은 특히 네트워크 프로그래밍에서 여러 번 보았고 사용한 패턴을 암시했습니다. 네트워크 스택에는 종종 긴 요청 시퀀스가 ​​있으며, 그 중 하나가 실패하여 프로세스가 중지됩니다.

일반적인 패턴은 do { } while (false);

매크로를 사용 while(false)하여 만들었습니다 do { } once;. 일반적인 패턴은 다음과 같습니다.

do
{
    bool conditionA = executeStepA();
    if (! conditionA) break;
    bool conditionB = executeStepB();
    if (! conditionB) break;
    // etc.
} while (false);

이 패턴은 비교적 읽기 쉬웠으며, 적절하게 파기하고 여러 리턴을 피하여 스테핑 및 디버깅을 조금 더 쉽게하는 객체를 사용할 수있었습니다.


4

Mathieu의 C ++ 11 답변을 개선하고를 사용하여 발생하는 런타임 비용을 피 std::function하려면 다음을 사용하는 것이 좋습니다.

template<typename functor>
class deferred final
{
public:
    template<typename functor2>
    explicit deferred(functor2&& f) : f(std::forward<functor2>(f)) {}
    ~deferred() { this->f(); }

private:
    functor f;
};

template<typename functor>
auto defer(functor&& f) -> deferred<typename std::decay<functor>::type>
{
    return deferred<typename std::decay<functor>::type>(std::forward<functor>(f));
}

이 간단한 템플릿 클래스는 매개 변수없이 호출 할 수있는 모든 functor를 허용하며 동적 메모리 할당없이 수행되므로 불필요한 오버 헤드없이 C ++의 추상화 목표를보다 잘 준수합니다. 추가 함수 템플릿은 템플릿 매개 변수 공제 (클래스 템플릿 매개 변수에는 사용할 수 없음)에 의한 사용을 단순화하기 위해 존재합니다.

사용 예 :

auto guard = defer(executeThisFunctionInAnyCase);
bool conditionA = executeStepA();
if (!conditionA) return;
bool conditionB = executeStepB();
if (!conditionB) return;
bool conditionC = executeStepC();
if (!conditionC) return;

Mathieu의 답변과 마찬가지로이 솔루션은 완전히 예외 안전 executeThisFunctionInAnyCase하며 모든 경우에 호출됩니다. 해야 executeThisFunctionInAnyCase자체가 던져 소멸자는 암시 적으로 표시됩니다 noexcept따라서를 호출 std::terminate예외를 발행하는 대신의 원인이 될 수는 스택 해제시 발생 될 수 있습니다.


+1이 답변을 찾고 있었으므로 게시하지 않아도됩니다. 'd 생성자 functor에서 완벽하게 전달 deferred해야합니다 move.
Yakk-Adam Nevraumont

@Yakk는 생성자를 전달 생성자로 변경했습니다
Joe

3

단일 블록에서 모든 통화를 수행하려는 것 같습니다. 다른 사람들이 제안했듯이 while루프 앤 휴가를 사용 break하거나 남겨 둘 수있는 새로운 기능을 사용해야 return합니다 (깨끗할 수 있음).

나는 goto기능 종료를 위해조차도 개인적으로 추방 합니다. 디버깅 할 때 발견하기가 더 어렵습니다.

워크 플로에 적합한 우아한 대안은 함수 배열을 작성하고이를 반복하는 것입니다.

const int STEP_ARRAY_COUNT = 3;
bool (*stepsArray[])() = {
   executeStepA, executeStepB, executeStepC
};

for (int i=0; i<STEP_ARRAY_COUNT; ++i) {
    if (!stepsArray[i]()) {
        break;
    }
}

executeThisFunctionInAnyCase();

다행히 디버거가이를 찾아냅니다. 디버깅하고 코드를 한 단계 씩 실행하지 않으면 잘못하고 있습니다.
코디 그레이

나는 당신이 무엇을 의미하는지 이해하지 못하며 왜 단일 스테핑을 사용할 수 없습니까?
AxFab

3

실행 사이에 [... block of code ...] 도 있기 때문에 메모리 할당이나 객체 초기화가 있다고 생각합니다. 이런 식으로 종료시 이미 초기화 된 모든 항목을 청소해야하며 문제가 발생하고 함수가 false를 반환하는 경우이를 청소해야합니다.

이 경우 내 경험에서 가장 좋은 것은 (CryptoAPI로 작업 할 때) 작은 클래스를 만드는 것이 었습니다. 생성자에서 데이터를 초기화하고 소멸자에서 초기화하지 않았습니다. 다음 각 함수 클래스는 이전 함수 클래스의 하위 여야합니다. 문제가 발생하면 예외를 던지십시오.

class CondA
{
public:
    CondA() { 
        if (!executeStepA()) 
            throw int(1);
        [Initialize data]
    }
    ~CondA() {        
        [Clean data]
    }
    A* _a;
};

class CondB : public CondA
{
public:
    CondB() { 
        if (!executeStepB()) 
            throw int(2);
        [Initialize data]
    }
    ~CondB() {        
        [Clean data]
    }
    B* _b;
};

class CondC : public CondB
{
public:
    CondC() { 
        if (!executeStepC()) 
            throw int(3);
        [Initialize data]
    }
    ~CondC() {        
        [Clean data]
    }
    C* _c;
};

그런 다음 코드에서 다음을 호출하면됩니다.

shared_ptr<CondC> C(nullptr);
try{
    C = make_shared<CondC>();
}
catch(int& e)
{
    //do something
}
if (C != nullptr)
{
   C->a;//work with
   C->b;//work with
   C->c;//work with
}
executeThisFunctionInAnyCase();

ConditionX를 호출 할 때마다 무언가를 초기화하고 메모리를 할당하는 등 모든 것이 깨끗해 지도록하는 것이 최선의 해결책이라고 생각합니다.


3

흥미로운 방법은 예외를 처리하는 것입니다.

try
{
    executeStepA();//function throws an exception on error
    ......
}
catch(...)
{
    //some error handling
}
finally
{
    executeThisFunctionInAnyCase();
}

그러한 코드를 작성하면 어떻게 든 잘못된 방향으로 가고 있습니다. 나는 그런 코드를 갖는 것이 "문제"라고 볼 수 없지만, 그런 지저분한 "아키텍처"를 갖는 것입니다.

팁 : 신뢰할 수있는 숙련 된 개발자와 사례를 토론하십시오. ;-)


이 아이디어가 모든 if-chain을 대체 할 수는 없다고 생각합니다. 어쨌든 많은 경우에 이것은 매우 좋은 접근법입니다!
WoIIe

3

또 다른 접근법- do - while루프는 그것이 어떻게 생겼는지 보여주는 예가 없었기 전에 언급되었지만, 다음과 같습니다.

do
{
    if (!executeStepA()) break;
    if (!executeStepB()) break;
    if (!executeStepC()) break;
    ...

    break; // skip the do-while condition :)
}
while (0);

executeThisFunctionInAnyCase();

( while반복적 으로 루프에 대한 답변이 있지만 do - while루프는 중복 (true)을 시작하지 않고 대신 xD 끝에 (이것은 건너 뛸 수 있음) 중복 확인합니다).


이봐 Zaffy-이 답변은 do (} while (false) 접근법에 대한 방대한 설명이 있습니다. stackoverflow.com/a/24588605/294884 다른 두 답변에서도 언급했습니다.
Fattie

이 상황에서 CONTINUE 또는 BREAK를 사용하는 것이 더 우아한 지 여부는 흥미로운 질문입니다!
Fattie

안녕하세요 @JoeBlow 나는 모든 답변을 보았습니다 ... 그냥 얘기하는 대신 보여주고 싶었습니다 :)
Zaffy

내 첫 번째 대답은 여기에 내가 ... "아무도 본을 언급하지 않았다"고 말했다 즉시 누군가가 친절가 2 위에 응답 :이었다 지적
Fattie

@JoeBlow Eh, 네 말이 맞아. 나는 그것을 고치려고 노력할 것이다. 난 느낌이 ... xD 어쨌든 고마워, 다음에 조금 더 attetion을 지불합니다 :)
Zaffy
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.