코드 흐름이 다음과 같은 경우 :
if(check())
{
...
...
if(check())
{
...
...
if(check())
{
...
...
}
}
}
위의 지저분한 코드 흐름을 피하기 위해 일반적 으로이 해결 방법을 보았습니다.
do {
if(!check()) break;
...
...
if(!check()) break;
...
...
if(!check()) break;
...
...
} while(0);
이 해결 방법 / 해킹을 피하여 더 높은 수준 (산업 수준) 코드가되는 더 좋은 방법은 무엇입니까?
즉시 사용 가능한 제안은 환영합니다!
답변
이러한 결정을 기능으로 분리하고 return
s 대신을 사용하는 것이 허용되는 관행으로 간주됩니다 break
. 이러한 모든 검사는 기능과 동일한 추상화 수준에 해당하지만 상당히 논리적입니다.
예를 들면 다음과 같습니다.
void foo(...)
{
if (!condition)
{
return;
}
...
if (!other condition)
{
return;
}
...
if (!another condition)
{
return;
}
...
if (!yet another condition)
{
return;
}
...
// Some unconditional stuff
}
답변
goto
적어도 ” goto
질문이 무엇이든 관계없이 답이 될 수 없다 ” 는 종교적 믿음에 얽매이지 않은 사람들에게는 실제로 사용하는 것이 정답 일 때가 있습니다.
이 코드의 해킹 사용 do { ... } while(0);
를 드레싱의 목적에 대한 goto
A와를 break
. 을 (를) 사용하려는 경우 goto
공개하십시오. 코드 HARDER를 읽도록 만드는 것은 중요하지 않습니다.
특정 상황은 매우 복잡한 조건의 코드가 많은 경우입니다.
void func()
{
setup of lots of stuff
...
if (condition)
{
...
...
if (!other condition)
{
...
if (another condition)
{
...
if (yet another condition)
{
...
if (...)
...
}
}
}
....
}
finish up.
}
실제로 그러한 복잡한 논리를 갖지 않으면 코드가 정확하다는 것을 명확하게 할 수 있습니다.
void func()
{
setup of lots of stuff
...
if (!condition)
{
goto finish;
}
...
...
if (other condition)
{
goto finish;
}
...
if (!another condition)
{
goto finish;
}
...
if (!yet another condition)
{
goto finish;
}
...
....
if (...)
... // No need to use goto here.
finish:
finish up.
}
편집 : 명확히하기 위해, 나는 결코 goto
일반적인 해결책으로 의 사용을 제안하지 않습니다 . 그러나 goto
다른 솔루션보다 더 나은 솔루션이있는 경우 가 있습니다.
예를 들어 일부 데이터를 수집하고 있고 테스트되는 여러 조건이 일종의 “이것은 수집되는 데이터의 끝”이라고 상상해보십시오. 위치에 따라 달라지는 “계속 / 종료”마커에 따라 다릅니다. 데이터 스트림에 있습니다.
이제 완료되면 데이터를 파일로 저장해야합니다.
그렇습니다. 합리적인 솔루션을 제공 할 수있는 솔루션이 종종 있지만 항상 그런 것은 아닙니다.
답변
bool
변수 와 함께 간단한 연속 패턴을 사용할 수 있습니다 .
bool goOn;
if ((goOn = check0())) {
...
}
if (goOn && (goOn = check1())) {
...
}
if (goOn && (goOn = check2())) {
...
}
if (goOn && (goOn = check3())) {
...
}
이 실행 체인은을 checkN
반환 하자마자 중지됩니다 false
. 작업자의 check...()
단락으로 인해 더 이상 전화를 걸지 않습니다 &&
. 또한, 최적화 컴파일러는 해당 설정을 인식하는 스마트 충분히 goOn
에 false
일방 통행로이며, 누락을 삽입 goto end
당신을 위해. 그 결과, 코드의 성능은 위의 것과 동일한 것 do
/ while(0)
만 가독성에 고통스러운 타격하지 않고.
답변
-
코드를 별도의 함수 (또는 둘 이상)로 추출하십시오. 점검에 실패하면 기능에서 복귀하십시오.
-
주변 코드와 너무 밀접하게 연결되어 있고 커플 링을 줄일 수있는 방법을 찾지 못하면이 블록 뒤의 코드를 확인하십시오. 아마도 함수에서 사용하는 일부 리소스를 정리합니다. RAII 오브젝트를 사용하여 이러한 자원을 관리하십시오 . 그런 다음 각 피지
break
를return
(또는throw
더 적절한 경우) 로 바꾸고 객체의 소멸자를 정리하십시오. -
프로그램 흐름이 (필요하게) 너무 구불 구불하게되어 정말로 필요하다면
goto
이상한 변장을주는 대신 사용하십시오. -
맹목적으로 금지하는 코딩 규칙이
goto
있고 실제로 프로그램 흐름을 단순화 할 수 없다면do
해킹 으로 위장해야 할 것입니다 .
답변
TLDR : RAII , 트랜잭션 코드 (결과가 이미 설정되어있을 때만 결과를 설정하거나 반환) 및 예외.
긴 대답 :
에서 C 코드의 이런 종류의 가장 좋은 방법은 EXIT / CLEANUP / 추가하는 것입니다 다른 지역 자원의 정리가 발생하고 오류 코드 (있는 경우)이 반환되는 경우, 코드에서 레이블을. 이것은 코드를 자연스럽게 초기화, 계산, 커밋 및 리턴으로 나누기 때문에 모범 사례입니다.
error_code_type c_to_refactor(result_type *r)
{
error_code_type result = error_ok; //error_code_type/error_ok defd. elsewhere
some_resource r1, r2; // , ...;
if(error_ok != (result = computation1(&r1))) // Allocates local resources
goto cleanup;
if(error_ok != (result = computation2(&r2))) // Allocates local resources
goto cleanup;
// ...
// Commit code: all operations succeeded
*r = computed_value_n;
cleanup:
free_resource1(r1);
free_resource2(r2);
return result;
}
C, 대부분의 코드베이스에서, if(error_ok != ...
그리고 goto
코드는 일반적으로 몇 가지 편리한 매크로 (뒤에 숨겨진 RET(computation_result)
, ENSURE_SUCCESS(computation_result, return_code)
등).
C ++ 을 통해 제공 추가 도구를 C :
-
정리 블록 기능은 RAII로 구현 될 수 있습니다. 즉, 더 이상 전체
cleanup
블록이 필요하지 않으며 클라이언트 코드가 초기 리턴 명령문을 추가 할 수 있습니다. -
계속할 수 없을 때마다 던져 모든
if(error_ok != ...
직통 전화로 변환 합니다.
동등한 C ++ 코드 :
result_type cpp_code()
{
raii_resource1 r1 = computation1();
raii_resource2 r2 = computation2();
// ...
return computed_value_n;
}
이것은 다음과 같은 이유로 모범 사례입니다.
-
명시 적입니다 (즉, 오류 처리가 명시 적이 지 않지만 알고리즘의 주요 흐름은 다음과 같습니다)
-
클라이언트 코드를 작성하는 것은 간단합니다
-
최소한입니다
-
간단하다
-
반복적 인 코드 구성이 없습니다
-
매크로를 사용하지 않습니다
-
이상한
do { ... } while(0)
구문을 사용하지 않습니다 -
최소한의 노력으로 재사용 할 수 있습니다 (즉, 호출을
computation2();
다른 함수 에 복사하려는 경우do { ... } while(0)
새 코드를 추가 하거나#define
goto wrapper 매크로 및 정리 레이블을 추가 할 필요가 없습니다. 다른 것).
답변
완전성을 위해 답변을 추가하고 있습니다. 다른 많은 답변은 큰 조건 블록이 별도의 기능으로 나눌 수 있다고 지적했습니다. 그러나 여러 번 지적 했듯이이 접근법은 조건부 코드를 원래 컨텍스트와 분리한다는 것입니다. 이것이 C ++ 11의 언어에 람다가 추가 된 이유 중 하나입니다. 람다 사용은 다른 사람들에 의해 제안되었지만 명확한 샘플은 제공되지 않았습니다. 나는이 답변에 하나를 넣었습니다. 나를 때리는 것은 do { } while(0)
여러면 에서 접근 방식 과 매우 유사하다는 느낌입니다. 아마도 여전히 goto
위장에 있음을 의미합니다 ….
earlier operations
...
[&]()->void {
if (!check()) return;
...
...
if (!check()) return;
...
...
if (!check()) return;
...
...
}();
later operations
답변
확실히 대답하지만 답변 (완전성을 위해서)
대신에 :
do {
if(!check()) break;
...
...
if(!check()) break;
...
...
if(!check()) break;
...
...
} while(0);
당신은 쓸 수 있습니다 :
switch (0) {
case 0:
if(!check()) break;
...
...
if(!check()) break;
...
...
if(!check()) break;
...
...
}
이것은 아직도이다 고토 변장하지만, 적어도 그것은 루프는 더 이상 아니다. 즉, 일부를 계속 점검하지 않아도 매우 신중하게 확인할 필요가 없습니다. 블록의 숨겨진 곳은.
구조는 컴파일러가 최적화하기를 희망 할 정도로 간단합니다.
@jamesdlin이 제안한 것처럼 매크로 뒤에 숨길 수도 있습니다.
#define BLOC switch(0) case 0:
그리고 그것을 사용하십시오
BLOC {
if(!check()) break;
...
...
if(!check()) break;
...
...
if(!check()) break;
...
...
}
이는 C 언어 구문이 대괄호 블록이 아니라 스위치 뒤의 명령문을 예상하고 해당 명령문 앞에 케이스 레이블을 넣을 수 있기 때문에 가능합니다. 지금까지 나는 그것을 허용하는 요점을 보지 못했지만이 특별한 경우 스위치를 멋진 매크로 뒤에 숨기는 것이 편리합니다.