IF 문 반전


39

그래서 저는 몇 년 동안 프로그래밍을 해왔으며 최근에는 ReSharper를 더 많이 사용하기 시작했습니다. ReSharper가 항상 나에게 제안하는 한 가지는 "네 스팅을 줄이기 위해 'if'문을 뒤집는 것"입니다.

이 코드가 있다고 가정 해 봅시다.

foreach (someObject in someObjectList) 
{
    if(someObject != null) 
    {
        someOtherObject = someObject.SomeProperty;
    }
}

그리고 ReSharper는 다음과 같이 제안합니다.

foreach (someObject in someObjectList)
{
    if(someObject == null) continue;
    someOtherObject = someObject.SomeProperty;
}

ReSharper는 중첩이 얼마나 진행 되더라도 항상 IF를 반전시킬 것을 제안합니다. 이 문제는 적어도 어떤 상황에서는 중첩과 같은 것입니다. 나에게 특정 장소에서 무슨 일이 일어나고 있는지 읽고 이해하기가 더 쉬워 보인다. 항상 그런 것은 아니지만, 때때로 더 편하게 둥지를 틀 수 있습니다.

내 질문은 : 개인적인 취향이나 가독성 이외의, 중첩을 줄이는 이유가 있습니까? 성능 차이 또는 내가 모르는 다른 것이 있습니까?



24
여기서 중요한 것은 "Resharper Suggests"입니다. 제안을 살펴보십시오. 코드를 읽기 쉽고 일관성있게 만들려면 사용하십시오. for / for for each 루프와 동일한 작업을 수행합니다.
Jon Raynor

나는 일반적으로 그 resharper 힌트를 강등하고, 항상 따라하지는 않으므로 사이드 바에 더 이상 표시되지 않습니다.
코드 InChaos

1
ReSharper는 부정적인 것을 제거했습니다. 이는 일반적으로 좋은 것입니다. 그러나 블록이 실제로 귀하의 예와 같이 단순하다면 여기에 잘 맞는 삼항 조건 을 제안하지 않았습니다 .
채점

2
ReSharper는 조건부를 쿼리로 옮길 것을 제안하지 않습니까? foreach (someObjectList.Where (object => object! = null)의 someObject) {...}
Roman Reiner

답변:


63

따라 다릅니다. 예제에서 반전 if되지 않은 문장을 갖는 것은 문제가되지 않습니다. 왜냐하면 본문 if은 매우 짧기 때문입니다. 그러나 몸이 자랄 때 일반적으로 진술을 뒤집기를 원합니다.

우리는 오직 인간 일 뿐이며 한 번에 여러 가지 일을하는 것이 어렵습니다.

명령문의 본문이 크면 명령문을 반전하면 중첩이 줄어들뿐만 아니라 개발자에게 해당 명령문 위의 모든 것을 잊고 나머지에만 집중할 수 있음을 알려줍니다. 가능한 빨리 잘못된 값을 제거하기 위해 반전 된 명령문을 사용할 가능성이 높습니다. 그것들이 게임에서 벗어난 것을 알고 나면 남은 유일한 것은 실제로 처리 할 수있는 값입니다.


64
개발자 의견의 흐름이 이런 식으로 진행되는 것을보고 싶습니다. 거기 특별한 인기 망상의 간단하기 때문에 코드에서 너무 많이 중첩있어 break, continue여러가 return구성되지는.
JimmyJames

6
문제는 브레이크 등입니다. 제어 흐름은 머리에 유지해야합니다 .'x가 될 수 없거나 맨 위의 루프를 종료했을 것입니다. '어쨌든 예외를 throw하는 null 검사 인 경우 더 이상 생각할 필요가 없습니다. 그러나 더 미묘한 '목요일 에이 상태에 대해이 코드의 절반 만 실행하십시오'
Ewan

2
@ Ewan : '이것은 x 일 수 없거나 상단의 루프를 종료했을 것입니다'와 '이것은 x 일 수 없거나이 블록에 들어 가지 않을 것입니다'의 차이점은 무엇입니까?
Collin

