함수에서 일찍 반환하거나 if 문을 사용해야합니까? [닫은]


304

나는 종종 이런 종류의 함수를 두 가지 형식으로 작성했으며 한 형식이 다른 형식보다 선호되는지, 왜 그런지 궁금했습니다.

public void SomeFunction(bool someCondition)
{
    if (someCondition)
    {
        // Do Something
    }
}

또는

public void SomeFunction(bool someCondition)
{
    if (!someCondition)
        return;

    // Do Something
}

나는 코딩하는 동안 두뇌가 작동하는 방식이기 때문에 일반적으로 첫 번째 코드로 코딩하지만, 오류 처리를 즉시 처리하고 읽기가 더 쉽기 때문에 두 번째 코드를 선호한다고 생각합니다.


9
나는이 토론에 약간 늦었 기 때문에 나는 이것을 대답에 넣지 않을 것이다. 나는 또한 2 년 전에 이것을 생각했습니다 : lecterror.com/articles/view/code-formatting-and-readability 두 번째는 읽기, 수정, 유지 및 디버깅하기가 더 쉽다는 것을 알았 습니다. 하지만 어쩌면 그것은 저뿐입니다 :)
Dr Hannibal Lecter


4
이제이 질문은 의견 기반 질문의 좋은 예입니다
Rudolf Olah

2
한 방향에서 다른 방향으로 절대적인 증거가 없다면 어떻게해야합니까? 한 방향과 다른 방향으로 충분한 논증이 제공되고 답변이 맞다면 투표가 이루어지면 매우 유용합니다. 이와 같은 닫는 질문이이 사이트의 가치에 해로운 것으로 나타났습니다.
gsf

9
그리고 의견 기반 질문과 답변을 좋아합니다. 그들은 대다수가 선호하는 것을 말해 주므로 다른 사람들이 읽을 수있는 코드를 작성할 수 있습니다.
Zygimantas

답변:


402

나는 두 번째 스타일을 선호합니다. 적절하게 예외를 끝내거나 예외를 발생시키고, 빈 줄을 넣은 다음 메소드의 "실제"본문을 추가하여 잘못된 사례를 먼저 가져옵니다. 읽는 것이 더 쉽다는 것을 알았습니다.


7
스몰 토크는 이러한 "가드 조항"을 호출합니다. 최소한 켄트 벡 (Kent Beck)은 스몰 토크 모범 사례 패턴에서 이들을 부릅니다. 그것이 일반적인 용어인지 모르겠습니다.
Frank Shearar

154
일찍 나가면 제한된 정신 스택에서 물건을 터뜨릴 수 있습니다. :)
Joren

50
나는 이것을 "바운서 패턴"이라고 불렀습니다. 문에 들어가기 전에 나쁜 경우를 없애십시오.
RevBingo

14
나는 심지어 멀리 나아가서 이것이 반드시 옳은 일이라고 말합니다.
Oliver Weiler

38
또한 처음에 테두리를 제거해도 들여 쓰기를 계속 추가하지 않아도됩니다.
doppelgreener

170

확실히 후자. 전자는 지금 나쁘게 보이지 않지만 더 복잡한 코드를 얻을 때 아무도 이것을 생각할 수 없다고 생각합니다.

