이것은 일반적인 상황이며 여러 가지 일반적인 방법으로 처리 할 수 있습니다. 정식 답변에 대한 나의 시도는 다음과 같습니다. 누락 된 부분이 있으면 의견을 말하고이 게시물을 최신 상태로 유지합니다.
이것은 화살표입니다
당신이 논의하는 것을 화살표 안티 패턴이라고 합니다. 중첩 된 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 조건과 관련된 전체 로직이 한 페이지에있을 가능성이 더 큼
- 코더가 페이지 / 아이 트랙을 스크롤 할 필요성이 크게 줄어 듭니다.
끝에 공통 코드를 추가하는 방법
가드 패턴의 문제점은 "기회 복귀"또는 "기회 종료"에 의존한다는 것입니다. 즉, 각 함수마다 정확히 하나의 종료 점이 있어야하는 패턴을 깨뜨립니다. 두 가지 이유로 문제가 있습니다.
- 파스칼을 코딩하는 법을 배운 사람들은 하나의 기능 = 하나의 출구 지점이라는 것을 알게되었습니다.
- 어떤 주제에 관계없이 종료시 실행되는 코드 섹션을 제공하지 않습니다 .
아래에서는 언어 기능을 사용하거나 문제를 완전히 피함으로써이 제한을 해결하기위한 몇 가지 옵션을 제공했습니다.
옵션 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로 평가되면 오른쪽은 평가되지 않습니다.
이 트릭은 소형 코드가 필요한 경우와 코드에서 유지 보수가 많이 이루어지지 않는 경우에 가장 유용합니다 (예 : 잘 알려진 알고리즘 구현). 보다 일반적인 코딩의 경우이 코드의 구조가 너무 취약합니다. 논리를 약간만 변경해도 전체 다시 쓰기가 트리거 될 수 있습니다.