program story

함수에 return 문이 하나만 있어야합니까?

inputbox 2020. 9. 29. 07:43
반응형

함수에 return 문이 하나만 있어야합니까?


함수에 return 문을 하나만 사용하는 것이 더 나은 방법 인 이유가 있습니까?

아니면 논리적으로 옳은대로 함수에서 리턴하는 것이 괜찮습니까? 즉, 함수에 많은 리턴 문이있을 수 있습니다.


나는 종종 "쉬운"상황으로 돌아 가기 위해 메소드의 시작 부분에 여러 문장을 가지고 있습니다. 예를 들면 다음과 같습니다.

public void DoStuff(Foo foo)
{
    if (foo != null)
    {
        ...
    }
}

... 다음과 같이 더 읽기 쉽게 (IMHO) 만들 수 있습니다.

public void DoStuff(Foo foo)
{
    if (foo == null) return;

    ...
}

그렇습니다. 함수 / 메소드에서 여러 "종료점"을 갖는 것이 괜찮다고 생각합니다.


아무도 Code Complete를 언급하거나 인용 하지 않았으므로 내가 할 것입니다.

17.1 반환

각 루틴의 반환 횟수를 최소화합니다 . 루틴을 맨 아래에서 읽으면 위 어딘가로 돌아올 가능성을 알지 못하는 경우 루틴을 이해하기가 더 어렵습니다.

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


이 기술이 실제로 계속 해서 유용하다는 것을 알게 되었기 때문에 여러 종료 지점에 대해 임의로 결정하는 것은 엄청나게 현명하지 않을 것이라고 말하고 싶습니다 . 사실 저는 명확성을 위해 기존 코드 를 여러 종료 지점으로 리팩토링하는 경우가 많습니다 . 따라서 두 가지 접근 방식을 비교할 수 있습니다.

string fooBar(string s, int? i) {
  string ret = "";
  if(!string.IsNullOrEmpty(s) && i != null) {
    var res = someFunction(s, i);

    bool passed = true;
    foreach(var r in res) {
      if(!r.Passed) {
        passed = false;
        break;
      }
    }

    if(passed) {
      // Rest of code...
    }
  }

  return ret;
}

이를 여러 출구 지점 허용되는 코드와 비교하십시오 .

string fooBar(string s, int? i) {
  var ret = "";
  if(string.IsNullOrEmpty(s) || i == null) return null;

  var res = someFunction(s, i);

  foreach(var r in res) {
      if(!r.Passed) return null;
  }

  // Rest of code...

  return ret;
}

나는 후자가 상당히 더 명확하다고 생각합니다. 여러 출구 지점에 대한 비판은 요즘 다소 오래된 관점이라고 말할 수 있습니다.


저는 현재이 작업을하는 두 사람이 "단일 출구"이론을 맹목적으로 구독하는 코드베이스를 작업 중이며 경험상 끔찍한 실행이라고 말할 수 있습니다. 이것은 코드를 유지하기 매우 어렵게 만들고 그 이유를 보여 드리겠습니다.

"단일 종료 지점"이론을 사용하면 필연적으로 다음과 같은 코드가 생성됩니다.

function()
{
    HRESULT error = S_OK;

    if(SUCCEEDED(Operation1()))
    {
        if(SUCCEEDED(Operation2()))
        {
            if(SUCCEEDED(Operation3()))
            {
                if(SUCCEEDED(Operation4()))
                {
                }
                else
                {
                    error = OPERATION4FAILED;
                }
            }
            else
            {
                error = OPERATION3FAILED;
            }
        }
        else
        {
            error = OPERATION2FAILED;
        }
    }
    else
    {
        error = OPERATION1FAILED;
    }

    return error;
}

이것은 코드를 따라 가기가 매우 어렵게 만들뿐만 아니라 나중에 돌아가서 1과 2 사이에 연산을 추가해야한다고 말합니다. 전체 이상한 함수에 대해 들여 쓰기를해야합니다. if / else 조건 및 중괄호가 올바르게 일치합니다.