public int SomeFunction(bool cond1, string name, int value, AuthInfo perms)
{
    int retval = SUCCESS;
    if (someCondition)
    {
        if (name != null && name != "")
        {
            if (value != 0)
            {
                if (perms.allow(name)
                {
                    // Do Something
                }
                else
                {
                    reval = PERM_DENY;
                }
            }
            else
            {
                retval = BAD_VALUE;
            }
        }
        else
        {
            retval = BAD_NAME;
        }
    }
    else
    {
        retval = BAD_COND;
    }
    return retval;
}

보다 읽기 쉽다

public int SomeFunction(bool cond1, string name, int value, AuthInfo perms)
{
    if (!someCondition)
        return BAD_COND;

    if (name == null || name == "")
        return BAD_NAME;

    if (value == 0)
        return BAD_VALUE;

    if (!perms.allow(name))
        return PERM_DENY;

    // Do something
    return SUCCESS;
}

나는 단일 출구 지점의 이점을 결코 이해하지 못했음을 인정합니다.


20
단일 출구 지점의 장점은 ... 단일 출구 지점이 있다는 것입니다! 귀하의 예를 들어, 몇 가지 포인트가 돌아올 수 있습니다. 더 복잡한 함수를 사용하면 반환 값의 형식이 변경되면 종료 지점으로 바뀔 수 있습니다. 물론, 단일 종료점을 강제하는 것이 의미가없는 경우가 있습니다.
JohnL

71
@JohnL 큰 기능은 여러 개의 종료 점이 아닌 문제입니다. 추가 함수 호출로 인해 코드가 엄청나게 느려지는 상황에서 작업하지 않는 한 ...
Dan Rosenstark

4
@Yar : 맞습니다. 그러나 요점은 의미합니다. 나는 누군가를 시도하고 개조하려고하지 않을 것이며, 가능한 적은 출구 점을 찾는 장점을 지적하고 있었다 (그리고 Jason의 사례는 어쨌든 일종의 짚맨이었다). 다음에 SomeFunction이 때때로 홀수 값을 반환하는 이유를 알아 내려고 머리카락을 뽑을 때 , 반환 직전에 로깅 호출을 추가 할 수 있습니다 . 버거 중 하나만 있으면 디버깅하기가 훨씬 쉽습니다! :)
JohnL

76
@Jason Viers 하나의 return value;도움 이되는 것처럼 !?! 그렇다면 value = ...이 할당과 최종 수익간에 가치가 바뀌지 않을 것이라는 확신을 갖지 못하는 단점이 있지만, 십여 개를 사냥 해야합니다. 적어도 즉각적인 결과는 더 이상 결과를 바꾸지 않을 것이라는 점이 분명합니다.
Sjoerd

5
@ Sjoed : 나는 두 번째 Sjoerd입니다. 결과를 기록하려면 발신자 사이트에 기록 할 수 있습니다. 이유를 알고 싶다면 각 종료점 / 할당에 기록해야이 측면에서 두 가지가 동일합니다.
Matthieu M.

32

그것은 일반적으로-일반적으로 함수를 조기에 벗어나기 위해 많은 코드를 이동하려고 시도하지 않을 것입니다. 컴파일러는 일반적으로 나를 위해 처리합니다. 그러나 상단에 필요한 기본 매개 변수가 있고 계속 진행할 수 없으면 조기에 나올 것입니다. 마찬가지로, 조건 if이 기능에서 거대한 블록을 생성 하면 그 결과로 조기에 돌파가 발생합니다.

그러나 함수가 호출 될 때 일부 데이터가 필요하면 반환하는 대신 일반적으로 예외 (예 참조)가 발생합니다.

public int myFunction(string parameterOne, string parameterTwo) {
  // Can't work without a value
  if (string.IsNullOrEmpty(parameterOne)) {
    throw new ArgumentNullException("parameterOne");
  } 
  if (string.IsNullOrEmpty(parameterTwo)) {
    throw new ArgumentNullException("parameterTwo");
  }

  // ...      
  // Do some work
  // ...

  return value;
}

9
그리고 최종 결과를 유지 관리 할 수 ​​있다면 누가 어떤 스타일을 선택했는지 걱정합니까?
Jeff Siver

3
@ Jeff Siver-왜 이것이 "전쟁적인"스타일 질문 인 경향이 있는가?
rjzii

1
여기서 중요한 것은 그가 일찍 돌아 오는 대신 예외를 던지고 있다는 것입니다. 유효성 검사를 위해 반환 값을 다른 용도로 사용해서는 안됩니다. 다양한 조건이 있고 메소드를 사용하여 코드에 실패한 이유를 알려주려면 어떻게해야합니까? 갑자기 하나의 단일 메소드에서 실제 비즈니스 데이터, 아무것도 (빈 결과) 또는 많은 다른 문자열, 코드, 숫자 등을 반환 할 수 있습니다. 왜 실패했는지 설명하기 위해. 고맙지 만 사양 할게.
DanMan

컴파일러가 제안한 것처럼 순환 복잡성에 대해서는 어떻습니까? 도움이된다면 코드를 중첩하지 않는 것이 낫지 않습니까?
l --''''''--------- '' '' '' '' '' '' ''

24

나는 일찍 돌아 오는 것을 선호합니다.

