다른 경우를 처리하는 우아한 방법


161

이것은 사소한 문제이지만, 이런 식으로 코딩해야 할 때마다 반복이 귀찮게하지만 솔루션 중 어느 것이 나쁘지 않은지 확실하지 않습니다.

if(FileExists(file))
{
    contents = OpenFile(file); // <-- prevents inclusion in if
    if(SomeTest(contents))
    {
        DoSomething(contents);
    }
    else
    {
        DefaultAction();
    }
}
else
{
    DefaultAction();
}
  • 이런 종류의 논리에 대한 이름이 있습니까?
  • 나는 너무 OCD인가?

호기심을 위해서만 악의적 인 코드 제안에 개방적입니다 ...


8
@Emmad Kareem : 두 DefaultAction통화가 DRY 원칙을 위반합니다
Abyx

답장을 보내 주셔서 감사하지만, 결과를 반환하지 않는 오류가있을 수 있고 (프로그래밍 언어에 따라) 이상 종료를 일으킬 수 있기 때문에 try / catch를 사용하지 않는 한 예외입니다.
NoChance

20
여기서 중요한 문제는 일관성없는 추상화 수준 에서 작업하고 있다는 것 입니다. 더 높은 추상화 레벨은 다음과 같습니다 make sure I have valid data for DoSomething(), and then DoSomething() with it. Otherwise, take DefaultAction().. DoSomething ()에 대한 데이터가 있는지 확인하기위한 구체적 세부 사항은 추상화 레벨이 낮으므로 다른 기능이어야합니다. 이 함수는 더 높은 추상화 레벨에서 이름을 가질 것이며, 그 구현은 로우 레벨이 될 것입니다. 아래의 좋은 답변이이 문제를 해결합니다.
Gilad Naor

6
언어를 지정하십시오. 가능한 해결책, 표준 관용구 및 오랜 문화적 규범은 언어마다 다르며 Q에 대한 다른 대답으로 이어질 것입니다.
Caleb

1
이 책 "리팩토링 : 기존 코드의 디자인 개선"을 참조 할 수 있습니다. if-else 구조에 대한 몇 가지 섹션이 있습니다.
Vacker

답변:


96

함수 (방법)를 분리하고 return명령문을 사용하도록 추출하십시오 .

if(FileExists(file))
{
    contents = OpenFile(file); // <-- prevents inclusion in if
    if(SomeTest(contents))
    {
        DoSomething(contents);
        return;
    }
}

DefaultAction();

또는 컨텐츠와 처리를 분리하여 분리하는 것이 좋습니다.

contents_t get_contents(name_t file)
{
    if(!FileExists(file))
        return null;

    contents = OpenFile(file);
    if(!SomeTest(contents)) // like IsContentsValid
        return null;

    return contents;
}

...

contents = get_contents(file)
contents ? DoSomething(contents) : DefaultAction();

Upd :

예외 OpenFile가 아닌 이유, IO 예외가 발생하지 않는 이유 :
파일 IO에 대한 질문이 아니라 실제로 일반적인 질문이라고 생각합니다. 같은 이름은 FileExists, OpenFile복잡 할 수 있지만, 그들을 대체 할 경우 Foo, Bar등, -이 명확 할 것 DefaultAction같은 자주 호출 할 수 있습니다 DoSomething이 아닌 예외적 인 경우가 될 수 있도록. Péter Török은 답변끝날 때 이에 대해 썼습니다

두 번째 변형에 삼항 조건 연산자가있는 이유 :
[C ++] 태그가 있으면 조건 부분에 if선언이 contents있는 명령문을 작성 했습니다.

if(contents_t contents = get_contents(file))
    DoSomething(contents);
else
    DefaultAction();

그러나 다른 (C와 같은) 언어의 경우 if(contents) ...; else ...;삼항 조건 연산자가있는 표현식 문과 정확히 동일하지만 더 길다. 코드의 주요 부분은 get_contents함수 였기 때문에 더 짧은 버전을 사용했습니다 (그리고 생략 된 contents유형). 어쨌든, 그것은이 질문을 넘어선 것입니다.


93
여러 반환 +1 - 방법이 때 충분히 작게 ,이 방법은 나에게 가장 일
모기

나는 때때로 그것을 사용하지만 여러 수익의 큰 팬이 아닙니다. 간단한 것에서는 상당히 합리적이지만 확장 성이 떨어집니다. 우리의 표준은 방법이 축소되는 것보다 크기가 커지는 경향이 있기 때문에 미친 간단한 방법을 제외하고는 피하는 것입니다.
Brian Knoblauch

3
다중 리턴 경로는 C ++ 프로그램에서 성능에 부정적인 영향을 줄 수 있으므로 RVO (각 경로가 동일한 오브젝트를 리턴하지 않는 한 NRVO)를 사용하려는 옵티마이 저의 노력을 무효화합니다.
Functastic