중요한 x의 복잡성과 의미는 없습니다
Ewan

4
@Ewan : 나는 생각 break/ continue/이 return오버 진정한 장점을 가지고 else블록. 일찍 나가면 2 개의 블록이 있습니다 . 즉, 나가기 블록과 머 무기 블록입니다. 로 else3 개 블록 : 다른, 그리고 어떤 (분기가 취해 무엇이든 사용되는) 다음에 오는 경우. 나는 더 적은 블록을 선호합니다.
Matthieu M.

33

부정적인 것을 피하지 마십시오. 사람들은 CPU가 그들을 사랑할 때조차도 그들을 파싱하는 것을 좋아합니다.

그러나 관용구를 존중하십시오. 우리 인간은 습관의 생물이므로 잡초로 가득 찬 길을 안내하기 위해 잘 착용 된 길을 선호합니다.

foo != null슬프게도 관용구가되었습니다. 더 이상 별도의 부품을 거의 알아 차리지 못합니다. 나는 그것을보고 "오, 이제는 안전하다"고 생각 foo합니다.

그것을 뒤집으려면 (오, 언젠가 제발) 정말로 강한 사건이 필요합니다. 내 눈을 쉽게 빼내고 즉시 이해할 수있는 무언가가 필요합니다.

그러나 당신이 우리에게 준 것은 다음과 같습니다.

foreach (someObject in someObjectList)
{
    if(someObject == null) continue;
    someOtherObject = someObject.SomeProperty;
}

어느 쪽도 구조적으로 동일하지 않습니다!

foreach (someObject in someObjectList)
{
    if(someObject == null) continue;
    someOtherObject = someObject.SomeProperty;

    if(someGlobalObject == null) continue;
    someSideEffectObject = someGlobalObject.SomeProperty;
}

그것은 게으른 뇌가 기대했던 일을하지 않습니다. 나는 독립적 인 코드를 계속 추가 할 수 있다고 생각했지만 할 수는 없습니다. 이것은 내가 지금 생각하고 싶지 않은 것들에 대해 생각하게합니다. 당신은 내 관용구를 깨뜨 렸지만 나에게 더 좋은 것을주지 않았다. 당신이 내 영혼에 불쾌한 작은 자아를 태워 버린 하나의 부정적인에게서 나를 보호하고 싶었 기 때문입니다.

죄송합니다, 아마도 ReSharper가이 90 %의 시간을 제안 할 것입니다. 그러나 null 검사에서 나는 당신이 가장 무시할만한 코너 케이스를 발견했다고 생각합니다. 도구는 읽을 수있는 코드가 무엇인지 가르치는 데 능숙하지 않습니다. 인간에게 물어보십시오. 동료 검토를 수행하십시오. 그러나 맹목적으로 도구를 따르지 마십시오.


1
글쎄, 루프에 더 많은 코드가 있다면, 그것이 어떻게 작동하는지는 그것이 어떻게 결합되는지에 달려 있습니다. 독립 코드가 아닙니다. 질문의 리팩토링 된 코드는 예제에는 맞았지만 격리되어 있지 않을 때는 정확하지 않을 수 있습니다. (+1은 부정을 피하지 않음)
Baldrickk

@Baldrickk을 if (foo != null){ foo.something() }사용하면 foonull이 아닌 경우 신경 쓰지 않고 코드를 계속 추가 할 수 있습니다 . 그렇지 않습니다. 지금은 그렇게 할 필요가 없더라도 "필요한 경우 리팩터링 할 수 있습니다"라고 말함으로써 미래를 망칠 동기가 없습니다. 페. 소프트웨어는 변경하기 쉬운 경우에만 부드럽습니다.
candied_orange

