에러를 몇가지 유용한 함수들로 랩핑한다.
제네릭을 사용하기 때문에 Go1.18+ 버전이 필요하다.
errwrap
은 에러의 랩핑, 타입변환, 타입체킹에 관한 함수들을 제공해준다. 이 함수들은 Factory
에 의해 생성되며, 이로 인해 에러 랩핑이 좀 더 시스템적으로 이루어질 수 있다.
제네릭을 사용하는 이유는 에러를 구별하기 위해서이다. 에러는 분명 서로 다른 메소드들에서 랩핑될 것이고, 그렇기에 그 메소드가 속한 타입을 에러의 타입으로 지정해두면 더 구별하기가 쉬워질 수 있다.
먼저 New
함수를 이용하여 기본 에러를 만들 수 있다. errwrap
은 New
, Newf
, NewTyped
, NewTypedf
와 같이 4가지의 New
함수를 제공한다.
errwrap.New("new")
errwrap.Newf("%s%s", "new", "f")
errwrap.NewTyped[T]("newTyped")
errwrap.NewTypedf[T]("%s%s", "newTyped", "f")
이렇게 생성된 에러는 아래에서 설명할 wrappedError처럼 포맷팅 될 수 있다.
Go의 error
인터페이스 인스턴스는 이 패키지의 wrappedError로 랩핑 될 수 있다. 랩핑하려면 먼저 팩토리를 통해 랩퍼를 생성해야 한다.
factory
는 이 에러가 발생한 곳의 타입을 제네릭으로 필요로 한다. 또한 에러가 무엇인지 설명할 메시지도 필요로 한다.
wrapper, assertor := Factory[UserController]("user controller")
// format specifier supplied
wrapper, assertor := Factoryf[UserController]("%s controller", "user")
factory
는 wrapper
와 assertor
두 함수를 반환한다. wrapper
는 에러를 랩핑할 수 있다. 랩핑 방법은 그냥 인자로 에러를 넣으면 된다.
err = wrapper(err)
에러 랩핑의 깊이가 깊을 경우 assertor
를 사용해서 특정 에러를 선택할 수 있다. 그럼 assertor
는 타입 변환된 에러와 성공 여부를 리턴한다. 만약 실패할 경우 첫번째 반환값은 nil
이 된다.
assertor
의 사용법은 wrapper
와 같다.
asserted, ok := assertor(err)
fmt.Println(asserted) // user controller: ...
checker
는 errors.Is
와 같이 동작하며 비교하려는 에러의 타입과 동일한지 알 수 있다. checker
를 만드려면 wrapper
가 필요하다. wrapper
는 WithChecker
의 인자가 되며 checker
를 생성할 수 있다.
checker := WithChecker(&wrapper)
checker
생성 후 wrapper
는 확인 가능한 에러를 만들 수 있다. checker
의 사용법은 아래와 같다:
if checker(err) {
fmt.Println("same")
}
wrappedError
는 에러 스택 저장을 통해 에러를 자세히 출력할 수 있으며 %+v
또는 %+s
를 통해 가능하다.
fmt.Printf("%+v\n", err)
# this will be shown like
github.com/p9595jh/errwrap.TestChecker
/Users/medium/Desktop/PJH/blockchain/__xyz/2023.03/errwrap/errwrap_test.go:69
github.com/p9595jh/errwrap.TestChecker
/Users/medium/Desktop/PJH/blockchain/__xyz/2023.03/errwrap/errwrap_test.go:68
github.com/p9595jh/errwrap.TestChecker
/Users/medium/Desktop/PJH/blockchain/__xyz/2023.03/errwrap/errwrap_test.go:61
github.com/p9595jh/errwrap.TestChecker
/Users/medium/Desktop/PJH/blockchain/__xyz/2023.03/errwrap/errwrap_test.go:60
Comparison with github.com/pkg/errors
.
Test code:
func BenchmarkWrappedError(b *testing.B) {
w, _ := Factory[NONE]("test")
err := New("sample")
for i := 0; i < b.N; i++ {
err := err
for j := 0; j < 100; j++ {
err = w(err)
}
_ = err
}
}
func BenchmarkErrors(b *testing.B) {
err := errors.New("sample")
for i := 0; i < b.N; i++ {
err := err
for j := 0; j < 100; j++ {
err = errors.Wrap(err, "test")
}
_ = err
}
}
Run:
$ go test -bench=. -benchtime=10s -benchmem -count 5
goos: darwin
goarch: amd64
pkg: github.com/p9595jh/errwrap
cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
BenchmarkWrappedError-12 165193 72756 ns/op 28000 B/op 300 allocs/op
BenchmarkWrappedError-12 152595 72621 ns/op 28000 B/op 300 allocs/op
BenchmarkWrappedError-12 155252 73013 ns/op 28000 B/op 300 allocs/op
BenchmarkWrappedError-12 175780 72772 ns/op 28000 B/op 300 allocs/op
BenchmarkWrappedError-12 149059 71417 ns/op 28000 B/op 300 allocs/op
BenchmarkErrors-12 152004 82268 ns/op 33600 B/op 400 allocs/op
BenchmarkErrors-12 150874 78414 ns/op 33600 B/op 400 allocs/op
BenchmarkErrors-12 152422 79438 ns/op 33600 B/op 400 allocs/op
BenchmarkErrors-12 155005 85094 ns/op 33600 B/op 400 allocs/op
BenchmarkErrors-12 115872 88641 ns/op 33600 B/op 400 allocs/op
Wrappable in 10s | ns/op | B/op | allocs/op | |
---|---|---|---|---|
errwrap | 159575.8 | 72515.8 | 28000 | 300 |
github.com/pkg/errors | 145235.4 | 82771 | 33600 | 400 |
github.com/pkg/errors
에 비해 처리 능력이 약 9.87% 향상되었다. 그 외에도 전반적으로 향상되었다.
go get github.com/p9595jh/errwrap
import "github.com/p9595jh/errwrap"