이 방법을 사용하면 코드 유지 관리가 매우 어렵고 오류가 발생하기 쉽습니다.


구조적 프로그래밍 은 함수 당 하나의 return 문만 있어야한다고 말합니다. 이것은 복잡성을 제한하기위한 것입니다. Martin Fowler와 같은 많은 사람들은 여러 return 문을 사용하여 함수를 작성하는 것이 더 간단하다고 주장합니다. 그는 그가 쓴 고전적인 리팩토링에서이 주장을 제시합니다 . 그의 다른 조언을 따르고 작은 함수를 작성하면 잘 작동합니다. 나는이 관점에 동의하며 엄격한 구조적 프로그래밍 순수 주의자 만이 함수 당 단일 return 문을 고수합니다.


Kent Beck이 구현 패턴 에서 가드 절을 논의 할 때 언급했듯이 루틴을 만드는 단일 진입 점과 출구 점은 ...

"동일한 루틴에서 여러 위치로 점프 할 때 발생할 수있는 혼란을 방지하기위한 것이 었습니다. 실행 된 명령문을 이해하는 것조차 어려운 작업 인 많은 글로벌 데이터로 작성된 FORTRAN 또는 어셈블리 언어 프로그램에 적용 할 때 의미가 있습니다 .. . 작은 방법과 대부분 로컬 데이터를 사용하면 불필요하게 보수적입니다. "

가드 절로 작성된 함수는 하나의 긴 중첩 if then else보다 따라 가기가 훨씬 쉽습니다 .


부작용이없는 함수에서는 한 번 이상 반환 할 이유가 없으며 함수 스타일로 작성해야합니다. 부작용이있는 메서드에서는 상황이 더 순차적 (시간 인덱스)이므로 실행을 중지하는 명령으로 return 문을 사용하여 명령형으로 작성합니다.

즉, 가능하면이 스타일을 선호합니다.

return a > 0 ?
  positively(a):
  negatively(a);

이것 위에

if (a > 0)
  return positively(a);
else
  return negatively(a);

여러 계층의 중첩 된 조건을 작성하는 경우 예를 들어 조건 자 목록을 사용하여 리팩토링 할 수있는 방법이있을 것입니다. ifs와 else가 구문 적으로 멀리 떨어져 있음을 발견하면이를 더 작은 함수로 나누는 것이 좋습니다. 한 화면 이상의 텍스트에 걸쳐있는 조건부 블록은 읽기가 어렵습니다.

모든 언어에 적용되는 엄격하고 빠른 규칙은 없습니다. 단일 return 문을 사용하는 것과 같은 것은 코드를 좋게 만들지 않습니다. 그러나 좋은 코드를 사용하면 함수를 그렇게 작성할 수 있습니다.


RAII 또는 다른 자동 메모리 관리가없는 것처럼 C ++의 코딩 표준에서이를 보았습니다. 마치 RAII 나 다른 자동 메모리 관리가없는 경우 각 반환을 정리해야합니다. 즉, 잘라 내기 및 붙여 넣기를 의미합니다. 정리 또는 goto (관리 언어의 '최종'과 논리적으로 동일 함), 둘 다 잘못된 형식으로 간주됩니다. C ++ 또는 다른 자동 메모리 시스템에서 스마트 포인터와 컬렉션을 사용하는 방법이 있다면 그 이유는 확실하지 않으며 가독성과 판단력에 관한 것입니다.


함수 중간 에있는 return 문 이 나쁘다 는 생각에 의지합니다 . return을 사용하여 함수 상단에 몇 가지 보호 절을 빌드 할 수 있으며, 물론 컴파일러에게 문제없이 함수 끝에서 반환 할 내용을 알려줄 수 있지만 함수 중간반환하는 것은 놓치기 쉬울 수 있으며 함수를 해석하기 어렵게 만듭니다.


