"만약" 체인을 피하는 방법
다음과 같은 의사 코드가 있다고 가정합니다.
bool conditionA = executeStepA();
if (conditionA){
bool conditionB = executeStepB();
if (conditionB){
bool conditionC = executeStepC();
if (conditionC){
...
}
}
}
executeThisFunctionInAnyCase();
★★★executeStepX
이전 성공 시에만 실행해야 합니다.경우든, 「」는executeThisFunctionInAnyCase
함수는 마지막에 호출해야 합니다. ( 긴 프로그래밍을 수 있는 요?기본적인 질문입니다.예를 들어 (C/C++에서) 그렇게 긴 시간을 피할 수 있는 방법이 있을까요?if
코드 가독성을 희생하면서 그런 종류의 "코드 편집"을 만들어내는 체인?
그 일을 수 , 있다.executeThisFunctionInAnyCase
함수 호출, 코드는 다음과 같이 단순화할 수 있습니다.
bool conditionA = executeStepA();
if (!conditionA) return;
bool conditionB = executeStepB();
if (!conditionB) return;
bool conditionC = executeStepC();
if (!conditionC) return;
, .executeThisFunctionInAnyCase
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★.break
떤떤식 으용 ?용 ?? ????
'어울리지 않다'를 사용할 수 요.&&
AND (논리 AND:
if (executeStepA() && executeStepB() && executeStepC()){
...
}
executeThisFunctionInAnyCase();
이는 두 가지 요건을 모두 충족합니다.
이것은 일반적인 상황이며, 대처하는 방법에는 여러 가지가 있습니다.이게 내가 정석적으로 대답하려는 시도야.제가 놓친 게 있으면 코멘트해 주세요.이 게시물을 최신 상태로 유지하겠습니다.
이것은 화살표입니다.
여기서 설명하는 것은 화살표 안티 패턴이라고 불립니다.중첩된 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;
}
이것은 객관적이고 수량적으로 읽기 쉽다.
- 지정된 논리 블록의 { 및 }자가 더 가깝습니다.
- 특정 라인을 이해하는 데 필요한 정신적 맥락의 양은 더 적다.
- if 조건과 관련된 논리 전체가 한 페이지에 표시될 가능성이 더 높습니다.
- 코더가 페이지/아이 트랙을 스크롤할 필요가 크게 감소합니다.
마지막에 공통 코드를 추가하는 방법
가드 패턴의 문제는 '기회주의적 회귀' 또는 '기회주의적 출구'라고 불리는 것에 의존한다는 것이다.즉, 각 기능 및 모든 기능이 정확히 하나의 출구 지점을 가져야 한다는 패턴을 깨는 것입니다.이 문제는 다음 두 가지 이유로 발생합니다.
- 예를 들어, Pascal을 사용하여 코드를 작성하는 방법을 배운 사람들은 하나의 함수 = 하나의 출구 지점이라는 것을 배웠습니다.
- 종료 시에 실행되는 코드 섹션은 제공되지 않습니다.이것은, 수중에 있는 주제입니다.
아래에서는 언어 기능을 사용하거나 문제를 회피하는 방법으로 이 제한을 회피하기 위한 몇 가지 옵션을 제시했습니다.
이은 할 수 사용하세요: 사용하세요.finally
안타깝게도 c++ 개발자로서 이 작업을 수행할 수 없습니다.그러나 이것은 최종 키워드를 포함하는 언어에서 가장 중요한 답입니다. 왜냐하면 이것이 바로 그 용도가기 때문입니다.
try
{
if (!ok)
{
_log.Error("oops");
return;
}
DoSomething(); //notice how this is already farther to the left than the example above
}
finally
{
DoSomethingNoMatterWhat();
}
옵션 2이 문제를 회피합니다.기능 재구축
코드를 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로 평가될 경우 오른쪽은 평가되지 않습니다.
이 트릭은 콤팩트코드가 필요하고 코드에 유지보수가 많지 않을 때(예: 잘 알려진 알고리즘을 구현하는 경우) 가장 유용합니다.보다 일반적인 코딩에서는 이 코드의 구조가 너무 취약합니다.논리를 조금만 변경해도 완전한 개서가 트리거될 수 있습니다.
다음 추가 기능을 사용하여 두 번째 버전을 사용할 수 있습니다.
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(첫 번째 변형) 또는 "함수의 일부"에서 벗어나고자 하는 욕구 중 하나를 사용하는 것은 보통 추가 함수가 필요하다는 것을 의미합니다.
는 'C'를 사용한다.goto
아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아goto
Linux 스타일가이드에 의해 실제로 장려된 것은 중앙집중형 기능 종료라고 불립니다.
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;
}
루프 어프로치에서는 이 경우 2레벨의 루프가 발생합니다.
다음은 C-what와 Java에서 여러 번 사용한 트릭입니다.
do {
if (!condition1) break;
doSomething();
if (!condition2) break;
doSomethingElse()
if (!condition3) break;
doSomethingAgain();
if (!condition4) break;
doYetAnotherThing();
} while(FALSE); // Or until(TRUE) or whatever your language likes
특히 각 조건에 대해 명확한 코멘트로 올바르게 포맷했을 경우, 알기 쉽게 하기 위해 중첩된 if보다 선호합니다.
[...]도 있으니까실행 사이에 메모리 할당 또는 객체 초기화가 있을 것입니다.이와 같이 하면, 이미 초기화한 모든 것을 클리닝 할 필요가 있습니다.또, 문제가 발생해, 기능 중 하나가 false가 반환되는 경우도 클리닝 할 필요가 있습니다.
이 경우 내 경험에서 가장 좋은 것은 (CryptoA와 함께 일했을 때)PI)는 생성자에서 데이터를 초기화하고 소멸자에서 초기화를 취소하는 작은 클래스를 만들고 있었습니다.다음 각 함수 클래스는 이전 함수 클래스의 하위 함수여야 합니다.뭔가 잘못되면 예외로 하세요.
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();
Condition X의 모든 호출이 초기화, 메모리 할당 등을 하는 것이 가장 좋은 해결책이라고 생각합니다.모든 것을 청소하는 것이 가장 좋습니다.
왜 아무도 가장 간단한 해결책을 제시하지 않는가? : D
모든 함수에 동일한 시그니처가 있는 경우 다음과 같이 실행할 수 있습니다(C 언어).
bool (*step[])() = {
&executeStepA,
&executeStepB,
&executeStepC,
...
};
for (int i = 0; i < numberOfSteps; i++) {
bool condition = step[i]();
if (!condition) {
break;
}
}
executeThisFunctionInAnyCase();
클린 C++ 솔루션에서는 실행 메서드를 포함하는 인터페이스 클래스를 생성하여 스텝을 오브젝트로 묶어야 합니다.
위의 솔루션은 다음과 같습니다.
Step *steps[] = {
stepA,
stepB,
stepC,
...
};
for (int i = 0; i < numberOfSteps; i++) {
Step *step = steps[i];
if (!step->execute()) {
break;
}
}
executeThisFunctionInAnyCase();
반환문(Itjax에서 규정하는 방법)에 추가 래퍼 기능이 필요 없는 멋진 기술이 있습니다.은 do를 한다.while(0)
★★★★★★while (0)
는 실제로는 루프가 아닌1회만 실행되도록 합니다.할 수 있습니다.「 「 break」 。
void foo()
{
// ...
do {
if (!executeStepA())
break;
if (!executeStepB())
break;
if (!executeStepC())
break;
}
while (0);
// ...
}
하고 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));
}
이 단순한 템플릿클래스는 파라미터 없이 호출할 수 있는 모든 펑터를 받아들이기 때문에 동적 메모리 할당 없이 실행할 수 있으므로 불필요한 오버헤드 없이 C++의 추상화 목표에 더 적합합니다.추가 함수 템플릿은 템플릿 매개 변수 차감에 의한 사용을 단순화하기 위해 있습니다(클래스 템플릿 매개 변수에는 사용할 수 없음).
사용 예:
auto guard = defer(executeThisFunctionInAnyCase);
bool conditionA = executeStepA();
if (!conditionA) return;
bool conditionB = executeStepB();
if (!conditionB) return;
bool conditionC = executeStepC();
if (!conditionC) return;
로 이 하며, 마티외는 안전합니다.executeThisFunctionInAnyCase
모든 경우에 호출됩니다.★★★★★★★★★★ 。executeThisFunctionInAnyCase
으로 「파괴자」라고 마크되어 있습니다.noexcept
에, 은 and때, and, to to to to to to to and and and and and and and and and and and and and.std::terminate
스택 언바인딩 중에 예외가 발생하는 대신 발행됩니다.
대체 해결책은 매크로 해크를 통해 관용구를 정의하는 것입니다.
#define block for(int block = 0; !block; block++)
'블록'으로 끝낼 수 있어요.break
같은 「」를 참조해 주세요.for(;;)
★★★★★★★★★★★★★★★★★」while()
" " 입니다. §:
int main(void) {
block {
if (conditionA) {
// Do stuff A...
break;
}
if (conditionB) {
// Do stuff B...
break;
}
if (conditionC) {
// Do stuff C...
break;
}
else {
// Do default stuff...
}
} /* End of "block" statement */
/* ---> The "break" sentences jump here */
return 0;
}
「block은 「for(;)」로됩니다.
은 '블록', '블록', '블록', '블록', '블록', '블록', '블록', '블록', '블록', '블록', '블록'이 나올 수 있습니다.break
★★★★★★★★★★★★★★★★★★.
그 때문에, 「」, 「」의 .if else if else if...
문장은 회피됩니다.
밖에 남지 않았다.else
는, 「블록」의 마지막에 행업 해, 「디폴트」의 케이스를 처리할 수 있습니다.
은 전형적이고 것을 위한 입니다.do { ... } while(0)
★★★★★★ 。
'''에서block
로서도 정의되어 있습니다'라고 합니다.block
의 반복이 정확하게1개 실행되도록 정의되어 있습니다. '''는block
block
으로 치환되지 에, 「재귀 치환」이 되지 않습니다.block
할 수 만, 「숨겨져 있다」라고 하는 「숨겨져 있다」라고 하는 제어에 합니다.for(;;)
루우프
은 숨겨진인 "블록"이 있기 될 수 .int block
을 사용하다
그냥 해
if( executeStepA() && executeStepB() && executeStepC() )
{
// ...
}
executeThisFunctionInAnyCase();
그렇게 간단하다.
각각 기본적으로 질문을 변경한 세 가지 편집(리비전을 버전 #1로 다시 카운트하는 경우 네 가지)으로 인해 다음과 같은 코드 예를 첨부합니다.
bool conditionA = executeStepA();
if (conditionA){
bool conditionB = executeStepB();
if (conditionB){
bool conditionC = executeStepC();
if (conditionC){
...
}
}
}
executeThisFunctionInAnyCase();
실제로 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
다음과 같이 할 수도 있습니다.
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) IMO의 가시성이 떨어지기 때문에 팬이 아닙니다.
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();
이게 먹힐까?이것은 당신의 코드와 동등하다고 생각합니다.
bool condition = true; // using only one boolean variable
if (condition) condition = executeStepA();
if (condition) condition = executeStepB();
if (condition) condition = executeStepC();
...
executeThisFunctionInAnyCase();
원하는 코드가 현재 표시된 코드라고 가정하면:
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();
이것에 의해, 다음의 요구를 회피할 수 있습니다.goto
s, "s", "s", "s"while
루프 또는 기타 어려운 구조물을 만들어 간단한 작업을 쉽게 수행할 수 있습니다.
브레이크 스테이트먼트가 어떤 식으로든 사용될 수 있을까요?
수도 있지만, 을 '아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아,do .. while (0)
및 use " " " 를 합니다.break
" " 가 return
.
수 요.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 lamda를 사용할 수 있는 경우(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();
goto
do { } while (0);
또한 C++를 사용하는 것과 마찬가지로 임시 람다를 사용하여 동일한 효과를 얻을 수도 있습니다.
[&]() { // create a capture all lambda
if (!executeStepA()) { return; }
if (!executeStepB()) { return; }
if (!executeStepC()) { return; }
}(); // and immediately call it
executeThisFunctionInAnyCase();
코드의 IF/ELSE 체인은 언어의 문제가 아니라 프로그램의 설계에 달려 있습니다.프로그램을 재요인화 또는 재작성할 수 있다면 디자인 패턴(http://sourcemaking.com/design_patterns)에서 더 나은 솔루션을 찾을 것을 제안합니다.
통상, 코드에 IF나 그 외의 것이 다수 포함되어 있는 경우는, 전략 설계 패턴(http://sourcemaking.com/design_patterns/strategy/c-sharp-dot-net) 또는 그 외의 패턴의 조합)을 실장할 수 있습니다.
if/else의 긴 목록을 쓸 수 있는 다른 방법이 있을 것입니다만, 체인이 예뻐 보이는 것 이외에는 아무것도 바꿀 수 없을 것입니다(그러나 보는 사람의 눈에 아름다움은 여전히 코드에 적용되고 있습니다:-). (6개월 후, 새로운 상태가 되어 아무것도 기억나지 않는 경우)와 같은 것에 대해 걱정해야 합니다.이 코드를 쉽게 추가할 수 있을까요?또는 체인이 변경되면 얼마나 빠르고 오류 없이 구현될 수 있습니까?
그냥 이렇게 하면..
coverConditions();
executeThisFunctionInAnyCase();
function coverConditions()
{
bool conditionA = executeStepA();
if (!conditionA) return;
bool conditionB = executeStepB();
if (!conditionB) return;
bool conditionC = executeStepC();
if (!conditionC) return;
}
100의 99배, 이게 유일한 방법이야.
절대로 컴퓨터 코드로 "기묘한" 것을 하려고 하지 마세요.
참고로, 당신이 생각하고 있던 실제 해결책은 다음과 같습니다.
continue 문은 알고리즘 프로그래밍에서 매우 중요합니다(대부분 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(Aside-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");
}
그러니까 그걸 잊지 마세요.
(false) 동안 { } 수행;
그냥 '이 블록을 한 번 해'라는 뜻이에요.
,, 쓰, 쓰, 쓰, 이, 이, 이, 이, 이, 이, 이, 이, 이, 이, 이, 이, 이, 이 ,do{}while(false);
'이렇게 써주세요'라고 써주세요.{}
.
이제 네가 원하는 대로 완벽하게 작동하게 됐어여기 결과물...
머리 속에서 알고리즘이 그렇게 보일 수도 있어요항상 머릿속에 있는 것을 쓰려고 노력해야 한다.(특히 술이 깨지 않았다면, 그 때 예쁜 것이 나오기 때문이다! : )
이런 일이 많이 일어나는 "알고리즘" 프로젝트에서는 목적 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");
}
두 가지 포인트가 있습니다.
a, 오브젝티브 c가 continue 스테이트먼트가 들어 있는 블록의 종류를 체크하는 것은 어리석은 일이지만, "그와 싸우는" 것은 곤란하다.그래서 어려운 결정입니다.
b, 이 예에서 그 블록을 들여써야 하는 질문이 있습니다.저는 그런 질문 때문에 잠을 설치기 때문에 조언할 수 없습니다.
도움이 됐으면 좋겠다.
false를 반환하지 않고 실행 함수가 실패하면 예외를 발생시킵니다.발신 코드는 다음과 같습니다.
try {
executeStepA();
executeStepB();
executeStepC();
}
catch (...)
물론 당신의 원래 예에서는 실행 스텝이 스텝 내에서 에러가 발생했을 경우에만 false를 반환한다고 가정합니다.
이미 많은 좋은 답변이 있지만, 대부분의 답변은 유연성의 일부(인정되는 것은 거의 없음)를 트레이드오프하고 있는 것 같습니다.이러한 트레이드오프를 필요로 하지 않는 일반적인 접근법은 상태/계속 변수를 추가하는 것입니다.물론 가격은 다음 사항을 추적해야 하는 추가 가치 중 하나입니다.
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;
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();
코드가 예시와 같이 간단하고 언어가 단락 평가를 지원하는 경우 다음을 시도해 볼 수 있습니다.
StepA() && StepB() && StepC() && StepD();
DoAlways();
인수를 함수에 전달하고 코드를 이전 방식으로 쓸 수 없도록 다른 결과를 얻는 경우 다른 많은 답변이 문제에 더 적합합니다.
C++11 이후에는 D의 스코프(출구) 메커니즘과 유사한 스코프 출구 시스템을 구현하는 것이 좋습니다.
C++11 lamda 및 일부 도우미 매크로를 사용하는 방법이 있습니다.
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()
직접 사용할 수 있습니다.
개별 조건 변수가 필요하지 않다고 가정할 때, 테스트를 뒤집고 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();
실패한 변수를 생략하면 코드가 너무 모호해집니다.
내부 변수를 선언해도 괜찮으니 = 대 ==에 대해 걱정하지 마십시오.
// 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();
이것은 스테이트 머신처럼 보입니다.스테이트 패턴으로 간단하게 실장할 수 있기 때문에 편리합니다.
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();
Rommik이 말한 대로 디자인 패턴을 적용할 수 있지만, 당신은 체인을 하고 싶기 때문에 Strategy보다는 Decorator 패턴을 사용합니다.만약 암호가 간단하다면, 둥지를 틀지 않도록 잘 구성된 답변 중 하나를 택할 것입니다.단, 복잡하거나 동적 체인이 필요한 경우에는 데코레이터 패턴을 선택하는 것이 좋습니다.다음은 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입니다.
몇 가지 답변은 특히 네트워크 프로그래밍에서 여러 번 보고 사용한 패턴을 암시했습니다.네트워크 스택에는 많은 경우 긴 시퀀스의 요구가 존재하며, 이러한 요구 중 하나가 실패하여 프로세스가 정지될 수 있습니다.
은 ""를 사용하는 입니다.do { } while (false);
★★★★★★★★★★★★★★★★★★★★★에 매크로를 사용했습니다.while(false)
을 가능하게 하다do { } once;
일반적인 패턴은 다음과 같습니다.
do
{
bool conditionA = executeStepA();
if (! conditionA) break;
bool conditionB = executeStepB();
if (! conditionB) break;
// etc.
} while (false);
이 패턴은 비교적 읽기 쉬웠기 때문에 오브젝트를 적절히 파괴할 수 있고 여러 번 반환되는 것을 피할 수 있어 스테핑과 디버깅이 조금 더 쉬워졌습니다.
한 블록에서 모든 통화를 하고 싶은 것 같네요.'어느 쪽인가'를.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();
언급URL : https://stackoverflow.com/questions/24430504/how-to-avoid-if-chains
'programing' 카테고리의 다른 글
'Java'가 내부 또는 외부 명령으로 인식되지 않습니다. (0) | 2022.07.02 |
---|---|
Vue.js는 마운트된 상태에서 코드를 실행하여 기능을 재시작합니다. (0) | 2022.07.02 |
Vue.js $emit이 부모에게 수신되지 않았습니다. (0) | 2022.07.02 |
정적 라이브러리의 내용 (0) | 2022.07.02 |
안드로이드:onIntercept의 차이Touch Event 및 dispatch Touch Event? (0) | 2022.07.02 |