하나의 진입 점과 하나의 출구 점이 있으면 항상 머리의 전체 코드를 출구 점까지 추적해야합니다 (다른 코드 조각이 결과에 다른 일을하는지 여부는 알 수 없으므로 존재할 때까지 추적해야합니다). 어떤 분기가 최종 결과를 결정하는지는 중요하지 않습니다. 이것은 따르기가 어렵다.

하나의 항목과 여러 항목이 있으면 결과가있을 때 돌아와서 결과를 추적 할 필요가 없습니다. 메소드 본문을 더 많은 단계로 나누는 것과 같습니다. 각 단계마다 결과를 반환하거나 다음 단계에서 운을 시험해 볼 수 있습니다.


13

수동으로 정리해야하는 C 프로그래밍에서는 원 포인트 리턴에 대해 언급해야 할 것이 많습니다. 지금 무언가를 정리할 필요가 없더라도 누군가가 함수를 편집하고 무언가를 할당하고 돌아 오기 전에 정리해야 할 수도 있습니다. 그런 일이 발생하면 모든 반환 진술을 살펴 보는 악몽이 될 것입니다.

C ++ 프로그래밍에는 소멸자가 있으며 심지어 스코프 종료 경비원이 있습니다. 코드가 처음부터 예외 안전을 보장하기 위해 이러한 모든 것이 여기에 있어야하므로 코드가 조기 종료로부터 잘 보호되므로 논리적 단점이 없으며 순수하게 스타일 문제입니다.

"최종"블록 코드가 호출되는지 여부와 종료자가 무언가를 보장해야하는 상황을 처리 할 수 ​​있는지 여부에 대해 Java에 대해 충분히 알지 못합니다.

C # 나는 확실히 대답 할 수 없다.

D- 언어는 적절한 내장 스코프 출구 보호대를 제공하므로 조기 퇴장 준비가 잘되어 있으므로 스타일 이외의 문제는 발생하지 않아야합니다.

물론 함수는 처음에는 그리 길지 않아야하며, 거대한 switch 문이 있으면 코드도 잘못 반영됩니다.


1
C ++에서는 반환 값 옵티마이 제이션을 통해 컴파일러에서 값을 반환 할 때 일반적으로 발생하는 복사 작업을 생략 할 수 있습니다. 그러나 다양한 조건에서 수행하기 어렵고 여러 반환 값이 그러한 경우 중 하나입니다. 즉, C ++에서 여러 반환 값을 사용하면 실제로 코드가 느려질 수 있습니다. 이것은 확실히 MSC를 보유하고 있으며 가능한 여러 반환 값으로 RVO를 구현하는 것이 매우 까다로운 (아직 불가능하지는 않지만) 모든 컴파일러에서 문제가 될 수 있습니다.
Eamon Nerbonne

1
C에서는 단지 goto2 점 리턴을 사용하십시오. 예 (주석에서는 코드 형식을 지정할 수 없음) :foo() { init(); if (bad) goto err; bar(); if (bad) goto err; baz(); return 0; err: cleanup(); return 1; }
mirabilos

1
goto 대신 "추출 방법"을 선호합니다. 반환 값 변수 또는 goto를 구현해야한다고 생각할 때 정리 코드가 항상 호출되도록하기 위해 여러 함수로 분리해야하는 냄새입니다. 이렇게하면 Guard Clauses 및 기타 조기 반환 기술을 사용하여 복잡한 조건부 코드를 단순화하면서 정리 코드가 항상 실행되도록 할 수 있습니다.
BrandonLWhite

"누군가가 당신의 기능을 편집 할 수 있습니다"-이것은 의사 결정에 대한 우스운 설명입니다. 미래에는 누구나 할 수있는 일이 있습니다. 그렇다고 미래에 누군가가 일을 중단하지 못하도록 오늘 특정한 일을해야한다는 의미는 아닙니다.
Victor Yarema

코드를 유지 보수 가능하게 만들었습니다. 실제 코드는 비즈니스 목적으로 작성되었으며 나중에 개발자가 나중에 코드를 변경해야하는 경우가 있습니다. 캐싱을 소개하고 현대적인 C ++로 제대로 다시 작성해야했던 스파게티 엉망을 상기시키기 위해 CURL을 패치했지만 그 대답을 썼을 때
CashCow