함수에 return 문을 하나만 사용하는 것이 더 나은 방법 인 이유가 있습니까?

, 다음이 있습니다.

  • 단일 출구 지점은 사후 조건을 주장 할 수있는 훌륭한 장소를 제공합니다.
  • 함수 끝에서 하나의 리턴에 디버거 중단 점을 넣을 수 있다는 것은 종종 유용합니다.
  • 반품이 적다는 것은 복잡성이 적다는 것을 의미합니다. 선형 코드는 일반적으로 이해하기 더 간단합니다.
  • 함수를 단일 반환으로 단순화하려고하면 복잡성이 발생하는 경우 더 작고 더 일반적이며 이해하기 쉬운 함수로 리팩토링하는 것이 좋습니다.
  • 소멸자가없는 언어를 사용하거나 RAII를 사용하지 않는 경우 한 번의 반환으로 정리해야하는 장소의 수가 줄어 듭니다.
  • 일부 언어에는 단일 출구가 필요합니다 (예 : Pascal 및 Eiffel).

이 질문은 종종 다중 반환 또는 깊게 중첩 된 if 문 사이의 잘못된 이분법으로 제기됩니다. 거의 항상 하나의 출구 지점 만있는 매우 선형적인 (깊은 중첩이없는) 세 번째 솔루션이 있습니다.

업데이트 : 분명히 MISRA 지침은 단일 출구를 장려 합니다.

분명히 말하면, 여러 번의 수익을 얻는 것이 항상 잘못 이라고 말하는 것은 아닙니다 . 그러나 동등한 솔루션이 주어지면 단일 수익을 가진 솔루션을 선호하는 많은 이유가 있습니다.


단일 종료점을 사용하면 실제로 반환 될 값을 확인하기 위해 함수 끝에 단일 중단 점을 설정할 수 있으므로 디버깅에 유리합니다.


일반적으로 나는 함수에서 하나의 출구 점 만 가지려고합니다. 그러나 그렇게하면 실제로 필요한 것보다 더 복잡한 함수 본문이 생성되는 경우가 있습니다.이 경우 여러 출구 지점을 갖는 것이 좋습니다. 결과적인 복잡성에 기반한 "판단 호출"이어야하지만, 목표는 복잡성과 이해성을 희생하지 않고 가능한 한 적은 출구 지점이어야합니다.


아니요, 우리는 더 이상 1970 년대에 살지 않기 때문 입니다. 함수가 충분히 길어 여러 반환이 문제가되는 경우 너무 깁니다.

(예외가있는 언어의 모든 다중 행 함수는 어쨌든 여러 종료점을 갖게된다는 사실과는 별개입니다.)


정말 복잡하지 않는 한 단일 출구를 선호합니다. 경우에 따라 여러 개의 존재하는 점이 다른 중요한 디자인 문제를 가릴 수 있음을 발견했습니다.

public void DoStuff(Foo foo)
{
    if (foo == null) return;
}

이 코드를 보면 즉시 물어볼 것입니다.

  • 'foo'가 null입니까?
  • 만약 그렇다면, 얼마나 많은 'DoStuff'클라이언트가 null 'foo'로 함수를 호출 했습니까?

이러한 질문에 대한 답변에 따라

  1. 확인은 사실이 아니므로 무의미합니다 (즉, 주장이어야 함).
  2. 검사는 거의 사실이 아니므로 어쨌든 다른 조치를 취해야하므로 특정 호출자 함수를 변경하는 것이 더 나을 수 있습니다.

위의 두 경우 모두 'foo'가 null이 아니고 관련 호출자가 변경되도록하기 위해 어설 션으로 코드를 다시 작업 할 수 있습니다.

여러 개가 실제로 부정적인 영향을 미칠 수있는 다른 두 가지 이유 (C ++ 코드에 대해 구체적으로 생각)가 있습니다 . 코드 크기와 컴파일러 최적화입니다.