두 번째 솔루션의 논리를 반대로 바꾸는 것이 좋습니다. {if (file exist) {set contents; if (sometest) {반환 내용; }} null을 반환합니다. } 흐름을 단순화하고 줄 수를 줄입니다.
웨지

1
안녕 Abyx, 나는 당신이 의견에 대한 피드백을 여기에 통합 한 것을 보았습니다. 귀하의 답변과 다른 답변에서 해결 된 모든 것을 정리했습니다.

56

사용하는 프로그래밍 언어가 (0) 단락 이진 비교 (즉, false를 반환 SomeTest하면 호출하지 않는 경우 FileExists) 및 (1) 할당이 값을 반환하면 (결과 OpenFile가 할당 된 contents다음 해당 값이 인수로 전달됨) to SomeTest), 다음과 같은 것을 사용할 수는 있지만 여전히 단일 코드 =가 의도적 이라는 점을 지적하면서 코드에 의견을 제시하는 것이 좋습니다 .

if( FileExists(file) && SomeTest(contents = OpenFile(file)) )
{
    DoSomething(contents);
}
else
{
    DefaultAction();
}

if의 복잡성에 따라 플래그 변수 ( DefaultAction이 경우 오류를 처리하는 코드로 성공 / 실패 조건 테스트를 분리)를 사용하는 것이 좋습니다.


이것이 내가하는 방법입니다.
Anthony

13
if내 의견으로 는, 성명서 에 너무 많은 코드를 넣는 것은 아주 거칠다.
moteutsch

15
반대로, "이것이 존재하고이 조건을 만족하는 경우"와 같은 이런 종류의 말처럼 +1
Gorpik

나도 그래! 나는 사람들이 여러 건물을 사용하는 방식을 개인적으로 싫어합니다. 그러한 ifs를 뒤집어서 코드 충족 되면 코드를 실행하지 않는 이유는 무엇 입니까?
klaar

"무언가 존재하고이 조건을 충족하는 경우"는 좋습니다. OTOH는 "무언가 존재하고 여기서 접선 적으로 관련이있는 어떤 일을한다면이 조건을 충족시킨다"고 혼동하고있다. 다시 말해, 나는 조건에서 부작용을 싫어합니다.
Piskvor

26

DefaultAction에 대한 호출을 반복하는 것보다 더 심각한 것은 코드가 직교하지 않기 때문에 스타일 자체입니다 ( 직교 적으로 작성해야하는 좋은 이유는 이 답변 참조 ).

비 직교 코드가 나쁜 이유를 보여주기 위해 네트워크 디스크에 저장된 경우 파일을 열지 않아야하는 새로운 요구 사항이 소개 될 때 원래 예를 고려하십시오. 그러면 코드를 다음과 같이 업데이트 할 수 있습니다.

if(FileExists(file))
{
    if(! OnNetworkDisk(file))
    {
        contents = OpenFile(file); // <-- prevents inclusion in if
        if(SomeTest(contents))
        {
            DoSomething(contents);
        }
        else
        {
            DefaultAction();
        }
    }
    else
    {
        DefaultAction();
    }
}
else
{
    DefaultAction();
}

그러나 2Gb 이상으로 큰 파일을 열지 않아야한다는 요구 사항도 있습니다. 글쎄, 우리는 다시 업데이트 :

if(FileExists(file))
{
    if(LessThan2Gb(file))
    {
        if(! OnNetworkDisk(file))
        {
            contents = OpenFile(file); // <-- prevents inclusion in if
            if(SomeTest(contents))
            {
                DoSomething(contents);
            }
            else
            {
                DefaultAction();
            }
        }
        else
        {
            DefaultAction();
        }
    else
    {
        DefaultAction();
    }
}
else
{
    DefaultAction();
}

이러한 코드 스타일은 유지 관리에 큰 어려움이 될 것입니다.

여기에 올바르게 직교로 작성된 답변 중 Abyx의 두 번째 예Jan Hudec의 답변 이 반복되지 않으므로 해당 답변에 두 가지 요구 사항을 추가하면

if(! LessThan2Gb(file))
    return null;

if(OnNetworkDisk(file))
    return null;

(또는 goto notexists;대신 return null;) 추가 된 행 이외의 다른 코드에는 영향을 미치지 않습니다 . 예를 들어 직교.

테스트 할 때 일반적인 규칙은 일반적인 경우가 아니라 예외테스트 해야합니다 .


8
나를 위해 +1. 조기 반품은 화살촉 안티 패턴을 방지하는 데 도움이됩니다. 참조 codinghorror.com/blog/2006/01/flattening-arrow-code.htmllostechies.com/chrismissal/2009/05/27/... 이전이 패턴에 대한 책을 읽은, 나는 항상 기능 당 1 입 / 출력에 가입 내가 15 년 정도 전에 배운 것에 기인 한 이론. 나는 이것이 코드를 훨씬 쉽게 읽고 유지 관리하기 쉽다고 생각합니다.
Mr Moose

3
@MrMoose : 화살촉 반 패턴에 대한 언급은 Benjol의 명백한 질문에 대한 답변입니다. "이런 종류의 논리에 대한 이름이 있습니까?" 답변으로 게시하면 내 투표권이 있습니다.
outis

좋은 답변입니다. 감사합니다. 그리고 @MrMoose : "화살촉 방지 패턴"은 아마도 첫 번째 글 머리 기호에 답할 수 있습니다. 동의하겠다고 약속 할 수는 없지만 투표 할 가치가 있습니다!
Benjol

@outis. 감사. 답변을 추가했습니다. 화살촉 안티 패턴은 확실히 hlovdal의 게시물과 관련이 있으며 그의 가드 조항은 주변을 돌아 다니는 데 효과적입니다. 두 번째 글 머리 기호에 어떻게 대답 할 수 있는지 모르겠습니다. 나는 그것을 진단 할 자격이 없다 :)
Mr Moose