9

승리를위한 초기 수익. 추악한 것처럼 보일 수 있지만 if특히 검사해야 할 여러 조건이있는 경우 큰 래퍼 보다 훨씬 덜 추 합니다.


9

둘 다 사용합니다.

경우 DoSomething코드의 3-5 라인 인 다음 코드는 최초의 포맷 방법을 사용하여 예쁘다.

그러나 그보다 많은 행이 있으면 두 번째 형식을 선호합니다. 개폐 브래킷이 동일한 화면에 있지 않은 경우가 마음에 들지 않습니다.


2
아름다운 방법은 없습니다! 들여 쓰기가 너무 많습니다!
JimmyKane

@JimmyKane 3-5 줄에서 발생할 수있는 들여 쓰기가 너무 많으며, 특히 레벨 당 2 (?) 줄이 필요할 때 나머지 부분은 들여 쓰기됩니다. 하나는 제어 구조 및 블록 시작, 하나는 블록 끝입니다.
중복 제거기

8

단일 엔트리 단일 종료의 고전적인 이유는 그렇지 않으면 공식적인 의미론이 말로 표현할 수 없을 정도로 추악 해지기 때문입니다 (GOTO가 유해한 것으로 간주 된 것과 동일한 이유).

다시 말해, 1 회만 반환하면 소프트웨어가 루틴을 종료 할시기를 쉽게 추론 할 수 있습니다. 또한 예외에 대한 논쟁입니다.

일반적으로 조기 반품 접근법을 최소화합니다.


그러나 공식 분석 도구의 경우 도구에 필요한 시맨틱으로 외부 함수를 합성하고 사람이 읽을 수있는 코드를 유지할 수 있습니다.
Tim Williscroft

@ 팀. 그것은 당신이하고 싶은 양과 당신이 분석하고있는 것에 달려 있습니다. 코더가 제정신이라면 SESE를 꽤 읽을 수 있습니다.
Paul Nathan

분석에 대한 나의 태도는 Self 프로젝트의 최적화에 의해 형성됩니다. 동적 메소드 호출의 99 %가 정적으로 해결되고 제거 될 수 있습니다. 그런 다음 꽤 직선 코드를 분석 할 수 있습니다. 작업중인 프로그래머로서, 내가 작업 할 대부분의 코드는 일반 프로그래머가 작성했다고 가정 할 수 있습니다. 그래서 그들은 아주 좋은 코드를 작성하지 않을 것입니다.
Tim Williscroft

@ 팀 : 충분합니다. 나는 정적 언어가 여전히 많은 놀이를하고 있다고 지적해야한다고 생각합니다.
Paul Nathan

예외에 직면하지 않는 이유. 그렇기 때문에 단일 출구가 C에서는 훌륭하지만 C ++에서는 아무것도 사지 않습니다.
peterchen

7

개인적으로, 나는 처음에 합격 / 불합격 상태 점검을 선호합니다. 이를 통해 함수의 맨 위에 가장 일반적인 실패를 대부분 나머지 로직과 그룹화 할 수 있습니다.


6

따라 다릅니다.

기능의 나머지 부분을 무의미하게 실행시킬 수있는 명백한 막 다른 데드 엔드 조건이있는 경우 조기 반환. *

함수가 더 복잡하고 그렇지 않으면 여러 개의 종료 점이있을 수있는 경우 Retval + 단일 리턴을 설정하십시오 (가독성 문제).

* 이것은 종종 디자인 문제를 나타낼 수 있습니다. 나머지 코드를 실행하기 전에 많은 메서드가 외부 / 매개 변수 상태 등을 확인해야하는 경우 호출자가 처리해야하는 것일 수 있습니다.


6
공유 할 수있는 코드를 작성할 때 제 진언은 "아무것도 가정하지 않습니다. 입력과 의존하는 외부 상태를 항상 확인해야합니다. 누군가가 잘못된 데이터를 제공했기 때문에 무언가를 손상시킬 가능성보다 예외를 던지는 것이 좋습니다.
TMN

@TMN : 좋은 지적입니다.
Bobby Tables