함수 종료시 범위에있는 비 POD C ++ 개체는 소멸자를 호출합니다. return 문이 여러 개있는 경우 범위에 다른 개체가있을 수 있으므로 호출 할 소멸자 목록이 다를 수 있습니다. 따라서 컴파일러는 각 return 문에 대한 코드를 생성해야합니다.

void foo (int i, int j) {
  A a;
  if (i > 0) {
     B b;
     return ;   // Call dtor for 'b' followed by 'a'
  }
  if (i == j) {
     C c;
     B b;
     return ;   // Call dtor for 'b', 'c' and then 'a'
  }
  return 'a'    // Call dtor for 'a'
}

코드 크기가 문제인 경우 피할 가치가 있습니다.

다른 문제는 "Named Return Value OptimiZation"(일명 Copy Elision, ISO C ++ '03 12.8 / 15)과 관련이 있습니다. C ++에서는 다음과 같은 경우 구현에서 복사 생성자 호출을 건너 뛸 수 있습니다.

A foo () {
  A a1;
  // do something
  return a1;
}

void bar () {
  A a2 ( foo() );
}

코드를 그대로 사용하면 객체 'a1'이 'foo'에 생성 된 다음 복사 구문이 호출되어 'a2'를 생성합니다. 그러나 복사 제거를 통해 컴파일러는 스택의 'a2'와 동일한 위치에 'a1'을 생성 할 수 있습니다. 따라서 함수가 반환 될 때 개체를 "복사"할 필요가 없습니다.

다중 종료점은이를 감지하려는 컴파일러의 작업을 복잡하게하며, 적어도 비교적 최근 버전의 VC ++에서는 함수 본문이 다중 반환을 갖는 곳에서 최적화가 수행되지 않았습니다. 자세한 내용 은 Visual C ++ 2005의 명명 된 반환 값 최적화 를 참조하십시오.


단일 종료 지점이 있으면 순환 복잡성이 감소 하므로 이론적 으로 코드를 변경할 때 코드에 버그가 발생할 가능성이 줄어 듭니다. 그러나 연습은보다 실용적인 접근이 필요하다는 것을 암시하는 경향이 있습니다. 따라서 단일 종료 지점을 목표로하는 경향이 있지만 더 읽기 쉬운 경우 내 코드에 여러 개를 허용합니다.


나는 return어떤 의미에서 코드 냄새를 생성 하기 때문에 하나의 문장 만을 사용하도록 강요 합니다. 설명하겠습니다.

function isCorrect($param1, $param2, $param3) {
    $toret = false;
    if ($param1 != $param2) {
        if ($param1 == ($param3 * 2)) {
            if ($param2 == ($param3 / 3)) {
                $toret = true;
            } else {
                $error = 'Error 3';
            }
        } else {
            $error = 'Error 2';
        }
    } else {
        $error = 'Error 1';
    }
    return $toret;
}

(조건은 조건입니다 ...)

조건이 많을수록 함수가 커질수록 읽기가 더 어려워집니다. 따라서 코드 냄새에 익숙해지면이를 깨닫고 코드를 리팩토링하고 싶을 것입니다. 두 가지 가능한 솔루션은 다음과 같습니다.

  • 복수 반품
  • 별도의 기능으로 리팩토링

복수 반품

function isCorrect($param1, $param2, $param3) {
    if ($param1 == $param2)       { $error = 'Error 1'; return false; }
    if ($param1 != ($param3 * 2)) { $error = 'Error 2'; return false; }
    if ($param2 != ($param3 / 3)) { $error = 'Error 3'; return false; }
    return true;
}

별도의 기능

function isEqual($param1, $param2) {
    return $param1 == $param2;
}

function isDouble($param1, $param2) {
    return $param1 == ($param2 * 2);
}

function isThird($param1, $param2) {
    return $param1 == ($param2 / 3);
}

function isCorrect($param1, $param2, $param3) {
    return !isEqual($param1, $param2)
        && isDouble($param1, $param3)
        && isThird($param2, $param3);
}

물론 더 길고 약간 지저분하지만 이런 식으로 함수를 리팩토링하는 과정에서

  • 재사용 가능한 여러 기능을 만들었습니다.
  • 사람이 읽기 쉽게 함수를 만들었습니다.
  • 함수의 초점은 값이 올바른 이유에 있습니다.

필요한만큼 많이 가지고 있거나 코드를 더 깔끔하게 만드는 것 (예 : guard clauses ) 을 가져야한다고 말하고 싶습니다 .

저는 개인적으로 반품 진술이 하나만 있어야한다는 "모범 사례"를 들어 본 적이 없습니다.

대부분의 경우 논리 경로를 기반으로 가능한 한 빨리 함수를 종료하는 경향이 있습니다 (가드 절이 이에 대한 훌륭한 예입니다).


여러 반환이 일반적으로 좋다고 생각합니다 (C #로 작성한 코드에서). 싱글 리턴 스타일은 C의 이월입니다.하지만 아마도 C로 코딩하지 않을 것입니다.

모든 프로그래밍 언어에서 메소드에 대해 하나의 종료점 만 요구하는 법은 없습니다 . 어떤 사람들은이 스타일의 우월성을 주장하고 때로는 그것을 "규칙"또는 "법"으로 끌어 올리지 만이 믿음은 어떤 증거 나 연구로도 뒷받침되지 않습니다.

둘 이상의 반환 스타일은 리소스를 명시 적으로 할당 해제해야하는 C 코드에서 나쁜 습관이 될 수 있지만 Java, C #, Python 또는 JavaScript와 같은 언어에는 자동 가비지 수집 및 try..finally블록 (및 usingC #의 블록) 과 같은 구문이 있습니다 . ),이 주장은 적용되지 않습니다. 이러한 언어에서는 중앙 집중식 수동 리소스 할당 해제가 필요한 경우가 매우 드뭅니다.

단일 반환이 더 읽기 쉬운 경우와 그렇지 않은 경우가 있습니다. 코드 줄 수를 줄이거 나 논리를 더 명확하게 만들거나 중괄호와 들여 쓰기 또는 임시 변수의 수를 줄이는 지 확인하십시오.

따라서 기술적 인 문제가 아니라 레이아웃 및 가독성 문제이므로 예술적 감성에 맞는만큼 많은 수익을 사용하십시오.

나는 이것에 대해 내 블로그에서 더 길게 이야기했다 .


결과적으로 피할 수없는 "화살표" 프로그래밍 에 대해 나쁜 말이있는 것처럼 단일 출구 지점을 갖는 것에 대해 좋은 말이 있습니다.

입력 유효성 검사 또는 리소스 할당 중에 여러 종료 지점을 사용하는 경우 모든 '오류 종료'를 매우 눈에 띄게 함수 상단에 배치하려고합니다.

"SSDSLPedia" Spartan Programming 기사와 "Portland Pattern Repository 's Wiki" 의 단일 기능 종료 지점 기사 모두이 문제에 대해 몇 가지 통찰력있는 주장이 있습니다. 물론 고려해야 할이 게시물이 있습니다.

예를 들어 하나의 단일 장소에서 리소스를 해제하기 위해 단일 종료 지점 (예를 들어 예외가 활성화되지 않은 언어)을 원한다면 goto를 신중하게 적용하는 것이 좋습니다. 예를 들어이 다소 인위적인 예를 참조하십시오 (화면 공간을 저장하기 위해 압축 됨).

int f(int y) {
    int value = -1;
    void *data = NULL;

    if (y < 0)
        goto clean;

    if ((data = malloc(123)) == NULL)
        goto clean;

    /* More code */

    value = 1;
clean:
   free(data);
   return value;
}

개인적으로 나는 일반적으로 여러 출구 지점을 싫어하는 것보다 화살표 프로그래밍을 더 싫어하지만 둘 다 올바르게 적용될 때 유용합니다. 물론 가장 좋은 방법은 둘 다 요구하지 않도록 프로그램을 구성하는 것입니다. 함수를 여러 청크로 나누면 일반적으로 도움이됩니다. :)

그렇게 할 때이 예제에서와 같이 어쨌든 여러 개의 출구 지점이 생깁니다. 여기서 일부 더 큰 함수는 여러 개의 작은 함수로 나뉩니다.

int g(int y) {
  value = 0;

  if ((value = g0(y, value)) == -1)
    return -1;

  if ((value = g1(y, value)) == -1)
    return -1;

  return g2(y, value);
}

프로젝트 또는 코딩 지침에 따라 대부분의 상용구 코드는 매크로로 대체 될 수 있습니다. 참고로,이 방법으로 분해하면 g0, g1, g2 함수를 개별적으로 테스트하기가 매우 쉽습니다.

분명히 OO 및 예외 지원 언어에서는 이와 같은 if 문을 사용하지 않을 것입니다 (또는 약간의 노력으로 해결할 수 있다면 전혀 사용하지 않을 것입니다). 그리고 코드는 훨씬 더 분명 할 것입니다. 그리고 비 화살. 그리고 대부분의 최종 수익은 예외 일 것입니다.

간단히 말해서

  • 적은 수익이 많은 수익보다 낫습니다.
  • 하나 이상의 반환이 거대한 화살보다 낫고 가드 조항 은 일반적으로 괜찮습니다.
  • 예외는 가능한 경우 대부분의 '보호 조항'을 대체 할 수 있거나 대체해야합니다.

당신은 속담- 아름다움이 보는 사람의 눈에 있다는 것을 압니다 .

어떤 사람들은 NetBeans , 어떤 사람들 IntelliJ IDEA , 어떤 사람들은 Python , 어떤 사람들은 PHP로 맹세합니다 .

일부 상점에서는 다음과 같이 고집하면 실직 할 수 있습니다.

public void hello()
{
   if (....)
   {
      ....
   }
}

문제는 가시성과 유지 보수성에 관한 것입니다.

나는 논리와 상태 머신의 사용을 줄이고 단순화하기 위해 부울 대수를 사용하는 것에 중독되어 있습니다. 그러나 코딩에 "수학적 기술"을 사용하는 것은 보이지 않고 유지 관리 할 수 ​​없기 때문에 부적절하다고 생각한 과거 동료가있었습니다. 그리고 그것은 나쁜 습관입니다. 죄송합니다. 제가 사용하는 기술은 매우 눈에 잘 띄고 유지 관리가 가능합니다. 6 개월 후 코드로 돌아 오면 속담적인 스파게티가 엉망이되는 것보다 코드를 명확하게 이해할 수 있기 때문입니다.

헤이 버디 (이전 고객이 말하곤했던 것처럼) 내가 고쳐야 할 때 고치는 방법을 아는 한 당신이 원하는 것을하십시오.

20 년 전, 오늘날의 애자일 개발 전략 을 채택한 동료가 해고당했습니다 . 그는 세심한 증분 계획을 가지고있었습니다. 그러나 그의 관리자는 "사용자에게 기능을 점진적으로 출시 할 수 없습니다 . 폭포수를 고수해야합니다 ."라고 소리 쳤다 . 매니저에 대한 그의 반응은 점진적 개발이 고객의 요구에 더 정확할 것이라는 것이었다. 그는 고객의 요구 사항을위한 개발을 믿었지만 관리자는 "고객의 요구 사항"에 맞는 코딩을 믿었습니다.

우리는 데이터 정규화, MVPMVC 경계 를 깨는 죄를 자주 범 합니다. 함수를 생성하는 대신 인라인합니다. 우리는 지름길을 택합니다.

개인적으로 나는 PHP가 나쁜 습관이라고 생각하지만 내가 아는 것은 무엇입니까? 모든 이론적 주장은 한 세트의 규칙을 충족시키려는 시도로 귀결됩니다.

품질 = 정밀도, 유지 보수성 및 수익성.

다른 모든 규칙은 배경으로 사라집니다. 물론이 규칙은 결코 사라지지 않습니다.

게으름은 좋은 프로그래머의 미덕입니다.


나는 가드 절을 사용하여 일찍 반환하고 그렇지 않으면 메서드의 끝에서 종료합니다. 단일 진입 및 종료 규칙은 역사적 의미가 있으며 여러 반환 (및 많은 결함)이있는 단일 C ++ 메서드에 대해 10 개의 A4 페이지로 실행되는 레거시 코드를 처리 할 때 특히 유용했습니다. 최근에 받아 들여진 모범 사례는 메서드를 작게 유지하여 다중 출구를 이해하는 데 방해가되지 않도록하는 것입니다. 위에서 복사 한 다음 Kronoz 예제에서 질문은 // Rest of code ... ? 에서 발생하는 것입니다 .

void string fooBar(string s, int? i) {

  if(string.IsNullOrEmpty(s) || i == null) return null;

  var res = someFunction(s, i);

  foreach(var r in res) {
      if(!r.Passed) return null;
  }

  // Rest of code...

  return ret;
}

이 예제가 다소 인위적이라는 것을 알고 있지만 foreach 루프를 가드 절로 간주 할 수있는 LINQ 문으로 리팩토링하고 싶을 것 입니다. 다시 말하지만, 인위적인 예제에서는 코드의 의도가 명확하지 않으며 someFunction () 은 다른 부작용이 있거나 그 결과가 // 나머지 코드 에서 사용될 수 있습니다 .

if (string.IsNullOrEmpty(s) || i == null) return null;
if (someFunction(s, i).Any(r => !r.Passed)) return null;

다음과 같은 리팩토링 된 기능을 제공합니다.

void string fooBar(string s, int? i) {

  if (string.IsNullOrEmpty(s) || i == null) return null;
  if (someFunction(s, i).Any(r => !r.Passed)) return null;

  // Rest of code...

  return ret;
}

내가 생각할 수있는 한 가지 좋은 이유는 코드 유지 관리 때문입니다. 단일 종료 지점이 있습니다. 결과 형식을 변경하려면 ... 구현하는 것이 훨씬 간단합니다. 또한 디버깅을 위해 거기에 중단 점을 붙일 수 있습니다. :)