4
"주의하지 않고 코드를 계속 추가하십시오"코드가 관련되어 있거나 (그렇지 않으면 분리되어 있어야 함) 코드 를 추가 하면 동작이 변경 될 가능성이 있으므로 수정중인 함수 / 블록의 기능을 이해하기 위해주의를 기울여야합니다. 그 이상으로. 이와 같은 간단한 경우에는 어느 쪽이든 중요하지 않다고 생각합니다. 더 복잡한 경우에는 코드를 이해하는 것이 우위를 차지할 것으로 예상되므로 가장 명확하다고 생각되는 스타일 중 하나를 선택할 수 있습니다.
Baldrickk

관련되고 얽힌 것은 같은 것이 아닙니다.
candied_orange

1
부정을 피하지 마십시오 : D
VitalyB

20

휴식이 중첩보다 나쁜지에 대한 오래된 주장입니다.

사람들은 매개 변수 확인을 위해 조기 반환을받는 것처럼 보이지만 대량의 방법으로 눈살을 찌푸 렸습니다.

개인적으로 나는 더 많은 중첩을 의미하더라도 계속, 휴식, 이동 등을 피합니다. 그러나 대부분의 경우 두 가지를 모두 피할 수 있습니다.

foreach (someObject in someObjectList.where(i=>i != null))
{
    someOtherObject = someObject.SomeProperty;
}

분명히 중첩은 많은 레이어가있을 때 따라하기가 어렵습니다.

foreach (someObject in someObjectList) 
{
    if(someObject != null) 
    {
        someOtherObject = someObject.SomeProperty;
        if(someObject.x > 5) 
        {
            x ++;
            if(someObject.y > 5) 
            {
                x ++;
            }
        }
    }
}

최악은 당신이 둘 다있을 때입니다

foreach (someObject in someObjectList) 
{
    if(someObject != null) 
    {
        someOtherObject = someObject.SomeProperty;
        if(someObject.x > 5) 
        {
            x ++;
            if(someObject.y > 5) 
            {
                x ++;
                return;
            }
            continue;
        }
        someObject.z --;
        if(myFunctionWithSideEffects(someObject) > 6) break;
    }
}

11
이 라인을 좋아하십시오. foreach (subObject in someObjectList.where(i=>i != null))왜 그런 생각을하지 않았는지 모르겠습니다.
Barry Franklin

@BarryFranklin ReSharper는 foreach에서 "LINQ- 표현식으로 변환"과 함께 녹색 점선 밑줄이 있어야합니다. 작업에 따라 Sum 또는 Max와 같은 다른 linq 메소드도 수행 할 수 있습니다.
케빈 요금

@BarryFranklin 아마도 낮은 수준으로 설정하고 임의로 사용하는 것이 좋습니다. 이 예제와 같은 사소한 경우에는 잘 작동하지만 더 복잡한 코드에서 더 복잡한 조건부 부분을 Linqifiable 비트로 선택하고 나머지 본문은 원래보다 이해하기 어려운 것을 만드는 경향이 있습니다. 나는 전체 루프를 Linq로 변환 할 수있을 때 결과가 종종 합리적이기 때문에 적어도 제안을 시도 할 것이다. 그러나 절반과 절반의 결과는 추악한 IMO를 얻는 경향이 있습니다.
Dan Neely

아이러니하게도, resharper에 의한 편집은 if 뒤에 하나의 라이너가 있기 때문에 정확히 동일한 수준의 중첩을 갖습니다.
피터

그래, 중괄호는 없어! 분명히 허용됩니다 : /
Ewan

6

문제 continue는 코드에 줄을 더 추가하면 로직이 복잡해지고 버그가 포함될 가능성이 있다는 것입니다. 예 :

foreach (someObject in someObjectList)
{
    if(someObject == null) continue;
    someOtherObject = someObject.SomeProperty;

    ...  imagine that there is some code here ...

    // added later by a Junior Developer
    doSomeOtherFunction(someObject);
}

