언제제로

23766 단어 gopitfall
Go의 첫 번째 프로젝트에서 나는 자신의 Errorstruct를 실현하여 상세한 오류 정보를 전달했다. Go의 내장errorinterface만이 제공하는 간단한 오류 메시지가 아니다.
내가 처음 그것을 실현했을 때, 나는 struct의 신분으로 실현되었다.그 후로 나는 interface 사용자로서 더욱 좋다는 것을 알게 되었다.
package myerr

type Code string

type Error interface {
    error            // implementations must satisfy error interface
    Code() Code      // project-specific error code
    Cause() Error    // the Error that caused this Error, if any
    OrigErr() error  // the original error
    // ...
}

Aside on Error Codes

The reason I chose a string for error codes rather than an integer is because the only advantage of integers is that they make it easy for C-like code to switch on them. However, there are a few disadvantages of integer codes including:

  1. They’re not good for human readers: you have to look up the codes or the error provider has to include string descriptions (in which case the provider might as well have made the codes a string in the first place).
  2. You have to ensure that the codes from different subsystems don’t conflict.

Yes, there’s a small performance penalty for comparing strings as opposed to integers, but:

  1. Presumably, handling errors is exceptional, so it doesn’t matter that the code is a bit slower since it’s not critical-path.
  2. If you’re doing a REST application where error codes are sent as part of a JSON response via HTTP, then you’re already taking a performance hit for itoaing and atoiing the integer code (not to mention parsing the entire JSON document) since it’s sent over the wire as text. Hence, a few extra string comparisons are noise.

현재 interface임에도 불구하고 Errorstruct에 의해 이루어져야 한다.
type MyError struct {
    code    Code
    origErr error
    cause   *MyError
}

func (e *MyError) Cause() Error {
    return e.cause
}

func (e *MyError) Code() Code {
    return e.code
}

func (e *MyError) OrigErr() error {
    return e.origErr
}
이 모든 것은 상당히 간단한 것 같다.(주의: 오류를 만드는 데 사용되는 다른 함수는 본문과 무관하지만 간단합니다.)

문제.


