언제제로
Error
struct
를 실현하여 상세한 오류 정보를 전달했다. Go의 내장error
interface
만이 제공하는 간단한 오류 메시지가 아니다.내가 처음 그것을 실현했을 때, 나는
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 toswitch
on them. However, there are a few disadvantages of integer codes including:
- 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).
- 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:
- Presumably, handling errors is exceptional, so it doesn’t matter that the code is a bit slower since it’s not critical-path.
- 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
itoa
ing andatoi
ing 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
임에도 불구하고 Error
는 struct
에 의해 이루어져야 한다.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
라서 e1
와e2
도 여기 있을 수 없는 거 맞죠?nil
그럴 리가e1 == nil
예요false
그럴 리가e1
?이유가 무엇인가
바둑에서 이런 상황이 발생할 수 있고 충분한 사람들을 곤혹스럽게 하여 FAQ문제가 생겼다는 사실이 증명되었다.간단하게 요약하면 an
nil
은 두 부분이 있는데 그것이 바로 유형과 상술한 유형의 값이다.유형 및 값이 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가 왜 이런 방식으로 일을 했는지, 그리고 그 이유가 좋은 이유인지는 잠시 언급하지 않겠습니다.복구
동시에 나는 내가 어떻게 비
nil
nil
와 하나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)
}
이제 Stringer
interface
변수를 사용하겠습니다.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
가 아무 것도 가리키지 않았기 때문nil
it 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)
Aint
형int
과 s
형*T
을 비교하는 것은 가능하다.fmt.Println(s == nil) // OK to compare
문제는 Go에서 nil
컨텍스트에 따라 두 가지 다른 의미가 있습니다.nil
는 값이 0인 포인터를 가리킵니다.nil
는 유형과 0값이 없는 nil
를 가리킨다.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
모든 곤혹은 사라질 것이다.해커
만약 이것들이 모두 번거롭다고 생각된다면, 함수를 만들어서
nil
의 Aint
값을 검사하고, 그 종류를 무시할 수 있습니다. (있을 경우)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인지 직접 검사하고 유형 부분을 무시합니다.
Reference
이 문제에 관하여(언제제로), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/pauljlucas/go-tcha-when-nil--nil-hic텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)