4
"일반적인 경우가 아니라 테스트 예외"인 경우 +1
Roy Tinker

25

명백하게:

Whatever(Arguments)
{
    if(!FileExists(file))
        goto notexists;
    contents = OpenFile(file); // <-- prevents inclusion in if
    if(!SomeTest(contents))
        goto notexists;
    DoSomething(contents);
    return;
notexists:
    DefaultAction();
}

당신은 당신이 악한 해결책에도 열려 있다고 말 했으니 악한 고토를 사용하십시오.

실제로 상황에 따라이 솔루션은 악의적 인 행동을 두 번하거나 악의적 인 추가 변수보다 덜 악할 수 있습니다. 긴 함수의 중간에 (적어도 중간에 리턴으로 인해) 확실하지 않기 때문에 함수에 래핑했습니다. 그러나 긴 기능보다 OK, 기간은 아닙니다.

예외가있을 때, 특히 OpenFile 및 DoSomething이 조건이 충족되지 않으면 예외를 던질 수 있으므로 예외를 쉽게 읽을 수 있으므로 명시적인 검사가 필요하지 않습니다. 반면에 C ++에서 Java 및 C #에서 예외를 발생시키는 작업은 느리게 수행되므로 성능면에서 goto가 여전히 바람직합니다.


"악"에 대한 참고 사항 : C ++ FAQ 6.15 는 "악"을 다음과 같이 정의합니다.

그것은 그런 것을 의미 하며 그런 것은 대부분 피해야 하는 것이지만 항상 피해야 하는 것은 아닙니다 . 예를 들어, "악한 대안 중에서 가장 악한 것이 가장 적을 때마다" "악한"것을 사용하게됩니다.

그리고 그것은 goto이 맥락에서 적용됩니다 . 구조화 된 흐름 제어 구조는 대부분 더 낫지 만 조건에 할당하거나 약 3 레벨 이상 깊이 중첩, 코드 중복 또는 긴 조건과 같이 너무 많은 자체 악을 축적하는 상황에 처하면 goto단순히 끝날 수 있습니다 덜 악한 것.


11
커서는 모든 순수 주의자들을 자극하기 위해 수락 버튼 위로 마우스를 가져갑니다. Oooohh 유혹 : D
Benjol

2
예! 이것은 코드를 작성하는 절대적으로 "올바른"방법입니다. 코드의 구조는 이제 "오류 인 경우 오류 처리, 정상 조치. 오류 인 경우 오류 처리, 정상 조치"입니다. 모든 "정상"코드는 단일 레벨 들여 쓰기로 작성되지만 모든 오류 관련 코드에는 두 레벨 들여 쓰기가 있습니다. 따라서 정상적이고 가장 중요한 코드가 가장 눈에 띄는 위치를 차지하며 순서대로 아래쪽으로 흐름을 매우 빠르고 쉽게 읽을 수 있습니다. 반드시이 답변을 수락하십시오.
hlovdal

2
또 다른 측면은 이런 식으로 작성된 코드가 직교한다는 것입니다. 예를 들어 "if (! FileExists (file)) \ n \ tgoto 존재하지 않는 두 줄;" 이 단일 오류 측면 (KISS) 처리에만 관련되며 가장 중요한 것은 다른 라인에는 영향을 미치지 않습니다 . 이 답변 stackoverflow.com/a/3272062/23118 에는 코드를 직교로 유지해야하는 몇 가지 이유가 나와 있습니다.
hlovdal

5
사악한 해결책에 대해 말하기 : 나는 고토없이 당신의 해결책을 가질 수 있습니다 :for(;;) { if(!FileExists(file)) break; contents = OpenFile(file); if(!SomeTest(contents)) break; DoSomething(contents); return; } /* broken out */ DefaultAction();
herby