그렇긴하지만 코딩 표준이 '함수 당 하나의 return 문'을 부과하는 라이브러리에서 작업해야했는데 꽤 힘들었습니다. 나는 많은 수치 계산 코드를 작성하고 종종 '특별한 경우'가 있으므로 코드를 따라 가기가 상당히 어려워졌습니다.


충분히 작은 기능, 즉 한 화면 길이에서 전체를 볼 수있는 기능의 경우 여러 출구 지점이 좋습니다. 마찬가지로 긴 함수에 여러 종료 점이 포함되어 있다면 함수가 더 잘릴 수 있다는 신호입니다.

그것은 절대적으로 필요하지 않는 한 다중 종료 기능을 피한다고 말했습니다 . 좀 더 복잡한 기능의 일부 모호한 라인에서 일부 길잃은 리턴으로 인한 버그의 고통을 느꼈습니다.


나는 당신에게 하나의 출구 경로를 강요하는 끔찍한 코딩 표준을 가지고 일해 왔으며 그 결과는 기능이 사소한 것이 아니라면 거의 항상 구조화되지 않은 스파게티입니다. 결국 많은 휴식을 취하고 계속 방해합니다.


단일 종료 지점 (다른 모든 항목이 동일)은 코드를 훨씬 더 읽기 쉽게 만듭니다. 하지만 함정이있다 : 대중적인 건축

