"if"문을 반전시켜 중첩을 줄입니다.


272

예를 들어 코드에서 ReSharper 를 실행했을 때 :

    if (some condition)
    {
        Some code...            
    }

ReSharper는 위의 경고 (네 스팅을 줄이기 위해 "if"문을 반전시킵니다)를 알려주고 다음 수정 사항을 제안했습니다.

   if (!some condition) return;
   Some code...

왜 더 좋은지 이해하고 싶습니다. 나는 항상 "goto"와 같은 문제가있는 방법의 중간에 "return"을 사용한다고 생각했다.


1
처음에 예외 확인 및 반환은 괜찮다고 생각하지만 조건을 변경하여 무언가 (예 : if (some condtion) return)가 아닌 직접 예외를 확인하고 있습니다.
bruceatk

36
아니요, 성능에는 아무런 영향을 미치지 않습니다.
세스 카네기

3
내 메소드가 나쁜 데이터를 대신 전달받는 경우 ArgumentException을 던지려고 유혹합니다.
asawyer

1
@asawyer 예, 어설 션 오류를 사용하는 것과는 달리 넌센스 입력을 너무 용서하는 함수에 대한 모든 측면 토론이 있습니다. 솔리드 코드 (Solid Code)를 작성 하면이 점에 눈을 뜨게되었습니다. 이 경우 다음과 같습니다 ASSERT( exampleParam > 0 ).
Greg Hendershott

4
어설 션은 매개 변수가 아닌 내부 상태에 대한 것입니다. 내부 상태가 올바른지 확인한 다음 작업을 수행하여 매개 변수의 유효성을 검사합니다. 릴리스 빌드에서는 어설 션을 생략하거나 구성 요소 종료에 맵핑 할 수 있습니다.
Simon Richter

답변:


296

메소드 중간의 리턴이 반드시 나쁘지는 않습니다. 코드의 의도가 더 명확 해지면 즉시 반환하는 것이 좋습니다. 예를 들면 다음과 같습니다.

double getPayAmount() {
    double result;
    if (_isDead) result = deadAmount();
    else {
        if (_isSeparated) result = separatedAmount();
        else {
            if (_isRetired) result = retiredAmount();
            else result = normalPayAmount();
        };
    }
     return result;
};

이 경우 _isDead사실이면 즉시 메소드에서 벗어날 수 있습니다. 대신 이런 식으로 구성하는 것이 좋습니다.

double getPayAmount() {
    if (_isDead)      return deadAmount();
    if (_isSeparated) return separatedAmount();
    if (_isRetired)   return retiredAmount();

    return normalPayAmount();
};   

리팩토링 카탈로그 에서이 코드를 선택했습니다 . 이 특정 리팩토링을 다음과 같이 부릅니다. 중첩 조건부를 가드 절로 바꿉니다.


13
이것은 정말 좋은 예입니다! 리팩토링 된 코드는 case 문과 비슷합니다.
Otherside

16
아마도 맛의 문제 일 것입니다. 가독성을 더 높이기 위해 두 번째와 세 번째 "if"를 "else if"로 변경하는 것이 좋습니다. "반환"명세서를 간과하는 경우, 이전 사례가 실패한 경우에만 다음 사례를 점검하는 것, 즉 점검 순서가 중요하다는 것은 여전히 ​​분명합니다.
foraidt

2
예를 들어,이 간단한, id는 두 번째 방법이 더 낫다는 것에 동의하지만, 그렇게 명백한 일이 일어나기 때문입니다.
Andrew Bullock

자, 우리는 어떻게 예쁜 코드 작업을 작성하고 Visual Studio가 그것을 깨뜨리고 수익을 별도의 줄에 올리지 못하게합니까? 실제로 코드를 그런 식으로 다시 포맷한다고 나에게 묻습니다. 이 코드를 읽기 쉬운 코드로 만듭니다.
Mark T

@Mark T Visual Studio에는 해당 코드가 손상되지 않도록하는 설정이 있습니다.
Aaron Smith

333

미적 일뿐만 아니라 분석법 내부의 최대 중첩 수준을 줄 입니다. 이것은 일반적으로 더하기 쉬운 방법으로 이해하기 쉽기 때문에 플러스로 간주됩니다 (실제로 많은 정적 분석 도구 는 코드 품질의 지표 중 하나로이를 측정합니다).