4
@herby : 아무도 goto악용 break하지 않기를 원하는 방식으로 학대하고 있기 때문에 귀하의 솔루션은 더 악합니다 . 따라서 코드를 읽는 사람들은 명시 적으로 말하는 goto보다 코드를 읽는 곳에서 더 많은 문제를 겪을 것입니다. 게다가 한 번만 실행되는 무한 루프를 사용하면 다소 혼란 스럽습니다. 불행히도 do { ... } while(0)정확하게 읽을 수있는 것은 아닙니다. 왜냐하면 마지막에 도착했을 때 단지 재미있는 블록 일 뿐이며 C는 (펄과 달리) 다른 블록에서 분리하는 것을 지원하지 않기 때문입니다.
Jan Hudec

12
function FileContentsExists(file) {
    return FileExists(file) ? OpenFile(file) : null;
}

...

contents = FileContentExists(file);
if(contents && SomeTest(contents))
{
    DoSomething(contents);
}
else
{
    DefaultAction();
}

또는 여분의 수컷으로 가서 추가 FileExistsAndConditionMet (file) 메소드를 작성하십시오.
UncleZeiv

@herby 는 파일 형식을 검사 SomeTest하는 경우 ( SomeTest예 : .gif가 실제로 GIF 파일 인지 확인 하는 경우) 파일 존재와 동일한 의미를 가질 수 있습니다 .
Abyx

1
예. 다릅니다. @Benjol은 더 잘 알고 있습니다.
herby

3
... 물론 나는 "여분의 추가 이동"을 의미했습니다 ... :)
UncleZeiv

2
즉, 심지어 내가 이동하지 않습니다 (내가 사지에 라비을 복용 오전 이 극단적 인) ... 나는 지금이 고려 잘 읽을 생각합니다 contents && f(contents). 다른 하나를 저장하는 두 가지 기능?!
herby

12

한 가지 가능성 :

boolean handled = false;

if(FileExists(file))
{
    contents = OpenFile(file); // <-- prevents inclusion in if
    if(SomeTest(contents))
    {
        DoSomething(contents);
        handled = true;
    }
}
if (!handled)
{
    DefaultAction();
}

물론 이것은 다른 방식으로 코드를 약간 더 복잡하게 만듭니다. 스타일 문제입니다.

다른 접근법은 예외를 사용하는 것입니다. 예 :

try
{
    contents = OpenFile(file); // throws IO exception if file not found
    DoSomething(contents); // calls SomeTest() and throws exception on failure
}
catch(Exception e)
{
    DefaultAction();
    // and the exception should be at least logged...
}

이것은 더 단순 해 보이지만 다음 경우에만 해당됩니다.

  • 우리는 어떤 종류의 예외를 예상 DefaultAction()하고 각각에 맞는지 정확하게 알고 있습니다.
  • 파일 처리가 성공할 것으로 예상되며 누락 된 파일 또는 실패 SomeTest()는 분명히 잘못된 조건이므로 예외를 처리하는 것이 적합합니다.

19
안돼 ~! 플래그 변수가 아니라 복잡하고 이해하기 어려우며 (플래그가 사실이 됨) 코드를 리팩토링하기가 어렵 기 때문에 분명히 잘못된 방법입니다.
Abyx

가능한 한 로컬로 제한하지 않습니다. (function () { ... })()Javascript, { flag = false; ... }C-like 등에서
herby

예외 로직의 경우 +1이며, 시나리오에 따라 가장 적합한 솔루션 일 수 있습니다.
Steven Jeuris

4
+1이 'Nooooo!' 재밌어요 특정 상황에서는 상태 변수와 조기 수익이 모두 합리적이라고 생각합니다. 더 복잡한 루틴에서는 복잡성을 추가하는 대신 실제로 수행하는 작업이 논리를 명시 적으로 만들기 때문에 상태 변수를 사용합니다. 아무 문제가 없습니다.
grossvogel

1
이것은 내가 일하는 곳에서 선호하는 형식입니다. 사용 가능한 두 가지 주요 옵션은 "다중 반환"및 "플래그 변수"인 것 같습니다. 평균적으로 진정한 이점이있는 것 같지는 않지만 둘 다 특정 상황에 적합합니다. 일반적인 경우와 함께 가야합니다. 또 다른 "이맥스"대 "Vi"종교 전쟁. :-)
Brian Knoblauch

11

이것은 더 높은 수준의 추상화입니다.

if (WeCanDoSomething(file))
{
   DoSomething(contents);
}
else
{
   DefaultAction();
} 

그리고 이것은 세부 사항을 채 웁니다.

boolean WeCanDoSomething(file)
{
    if FileExists(file)
    {
        contents = OpenFile(file);
        return (SomeTest(contents));
    }
    else
    {
        return FALSE;
    }
}

11

함수는 한 가지 일을해야합니다. 그들은 잘해야합니다. 그들은 그것을해야합니다.
깨끗한 코드의 Robert Martin

어떤 사람들은 그 접근 방식이 약간 극단적 인 것을 알고 있지만 매우 깨끗합니다. 파이썬으로 설명해 드리겠습니다.

def processFile(self):
    if self.fileMeetsTest():
        self.doSomething()
    else:
        self.defaultAction()

def fileMeetsTest(self):
    return os.path.exists(self.path) and self.contentsTest()