resulttype res;
if if if...
return res;

가짜입니다. "res ="는 "return"보다 낫지 않습니다. 단일 return 문이 있지만 실제로 함수가 끝나는 여러 지점이 있습니다.

여러 개의 반환 (또는 "res ="s)이있는 함수가있는 경우 단일 종료 지점을 사용하여 여러 개의 작은 함수로 나누는 것이 좋습니다.


내 일반적인 정책은 코드를 더 추가하여 코드의 복잡성을 크게 줄이지 않는 한 함수 끝에 하나의 return 문만있는 것입니다. 사실, 저는 return 문이 없어서 유일한 반환 규칙을 적용하는 Eiffel의 팬입니다 (결과를 입력 할 자동 생성 된 'result'변수 만 있습니다).

코드가없는 명백한 버전보다 여러 반환으로 코드를 더 명확하게 만들 수있는 경우가 확실히 있습니다. 여러 개의 return 문 없이는 이해할 수없는 너무 복잡한 함수가 있다면 더 많은 재 작업이 필요하다고 주장 할 수 있지만, 때로는 그러한 것에 대해 실용적이되는 것이 좋습니다.


몇 번 이상 반환하면 코드에 문제가있을 수 있습니다. 그렇지 않으면 특히 코드를 더 깔끔하게 만들 때 서브 루틴의 여러 위치에서 돌아올 수있는 것이 좋을 때가 있습니다.