2
여기서 중요한 점은 OO에서 반환 하지 않고 예외던진다는 것 입니다. 여러 반환 값이 나빠질 수 있으며, 여러 예외 발생이 코드 냄새 일 필요는 없습니다.
Michael K

2
@MichaelK : 사후 조건을 충족시킬 수없는 경우 메소드에서 예외가 발생합니다. 경우에 따라 함수가 시작되기 전에도 사후 조건이 달성 되었기 때문에 메소드가 일찍 종료되어야합니다. 예를 들어, 컨트롤 레이블을로 변경하기 위해 "컨트롤 레이블 설정"메소드를 호출 Fred하면 창의 레이블이 이미 Fred있고 컨트롤 이름을 현재 상태로 설정하면 다시 그리기가 발생합니다 (일부 경우 유용 할 수 있음) 이전 이름과 새 이름이 일치하는 경우 set-name 메소드를 조기 종료하는 것이 완벽하게 합리적입니다.
supercat

3

If를 사용하십시오

GOTO에 관한 Don Knuth의 저서에서 나는 그가 가장 가능성이 높은 조건을 if 문에서 항상 우선하는 이유를 읽었습니다. 이것이 여전히 합리적인 아이디어라는 가정하에 (그리고 시대의 속도를 순수하게 고려하지 않는 아이디어). 나는 초기 리턴이 좋은 프로그래밍 관행이 아니라고 말하고 싶습니다. 특히 코드가 실패하지 않는 것보다 실패하지 않는 한 특히 오류 처리에 사용되지 않는다는 사실을 고려할 때 :-)

위의 조언을 따르면 함수의 맨 아래에 해당 반환을 넣어야하며 거기에서 반환을 호출하지 않을 수도 있습니다. 오류 코드를 설정하고 두 줄을 반환하면됩니다. 이에 의해 1 입구 1 출구 이상을 달성한다.

델파이 특정 ...

증거가 없지만 델파이 프로그래머에게는 이것이 좋은 프로그래밍 관행이라는 것을 알고 있습니다. 사전 D2009, 우리가 값을 반환하는 원자 방법이없는, 우리가 exit;하고 result := foo;또는 우리는 단지 예외를 던질 수있다.

대체해야한다면

if (true) {
 return foo;
} 

...에 대한

if true then 
begin
  result := foo; 
  exit; 
end;

당신은 모든 기능의 상단에서 그것을보고 아플 수도 있고 선호합니다.

if false then 
begin
  result := bar;

   ... 
end
else
   result := foo;

그냥 피하십시오 exit.


2
새로운 Delphis으로,에 생략 할 수 if true then Exit(foo);내가 처음 초기화 종종 기술을 사용 result으로 nil또는 FALSE모든 오류 상태를 확인하고 다만, 각각 Exit;의 경우 하나가 충족된다. 성공 사례 result는 일반적으로 방법의 끝 부분에 설정됩니다.
JensG

그래, 나는 새로운 기능을 좋아하지만 Java 프로그래머를 진정시키는 것은 사탕 인 것처럼 보이지만 다음은 프로 시저 내에서 변수를 정의하도록 허용 할 것입니다.
피터 터너

그리고 C # 프로그래머들. 그렇습니다. 솔직히 말하면 선언과 사용 사이의 줄 수를 줄임으로써 실제로 유용하다는 것을 알 수 있습니다 (IIRC에는 그에 대한 메트릭도 있습니다. 이름을 잊어 버렸습니다).
JensG

2

다음 진술에 동의합니다.

나는 개인적으로 함수의 들여 쓰기를 줄이는 가드 조항의 팬입니다 (두 번째 예). 함수에서 여러 리턴 포인트가 발생하기 때문에 일부 사람들은 마음에 들지 않지만 더 명확하다고 생각합니다.

이 질문에서 stackoverflow 에서 가져 왔습니다 .


+1 가드 조항. 나는 또한 긍정적으로 지향 된 가드를 선호합니다. 예 : if (! condition) 반환과 반대로 if (condition = false) 반환
JustinC

1

나는 요즘 거의 독점적으로 조기 반품을 사용합니다. 나는 이것을 쓴다

self = [super init];

if (self != nil)
{
    // your code here
}

return self;

같이

self = [super init];
if (!self)
    return;

// your code here

return self;