다른 한편으로, 그것은 또한 당신의 분석법이 여러 출구 점을 가지도록합니다.

개인적으로 저는 ReSharper와 첫 번째 그룹에 동의합니다 (예외가있는 언어에서는 "다중 출구 점"에 대해 설명하기 어리석은 것으로 생각됩니다. 거의 모든 것이 던져 질 수 있으므로 모든 방법에 수많은 잠재적 출구 점이 있습니다).

성능과 관련 하여 모든 언어에서 두 버전 모두 동일 해야 합니다 (IL 수준이 아닌 경우 지터가 코드를 통과 한 후). 이론적으로 이것은 컴파일러에 따라 다르지만 실제로 널리 사용되는 오늘날의 컴파일러는 이보다 훨씬 고급 코드 최적화 사례를 처리 할 수 ​​있습니다.


41
단일 출구 지점? 누가 필요합니까?
sq33G

3
@ sqqG : SESE (물론 대답)에 관한 질문은 환상적입니다. 링크 주셔서 감사합니다!
Jon

나는 단일 출구 지점을 옹호하는 사람들이 있다는 답변을 계속 듣고 있지만 실제로 C #과 같은 언어로 그것을 옹호하는 사람을 본 적이 없습니다 .
토마스 보니 니

1
@AndreasBonini : 증거의 부재는 부재의 증거가 아닙니다. :-)
Jon

물론, 모든 사람들이 자신이 말할 필요가 없다고 생각하는 사람들이 다른 접근법을 좋아한다고 말할 필요성이 있다고 생각합니다. =)
Thomas Bonini

102

이것은 약간의 종교적인 주장이지만, ReSharper에 동의하면 중첩이 적어야한다는 데 동의합니다. 나는 이것이 함수에서 여러 리턴 경로를 갖는 것의 단점보다 중요하다고 생각합니다.

중첩이 적은 주요 이유는 코드 가독성과 유지 관리 성 을 향상시키기위한 입니다. 앞으로 많은 다른 개발자들이 코드를 읽을 필요가 있으며 들여 쓰기가 적은 코드는 일반적으로 읽기가 훨씬 쉽습니다.

전제 조건 은 함수 시작시 일찍 리턴하는 것이 좋은 예입니다. 전제 조건 점검의 존재로 나머지 기능의 가독성에 영향을 미치는 이유는 무엇입니까?

메소드에서 여러 번 리턴하는 것에 대한 부정적인 점은 디버거가 지금 매우 강력하며 특정 함수가 언제 어디서 언제 반환되는지를 쉽게 찾을 수 있습니다.

함수에 여러 개의 리턴이 있어도 유지 보수 프로그래머의 작업에는 영향을 미치지 않습니다.

코드 가독성이 좋지 않습니다.


2
함수의 복수 리턴은 유지 보수 프로그래머에게 영향을줍니다. 함수를 디버깅 할 때 불량 반환 값을 찾으려면 관리자는 반환 할 수있는 모든 위치에 중단 점을 배치해야합니다.
EvilTeach

10
오프닝 브레이스에 중단 점을 두었습니다. 기능을 단계별로 실행하면 검사가 순서대로 수행되는 것을 볼 수있을뿐만 아니라 해당 실행에 대해 어떤 기능이 착륙했는지 확인할 수 있습니다.
John Dunagan

3
여기서 John과 동의하십시오 ... 전체 기능을 단계별로 실행하는 데 문제가 없습니다.
Nailer

5
@Nailer 매우 늦습니다. 특히 동의합니다. 함수가 너무 커서 단계별로 진행할 수 없다면 어쨌든 여러 함수로 분리되어야합니다!
Aidiakapi

1
John 과도 동의하십시오. 어쩌면 구식 학교가 중단 점을 맨 아래에 놓을 것이라고 생각했지만 함수가 무엇을 반환하는지 궁금한 경우 해당 함수를 단계별로 실행하여 왜 돌아 오는 것을 반환하는지 알 수 있습니다. 그것이 반환하는 것을보고 싶다면 마지막 브래킷에 넣으십시오.
user441521

70

다른 사람들이 언급했듯이 성능 저하는 없어야하지만 다른 고려 사항이 있습니다. 이러한 유효한 우려 외에도 일부 상황에서는 문제가 발생할 수 있습니다. 당신이 double대신 처리하고 있다고 가정 해보십시오 .

