이것은 사소한 문제이지만, 이런 식으로 코딩해야 할 때마다 반복이 귀찮게하지만 솔루션 중 어느 것이 나쁘지 않은지 확실하지 않습니다.
if(FileExists(file))
{
contents = OpenFile(file); // <-- prevents inclusion in if
if(SomeTest(contents))
{
DoSomething(contents);
}
else
{
DefaultAction();
}
}
else
{
DefaultAction();
}
- 이런 종류의 논리에 대한 이름이 있습니까?
- 나는 너무 OCD인가?
호기심을 위해서만 악의적 인 코드 제안에 개방적입니다 …
답변
함수 (방법)를 분리하고 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
유형). 어쨌든, 그것은이 질문을 넘어선 것입니다.
답변
사용하는 프로그래밍 언어가 (0) 단락 이진 비교 (즉, false를 반환 SomeTest
하면 호출하지 않는 경우 FileExists
) 및 (1) 할당이 값을 반환하면 (결과 OpenFile
가 할당 된 contents
다음 해당 값이 인수로 전달됨) to SomeTest
), 다음과 같은 것을 사용할 수는 있지만 여전히 단일 코드 =
가 의도적 이라는 점을 지적하면서 코드에 의견을 제시하는 것이 좋습니다 .
if( FileExists(file) && SomeTest(contents = OpenFile(file)) )
{
DoSomething(contents);
}
else
{
DefaultAction();
}
if의 복잡성에 따라 플래그 변수 ( DefaultAction
이 경우 오류를 처리하는 코드로 성공 / 실패 조건 테스트를 분리)를 사용하는 것이 좋습니다.
답변
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;
) 추가 된 행 이외의 다른 코드에는 영향을 미치지 않습니다 . 예를 들어 직교.
테스트 할 때 일반적인 규칙은 일반적인 경우가 아니라 예외 를 테스트 해야합니다 .
답변
명백하게:
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
단순히 끝날 수 있습니다 덜 악한 것.
답변
function FileContentsExists(file) {
return FileExists(file) ? OpenFile(file) : null;
}
…
contents = FileContentExists(file);
if(contents && SomeTest(contents))
{
DoSomething(contents);
}
else
{
DefaultAction();
}
답변
한 가지 가능성 :
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()
는 분명히 잘못된 조건이므로 예외를 처리하는 것이 적합합니다.
답변
이것은 더 높은 수준의 추상화입니다.
if (WeCanDoSomething(file))
{
DoSomething(contents);
}
else
{
DefaultAction();
}
그리고 이것은 세부 사항을 채 웁니다.
boolean WeCanDoSomething(file)
{
if FileExists(file)
{
contents = OpenFile(file);
return (SomeTest(contents));
}
else
{
return FALSE;
}
}