당신이 전화를하고 싶은가 doSomeOtherFunction()에 대한 모든 someObject라는, 또는 단지 null이 아닌 경우? 원래 코드를 사용하면 옵션이 있으며 더 명확합니다. 으로 continue한 "오, 그래, 다시 거기 우리가 널 (null)을 생략"잊어 버릴 당신이 불가능하거나 정확하지 않을 수 있습니다 방법의 시작이 통화를 보류하지 않는 한 당신은 모든 someObjects 위해 호출 할 수있는 옵션이 없습니다 다른 이유로.

예, 많은 중첩이 추악하고 혼란 스러울 수 있지만이 경우 전통적인 스타일을 사용합니다.

보너스 질문 : 왜 그 목록에 null이 있습니까? 처음에는 null을 추가하지 않는 것이 좋습니다.이 경우 처리 논리가 훨씬 간단합니다.


12
루프 내부에 코드가 충분하면 스크롤없이 맨 위에 null 검사가 표시되지 않습니다.
Bryan Boettcher

@Bryan Boettcher는 동의했지만 모든 코드가 잘 작성된 것은 아닙니다. 심지어 몇 줄의 복잡한 코드조차도 계속을 잊어 버릴 수 있습니다. 실제로 난에 확인 해요 return들은 "깨끗한 휴식"을 형성하지만, IMO 원인들 breakcontinue혼란을 야기하고, 대부분의 경우, 가장 피해야합니다.
user949300

4
@ user949300 10 년의 개발 끝에, 나는 결코 휴식을 찾거나 혼란을 야기하지 않았다. 나는 그들을 혼란에 빠뜨 렸지만 결코 원인이되지는 않았다. 일반적으로 개발자에 대한 잘못된 결정은 원인이었으며, 어떤 무기를 사용하든 혼란 스러웠을 것입니다.
corsiKa

1
@corsiKa Apple SSL 버그 는 실제로는 버그이지만, 쉽게 중단되거나 버그 일 수 있습니다. 그러나 코드는 끔찍한 ...
user949300

3
@ Brian Boettcher 문제는 계속하거나 여러 수익을 얻는다고 생각하는 동일한 개발자도 큰 메소드 본문에 파 묻는 것도 괜찮다고 생각합니다. 따라서 계속 / 다중 리턴에 대한 모든 경험은 코드의 혼란에 빠졌습니다.
Andy

3

아이디어는 초기 수익입니다. 초기 return루프에서 조기된다 continue.

이 질문에 대한 많은 좋은 대답 : 함수에서 일찍 반환하거나 if 문을 사용해야합니까?

이 질문은 의견에 근거하여 종결되었지만 430 개 이상의 투표가 조기 반환에 찬성하고, ~ 50 투표는 "의존적"이며, 8 개는 조기 반환에 반대합니다. 따라서 예외적으로 강한 합의가 있습니다 (그렇거나 계산이 잘못되어 그럴듯합니다).

개인적으로 루프 본문이 조기 계속되고 문제가되기에 충분히 길다면 (3-4 줄) 루프 본문을 조기 계속 대신 조기 리턴을 사용하는 함수로 추출하는 경향이 있습니다.


2

이 기술은 해당 조건이 충족 될 때 주요 방법이 발생할 때 사전 조건을 인증하는 데 유용합니다.

우리는 종종 ifs직장을 거꾸로 돌리고 다른 팬텀을 사용합니다. PR을 검토하는 동안 동료가 어떻게 세 가지 수준의 중첩을 제거 할 수 있는지 지적 할 수있었습니다. if를 롤아웃하기 때문에 덜 자주 발생합니다.

다음은 롤아웃 할 수있는 장난감의 예입니다.

function foobar(x, y, z) {
    if (x > 0) {
        if (z != null && isGamma(y)) {
            // ~10 lines of code
            // return something
        }
        else {
           return y
        }
    }
    else {
      return y + z
    }
}

이와 같은 코드는 흥미로운 사례가 하나 있습니다 (나머지는 단순히 리턴 또는 작은 문장 블록). 위의 내용을 다음과 같이 바꾸는 것은 쉽지 않습니다.