public void myfunction(double exampleParam){
    if(exampleParam > 0){
        //Body will *not* be executed if Double.IsNan(exampleParam)
    }
}

외관상 동등한 반전 과는 대조적입니다 .

public void myfunction(double exampleParam){
    if(exampleParam <= 0)
        return;
    //Body *will* be executed if Double.IsNan(exampleParam)
}

따라서 어떤 상황에서는 올바르게 뒤집힌 것으로 보이는 것이 아닐 if수도 있습니다.


4
또한 resharper 빠른 수정은 exampleParam> 0을 exampleParam <0이 아니라 exampleParam <= 0으로 되돌려 놓았습니다.
nickd

1
그렇기 때문에 개발자가 nan이 쫓겨날 것이라고 생각하는 경우 수표를 미리 제출해야합니다.
user441521

51

언어가 예외를 지원하기 전날부터 함수가 끝날 때만 복귀한다는 아이디어가 나왔습니다. 그것은 프로그램이 메소드의 끝에 클린업 코드를 넣을 수 있고, 그것이 호출되고 다른 프로그래머가 클린업 코드를 건너 뛰게하는 메소드의 리턴을 숨기지 않을 것을 확신 할 수있게했습니다. . 정리 코드를 건너 뛰면 메모리 또는 리소스 누수가 발생할 수 있습니다.

그러나 예외를 지원하는 언어에서는 그러한 보장을 제공하지 않습니다. 예외를 지원하는 언어에서 명령문 또는 표현식을 실행하면 제어 플로우가 발생하여 메소드가 종료 될 수 있습니다. 즉, finally 또는 키워드를 사용하여 정리를 수행해야합니다.

어쨌든, 나는 많은 사람들이 왜 메소드가 좋은지 이해하지 못하고 '메소드의 끝에서만 리턴'가이드 라인을 인용한다고 생각합니다. 가독성을 높이기 위해 중첩을 줄이는 것이 더 나은 목표 일 것입니다.


6
당신은 예외가 왜 UglyAndEvil [tm] ... ;-)인지 알아 냈습니다. 예외는 공상적이고 값 비싼 변장에 있습니다.
EricSchaefer

16
@Eric 당신은 매우 나쁜 코드에 노출되었을 것입니다. 그것들이 잘못 사용되는 것은 분명하며 일반적으로 고품질 코드를 작성할 수 있습니다.
Robert Paulson

이것은 내가 정말로 찾고있는 대답입니다! 여기에는 큰 대답이 있지만 대부분이 권장 사항이 나온 실제 이유가 아니라 예에 중점을 둡니다.
구스타보 모리

이것이 왜이 전체 논쟁이 처음에 일어 났는지 설명하기 때문에 가장 좋은 대답입니다.
Buttle Butkus

If you've got deep nesting, maybe your function is trying to do too many things.=> 이것은 이전 문구의 올바른 결과가 아닙니다. 코드 C가있는 동작 A를 코드 D가있는 동작 A로 리팩터링 할 수 있다고 말하기 직전에 코드 D는 더 깔끔하고 당연하지만 "너무 많은 것"은 동작이 변경되지 않은 것을 말합니다. 따라서이 결론에는 아무런 의미가 없습니다.
v.oddou

30

나는 가드 조항 인 경우 거꾸로 된 사람들의 이름이 있다고 덧붙이고 싶습니다. 가능할 때마다 사용합니다.

처음에 두 개의 코드 화면이 있고 다른 곳이 없다면 코드를 읽는 것이 싫습니다. 그냥 뒤집어 놓고 돌아옵니다. 그렇게하면 아무도 스크롤 시간을 낭비하지 않을 것입니다.

http://c2.com/cgi/wiki?GuardClause


2
바로 그거죠. 리팩토링에 가깝습니다. 언급 한대로 코드를 쉽게 읽을 수 있습니다.
rpattabi