def contentsTest(self):
    with open(self.path) as file:
        line = file.readline()
        return self.firstLineTest(line)

그는 기능이 한 가지를해야한다고 말하면 가지 를 의미 합니다. processFile()테스트 결과에 따라 작업을 선택하면 그게 전부입니다. fileMeetsTest()테스트의 모든 조건을 결합한 것이 전부입니다. contentsTest()첫 번째 줄을로 전송하면 firstLineTest()그게 전부입니다.

많은 기능처럼 보이지만 실제로는 영어와 거의 같습니다.

파일을 처리하려면 파일이 테스트를 충족하는지 확인하십시오. 그렇다면, 무언가를하십시오. 그렇지 않으면 기본 조치를 수행하십시오. 파일이 있으면 테스트를 충족하고 내용 테스트를 통과합니다. 내용을 테스트하려면 파일을 열고 첫 번째 줄을 테스트하십시오. 첫 번째 줄에 대한 테스트 ...

물론, 조금 말도 안되지만, 관리자가 세부 사항에 신경 쓰지 않으면 코드의 4 줄만 읽은 후에도 읽기를 중단 할 수 있으며 processFile()여전히 기능에 대한 높은 수준의 지식을 갖습니다.


5
+1 좋은 조언이지만 "한 가지"를 구성하는 것은 현재 추상화 계층에 달려 있습니다. processFile ()은 "하나"이지만 fileMeetsTest ()와 doSomething () 또는 defaultAction () 중 하나입니다. 나는 "한 가지"측면이 선험적 개념을 이해 하지 못하는 초보자들에게 혼란을 줄 수 있다고 우려한다 .
Caleb

1
좋은 목표입니다 ... 그게 내가 말해야 할 전부 ... ;-)
Brian Knoblauch

1
나는 인수를 그런 인스턴스 변수로 암시 적으로 전달하는 것을 좋아하지 않습니다. "불필요한"인스턴스 변수로 가득 차 있으며 상태를 부수고 불변성을 깰 수있는 여러 가지 방법이 있습니다.
hugomg

@Caleb, ProcessFile ()은 실제로 한 가지 일을하고 있습니다. Karl은 자신의 직위에서 수행 할 조치를 결정하기 위해 테스트를 사용하고 다른 방법에 대한 조치 가능성의 실제 구현을 연기하고 있습니다. 더 많은 대체 조치를 추가하는 경우, 즉각적인 메소드에서 로직 중첩이 발생하지 않는 한 메소드의 단일 목적 기준이 여전히 충족됩니다.
S.Robins

6

이것이 호출되는 것과 관련하여 더 많은 요구 사항을 처리하기 위해 코드가 커짐에 따라 화살촉 안티 패턴 으로 쉽게 발전 할 수 있습니다 ( https://softwareengineering.stackexchange.com/a/122625/33922에 제공된 답변에 나와 있음 ) 그런 다음 화살표와 유사한 중첩 된 조건문이있는 코드의 거대한 섹션을 갖는 함정에 빠지게됩니다.

다음과 같은 링크를 참조하십시오.

http://codinghorror.com/blog/2006/01/flattening-arrow-code.html

http://lostechies.com/chrismissal/2009/05/27/anti-patterns-and-worst-practices-the-arrowhead-anti-pattern/

Google에서 찾을 수있는 이것과 다른 안티 패턴에 대해서는 더 많은 것이 있습니다.

Jeff가 자신의 블로그에서 이와 관련하여 제공하는 유용한 팁은 다음과 같습니다.

1) 조건을 보호구로 대체하십시오.

2) 조건부 블록을 별도의 기능으로 분해합니다.

3) 부정 체크를 긍정적 체크로 변환

4) 항상 가능한 빨리 기능적으로 복귀하십시오.

Jeff McConnells의 초기 수익 제안에 관한 Jeff의 블로그 의견도 참조하십시오.

"가독성을 향상시킬 때 리턴을 사용하십시오. 특정 루틴에서는 일단 응답을 알고 있으면 즉시 호출 루틴으로 리턴하려고합니다. 루틴이 추가 정리가 필요하지 않은 방식으로 정의 된 경우 루틴 오류를 감지하고 즉시 반환하지 않으면 더 많은 코드를 작성해야합니다. "

...

"각 루틴의 반품 횟수를 최소화하십시오. 맨 아래에서 읽을 때 루틴을 이해하기가 더 어려워서 어딘가에 리턴 될 가능성을 알지 못합니다. 이러한 이유로, 개선 된 경우에만 신중하게 리턴을 사용하십시오. 가독성. "

나는 15 년 전 제가 배운 내용 때문에 항상 기능 당 1 개의 출입 통제 이론을 구독했습니다. 나는 이것이 코드를 훨씬 쉽게 읽을 수있게하고 유지 관리가 더 쉽다고 생각합니다.


6

이것은 DRY, nogoto 및 no-multiple-returns 규칙을 준수하며 내 의견으로는 확장 가능하고 읽을 수 있습니다.