Perl 6 : 나쁜 예

sub Int_to_String( Int i ){
  given( i ){
    when 0 { return "zero" }
    when 1 { return "one" }
    when 2 { return "two" }
    when 3 { return "three" }
    when 4 { return "four" }
    ...
    default { return undef }
  }
}

이렇게 작성하는 것이 좋습니다

Perl 6 : 좋은 예

@Int_to_String = qw{
  zero
  one
  two
  three
  four
  ...
}
sub Int_to_String( Int i ){
  return undef if i < 0;
  return undef unless i < @Int_to_String.length;
  return @Int_to_String[i]
}

이것은 간단한 예일뿐입니다.


나는 가이드 라인으로 마지막에 단일 반품에 투표합니다. 이것은 일반적인 코드 정리 처리에 도움이됩니다 . 예를 들어 다음 코드를 살펴보십시오.

void ProcessMyFile (char *szFileName)
{
   FILE *fp = NULL;
   char *pbyBuffer = NULL:

   do {

      fp = fopen (szFileName, "r");

      if (NULL == fp) {

         break;
      }

      pbyBuffer = malloc (__SOME__SIZE___);

      if (NULL == pbyBuffer) {

         break;
      }

      /*** Do some processing with file ***/

   } while (0);

   if (pbyBuffer) {

      free (pbyBuffer);
   }

   if (fp) {

      fclose (fp);
   }
}

이것은 아마도 비정상적인 관점이지만 여러 개의 return 문을 선호한다고 믿는 사람은 4 개의 하드웨어 중단 점 만 지원하는 마이크로 프로세서에서 디버거를 사용할 필요가 없었습니다. ;-)

"화살표 코드"의 문제는 완전히 정확하지만 여러 return 문을 사용할 때 사라지는 것처럼 보이는 문제는 디버거를 사용하는 상황입니다. 출구와 반환 조건을 볼 수 있도록 중단 점을 놓을 편리한 포괄 위치가 없습니다.


함수에 return 문이 많을수록 해당 메서드의 복잡성이 높아집니다. return 문이 너무 많은지 궁금하다면 해당 함수에 너무 많은 코드 줄이 있는지 스스로에게 물어볼 수 있습니다.

그러나 그렇지 않습니다. 하나 / 다 반환 문에는 잘못된 것이 없습니다. 일부 언어에서는 다른 언어 (C)보다 더 나은 방법 (C ++)입니다.

참고 URL : https://stackoverflow.com/questions/36707/should-a-function-have-only-one-return-statement

반응형