그러나 그것은 정말로 중요하지 않습니다. 함수에 하나 이상의 레벨이 중첩 된 경우 버스트되어야합니다.


동의한다. 들여 쓰기는 읽기에 문제를 일으키는 원인입니다. 들여 쓰기가 적을수록 좋습니다. 간단한 예제는 들여 쓰기 수준이 동일하지만 첫 번째 예제는 분명히 더 많은 두뇌 능력을 요구하는 더 많은 들여 쓰기로 커질 것입니다.
user441521

1

나는 쓰는 것을 선호합니다 :

if(someCondition)
{
    SomeFunction();
}

2
eww. 사전 검증입니까? 아니면 유효성 검사 전용 mehtod에 DoSomeFunctionIfSomeCondition있습니까?
STW

이렇게하면 걱정이 어떻게 분리됩니까?
justkt November

2
함수의 구현 (종속성 논리)을 외부로 만들어 캡슐화를 위반하고 있습니다.
TMN

1
이것이 공용 메소드에서 실행되고 SomeFunction ()이 동일한 클래스의 전용 메소드 인 경우 괜찮을 수 있습니다. 그러나 다른 사람이 SomeFunction ()을 호출하는 경우 수표를 복제해야합니다. 나는 각 방법이 작업을 수행하는 데 필요한 것을 돌보는 것이 더 낫다는 것을 알았습니다.
Per Wiklander

2
이것은 Robert C. Martin이 "청결한 코드"에서 제안한 스타일입니다. 함수는 한 가지만 수행해야합니다. 그러나 "구현 패턴"에서 Kent Beck은 OP에 제안 된 두 가지 옵션 중 두 번째 옵션이 더 낫다고 제안합니다.
Scott Whitlock 1

1

당신처럼, 나는 보통 첫 번째 것을 쓰지만 마지막 것을 선호합니다. 중첩 검사가 많으면 일반적으로 두 번째 방법으로 리팩터링합니다.

오류 처리가 검사에서 어떻게 다른지 좋아하지 않습니다.

if not error A
  if not error B
    if not error C
      // do something
    else handle error C
  else handle error B
else handle error A

나는 이것을 선호한다 :

if error A
  handle error A; return
if error B
  handle error B; return
if error C
  handle error C; return

// do something

0

상단의 조건을 "전제 조건"이라고합니다. 를 넣으면 if(!precond) return;모든 전제 조건을 시각적으로 나열하게됩니다.

큰 "if-else"블록을 사용하면 들여 쓰기 오버 헤드가 증가 할 수 있습니다 (3 단계 들여 쓰기에 대한 인용문을 잊어 버렸습니다).


2
뭐? Java에서 조기 반품을 할 수 없습니까? C #, VB (.NET 및 6) 및 분명히 Java (15 년 동안 언어를 사용하지 않았으므로 조회해야했지만)는 모두 일찍 반환 할 수 있습니다. 따라서 '강력한 언어'가 기능을 가지고 있지 않다고 비난하지 마십시오. stackoverflow.com/questions/884429/…
ps2goat

-1

if 문을 작게 유지하는 것을 선호합니다.

따라서 다음 중에서 선택하십시오.

if condition:
   line1
   line2
    ...
   line-n

if not condition: return

line1
line2
 ...
line-n

"초기 반품"으로 설명한 것을 선택하겠습니다.

나는 당신에게 초기 반품이나 다른 것에 신경 쓰지 않는다. 코드를 단순화하고 if 문의 본문을 단축하는 것을 정말로 좋아한다.

if and for and and 's and is 's 끔찍한 중첩 된 모든 비용을 피하십시오.


-2

다른 사람들이 말했듯이, 그것은 달려 있습니다. 값을 반환하는 작은 함수의 경우 조기 반환을 코딩 할 수 있습니다. 그러나 크기 조정 가능한 함수의 경우 항상 코드에 반환하기 전에 실행될 항목을 넣을 수있는 위치를 알고 싶습니다.


-2

나는 기능 수준에서 실패를 연습합니다. 코드가 일관되고 깨끗하게 유지됩니다 (나와 함께한 사람들에게). 그 때문에 나는 항상 일찍 돌아온다.

종종 확인되는 일부 조건의 경우 AOP를 사용하는 경우 해당 확인에 대한 측면을 구현할 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.