success = FileExists(file);
if (success)
{
    contents = OpenFile(file);
    success = SomeTest(contents);
}
if (success)
{
    DoSomething(contents);
}
else
{
    DefaultAction();
}

1
표준을 따르는 것이 반드시 좋은 코드는 아닙니다. 현재이 코드 스 니펫에서 미정입니다.
Brian Knoblauch

이것은 단지 2 개의 defaultAction ()을 대체합니다. 조건이 2이면 동일하고 imo가 훨씬 더 나쁜 플래그 변수를 추가합니다.
Ryathal

3
이와 같은 구문을 사용하면 테스트 횟수가 증가함에 따라 다른 코드 if안에 더 많은 코드가 중첩되지 않습니다 if. 또한 실패한 경우 ( DefaultAction()) 를 처리하는 코드 는 한곳에 있으며 디버깅 목적으로 코드가 도우미 함수를 뛰어 넘지 않으며 success변수가 변경된 행에 중단 점을 추가 하면 트리거 된 위의 테스트를 신속하게 표시 할 수 있습니다 중단 점) 및 테스트되지 않은 항목 (아래)
frozenkoi

1
Yeeaah, 나는 그것을 좋아하고 있지만, 나는 다음과 같이 이름 success을 바꿀 것이라고 생각 합니다 ok_so_far:)
Benjol

이것은 (1) 모든 것이 올바르게 될 때 프로세스가 매우 선형 적이며 (2) 화살표 안티 패턴이있을 때 내가하는 일과 매우 유사합니다. 그러나 추가 변수를 추가하지 마십시오. 다음 단계의 전제 조건으로 생각하면 쉽습니다 (이전 단계가 실패했는지 묻는 것과 미묘하게 다릅니다). 파일이 존재하면 파일을여십시오. 파일이 열려 있으면 내용을 읽으십시오. 내용이 있으면 처리하고 기본 조치를 수행하십시오.
Adrian McCarthy가

3

별도의 방법으로 추출한 다음 :

if(!FileExists(file))
{
    DefaultAction();
    return;
}

contents = OpenFile(file);
if(!SomeTest(contents))
{
    DefaultAction();
    return;
}

DoSomething(contents);

또한 허용

if(!FileExists(file))
{
    DefaultAction();
    return Result.FileNotFound;
}

contents = OpenFile(file);
if(!SomeTest(contents))
{
    DefaultAction();
    return Result.TestFailed;
}

DoSomething(contents);
return Result.Success;            

그런 다음 DefaultAction호출을 제거 DefaultAction하고 호출자를 위해 실행을 남길 수 있습니다 .

Result OurMethod(file)
{
    if(!FileExists(file))
    {
        return Result.FileNotFound;
    }

    contents = OpenFile(file);
    if(!SomeTest(contents))
    {
        return Result.TestFailed;
    }

    DoSomething(contents);
    return Result.Success;            
}

void Caller()
{
    // something, something...

    var result = OurMethod(file);
    // if (result == Result.FileNotFound || result == Result.TestFailed), or just
    if (result != Result.Success)        
    {
        DefaultAction();
    }
}

나는 Jeanne Pindar의 접근 방식 도 좋아 합니다.


3

이 특별한 경우에 대한 대답은 충분히 쉽습니다 ...

FileExists와 사이에 경쟁 조건이 있습니다 OpenFile. 파일을 제거하면 어떻게됩니까?

이 특별한 경우를 처리하는 유일한 방법은 건너 뛰는 것입니다 FileExists.

contents = OpenFile(file);
if (!contents) // open failed
    DefaultAction();
else (SomeTest(contents))
    DoSomething(contents);

이 깔끔하게 이러한 문제를 해결 하고 코드 청소기를합니다.

일반적으로 : 문제를 다시 생각하고 문제를 완전히 피하는 다른 솔루션을 고안하십시오.


2

너무 많은 다른 사람을보고 싶어하지 않을 경우 또 다른 가능성의 사용을 드롭하는 것입니다 다른 사람을 모두와 여분의 return 문에 던져. 그렇지 않으면 두 가지 이상의 조치 가능성이 있는지 판별하기 위해 더 복잡한 논리가 필요하지 않는 한 불필요합니다.

따라서 귀하의 예는 다음과 같습니다.

void DoABunchOfStuff()
{
    if(FileExists(file))
    {
        DoSomethingWithFileContent(file);
        return;
    }

    DefaultAction();
}

void DoSomethingWithFileContent(file)
{        
    var contents = GetFileContents(file)

    if(SomeTest(contents))
    {
        DoSomething(contents);
        return;
    }

    DefaultAction();
}

AReturnType GetFileContents(file)
{
    return OpenFile(file);
}

개인적으로 나는 else 절을 사용하여 논리가 어떻게 작동 해야하는지 명시 적으로 언급 하지 않으므로 코드의 가독성을 향상시킵니다. 그러나 일부 코드 미화 도구 는 중첩 논리를 억제 하기 위해 단일 if 문으로 단순화하는 것을 선호합니다 .