'반환'은 가드 조항에 충분하지 않으며 끔찍합니다. 내가 할 것 : 경우 (예 : <= 0) WrongParamValueEx ( "[methodName로] 잘못된 입력 PARAM 1 개 값 {0} 던져 ... 그리고 당신은 당신의 응용 프로그램 로그에 예외 및 쓰기를 잡을 필요가있을 것이다
JohnJohnGa

3
@남자. 이것이 실제로 오류라면 당신이 맞습니다. 그러나 종종 그렇지 않습니다. 메소드가 호출되는 모든 곳에서 무언가를 확인하는 대신 메소드를 확인하고 아무것도하지 않고 반환합니다.
Piotr Perak

3
나는 두 화면의 코드, period, ifs 또는 no 인 메소드를 읽는 것을 싫어합니다 . lol
jpmc26

1
나는 개인적으로 정확하게이 이유로 초기 반품을 선호합니다 (또한 중첩 방지). 그러나 이것은 성능에 대한 원래의 질문과 관련이 없으며 아마도 주석이어야합니다.
Patrick M

22

그것은 미학에 영향을 줄뿐만 아니라 코드 중첩을 방지합니다.

실제로 데이터가 유효한지 확인하기위한 전제 조건으로 작동 할 수 있습니다.


18

이것은 물론 주관적이지만 두 가지 측면에서 크게 향상된다고 생각합니다.

  • 함수가 condition보유하고 있는 경우 수행 할 작업이 없음을 즉시 알 수 있습니다.
  • 중첩 수준을 낮 춥니 다. 중첩은 생각보다 가독성을 떨어 뜨립니다.

15

여러 반환 지점은 C에서 문제가되었으며 C ++에서는 각 반환 지점 전에 정리 코드를 복제해야했기 때문에 문제가있었습니다. 가비지 콜렉션으로 try| finally건설 및 using블록, 정말 당신이 그들을 두려워해야 할 이유가 없습니다.

궁극적으로 그것은 당신과 당신의 동료들이 더 쉽게 읽을 수있는 것으로 귀착됩니다.


그게 유일한 이유는 아닙니다. 청소 물건과 같은 실제적인 고려 사항과 관련이없는 의사 코드를 언급하는 학문적 인 이유가 있습니다. 이 엉뚱한 이유는 기본 형태의 명령 구조와 관련이 있습니다. 루프 출구를 중간에 두지 않는 것과 같은 방법입니다. 이러한 방식으로 불변량을 엄격하게 감지하고 행동을 입증 할 수 있습니다. 또는 종료가 입증 될 수 있습니다.
v.oddou

1
뉴스 : 실제로 조기 중단과 수익률이 나쁜 실질적인 이유를 발견했습니다. 이것이 바로 정적 분석이라는 말의 직접적인 결과입니다. 여기, 인텔 C ++ 컴파일러 사용 안내서 종이 : d3f8ykwhia686p.cloudfront.net/1live/intel/… . 핵심 문구 :a loop that is not vectorizable due to a second data-dependent exit
v.oddou

12

성능 측면에서 두 접근 방식간에 눈에 띄는 차이는 없습니다.

그러나 코딩은 성능 이상의 것입니다. 명확성과 유지 관리도 매우 중요합니다. 그리고 성능에 영향을 미치지 않는 이와 같은 경우에는 이것이 유일합니다.

어떤 접근 방식이 선호되는지에 대한 경쟁 학교가 있습니다.

한 가지 견해는 다른 사람들이 언급 한 견해입니다. 두 번째 접근법은 중첩 수준을 줄여 코드 선명도를 향상시킵니다. 이것은 필수 스타일로 자연 스럽습니다. 할 일이 없으면 일찍 돌아올 수도 있습니다.

보다 기능적인 스타일의 관점에서 볼 때 메소드에는 종료 점이 하나만 있어야한다는 것이 또 다른 견해입니다. 기능적 언어의 모든 것은 표현입니다. 따라서 if 문에는 항상 else 절이 있어야합니다. 그렇지 않으면 if 표현식에 항상 값이있는 것은 아닙니다. 따라서 기능적 스타일에서 첫 번째 접근 방식은 더 자연 스럽습니다.


11

가드 절 또는 사전 조건 (아마도 알 수 있듯이)은 특정 조건이 충족되는지 확인한 다음 프로그램 흐름을 중단시킵니다. if성명서의 결과에만 관심이있는 장소에 유용 합니다. 따라서 말하기보다는

if (something) {
    // a lot of indented code
}

조건을 역전시키고 역 조건이 충족되면 중단

if (!something) return false; // or another value to show your other code the function did not execute

// all the code from before, save a lot of tabs

return더럽지 않은 곳은 없습니다 goto. 함수가 실행할 수 없었던 나머지 코드를 보여주기 위해 값을 전달할 수 있습니다.

중첩 된 조건에서이를 적용 할 수있는 가장 좋은 예를 볼 수 있습니다.

if (something) {
    do-something();
    if (something-else) {
        do-another-thing();
    } else {
        do-something-else();
    }
}

vs :

if (!something) return;
do-something();

if (!something-else) return do-something-else();
do-another-thing();

첫 번째 사람이 더 깨끗하다고 ​​주장하는 사람은 거의 없지만 물론 주관적입니다. 일부 프로그래머는 들여 쓰기로 어떤 조건이 작동하는지 알고 싶어하지만 메서드 흐름을 선형으로 유지하는 것이 좋습니다.

나는 precons이 당신의 인생을 바꾸거나 누워있게 할 것을 잠시 제안하지는 않지만 코드를 읽기가 더 쉽다는 것을 알 수 있습니다.


6
나는 내가 소수의 사람이라고 생각합니다. 첫 번째 버전을 읽는 것이 더 쉽다는 것을 알았습니다. 중첩 된 if는 의사 결정 트리를보다 분명하게 만듭니다. 반면에, 몇 가지 전제 조건이 있다면, 그것들을 모두 함수의 맨 위에 두는 것이 낫다는 데 동의합니다.
Otherside

@Otherside : 완전히 동의합니다. 직렬 방식으로 작성된 수익은 두뇌가 가능한 경로를 직렬화해야합니다. 예를 들어 if-tree를 일부 일시적인 로직에 직접 매핑 할 수있는 경우 lisp로 컴파일 할 수 있습니다. 그러나 직렬 리턴 방식은 훨씬 더 어려운 컴파일러를 요구합니다. 여기서 요점은 분명히이 가상 시나리오와는 아무런 관련이 없으며 코드 분석, 최적화 가능성, 수정 증명, 종료 증명 및 변이 감지와 관련이 있습니다.
v.oddou

1
더 많은 둥지를 가진 코드를 쉽게 읽을 수있는 사람은 없습니다. 무언가처럼 블록이 많을수록 한 눈에 쉽게 읽을 수 있습니다. 여기에서 첫 번째 예제를 더 쉽게 읽을 수있는 방법이 없으며 실제 세계에서 이와 같은 대부분의 코드가 더 깊어지고 모든 둥지마다 따라야 할 더 많은 두뇌 힘이 필요할 때 2 개의 둥지 만갑니다.
user441521

1
중첩이 두 배나 깊으면 "언제 뭘해야합니까?"라는 질문에 대답하는 데 시간이 얼마나 걸립니까? 펜과 종이없이 대답하기가 힘들다고 생각합니다. 그러나 모든 가드 절을 사용하면 쉽게 대답 할 수 있습니다.
David Storfer

9

여기에는 몇 가지 좋은 점이 있지만 방법이 매우 길면 여러 반환 점 을 읽을 수 없습니다 . 여러 리턴 포인트를 사용하려는 경우 메소드가 짧아야합니다. 그렇지 않으면 여러 리턴 포인트의 가독성 보너스가 손실 될 수 있습니다.


8

성능은 두 부분으로 구성됩니다. 소프트웨어가 프로덕션 환경에있을 때는 성능이 있지만 개발 및 디버깅 중에 성능을 원합니다. 개발자가 원하는 마지막 것은 사소한 것을 "기다리는 것"입니다. 결국 최적화를 활성화하여 컴파일하면 비슷한 코드가 생성됩니다. 따라서 두 시나리오 모두에서이 작은 요령을 아는 것이 좋습니다.

문제의 사례는 분명합니다. ReSharper가 맞습니다. if명령문을 중첩 하고 코드에서 새 범위를 작성하는 대신 메소드 시작시 명확한 규칙을 설정합니다. 가독성을 높이고 유지 관리가 더 쉬워지고 가고 싶은 곳을 찾기 위해 걸러야하는 규칙의 양을 줄입니다.


7

개인적으로 나는 1 개의 출구를 선호합니다. 메소드를 짧고 요점으로 유지하면 쉽게 달성 할 수 있으며 코드를 작성하는 다음 사람에게 예측 가능한 패턴을 제공합니다.

예.

 bool PerformDefaultOperation()
 {
      bool succeeded = false;

      DataStructure defaultParameters;
      if ((defaultParameters = this.GetApplicationDefaults()) != null)
      {
           succeeded = this.DoSomething(defaultParameters);
      }

      return succeeded;
 }

이것은 함수 내에서 특정 지역 변수의 값을 확인하기 전에 확인하려는 경우에도 매우 유용합니다. 최종 수익에 중단 점을두기 만하면됩니다 (예외가 발생하지 않는 한).


4
bool PerformDefaultOperation () {DataStructure defaultParameters = this.GetApplicationDefaults (); return (defaultParameters! = NULL && this.DoSomething (defaultParameters);} 거기서 수정했습니다 : :)
tchen

1
이 예제는 매우 기본적이고이 패턴의 요점을 놓치고 있습니다. 이 함수 내부에 약 4 개의 다른 검사가있는 것이 아니라 읽기 쉽도록 알려줍니다.
user441521

5

코드가 어떻게 생겼는지 에 대한 많은 좋은 이유 . 그러나 결과는 어떻습니까?

C # 코드와 IL 컴파일 형식을 살펴 보겠습니다.


using System;

public class Test {
    public static void Main(string[] args) {
        if (args.Length == 0) return;
        if ((args.Length+2)/3 == 5) return;
        Console.WriteLine("hey!!!");
    }
}

이 간단한 스 니펫을 컴파일 할 수 있습니다. ildasm으로 생성 된 .exe 파일을 열고 결과를 확인할 수 있습니다. 모든 어셈블러를 게시하지는 않지만 결과를 설명하겠습니다.

생성 된 IL 코드는 다음을 수행합니다.

  1. 첫 번째 조건이 거짓이면 두 번째 조건이있는 코드로 이동합니다.
  2. 그것이 사실이라면 마지막 명령으로 이동합니다. (참고 : 마지막 명령은 반환입니다).
  3. 두 번째 조건에서는 결과가 계산 된 후에도 마찬가지입니다. 비교하고 : Console.WriteLine에 false가 있거나 true이면 끝납니다.
  4. 메시지를 인쇄하고 돌아갑니다.

따라서 코드가 끝까지 점프하는 것 같습니다. 중첩 코드를 사용하여 정상적인 경우 어떻게해야합니까?

using System;

public class Test {
    public static void Main(string[] args) {
        if (args.Length != 0 && (args.Length+2)/3 != 5) 
        {
            Console.WriteLine("hey!!!");
        }
    }
}

결과는 IL 명령어와 매우 유사합니다. 차이점은 조건마다 점프하기 전에 다음과 같습니다. false 인 경우 다음 코드로 이동하고 true 인 경우 종료합니다. 이제 IL 코드가 더 잘 흐르고 3 번 점프합니다 (컴파일러가이 부분을 조금 최적화했습니다). 1. 첫 번째 점프 : 길이가 0 일 때 코드가 다시 점프하는 부분 (3 번째 점프)이 끝납니다. 2. 두 번째 : 두 번째 조건의 중간에 하나의 명령을 피하십시오. 3. 세 번째 : 두 번째 조건이 거짓이면 끝으로 이동합니다.

어쨌든 프로그램 카운터는 항상 점프합니다.


5
이것은 좋은 정보입니다. 그러나 코드를 관리하려는 사람들이 어떤 IL도 보지 못하기 때문에 IL이 "흐르면 더 좋을 것"이라면 개인적으로 그다지 신경 쓰지 않았습니다
JohnIdol

1
C # 컴파일러의 다음 업데이트에서 최적화 과정이 오늘날의 방식과 비교하여 어떻게 작동하는지 실제로 예측할 수는 없습니다. 개인적으로, IL에서 다른 결과를 생성하기 위해 C # 코드를 조정하는 데 시간을 소비하지 않을 것입니다. . 그럼에도 불구하고 다른 옵션에 대해서는 더 열심히 생각합니다. 같은 맥락에서, 나는 JIT 컴파일러가 각 플랫폼에 대해 제공되는 IL로 어떤 종류의 최적화를 수행하고 있는지 전혀 모른다고 생각합니다.
Craig

5

이론적으로 반전 if은 분기 예측 적중률을 높이면 성능이 향상 될 수 있습니다. 실제로 분기 예측이 어떻게 작동하는지, 특히 컴파일 한 후에 정확히 어떻게 작동하는지 알기가 매우 어렵다고 생각하므로 어셈블리 코드를 작성하는 경우를 제외하고는 일상적인 개발에서는 그렇게하지 않을 것입니다.

분기 예측에 대한 자세한 내용은 여기를 참조하십시오 .


4

그것은 논란의 여지가 있습니다. 조기 귀환 문제에 대해서는 "프로그래머들 사이의 합의"가 없습니다. 내가 아는 한 항상 주관적입니다.

가장 자주 적용되는 조건을 작성하는 것이 더 낫기 때문에 성능 주장을하는 것이 가능합니다. 또한 더 명확하다고 주장 할 수 있습니다. 반면에 중첩 테스트를 생성합니다.

나는 당신 이이 질문에 대한 결정적인 대답을 얻을 것이라고 생각하지 않습니다.


나는 당신의 수행 주장을 이해하지 못합니다. 조건은 부울이므로 결과에 관계없이 항상 두 가지 결과가 있습니다. 왜 진술을 취소하면 결과가 변경되는지 알 수 없습니다. (조건에 "NOT"를 추가하면 상당한 양의 처리가 추가됩니다.
Oli

2
최적화는 다음과 같이 작동합니다. 가장 일반적인 경우가 else 블록 대신 if 블록에있는 경우 CPU는 일반적으로 파이프 라인에로드 된 if 블록의 명령문을 이미 가지고 있습니다. 조건이 false를 반환하면 CPU는 파이프 라인을 비우고 ...
Otherside

나는 또한 다른 사람이 가독성을 위해 말하는 것을 좋아합니다. "else"가 아닌 "then"블록에 부정적인 경우가있는 것을 싫어합니다. CPU가 무엇을하는지 모르거나 고려하지 않아도이 작업을 수행하는 것이 좋습니다
앤드류 불락

3

이미 많은 통찰력있는 답변이 있지만 여전히 약간 다른 상황을 지시하고 싶습니다. 사전 조건 대신 실제로 함수 위에 배치 해야하는 단계별 초기화를 생각하십시오. 성공하려면 각 단계를 확인한 후 다음 단계를 계속해야합니다. 이 경우 맨 위에서 모든 것을 확인할 수 없습니다.

중첩 패러다임을 따라 Steinberg의 ASIOSDK로 ASIO 호스트 응용 프로그램을 작성할 때 내 코드를 실제로 읽을 수 없다는 것을 알았습니다. Andrew Bullock이 언급 한 것처럼 8 단계 깊이로 갔고 거기에 디자인 결함이 보이지 않습니다. 물론 내부 코드를 다른 함수로 압축 한 다음 나머지 레벨을 중첩하여 더 읽기 쉽게 만들 수 있었지만 이것은 다소 무작위 인 것처럼 보입니다.

중첩을 가드 절로 대체하여 심지어 종료 코드 대신 함수 내에서 훨씬 일찍 발생 해야하는 정리 코드 부분에 대한 오해를 발견했습니다. 중첩 된 가지로, 나는 결코 그것을 보지 못했을 것입니다.

따라서 반전 된 if가 더 명확한 코드에 기여할 수있는 또 다른 상황 일 수 있습니다.


3

여러 종료 지점을 방지 할 수 있습니다 하면 성능이 향상 . C #에 대해서는 확실하지 않지만 C ++에서 명명 된 반환 값 최적화 (Copy Elision, ISO C ++ '03 12.8 / 15)는 단일 종료 지점이 있어야합니다. 이 최적화는 반환 값을 구성하는 복사를 피합니다 (구체적인 예에서는 중요하지 않습니다). 함수가 호출 될 때마다 생성자와 소멸자를 저장하므로 타이트한 루프에서 성능이 크게 향상 될 수 있습니다.

그러나 99 %의 경우 추가 생성자 및 소멸자 호출을 저장하면 중첩 된 if블록으로 인해 가독성이 손실되지 않습니다 (다른 사람들이 지적했듯이).


2
그곳에. NRVO에 대해 언급 한 사람을 찾기 위해 같은 질문을하는 3 가지 질문에 대한 수십 번의 답변을 3 번 읽었습니다. 고마워
v.oddou

2

그것은 의견의 문제입니다.

내 정상적인 접근 방식은 한 줄 if를 피하고 메소드 중간에 반환하는 것입니다.

메서드의 모든 곳에서 제안하는 것과 같은 줄을 원하지는 않지만 메서드의 맨 위에 많은 가정을 확인하고 실제 작업이 모두 완료되는 경우에만 할 말이 있습니다.


2

내 의견으로는, 당신이 void (또는 쓸모없는 반환 코드를 반환하지 않으면)를 반환하고 중첩을 피하고 동시에 함수가 완료되었음을 명시 적으로 나타내면 가독성이 향상 될 수 있습니다.

실제로 returnValue를 반환하는 경우 중첩은 일반적으로 더 나은 방법으로 returnValue를 한 곳에서 (뒤로) 반환하므로 많은 경우 코드를 유지 관리하기가 더 쉬워 질 수 있습니다.


1

확실하지는 않지만 R #은 멀리뛰기를 피하려고합니다. IF-ELSE가 있으면 컴파일러는 다음과 같은 작업을 수행합니다.

조건 false-> false_condition_label로 멀리 이동

true_condition_label : instruction1 ... instruction_n

false_condition_label : instruction1 ... instruction_n

엔드 블록

조건이 true이면 점프 및 롤아웃 L1 캐시가 없지만 false_condition_label로 점프 할 수 있으며 프로세서가 자체 캐시를 롤아웃해야합니다. 캐시 동기화 비용이 많이 듭니다. R #은 먼 점프를 짧은 점프로 바꾸려고 시도하며이 경우 모든 명령어가 이미 캐시에있을 가능성이 더 큽니다.


0

나는 그것이 언급 한 바와 같이 일반적인 동의가 없다는 것을 당신이 선호하는 것에 달려 있다고 생각합니다. 성가심을 줄이려면 이러한 종류의 경고를 "힌트"로 줄일 수 있습니다.


0

내 생각은 "함수 중간에"의 반환이 "주관적"이어서는 안된다는 것입니다. 이유는 매우 간단합니다.이 코드를 사용하십시오.

    함수 do_something (data) {

      if (! is_valid_data (data)) 
            거짓을 반환;


       do_something_that_take_an_hour (데이터);

       인스턴스 = 새 object_with_very_painful_constructor (데이터);

          if (인스턴스가 유효하지 않음) {
               에러 메시지( );
                return;

          }
       connect_to_database ();
       get_some_other_data ();
       반환;
    }

어쩌면 첫 번째 "반환"은 그렇게 직관적이지 않지만 실제로 절약됩니다. 깨끗한 코드에 대한 "아이디어"가 너무 많기 때문에 "주관적인"나쁜 아이디어를 잃기 위해서는 더 많은 연습이 필요합니다.


예외 언어를 사용하면 이러한 문제가 없습니다.
user441521

0

이런 종류의 코딩에는 몇 가지 장점이 있지만 큰 장점은 빨리 돌아올 수 있다면 응용 프로그램의 속도를 향상시킬 수 있다는 것입니다. IE 나는 전제 조건 X 때문에 오류와 함께 빨리 돌아갈 수 있음을 알고 있습니다. 이를 통해 먼저 오류 사례를 제거하고 코드의 복잡성을 줄입니다. 많은 경우 CPU 파이프 라인이 더 깨끗해 지므로 파이프 라인 충돌 또는 스위치를 중지 할 수 있습니다. 둘째, 루프에 빠진 경우 빠르게 깨거나 돌아 오면 많은 CPU를 절약 할 수 있습니다. 일부 프로그래머는 루프 불변량을 사용하여 이러한 종류의 빠른 종료를 수행하지만 CPU 파이프 라인을 중단하고 메모리 검색 문제를 일으킬 수 있으며 CPU가 외부 캐시에서로드해야한다는 것을 의미합니다. 하지만 기본적으로 당신이 의도 한대로해야한다고 생각합니다. 즉, 루프 또는 함수는 올바른 코드의 추상 개념을 구현하기 위해 복잡한 코드 경로를 만들지 않습니다. 당신이 가지고있는 유일한 도구가 망치라면 모든 것이 못처럼 보입니다.


graffic의 답변을 읽으면 컴파일러가 중첩 코드가 여러 리턴을 사용하는 것보다 실행 코드가 더 빠르다는 방식으로 최적화 된 것처럼 들립니다. 그러나 여러 번의 수익이 더 빠르
더라도이

1
동의하지 않습니다-첫 번째 법칙은 알고리즘을 올바르게 얻는 것에 관한 것입니다. 그러나 우리는 모두 엔지니어입니다. 이는 우리가 보유하고있는 예산으로 수행 할 수있는 자원의 올바른 문제를 해결하는 것입니다. 너무 느린 응용 프로그램은 목적에 맞지 않습니다.
David Allan Finch
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.