문제

내가 보기에 예외에 대한 Google의 대안은 다음과 같습니다.

  • 가다:다중 값 반환 "return val, err;"
  • GO, C++:수표 없음(조기 반납)
  • GO, C++:"망할 오류 처리"(내 용어)
  • C++:주장(표현식)

  • 가다:지연/패닉/복구는 이 질문을 받은 후에 추가된 언어 기능입니다.

다중값 반환이 대안으로 작용할 만큼 유용합니까?"어설션"이 대안으로 간주되는 이유는 무엇입니까?Google은 괜찮다고 생각합니까?올바르게 처리되지 않은 오류가 발생하면 프로그램이 중단되는 경우?

효과적인 이동:여러 반환 값

Go의 특이한 기능 중 하나는 함수와 메서드가 여러 값을 반환할 수 있다는 것입니다.이는 C 프로그램의 몇 가지 서투른 관용구를 개선하는 데 사용될 수 있습니다.대역 내 오류가 반환되고(예: EOF의 경우 -1) 인수가 수정됩니다.

C에서 쓰기 오류는 오류 코드가 있는 음수 개수 불안정한 장소에 숨겨져 있습니다.Go에서 Write는 개수와 오류:"예, 당신은 몇 바이트를 썼지만 당신이 채웠기 때문에 그들 모두가 아닙니다. 장치".*File.Write의 서명 패키지 OS는 다음과 같습니다.

func (file *File) Write(b []byte) (n int, err Error)

그리고 설명서에 나와 있듯이 쓰여진 바이트 수를 반환합니다. n != len(b) 일 때 nil이 아닌 오류입니다.이것은 일반적인 스타일입니다.자세한 내용은 자세한 내용은 오류 처리에 대한 섹션 예제.

효과적인 이동:명명된 결과 매개변수

의 반환 또는 결과 "매개 변수" Go 함수에는 이름을 지정할 수 있으며 다음과 같이 일반 변수로 사용됩니다. 들어오는 매개 변수.이름이 지정되면, 그들은 0으로 초기화됩니다 values는 기능이 시작됩니다.함수가 no 없이 return 문을 실행합니다. arguments의 현재 값인 result 매개 변수는 반환된 값.

이름은 필수는 아니지만 코드를 더 짧고 명확하게 만들 수 있습니다.그것은 문서입니다.이름을 지정하면 nextInt의 결과는 명백해진다 int를 반환 한 것은 which 입니다.