2

샘플 코드에 표시된 사례는 일반적으로 단일 if문 으로 축소 될 수 있습니다 . 많은 시스템에서 파일 열기 기능은 파일이 없으면 유효하지 않은 값을 반환합니다. 때로는 이것이 기본 동작입니다. 다른 경우에는 인수를 통해 지정해야합니다. 이는 FileExists테스트가 중단 될 수 있음을 의미하며 , 존재 테스트와 파일 열기 간의 파일 삭제로 인한 경쟁 조건에 도움이 될 수 있습니다.

file = OpenFile(path);
if(isValidFileHandle(file) && SomeTest(file)) {
    DoSomething(file);
} else {
    DefaultAction();
}

파일 존재 테스트를 제거하는 것이 추상화 레벨 분리와 호환되지 않지만, 추상화 할 수없는 여러 테스트 문제를 완전히 회피하는 추상화 레벨 혼합 문제를 직접적으로 다루지는 않습니다. 유효하지 않은 파일 핸들이 "false"에 해당하고 파일 핸들이 범위를 벗어나면 닫힙니다.

OpenFileIfSomething(path:String) : FileHandle {
    file = OpenFile(path);
    if (file && SomeTest(file)) {
        return file;
    }
    return null;
}

...

if ((file = OpenFileIfSomething(path))) {
    DoSomething(file);
} else {
    DefaultAction();
}

2

그러나 frozenkoi와 동의하지만 C #의 경우 TryParse 메서드의 구문을 따르는 것이 도움이 될 것이라고 생각했습니다.

if(FileExists(file) && TryOpenFile(file, out contents))
    DoSomething(contents);
else
    DefaultAction();
bool TryOpenFile(object file, out object contents)
{
    try{
        contents = OpenFile(file);
    }
    catch{
        //something bad happened, computer probably exploded
        return false;
    }
    return true;
}

1

단일 기능으로 너무 많은 일을하기 때문에 코드가보기 흉하다 파일을 처리하거나 기본 조치를 취하려면 다음과 같이 시작하십시오.

if (!ProcessFile(file)) { 
  DefaultAction(); 
}

펄과 루비 프로그래머들은 processFile(file) || defaultAction()

이제 ProcessFile을 작성하십시오.

if (FileExists(file)) { 
  contents = OpenFile(file);
  if (SomeTest(contents)) {
    processContents(contents);
    return true;
  }
}
return false;

1

물론 이와 같은 시나리오에서만 지금까지 갈 수 있지만 다음과 같은 방법이 있습니다.

interface File<T> {
    function isOK():Bool;
    function getData():T;
}

var appleFile:File<Apple> = appleStorage.get(fileURI);
if (appleFile.isOK())
    eat(file.getData());
else
    cry();

추가 필터가 필요할 수 있습니다. 그런 다음이 작업을 수행하십시오.

var appleFile = appleStorage.get(fileURI, isEdible);
//isEdible is of type Apple->Bool and will be used internally to answer to the isOK call
if (appleFile.isOK())
    eat(file.getData());
else
    cry();

이것은 또한 의미가있을 수 있습니다.

function eat(apple:Apple) {
     if (isEdible(apple)) 
         digest(apple);
     else
         die();
}
var appleFile = appleStorage.get(fileURI);
if (appleFile.isOK())
    eat(appleFile.getData());
else
    cry();

어느 것이 최고입니까? 그것은 당신이 직면하고 있는 실제 문제 에 달려 있습니다.
그러나 제거해야 할 것은 구성과 다형성으로 많은 것을 할 수 있다는 것입니다.


1

명백한 문제

if(!FileExists(file)) {
    DefaultAction();
    return;
}
contents = OpenFile(file);
if(!SomeTest(contents))
{
    DefaultAction();
    return;
}        
DoSomething(contents);

나에게 꽤 표준적인 것 같아? 많은 작은 일들이 일어나야하는 그런 종류의 큰 절차에 대해서는, 실패가 후자를 막을 것입니다. 그것이 옵션이라면 예외는 조금 더 깨끗합니다.


0

나는 이것이 오래된 질문이라는 것을 알고 있지만 언급되지 않은 패턴을 발견했다. 주로 변수를 설정하여 나중에 호출하려는 메소드를 결정합니다 (if ... else ... 외부).

이것은 코드 작업을 더 쉽게하기 위해 살펴볼 또 다른 각도입니다. 또한 호출 할 다른 메소드를 추가하거나 특정 상황에서 호출해야하는 적절한 메소드를 변경하려는 경우도 있습니다.

메소드의 모든 언급을 대체하지 않고 (아마도 일부 시나리오가 누락 될 수 있음), 모두 if ... else ... 블록의 끝에 나열되며 읽고 변경하기가 더 쉽습니다. 예를 들어 여러 메소드가 호출 될 수 있지만 중첩 된 if ... else ...에서 메소드가 여러 일치로 호출 될 수있을 때 이것을 사용하는 경향이 있습니다.