function foobar(x, y, z) {
    if (x <=0) {
        return y + z
    }
    if (z == null || !isGamma(y) {
       return y
    }
    // ~ 10 lines of code
    // return something
}

여기에 포함되지 않은 10 줄의 중요한 코드는 함수에 삼중 들여 쓰기가 아닙니다. 첫 번째 스타일이 있다면 긴 블록을 메소드로 리팩토링하거나 들여 쓰기를 허용하려는 유혹을받습니다. 한곳에서 이것은 간단한 절약이지만 많은 클래스의 많은 메소드에서 코드를 깨끗하게하기 위해 추가 함수를 생성 할 필요가 없으며 많은 끔찍한 멀티 레벨이 없습니다. 들여 쓰기 .


OP의 코드 샘플에 대한 응답으로 지나치게 열악한 붕괴에 동의합니다. 특히 내가 사용하는 스타일 인 1TB부터 반전은 코드 크기를 증가시킵니다.


0

Resharpers 제안은 종종 유용하지만 항상 비판적으로 평가해야합니다. 미리 정의 된 코드 패턴을 찾고 변환을 제안하지만, 사람이 코드를 읽을 수있게 만드는 모든 요소를 ​​고려할 수는 없습니다.

다른 모든 것은 동일하고 중첩이 적은 것이 바람직하므로 ReSharper가 중첩을 줄이는 변환을 제안합니다. 그러나 다른 모든 것들은 같지 않습니다 .이 경우 변환을하려면을 도입해야합니다. continue즉, 결과 코드를 실제로 따르기가 더 어렵습니다.

에서 일부 의 경우, A의 중첩 수준을 교환하는 것은 continue 수있는 참으로 개선 될 수 있지만이 ReSharpers 기계적인 규칙의 이해를 넘어 문제의 코드의 의미에 따라 달라집니다.

귀하의 경우 ReSharper 제안은 코드를 악화시킵니다. 그냥 무시하십시오. 또는 더 나은 방법 : 제안 된 변환을 사용하지 말고 코드를 개선 할 수있는 방법에 대해 약간의 생각을하십시오. 동일한 변수를 여러 번 할당 할 수 있지만 이전 할당은 덮어 쓰기 때문에 마지막 할당 만 적용됩니다. 이것은 않는 버그와 같은 모습을. ReSharpers 제안은 버그를 수정하지는 않지만 일부 모호한 논리가 진행되고 있음을 조금 더 분명하게 만듭니다.


0

여기서 더 큰 요점은 루프의 목적이 루프 내에서 흐름을 구성하는 가장 좋은 방법을 결정 한다는 것입니다 . 나머지 요소를 반복하기 전에 일부 요소를 필터링하는 경우 continue조기 종료는 의미가 있습니다. 그러나 루프 내에서 다른 종류의 처리를 수행하는 경우 다음 if과 같이 분명 하게 이해하는 것이 더 합리적 일 수 있습니다 .

for(obj in objectList) {
    if(obj == null) continue;
    if(obj.getColour().equals(Color.BLUE)) {
        // Handle blue objects
    } else {
        // Handle non-blue objects
    }
}

Java Streams 또는 다른 기능 관용구에서 다음을 사용하여 필터링과 루핑을 분리 할 수 ​​있습니다.

items.stream()
    .filter(i -> (i != null))
    .filter(i -> i.getColour().equals(Color.BLUE))
    .forEach(i -> {
        // Process blue objects
    });

우연히 이것은 이것이 Perl의 반전에 대해 좋아하는 것 중 하나가 ( unless) 단일 문에 적용되어 다음과 같은 코드를 허용하므로 매우 읽기 쉽습니다.

foreach my $item (@items) {
    next unless defined $item;
    carp "Only blue items are supported! Failed on item $item" 
       unless ($item->get_colour() eq 'blue');
    ...
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.