그리고 나는 부정적인 단원 테스트 (오류가 정확하게 검출되고 보고되었는지 확인하는 테스트) 를 썼기 때문에, 나는 두 개 Error s를 비교하는 방법이 필요하다. Go의 표준 라이브러리에는 DeepEqual() 있지만, 비록 그것이 작동하지만, 두 대상이 같은지만 알려줄 뿐이다.테스트에 있어서 만약에 두 대상이 같지 않으면 구체적인 같지 않은 내용을 이해하는 것이 도움이 된다. 그래서 나는 ErrorDeepEqual()를 썼고 하나error로 되돌아갔다. 설명은 다음과 같다.
func ErrorDeepEqual(e1, e2 myerror.Error) error {
    if e1 == nil {
        if e2 != nil {
            return errors.New("e1(nil) != e2(!nil)")
        }
        return nil
    }
    if e2 == nil {
        return errors.New("e1(!nil) != e2(nil)")
    }
    if e1.Code() != e2.Code() {
        return fmt.Errorf("e1.Code(%v) != e2.Code(%v)", e1.Code(), e2.Code())
    }
    // ...
    return ErrorDeepEqual(e1.Cause(), e2.Cause())
}
Error에 원인이 있을 수 있기 때문에ErrorDeepEqual() 귀속 호출로 종료하여 원인이 같은지 확인합니다.문제는 이 줄nil의 지침에 공황이 나타난 테스트가 있었다는 것이다.
    if e1.Code() != e2.Code() {  // panics here because e1 is nil
그런데 이 줄 이전if행 검사nil라서 e1e2도 여기 있을 수 없는 거 맞죠?nil그럴 리가e1 == nil예요false그럴 리가e1?

이유가 무엇인가


바둑에서 이런 상황이 발생할 수 있고 충분한 사람들을 곤혹스럽게 하여 FAQ문제가 생겼다는 사실이 증명되었다.간단하게 요약하면 annil은 두 부분이 있는데 그것이 바로 유형과 상술한 유형의 값이다.유형 및 값이 interface인 경우에만 interface:
var i interface{}                // i = (nil,nil)
fmt.Println(i == nil)            // true
var p *int                       // p = nil
i = p                            // i = (*int,nil)
fmt.Println(i == nil)            // false: (*int,nil) != (nil,nil)
fmt.Println(i == (*int)(nil))    // true : (*int,nil) == (*int,nil)
fmt.Println(i.(*int) == nil)     // true : nil == nil
이제 Go가 왜 이런 방식으로 일을 했는지, 그리고 그 이유가 좋은 이유인지는 잠시 언급하지 않겠습니다.

복구


동시에 나는 내가 어떻게 비nilnil와 하나nil의 값을 얻었는지 알아야 한다.사실이 증명하듯이 이것이 바로 원흉이다.
func (e *MyError) Cause() Error {
    return e.cause               // WRONG!
}
문제는 언제든지 (값을 부여하거나 Error 은밀하게 nil 값을 부여하면, 값과 구체적인 유형을 동시에 받아들일 수 있다는 것이다.일단 그것이 구체적인 유형을 채택한다면, 그것은 영원히 (유형이 없는) interface 와 비교할 수 없을 것이다.해결 방법:
func (e *MyError) Cause() Error {
    if e.cause == nil {          // if typed pointer is nil ...
        return nil               //     return typeless nil explicitly
    }
    return e.cause               // else return typed non-nil
}
즉, 함수의 반환 유형이 return이고 반환nil 바늘이 있을 때는 반드시 검사interface, 바늘이 nil일 경우 텍스트nil를 반환해야 한다.

까닭


Go가 이렇게 일할 수 있었던 데는 몇 가지 이유가 있다.discussion thread에는 긴 go-nuts mailing list이 있다.내가 자세히 연구해 보니 주요 원인을 추출해냈다.
첫 번째 이유는 Go가 nil 형식뿐만 아니라 모든 사용자가 정의할 수 있는 유형을 정의할 수 있기 때문이다.예를 들어 우리는 자신의 nil 유형을 정의하고 이를 실현할 수 있다 struct int .
type Aint int                    // accounting int: print negatives in ()

func (n Aint) String() string {
    if n < 0 {
        return fmt.Sprintf("(%d)", -n)
    }
    return fmt.Sprintf("%d", n)
}
이제 Stringerinterface 변수를 사용하겠습니다.
var n Aint                       // n = (Aint,0)
var s fmt.Stringer = n           // s = (Aint,0)
fmt.Println(s == nil)            // false: (Aint,0) != (nil,nil)
여기Stringer의 시비-interface는 하나s를 가리키기 때문에 값이 딱 0이다.0값은 주의할 만한 것이 없다. 왜냐하면 0은 nil에 매우 좋은 값이기 때문이다.
두 번째 이유는 바둑의 타자 능력이 뛰어나기 때문이다.예를 들면 다음과 같습니다.
fmt.Println(s == 0)              // error: can't compare (Aint,0) to (int,0)
Aint (int 를 0과 비교할 수 없기 때문에 (일반 0의 유형이 s 이기 때문에) 컴파일할 때 오류가 발생했습니다.하지만 강제 변환 또는 유형 선언을 사용할 수 있습니다.
fmt.Println(s == Aint(0))        // true: (Aint,0) == (Aint,0)
fmt.Println(s.(Aint) == 0)       // true: 0 == 0
세 번째 이유Go explicitly allows Aint for methods having pointer receivers:
type T struct {
    // ...
}

func (t *T) String() string {
    if t == nil {
        return "<nil>"
    }
    // ...
}
0대int가 아주 좋은 값인 것처럼 nil바늘에도 아주 좋은 값이다.Aint가 아무 것도 가리키지 않았기 때문nilit can be used to implement default behavior for nil .쌍T은 앞의 예시를 중복하지만 지금은 T에 대해 앞의 예시를 중복한다.
var p *T                         // p = (*T,nil)
var s fmt.Stringer = p           // s = (*T,nil)
fmt.Println(s == nil)            // false: (*T,nil) != (nil,nil)
fmt.Println(s == (*T)(nil))      // true : (*T,nil) == (*T,nil)
fmt.Println(s.(*T) == nil)       // true : nil == nil
지침에 대해 우리는 비슷한 결과를 얻었다. 거의.스케줄러: 일치하지 않는 점을 찾아낼 수 있겠어?

어디에 문제가 생겼는가


앞에서 사용한 예제Aint와 달리 *T(유형 Aint와 0s을 비교하는 것은 오류입니다.
fmt.Println(s == 0)              // error: can't compare (Aint,0) to (int,0)
Aintints*T을 비교하는 것은 가능하다.
fmt.Println(s == nil)            // OK to compare
문제는 Go에서 nil 컨텍스트에 따라 두 가지 다른 의미가 있습니다.
  • nil는 값이 0인 포인터를 가리킵니다.
  • nil는 유형과 0값이 없는 nil를 가리킨다.
  • Suppose Go had another keyword nil for case 2 and interface were reserved exclusively for case 1 . 그러면 위의 줄은 다음과 같이 써야 한다.
    fmt.Println(s == nilinterface)   // false: (*T,nil) != nilinterface
    
    분명히 당신은 nilinterface 자체에 유형과 0 값이 있는지 검사하는 것이지 검사하는 것이 아닙니다.
    또한 원시nil를 사용하면 오류가 발생합니다(같은 이유로 강제 변환이나 유형 단언 없이 interface와 0을 비교할 수 없습니다).
    fmt.Println(s == nil)            // what-if error: can't compare (*T,nil) to (nil,nil)
    fmt.Println(s == (*T)(nil))      // still true: (*T,nil) == (*T,nil)
    fmt.Println(s.(*T) == nil)       // still true: nil == nil
    
    Go의 당혹스러움은 새 프로그래머가 작성할 때 다음과 같다.
    fmt.Println(s == nil)            // checks interface for empty, not the value
    
    그들은 인터페이스의 값을 검사하고 있다고 생각하지만, 실제로는 인터페이스 자체에 유형과 0값이 없는지 검사하고 있다.만약 Go가 독특한 키워드를 가지고 있다면 interface 모든 곤혹은 사라질 것이다.

    해커


    만약 이것들이 모두 번거롭다고 생각된다면, 함수를 만들어서 nilAint 값을 검사하고, 그 종류를 무시할 수 있습니다. (있을 경우)
    func isNilValue(i interface{}) bool {
        return i == nil || reflect.ValueOf(i).IsNil()
    }
    
    비록 이것은 가능하지만, 그것은 매우 느리다. 왜냐하면 반사가 매우 느리기 때문이다.신속한 구현은 다음과 같습니다.
    func isNilValue(i interface{}) bool {
        return (*[2]uintptr)(unsafe.Pointer(&i))[1] == 0
    }
    
    이것은 nilinterface의 값 부분이 0인지 직접 검사하고 유형 부분을 무시합니다.

    좋은 웹페이지 즐겨찾기