상태를 정의하는 변수를 설정하면 깊이 중첩 된 옵션이 많이있을 수 있으며 무언가를 수행하거나 수행하지 않을 때 상태를 업데이트 할 수 있습니다.

이것은 'DoSomething'이 발생했는지 확인하는 질문의 예에서와 같이 사용할 수 있으며 그렇지 않은 경우 기본 조치를 수행합니다. 또는 호출 할 수있는 각 메소드의 상태를 가질 수 있으며 해당되는 경우 설정 한 다음 if ... else ...

중첩 된 if ... else ... 문의 끝에 상태를 점검하고 그에 따라 조치를 취하십시오. 즉, 적용해야하는 모든 위치 대신 메소드에 대한 언급이 하나만 필요합니다.

bool ActionDone = false;

if (Method_1(object_A)) // Test 1
{
    result_A = Method_2(object_A); // Result 1

    if (Method_3(result_A)) // Test 2
    {
        Method_4(result_A); // Action 1
        ActionDone = true;
    }
}

if (!ActionDone)
{
    Method_5(); // Default Action
}

0

중첩 된 IF를 줄이려면

1 / 조기 반품;

2 / 복합 표현 (단락 인식)

따라서 예제는 다음과 같이 리팩토링 될 수 있습니다.

if( FileExists(file) && SomeTest(contents = OpenFile(file)) )
{
    DoSomething(contents);
    return;
}
DefaultAction();

0

나는 "return"을 사용하는 많은 예제를 보았지만 때로는 새로운 함수 생성을 피하고 대신 루프를 사용하고 싶습니다.

while (1) {
    if (FileExists(file)) {
        contents = OpenFile(file);
        if (SomeTest(contents)) {
           DoSomething(contents);
           break;
        } 
    }
    DefaultAction();
    break;
}

줄 수를 줄이거 나 무한 루프를 싫어하는 경우 루프 유형을 "do ... while (0)"으로 변경하고 마지막 "break"를 피할 수 있습니다.


0

이 솔루션은 어떻습니까?

content = NULL; //I presume OpenFile returns a pointer 
if(FileExists(file))
    contents = OpenFile(file);
if(content != NULL && SomeTest(contents))
    DoSomething(contents);
else
    DefaultAction();

OpenFile이 포인터를 반환한다고 가정했지만 반환 할 수없는 기본값 (오류 코드 또는 이와 유사한 것)을 지정하여 값 유형 반환과 함께 작동 할 수도 있습니다.

물론 NULL 포인터에서 SomeTest 메소드를 통해 가능한 조치를 기대하지는 않지만 (아직 알 수 없음) SomeTest (contents) 호출에 대한 NULL 포인터에 대한 추가 검사로 볼 수도 있습니다.


0

가장 우아하고 간결한 솔루션은 전 처리기 매크로를 사용하는 것입니다.

#define DOUBLE_ELSE(CODE) else { CODE } } else { CODE }

다음과 같이 아름다운 코드를 작성할 수 있습니다.

if(FileExists(file))
{
    contents = OpenFile(file);
    if(SomeTest(contents))
    {
        DoSomething(contents);
    }
    DOUBLE_ELSE(DefaultAction();)

이 기술을 자주 사용하면 자동 서식에 의존하기 어려울 수 있으며 일부 IDE에서는 잘못 가정 한 내용에 대해 약간 소리를지를 수 있습니다. 그리고 그 말이 진행됨에 따라 모든 것이 절충되지만 반복되는 코드의 악을 피하기 위해 지불하는 것은 나쁜 가격이 아니라고 생각합니다.


어떤 사람들에게는, 어떤 언어에서는 전 처리기 매크로 사악한 코드입니다. :)
Benjol

@Benjol 당신은 당신이 악한 제안에 개방적이라고 말했습니까? ;)
Peter Olson

네, 절대적으로, 그것은 "악을 피하십시오":)
Benjol

4
이것은 너무 끔찍하다, 난 그냥 그것을 upvote했다 : D
back2dos

셜리, 당신은 심각하지 않습니다 !!!!!!
Jim In Texas

-1

호기심으로 물었고 질문에 특정 언어로 태그가 지정되지 않았기 때문에 (명령어가 마음에 들었음에도 불구하고) 게으른 평가를 지원하는 언어가 완전히 다른 접근법을 허용한다는 점을 추가하는 것이 좋습니다. 이러한 언어에서는식이 필요할 때만 평가되므로 "변수"를 정의하고 의미가있는 경우에만 사용할 수 있습니다. 예를 들어, 게으른 let/ in구조 가있는 가상의 언어에서는 흐름 제어를 잊어 버리고 다음과 같이 씁니다.

let
  contents = ReadFile(file)
in
  if FileExists(file) && SomeTest(contents) 
    DoSomething(contents)
  else 
    DefaultAction()
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.