Go 1.13 이후의 error 검사
55815 단어 Go
Go 1.13 이후의 error 검사
기사 목록
시작
만약 Go가 많은 비난을 받고 있다면, Go 중의 error의 처리는 반드시 토조 차트 10위 안에 들어갈 수 있을 것이다.기왕 try
문장의 제의가 거절당하고 거절당했으니 우리도 오래된if로 오류를 선별할 수밖에 없다.Go 공식은 error의 계륵 문제를 의식하지 못한 것이 아니다. 그래서 Go 1.13에서 새로운 해결 방안을 제시했다. 전체적으로 말하자면'3개api+하나의 형식 점유자'이다.
error
Go에서 error는 어디에서 왔습니까?Go를 능숙하게 사용하는 사람들은 반드시 다음과 같은 몇 가지 방법을 알고 있다.
Go에서 error는 어디에서 왔습니까?Go를 능숙하게 사용하는 사람들은 반드시 다음과 같은 몇 가지 방법을 알고 있다.
type error Interface { Error() string }
파일 열기를 예로 들면 위의 처리 방식에 따라 코드는 다음과 같습니다.
// errors.New
func openConfigFile(path string) error {
_, err := os.Open(path)
if err != nil {
// error
return errors.New("can not open the specific file")
}
return nil
}
// fmt.Errorf
func openConfigFile(path string) error {
_, err := os.Open(path)
if err != nil {
// error , error
return fmt.Errorf("can not open the specific file, reason: %v", err)
}
return nil
}
// return error that called function returned
func openConfigFile(path string) error {
_, err := os.Open(path)
if err != nil {
// error,
return err
}
return nil
}
// error
type OpenErr struct {
Err error // error
}
// error
func (*OpenErr) Error() string {
return "can not open the specific file"
}
func openConfigFile(path string) error {
_, err := os.Open(path)
if err != nil {
return &OpenErr{Err:err}
}
return nil
}
상술한 네 가지 방법은 각각 천추가 있다.방법 1, errors.New는 새 error를 되돌려줍니다. 저장된 데이터는 우리가 전송한 텍스트 ("can not open the specific file") 입니다.이런 방식을 채택하는 것은 통상적으로 호출자에게 오류가 발생했다는 것을 알려주기 위해서이지만, 실제 오류의 세부 사항은 폭로하기를 원하지 않는다.호출자에 대해서 말하자면, 그는 무슨 잘못이 생겼는지에 대해 그다지 관심을 가지지 않고, 단지 잘못이 있었는지에 대해서만 신경을 쓴다.
방법 2는 방법 1과 같고 원시 오류를 숨길 수도 있지만 보통 원시 오류의 문자열 설명을 함께 되돌려줍니다.이 처리 방식에서 "can not open the specific file"은 추가 제시어이고, "reason:% v"는 오류 디테일을 표시합니다.일반적으로 fmt를 호출합니다.Errorf의 더 큰 확률은 코드를 해석하는 것이 아니라 보여 주는 오류가 발생하는 것이다.
방법 3, 일반적으로 함수 호출자는 error의 실제 유형이나 실제 값에 따라 다음 실행 전략을 확정해야 한다.즉, 이것은 error를 해석해야 하기 때문에 함수는'플레인'error를 되돌려줍니다.이렇게 하는 단점은 error에 추가 정보를 추가할 수 없다는 것이다.
방법4는 상술한 세 가지 방법의 집합으로 볼 수 있으며 원시적인 error를 보존할 수 있을 뿐만 아니라 추가 정보도 추가할 수 있다.이 메타데이터를 마음대로 조합하여 호출자가 원하는 모양을 모두 가지고 있다.단점은 코드를 좀 더 써야 한다는 것이다.
오류 확인 방법
지금 error가 생겼습니다. 우리는 어떻게 오류를 검사해야 합니까?1.13 이전에 흔히 볼 수 있는 것은: 1.비교 값;2. 유형 비교.
정부 원본을 예로 들면 더욱 설득력이 있다.우리 먼저 비교해 보자.func (db *DB) QueryContext(ctx context.Context,
query string,
args ...interface{}) (*Rows, error) {
var rows *Rows
var err error
for i := 0; i < maxBadConnRetries; i++ {
rows, err = db.query(ctx, query, args, cachedOrNewConn)
if err != driver.ErrBadConn { //
break
}
}
if err == driver.ErrBadConn { //
return db.query(ctx, query, args, alwaysNewConn)
}
return rows, err
}
func (db *DB) Query(query string,
args ...interface{}) (*Rows, error) {
return db.QueryContext(context.Background(), query, args...)
}
var ErrBadConn = errors.New("driver: bad connection")
상술한 것은 sql.Query
의 원본 코드로 ErrBadConn 오류를 처리하는 것이 바로 비교 값을 통해 이루어진 것이다.
비교 유형 처리 error는 Go 원본 코드에서 더욱 자주 사용되는데 주로 이런 처리 방식이 더욱 유연하고 오류 정보가 풍부하다.
코드의 주요 목적은 어떤 사이트를 방문하는 것이다.방문 과정에서 여러 가지 이상이 발생할 수 있지만, 우리는 시간 초과 이상만 처리합니다.// http
client := &http.Client{
Timeout: 3 * time.Second, // 3
}
// url
_, err := client.Get("http://www.meiyuouzhegewangzhan.com")
if err != nil {
if os.IsTimeout(err) { // err
fmt.Println("timeout")
} else { // err
fmt.Println("other errors")
}
}
위에서 알 수 있듯이 오류가 시간 초과 이상인지 판단하려면 호출os.IsTimeout
이 필요합니다. 원본 코드는 다음과 같습니다.func IsTimeout(err error) bool {
//
terr, ok := underlyingError(err).(timeout)
return ok && terr.Timeout()
}
func underlyingError(err error) error {
// err
switch err := err.(type) {
case *PathError:
return err.Err
case *LinkError:
return err.Err
case *SyscallError:
return err.Err
}
// err,
return err
}
IsTimeout
든 underlyingError
든 유형 비교를 통해 서로 다른 오류 유형을 처리한다.또한 Path Error, Link Error, SysCall Error를 계속 따라가면 이런 error는 모두 앞서 언급한 네 번째 방법이라는 것을 알게 될 것이다.그것들은 원시 error를 포장했지만 일정한 상황에서 그것을 꺼낸다 (예를 들어 상기 코드의 err.Err
.
1.13 + 오류 확인 방법
과거에 error를 검사하는 방식이 좀 번거롭다는 것을 인정할 수 밖에 없다. 특히 error가 너무 많이 포장되었을 때.만약에 error가 3층으로 포장되었다고 가정하면 가장 안쪽에 있는 error를 검사해야 합니다. 이것은 코드가 이렇게 써야 한다는 것을 의미합니다.e1, _ := e.(Err1)
e2, _ := e1.(Err2)
e3, _ := e2.(Err3)
if e3 == targetErr {
// handle
}
이러한 번거로움을 피하기 위해 1.13부터 Go는 체인의 error를 검사하는 두 가지 방법을 제공했다.비교값errors.Is
과 비교유형errors.As
이다.error 체인 검사를 지원하려면 구조체 (또는 다른 오류 형식) 에 익명 인터페이스 interface { Unwrap() error }
를 실행해야 합니다.
Unwrap
Unwrap 인터페이스는 구조체의 원시 error를 되돌려 주는 데 사용됩니다.PathError의 소스 코드 예:type PathError struct {
Op string
Path string
Err error
}
func (e *PathError) Unwrap() error { return e.Err }
errors.Is
errors.Is는 값을 통해 오류를 비교적 확인합니다. 여기에 오류가 있기 쉽습니다.많은 error가 바늘 형식이기 때문에, 바늘 형식의 비교는 바늘 변수가 저장된 주소가 같은지 비교하는 것이다.코드로 설명하면 이해하기 쉽습니다.err1 := errors.New("error")
err2 := errors.New("error")
//
fmt.Println(err1 == err2) // false
//
err1Elem := reflect.ValueOf(err1).Elem().Interface()
err2Elem := reflect.ValueOf(err2).Elem().Interface()
fmt.Println(err1Elem == err2Elem) // true
err1과 err2는 본질적으로 오류 정보까지 같지만 직접적으로 비교하면false를 얻을 수 있다.이것은 err1과 err2가 다른 주소를 가지고 있기 때문이다.두 번째 방식은 두 개의 바늘이 가리키는 구조체를 추출하여 두 구조체의 값을 비교하여true의 결과를 얻는 것이다.
공식 패키지가 이런 문제에 대한 처리는 종종 오류 변수를 정의하고 그 후의 전체 프로그램이 실행되는 동안 값을 부여한 오류 변수를 사용합니다. 같은 오류의 바늘이 항상 같은 주소를 가리키는지 확인하십시오.// go
var (
// ErrInvalid indicates an invalid argument.
// Methods on File will return this error when the receiver is nil.
ErrInvalid = errInvalid() // "invalid argument"
ErrPermission = errPermission() // "permission denied"
ErrExist = errExist() // "file already exists"
ErrNotExist = errNotExist() // "file does not exist"
ErrClosed = errClosed() // "file already closed"
ErrNoDeadline = errNoDeadline() // "file type does not support deadline"
)
func errInvalid() error { return oserror.ErrInvalid }
func errPermission() error { return oserror.ErrPermission }
func errExist() error { return oserror.ErrExist }
func errNotExist() error { return oserror.ErrNotExist }
func errClosed() error { return oserror.ErrClosed }
func errNoDeadline() error { return poll.ErrNoDeadline }
파일의 존재 여부를 판단하는 두 가지 방법:var err error
f, err := os.Open(" ")
defer f.Close()
// 1
if os.IsNotExist(err) { // if stmt
fmt.Println(" ")
}
// 2
if errors.Is(err, os.ErrNotExist) { // if stmt
fmt.Println(" ")
}
그래서 상기 특성에 따라errors.Is는 error 체인을 거의 처리할 수 없습니다.똑똑한 공식 라이브러리가 어떻게 이런 점을 생각하지 못했을까. 해결 방안은 원본에서 찾아야 한다.// go
func Is(err, target error) bool {
...
isComparable := reflectlite.TypeOf(target).Comparable()
for {
//
if isComparable && err == target {
return true
}
// Is
if x, ok := err.(interface{ Is(error) bool }); ok && x.Is(target) {
return true
}
...
if err = Unwrap(err); err == nil {
return false
}
}
}
errors.Is는 선진적인 값을 비교할 것입니다. 만약 실패하면 Is가 Is를 호출하기 때문에 사용자 정의 error에 Is를 실현하는 방법을 줄 수 있습니다. 이것은 error가 같은 처리 논리를 작성하는 데 사용됩니다.// Err1
type Err1 struct {
Err error
}
func (e *Err1) Error() string { return "err1" }
func (e *Err1) Unwrap() error { return e.Err }
func (e * Err1) Is(other error) bool {
v1 := reflect.ValueOf(e)
v2 := reflect.ValueOf(other)
//
if v1.IsNil() || v2.IsNil() {
return false
}
//
v1 = v1.Elem()
if v2.Kind() == reflect.Ptr {
v2 = v2.Elem()
}
return v1.IsValid() && v2.IsValid() && v1.Interface() == v2.Interface()
}
// Err2
type Err2 struct {
Err error
}
func (e *Err2) Error() string { return "err2" }
func (e *Err2) Unwrap() error { return e.Err }
func (e *Err2) Is(other error) bool {
v1 := reflect.ValueOf(e)
v2 := reflect.ValueOf(other)
//
if v1.IsNil() || v2.IsNil() {
return false
}
//
v1 = v1.Elem()
if v2.Kind() == reflect.Ptr {
v2 = v2.Elem()
}
return v1.IsValid() && v2.IsValid() && v1.Interface() == v2.Interface()
}
// Err3
type Err3 struct {
Err error
}
func (e *Err3) Error() string { return "err3" }
func (e *Err3) Unwrap() error { return e.Err }
func (e *Err3) Is(other error) bool {
v1 := reflect.ValueOf(e)
v2 := reflect.ValueOf(other)
//
if v1.IsNil() || v2.IsNil() {
return false
}
//
v1 = v1.Elem()
if v2.Kind() == reflect.Ptr {
v2 = v2.Elem()
}
return v1.IsValid() && v2.IsValid() && v1.Interface() == v2.Interface()
}
// error
func genErr() error {
return &Err1{
Err: &Err2{
Err: &Err3{
Err: nil,
},
},
}
}
func main() {
err := genErr()
err3 := &Err3{Err:nil}
fmt.Println(errors.Is(err, err3))
}
코드가 분명히 거추장스러워져서 아래를 내려다보면 더 우아한 방법이 있다.
errors.As
오류 체인이 존재할 때, 우리는 유형으로 오류를 포지셔닝하고, errors를 사용하는 경향이 있다.오류가 아닌.Is.이렇게 하면 불필요한 Is 방법을 피할 수 있다.// Err1
type Err1 struct {
Err error
}
func (e *Err1) Error() string { return "err1" }
func (e *Err1) Unwrap() error { return e.Err }
// Err2
type Err2 struct {
Err error
}
func (e *Err2) Error() string { return "err2" }
func (e *Err2) Unwrap() error { return e.Err }
// Err3
type Err3 struct {
Err error
}
func (e *Err3) Error() string { return "err3" }
func (e *Err3) Unwrap() error { return e.Err }
// error
func genErr() error {
return &Err1{
Err: &Err2{
Err: &Err3{
Err: nil,
},
},
}
}
func main() {
err := genErr()
var err3 *Err3
fmt.Println(errors.As(err, &err3)) //
}
자리 표시자%w
대부분의 경우 우리는 사용자 정의 오류 유형을 크게 조작할 필요가 없고, 두 번째 방식fmt.Errorf
을 즐겨 사용한다.1.13부터%w 자리 표시자를 사용하면 원시 error를 감싸서 err 필드에 넣고 msg에 새 error 문자열을 구축합니다. 대응하는 구조체는 wrapError입니다.type wrapError struct {
msg string
err error
}
func (e *wrapError) Error() string {
return e.msg
}
func (e *wrapError) Unwrap() error {
return e.err
}
즉, 다음과 같은 사용법은 원시 error를 잃지 않고 오히려 error 체인을 구축하여 errors를 편리하게 할 수 있다.Is 및 errors.As에서 오류 검사를 진행합니다.if _, err := os.Open("xxx.txt") {
return fmt.Errorf("failed open, reason: %w", err)
}
감사
func (db *DB) QueryContext(ctx context.Context,
query string,
args ...interface{}) (*Rows, error) {
var rows *Rows
var err error
for i := 0; i < maxBadConnRetries; i++ {
rows, err = db.query(ctx, query, args, cachedOrNewConn)
if err != driver.ErrBadConn { //
break
}
}
if err == driver.ErrBadConn { //
return db.query(ctx, query, args, alwaysNewConn)
}
return rows, err
}
func (db *DB) Query(query string,
args ...interface{}) (*Rows, error) {
return db.QueryContext(context.Background(), query, args...)
}
var ErrBadConn = errors.New("driver: bad connection")
// http
client := &http.Client{
Timeout: 3 * time.Second, // 3
}
// url
_, err := client.Get("http://www.meiyuouzhegewangzhan.com")
if err != nil {
if os.IsTimeout(err) { // err
fmt.Println("timeout")
} else { // err
fmt.Println("other errors")
}
}
func IsTimeout(err error) bool {
//
terr, ok := underlyingError(err).(timeout)
return ok && terr.Timeout()
}
func underlyingError(err error) error {
// err
switch err := err.(type) {
case *PathError:
return err.Err
case *LinkError:
return err.Err
case *SyscallError:
return err.Err
}
// err,
return err
}
과거에 error를 검사하는 방식이 좀 번거롭다는 것을 인정할 수 밖에 없다. 특히 error가 너무 많이 포장되었을 때.만약에 error가 3층으로 포장되었다고 가정하면 가장 안쪽에 있는 error를 검사해야 합니다. 이것은 코드가 이렇게 써야 한다는 것을 의미합니다.
e1, _ := e.(Err1)
e2, _ := e1.(Err2)
e3, _ := e2.(Err3)
if e3 == targetErr {
// handle
}
이러한 번거로움을 피하기 위해 1.13부터 Go는 체인의 error를 검사하는 두 가지 방법을 제공했다.비교값
errors.Is
과 비교유형errors.As
이다.error 체인 검사를 지원하려면 구조체 (또는 다른 오류 형식) 에 익명 인터페이스 interface { Unwrap() error }
를 실행해야 합니다.Unwrap
Unwrap 인터페이스는 구조체의 원시 error를 되돌려 주는 데 사용됩니다.PathError의 소스 코드 예:
type PathError struct {
Op string
Path string
Err error
}
func (e *PathError) Unwrap() error { return e.Err }
errors.Is
errors.Is는 값을 통해 오류를 비교적 확인합니다. 여기에 오류가 있기 쉽습니다.많은 error가 바늘 형식이기 때문에, 바늘 형식의 비교는 바늘 변수가 저장된 주소가 같은지 비교하는 것이다.코드로 설명하면 이해하기 쉽습니다.
err1 := errors.New("error")
err2 := errors.New("error")
//
fmt.Println(err1 == err2) // false
//
err1Elem := reflect.ValueOf(err1).Elem().Interface()
err2Elem := reflect.ValueOf(err2).Elem().Interface()
fmt.Println(err1Elem == err2Elem) // true
err1과 err2는 본질적으로 오류 정보까지 같지만 직접적으로 비교하면false를 얻을 수 있다.이것은 err1과 err2가 다른 주소를 가지고 있기 때문이다.두 번째 방식은 두 개의 바늘이 가리키는 구조체를 추출하여 두 구조체의 값을 비교하여true의 결과를 얻는 것이다.
공식 패키지가 이런 문제에 대한 처리는 종종 오류 변수를 정의하고 그 후의 전체 프로그램이 실행되는 동안 값을 부여한 오류 변수를 사용합니다. 같은 오류의 바늘이 항상 같은 주소를 가리키는지 확인하십시오.
// go
var (
// ErrInvalid indicates an invalid argument.
// Methods on File will return this error when the receiver is nil.
ErrInvalid = errInvalid() // "invalid argument"
ErrPermission = errPermission() // "permission denied"
ErrExist = errExist() // "file already exists"
ErrNotExist = errNotExist() // "file does not exist"
ErrClosed = errClosed() // "file already closed"
ErrNoDeadline = errNoDeadline() // "file type does not support deadline"
)
func errInvalid() error { return oserror.ErrInvalid }
func errPermission() error { return oserror.ErrPermission }
func errExist() error { return oserror.ErrExist }
func errNotExist() error { return oserror.ErrNotExist }
func errClosed() error { return oserror.ErrClosed }
func errNoDeadline() error { return poll.ErrNoDeadline }
파일의 존재 여부를 판단하는 두 가지 방법:
var err error
f, err := os.Open(" ")
defer f.Close()
// 1
if os.IsNotExist(err) { // if stmt
fmt.Println(" ")
}
// 2
if errors.Is(err, os.ErrNotExist) { // if stmt
fmt.Println(" ")
}
그래서 상기 특성에 따라errors.Is는 error 체인을 거의 처리할 수 없습니다.똑똑한 공식 라이브러리가 어떻게 이런 점을 생각하지 못했을까. 해결 방안은 원본에서 찾아야 한다.
// go
func Is(err, target error) bool {
...
isComparable := reflectlite.TypeOf(target).Comparable()
for {
//
if isComparable && err == target {
return true
}
// Is
if x, ok := err.(interface{ Is(error) bool }); ok && x.Is(target) {
return true
}
...
if err = Unwrap(err); err == nil {
return false
}
}
}
errors.Is는 선진적인 값을 비교할 것입니다. 만약 실패하면 Is가 Is를 호출하기 때문에 사용자 정의 error에 Is를 실현하는 방법을 줄 수 있습니다. 이것은 error가 같은 처리 논리를 작성하는 데 사용됩니다.
// Err1
type Err1 struct {
Err error
}
func (e *Err1) Error() string { return "err1" }
func (e *Err1) Unwrap() error { return e.Err }
func (e * Err1) Is(other error) bool {
v1 := reflect.ValueOf(e)
v2 := reflect.ValueOf(other)
//
if v1.IsNil() || v2.IsNil() {
return false
}
//
v1 = v1.Elem()
if v2.Kind() == reflect.Ptr {
v2 = v2.Elem()
}
return v1.IsValid() && v2.IsValid() && v1.Interface() == v2.Interface()
}
// Err2
type Err2 struct {
Err error
}
func (e *Err2) Error() string { return "err2" }
func (e *Err2) Unwrap() error { return e.Err }
func (e *Err2) Is(other error) bool {
v1 := reflect.ValueOf(e)
v2 := reflect.ValueOf(other)
//
if v1.IsNil() || v2.IsNil() {
return false
}
//
v1 = v1.Elem()
if v2.Kind() == reflect.Ptr {
v2 = v2.Elem()
}
return v1.IsValid() && v2.IsValid() && v1.Interface() == v2.Interface()
}
// Err3
type Err3 struct {
Err error
}
func (e *Err3) Error() string { return "err3" }
func (e *Err3) Unwrap() error { return e.Err }
func (e *Err3) Is(other error) bool {
v1 := reflect.ValueOf(e)
v2 := reflect.ValueOf(other)
//
if v1.IsNil() || v2.IsNil() {
return false
}
//
v1 = v1.Elem()
if v2.Kind() == reflect.Ptr {
v2 = v2.Elem()
}
return v1.IsValid() && v2.IsValid() && v1.Interface() == v2.Interface()
}
// error
func genErr() error {
return &Err1{
Err: &Err2{
Err: &Err3{
Err: nil,
},
},
}
}
func main() {
err := genErr()
err3 := &Err3{Err:nil}
fmt.Println(errors.Is(err, err3))
}
코드가 분명히 거추장스러워져서 아래를 내려다보면 더 우아한 방법이 있다.
errors.As
오류 체인이 존재할 때, 우리는 유형으로 오류를 포지셔닝하고, errors를 사용하는 경향이 있다.오류가 아닌.Is.이렇게 하면 불필요한 Is 방법을 피할 수 있다.
// Err1
type Err1 struct {
Err error
}
func (e *Err1) Error() string { return "err1" }
func (e *Err1) Unwrap() error { return e.Err }
// Err2
type Err2 struct {
Err error
}
func (e *Err2) Error() string { return "err2" }
func (e *Err2) Unwrap() error { return e.Err }
// Err3
type Err3 struct {
Err error
}
func (e *Err3) Error() string { return "err3" }
func (e *Err3) Unwrap() error { return e.Err }
// error
func genErr() error {
return &Err1{
Err: &Err2{
Err: &Err3{
Err: nil,
},
},
}
}
func main() {
err := genErr()
var err3 *Err3
fmt.Println(errors.As(err, &err3)) //
}
자리 표시자%w
대부분의 경우 우리는 사용자 정의 오류 유형을 크게 조작할 필요가 없고, 두 번째 방식fmt.Errorf
을 즐겨 사용한다.1.13부터%w 자리 표시자를 사용하면 원시 error를 감싸서 err 필드에 넣고 msg에 새 error 문자열을 구축합니다. 대응하는 구조체는 wrapError입니다.type wrapError struct {
msg string
err error
}
func (e *wrapError) Error() string {
return e.msg
}
func (e *wrapError) Unwrap() error {
return e.err
}
즉, 다음과 같은 사용법은 원시 error를 잃지 않고 오히려 error 체인을 구축하여 errors를 편리하게 할 수 있다.Is 및 errors.As에서 오류 검사를 진행합니다.if _, err := os.Open("xxx.txt") {
return fmt.Errorf("failed open, reason: %w", err)
}
감사
type wrapError struct {
msg string
err error
}
func (e *wrapError) Error() string {
return e.msg
}
func (e *wrapError) Unwrap() error {
return e.err
}
if _, err := os.Open("xxx.txt") {
return fmt.Errorf("failed open, reason: %w", err)
}
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
Golang과 DB를 연결하여 주가 데이터를 그래프로 표시합니다.진화 제작된 프로그램. 지난번에 한 품종에 집중되어 데이터를 보였는데, 이번에는 텍스트 상자에 입력한 품종 코드를 바탕으로 나는 도표를 표시하는 기능을 만들고 싶다. html에서 텍스트 상자와 단추를 준비하세요. w...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.