func nextInt(b []byte, pos int) (value, nextPos int) {

명명된 결과가 초기화되고 꾸밈없는 반환, 그들은 다음과 같이 단순화 할 수 있습니다. 뿐만 아니라 명확히합니다.다음은 버전입니다. io입니다. ReadFull 잘 사용합니다.

func ReadFull(r Reader, buf []byte) (n int, err os.Error) {
  for len(buf) > 0 && err == nil {
    var nr int;
    nr, err = r.Read(buf);
    n += nr;
    buf = buf[nr:len(buf)];
  }
  return;
}

Go에는 왜 예외가 없나요?

예외도 비슷한 이야기입니다.예외에 대한 다양한 디자인이 제안되었지만 각 디자인은 언어와 런타임에 상당한 복잡성을 추가합니다.본질적으로 예외는 함수와 심지어 고루틴까지 확장됩니다.그것들은 광범위한 의미를 가지고 있습니다.도서관에 미칠 영향에 대한 우려도 있습니다.이는 정의상 예외적이지만 이를 지원하는 다른 언어에 대한 경험을 통해 라이브러리 및 인터페이스 사양에 큰 영향을 미친다는 것을 알 수 있습니다.일반적인 오류가 모든 프로그래머가 보상해야 하는 특별한 제어 흐름으로 전환되도록 조장하지 않고 진정으로 예외적일 수 있는 디자인을 찾는 것이 좋을 것입니다.

제네릭과 마찬가지로 예외도 여전히 미해결 문제로 남아 있습니다.

Google C++ 스타일 가이드:예외

결정:

겉으로 보기에는 사용의 이점 예외가 비용보다 크고, 특히 새로운 프로젝트에서.그렇지만 기존 코드의 경우 예외는 모든 종속 코드.예외가 발생할 수 있는 경우 새로운 프로젝트를 넘어 전파 된 또한 통합에 문제가 있습니다. 새 프로젝트를 기존 프로젝트로 예외 없는 코드.대부분의 Google의 기존 C++ 코드는 예외를 처리할 준비가 되어 있습니다. 비교적 채택하기 어렵다 예외를 생성하는 새 코드입니다.

Google의 기존 코드가 다음과 같다는 점을 감안할 때 예외를 허용하지 않는 비용 예외를 사용하는 것이 다소 큽니다. 새 프로젝트의 비용보다.변환 프로세스가 느릴 수 있습니다 오류가 발생하기 쉽습니다.우리는 그것을 믿지 않습니다 이 사용 가능한 대안 예외(예: 오류 코드 및 어설션을 중요한 소개 부담.

예외 사용에 대한 조언은 다음과 같습니다. 철학적 또는 도덕적 근거가 있지만 실용적인 근거가 있습니다.우리는 우리의 Google의 오픈 소스 프로젝트 및 그렇게하기가 어렵습니다. 프로젝트는 예외를 사용합니다. Google의 예외에 대한 조언 오픈 소스 프로젝트도.것 우리가 가지고 있었다면 아마 달랐을 것입니다. 처음부터 다시 할 수 있습니다.

가다:연기, 패닉 및 복구

Defer 문을 사용하면 각 파일을 연 직후 닫는 것에 대해 생각할 수 있으므로 함수의 반환 문 수에 관계없이 파일이 닫히게 됩니다.

defer 문의 동작은 간단하고 예측 가능합니다.세 가지 간단한 규칙이 있습니다.

1.지연된 함수의 인수는 defer 문이 평가될 때 평가됩니다.

이 예에서는 Println 호출이 지연될 때 "i" 표현식이 평가됩니다.지연된 호출은 함수가 반환된 후 "0"을 인쇄합니다.

    func a() {
         i := 0
         defer fmt.Println(i)
         i++
         return    
    }

2.지연된 함수 호출은 주변 함수가 반환된 후 Last In First Out 순서로 실행됩니다. 이 함수는 "3210"을 인쇄합니다.

     func b() {
        for i := 0; i < 4; i++ {
            defer fmt.Print(i)
        }   
     }

삼.지연된 함수는 반환 함수의 명명된 반환 값을 읽고 할당할 수 있습니다.

이 예에서 지연된 함수는 주변 함수가 반환된 후 반환 값 i를 증가시킵니다.따라서 이 함수는 2를 반환합니다.

    func c() (i int) {
        defer func() { i++ }()
        return 1 
    }

이는 함수의 오류 반환 값을 수정하는 데 편리합니다.이에 대한 예를 곧 살펴보겠습니다.

패닉은 일반적인 제어 흐름을 중단하고 패닉을 시작하는 내장 기능입니다. 함수 F가 패닉을 호출하면 F의 실행이 중지되고 F의 모든 지연된 함수가 정상적으로 실행된 다음 F가 호출자에게 반환됩니다.발신자에게 F는 당황하라는 요청처럼 행동합니다.프로세스는 현재 고루틴의 모든 함수가 반환될 때까지 스택을 계속 진행하며, 이 시점에서 프로그램이 충돌합니다.패닉은 패닉을 직접 호출하여 시작할 수 있습니다.범위를 벗어난 배열 액세스와 같은 런타임 오류로 인해 발생할 수도 있습니다.

Recover는 당황한 고루틴의 제어권을 다시 얻는 내장 함수입니다. 복구는 지연된 함수 내에서만 유용합니다.정상적인 실행 중에 복구 호출은 nil을 반환하며 다른 효과는 없습니다.현재 고루틴이 패닉 상태인 경우 다음을 호출합니다. 복구는 패닉에 제공된 값을 캡처하고 정상적인 실행을 재개합니다..

다음은 패닉과 지연의 메커니즘을 보여주는 예제 프로그램입니다:

<snip>

패닉 및 복구의 실제 예를 보려면 Go 표준 라이브러리의 json 패키지를 참조하세요.일련의 재귀 함수를 사용하여 JSON으로 인코딩된 데이터를 디코딩합니다.잘못된 형식의 JSON이 발견되면 파서 호출 패닉은 스택을 최상위 함수 호출로 해제하여 패닉을 복구하고 적절한 오류 값을 반환합니다(decode.go의 'error' 및 'unmarshal' 함수 참조). .regexp 패키지의 컴파일 루틴에 이 기술과 유사한 예가 있습니다.Go 라이브러리의 규칙은 패키지가 내부적으로 패닉을 사용하는 경우에도 외부 API가 여전히 명시적인 오류 반환 값을 제공한다는 것입니다.

앞서 설명한 file.Close() 예제 외에 defer의 다른 용도에는 뮤텍스 해제가 포함됩니다.

mu.Lock()  
defer mu.Unlock
도움이 되었습니까?

해결책

다중 반환은 고유 한 것이 아니며 예외를 대신 할 수 없습니다. C (또는 C ++) 용어로, 그들은 여러 값을 포함하는 구조물 (객체)을 반환하기위한 간결하고 사용자 친화적 인 대체물입니다.

그것들은 당신이 의미한다면 오류를 나타내는 편리한 수단을 제공합니다.

"Asserts"가 대안으로 간주되는 이유는 무엇입니까?

어설 즈는 처음에 디버깅을위한 것입니다. 그들은 "불가능한"상태에있는 상황에서 프로그램을 중단 시켰습니다. 오류를 반환하는 것은 많은 도움이되지 않습니다. 코드 기반은 아직 아직 작동하지 않으므로 어떻게 지구상에서 성공적으로 복구 할 수 있습니까? 주의가 필요한 버그가있을 때 왜 원 하시겠습니까?

생산 코드에 어서를 사용하는 것은 약간 다른 문제입니다. 분명히 성능 및 코드 크기 문제가 있으므로 일반적인 접근 방식은 코드 분석과 테스트가 "불가능한"상황이 실제로 불가능하다는 것을 확신하면이를 제거하는 것입니다. 그러나이 수준의 편집증에서 코드를 실행하고 있다면, 감사 자체가되고 있다면, "불가능한"상태로 계속 실행하도록하면 위험한 일을 할 수 있다는 편집증이있을 수 있습니다. 귀중한 데이터, 스택 할당을 초과하고 보안 취약점을 생성 할 수 있습니다. 다시 말하지만, 당신은 가능한 한 빨리 종료하고 싶습니다.

당신이 사용하는 것들에 대해 사용하는 것들은 당신이 사용하는 것과 같지 않습니다. c ++ 및 Java와 같은 언어가 "불가능한"상황에 대한 예외를 제공 할 때 (logic_error, ArrayOutOfBoundsException), 그들은 의도적으로 일부 프로그래머가 자신의 프로그램을 생각하도록 권장합니다. ~해야 한다 실제로 통제 할 수없는 상황에서 회복하려고 시도합니다. 때때로 그것은 적절하지만, runtimeexextions를 포착하지 않는 Java의 조언은 그럴만 한 이유가 있습니다. 매우 때때로 하나를 잡는 것이 좋은 생각이므로 존재하는 이유입니다. 거의 항상 그들을 잡는 것은 좋은 생각이 아닙니다. 즉, 그들은 어쨌든 프로그램 (또는 적어도 스레드)을 멈추는 것입니다.

다른 팁

반환 값이 예외가 아니라는 것을 인식하기 위해 예외에 관한 몇 개의 기사를 읽어야합니다. C '대역'방식이나 다른 방법이 아닙니다.

깊은 논증에 들어 가지 않으면 오류 조건이 발견되고 오류 조건이 의미있게 처리 될 수있는 곳에 예외가 발생하여 포착됩니다. 반환 값은 첫 번째 기능에서만 계층 구조 스택에서만 처리되므로 문제를 처리하는 방법이거나 방법이 없습니다. 간단한 예제는 값을 문자열로 검색 할 수 있고 입력 된 리턴 문으로 처리하는 것을 지원하는 구성 파일입니다.

class config {
   // throws key_not_found
   string get( string const & key );
   template <typename T> T get_as( string const & key ) {
      return boost::lexical_cast<T>( get(key) );
   }
};

이제 문제는 키를 찾을 수없는 경우 어떻게 처리합니까? 리턴 코드를 사용하는 경우 (이동 중에도) 문제는 다음과 같습니다. get_as 오류 코드를 처리해야합니다 get 그리고 그에 따라 행동하십시오. 실제로 무엇을 해야할지 모르기 때문에 유일한 현명한 것은 수동으로 상류 오류 전파 :

class config2 {
   pair<string,bool> get( string const & key );
   template <typename T> pair<T,bool> get_as( string const & key ) {
      pair<string,bool> res = get(key);
      if ( !res.second ) {
          try {
             T tmp = boost::lexical_cast<T>(res.first);
          } catch ( boost::bad_lexical_cast const & ) {
             return make_pair( T(), false ); // not convertible
          }
          return make_pair( boost::lexical_cast<T>(res.first), true );
      } else {
          return make_pair( T(), false ); // error condition
      }
   }
}

클래스의 구현자는 오류를 전달하기 위해 추가 코드를 추가해야하며 해당 코드는 문제의 실제 논리와 혼합됩니다. C ++에서 이것은 아마도 여러 과제를 위해 설계된 언어보다 더 부담이 될 것입니다 (a,b=4,5) 그러나 여전히 논리가 가능한 오류에 의존하는 경우 (여기에서 lexical_cast 실제 문자열이있는 경우에만 수행해야합니다.) 어쨌든 값을 변수로 캐시해야합니다.

갈 수는 없지만 LUA에서는 다중 반환이 예외를 처리하기위한 매우 일반적인 관용구입니다.

당신이 같은 기능을 가지고 있다면

function divide(top,bottom)
   if bottom == 0 then 
        error("cannot divide by zero")
   else
        return top/bottom
   end
end

그럼 언제 bottom 0이었고, 예외가 제기되고 프로그램의 실행은 기능을 감싸지 않는 한 중단됩니다. divide 안에 pcall (또는 보호 된 전화).

pcall 항상 두 가지 값을 반환합니다. 첫 번째는 결과가 부울입니다. 함수가 성공적으로 반환되는지 여부를 알려주고 두 번째 결과는 반환 값 또는 오류 메시지입니다.

다음 (고려 된) LUA 스 니펫은 사용중인 것을 보여줍니다.

local top, bottom = get_numbers_from_user()
local status, retval = pcall(divide, top, bottom)
if not status then
    show_message(retval)
else
    show_message(top .. " divided by " .. bottom .. " is " .. retval)
end

물론 사용할 필요가 없습니다 pcall, 당신이 호출하는 함수가 이미 status, value_or_error.

다중 수익률은 몇 년 동안 LUA에게 충분히 좋았으므로 그렇지 않습니다. 보장하다 그것이 충분히 좋다는 것은 아이디어를지지합니다.

예, 오류 반환 값은 좋지만 예외 처리의 진정한 의미를 포착하지는 않습니다. 즉, 일반적으로 의도하지 않는 예외적 인 사례의 능력과 관리입니다.

Java (IE) 디자인은 예외를 유효한 워크 플로로 간주합니다. 시나리오 그리고 그들은 인터페이스와 라이브러리의 복잡성에 대한 요점을 가지고 있으며, 이러한 예외를 선언하고 버전으로 버전을 버전하지만 아쉽게도 스택 도미노에서 중요한 역할을합니다.

예외적 인 반환 코드가 조건부로 수십 개의 메소드 호출을 깊게 처리하는 대체 사례를 생각해보십시오. 문제가 발생한 줄 번호가 어디에 있는지 스택 추적은 어떻게 생겼습니까?

이 질문은 객관적으로 대답하기가 다소 까다로우며 예외에 대한 의견은 상당히 다를 수 있습니다.

하지만 추측해 보면 Go에 예외가 포함되지 않은 주된 이유는 예외가 컴파일러를 복잡하게 만들고 라이브러리를 작성할 때 적지 않은 영향을 미칠 수 있기 때문이라고 생각합니다.예외는 올바르게 수행하기 어렵고 무언가 작동하도록 하는 데 우선순위를 두었습니다.

반환 값과 예외를 통한 오류 처리의 주요 차이점은 예외로 인해 프로그래머가 비정상적인 조건을 처리해야 한다는 것입니다.명시적으로 예외를 포착하고 catch 블록에서 아무 작업도 수행하지 않는 한 "자동 오류"가 발생할 수 없습니다.반면에, 다른 유형의 버그로 이어질 수 있는 함수 내부의 모든 곳에서 암시적인 반환 지점을 얻습니다.이는 특히 메모리를 명시적으로 관리하고 할당한 항목에 대한 포인터를 잃지 않도록 해야 하는 C++에서 널리 퍼져 있습니다.

C++의 위험한 상황 예:

struct Foo {
    // If B's constructor throws, you leak the A object.
    Foo() : a(new A()), b(new B()) {}
    ~Foo() { delete a; delete b; }

    A *a;
    B *b;
};

다중 반환 값을 사용하면 함수에 대한 out 인수에 의존하지 않고도 반환 값 기반 오류 처리를 더 쉽게 구현할 수 있지만 근본적으로 아무것도 변경되지는 않습니다.

일부 언어에는 여러 반환 값과 예외(또는 유사한 메커니즘)가 모두 있습니다.한 가지 예는 다음과 같습니다 루아.

다음은 C ++에서 여러 반환 값이 어떻게 작동하는지에 대한 예입니다. 나는이 코드를 직접 쓰지 않을 것이지만, 그러한 접근법을 사용하는 것이 전적으로 문제가되지 않는다고 생각합니다.

#include <iostream>
#include <fstream>
#include <string>
using namespace std;

// return value type
template <typename T> 
struct RV {
    int mStatus;
    T mValue;

    RV( int status, const T & rv ) 
        : mStatus( status ), mValue( rv ) {}
    int Status() const { return mStatus; }
    const T & Value() const {return mValue; }
};

// example of possible use
RV <string> ReadFirstLine( const string & fname ) {
    ifstream ifs( fname.c_str() );
    string line;
    if ( ! ifs ) {
        return RV <string>( -1, "" );
    }
    else if ( getline( ifs, line ) ) {
        return RV <string>( 0, line );
    }
    else {
        return RV <string>( -2, "" );
    }
}

// in use
int main() {
    RV <string> r = ReadFirstLine( "stuff.txt" );
    if ( r.Status() == 0 ) {
        cout << "Read: " << r.Value() << endl;
    }
    else {
        cout << "Error: " << r.Status() << endl;
    }
}

"Nullable"객체 사용 Boost :: Optional <T>를 수행하는 C ++ 방법이 필요한 경우. 당신은 그것을 부울로 테스트하고 그것이 진실을 평가하면 유효한 T로 연도합니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top