当前位置:网站首页>Golang error handling
Golang error handling
2022-07-01 18:51:00 【leeo】
To acquaint you with golang error Relevant knowledge .
0. introduction
In this article , I'll show you golang error Relevant knowledge , The general content is as follows :
golang error Comparison with other languages , Advantages and disadvantages golang Various types of errors in and recommended error types How to handle errors Yes go1.13 The error package of Yes golang error Summarize relevant knowledge 、 carding Thinking questions If there are any errors in this article 、 Or you can put forward some good ideas in time , I hope we can make progress together .
notes : All the code in this article is in :https://github.com/driftingboy/advance-go/tree/master/master_go/erroruse
1.Error vs Exception
1.1 Error The essence
Error It's essentially an interface
type error interface{
Error() string
}
Regular use errors.New() To return to a error object
For example, in the standard library error Definition , adopt bufio Prefix with context information
var (
ErrInvalidUnreadByte = errors.New("bufio: invalid use of UnreadByte")
ErrInvalidUnreadRune = errors.New("bufio: invalid use of UnreadRune")
ErrBufferFull = errors.New("bufio: buffer full")
ErrNegativeCount = errors.New("bufio: negative count")
)
errors.New() It's back error Object pointer
In order to prevent error When comparing , because error The same definition of internal content leads to two different types error Misjudge equality
1.2 Error and Exception The difference between
The evolution of languages :
C: Generally, the pointer is passed , By returning int Value to judge success or failure C++: There is no way to know what exception is thrown , Whether to throw an exception ( Only through documents ) JAVA: If you need to throw an exception, the owner of the method must declare , The caller must also handle . Processing mode 、 The degree of severity is distinguished by the caller . GO: Do not introduce exception, Return with multiple parameters , Generally, the last return value is error
error or panic: If you did java You will feel go Medium panic Processing is very similar java Throw in exception, Then we are using error still panic?
stay Go in panic Will cause the program to exit directly , It was a fatal mistake , If you use panic recover If you deal with it , There will be many problems 1) Performance issues , frequent panic recover Poor performance 2) It is easy to cause the program to exit abnormally , As long as one place is not handled, it will cause the whole program process to exit 3) Out of control , once panic The processing logic is handed over to the outside , We cannot assume that external packages will be processed When to use panic Well ? For really unexpected situations , Those that indicate unrecoverable program errors , For example, the index is out of bounds 、 Unrecoverable environmental problems 、 Stack overflow , We use panic
Use error Instead of exception The benefits of :
Simple Considering failure is not success There is no hidden control flow error are value
2.Error Type
2.1 Sentinel Error
ErrInvalidUnreadByte = errors.New("bufio: invalid use of UnreadByte")
Predefined errors , shortcoming :
inflexible , Caller use == To compare whether the error values are equal ; Once it appears fmt.Errorf This kind of carrying context information error, It will destroy the equality check Become your public api; such as io.reader,io.copy Such functions need to judge whether the error type is io.eof, But this is not a mistake . Create a dependency between the two packages
2.2 Error Types
Error types Is to implement the error Custom type of interface . for example MyError The type records the file and line number to show what happened :
os Under bag error type :https://golang.org/src/os/error.go
// PathError records an error and the operation and file path that caused it.
type PathError struct {
Op string
Path string
Err error
}
func (e *PathError) Error() string { return e.Op + " " + e.Path + ": " + e.Err.Error() }
func (e *PathError) Unwrap() error { return e.Err }
Error types advantage :
Carry more context
Error types shortcoming :
The caller uses type assertions and types switch, Let the customized error Turn into public. This model will lead to strong coupling with the caller , Which leads to API Become vulnerable . share error values Many of the same problems .
2.3 Opaque Error
When you start using errors.Cause(err, sql.ErrNoRows) or xerrors.Is(err, sql.ErrNoRows) when , Means sql.ErrNoRows As implementation details are exposed to the outside world , It became API Part of .
If you only use library code for business development , The judgment after packaging can be understood and accepted .
And for API For the definer of , This problem needs special attention , We need to Opaque error handling . Its advantage is : Reduce coupling between codes , The caller only needs to care about success or failure , Don't worry about the inside of the error
// Just return an error without assuming its contents
func fn()error{
x, err := bar.Foo()
if err != nil {
return err
}
// to do something
}
To put it bluntly, it just doesn't pass err To judge various situations , As a caller, I only care about success or failure (err Is it nil)
Assert errors for behaviour, not type
In a few cases , This dichotomy is not enough ( Only success 、 Two states of failure ).
such as net Operations in the package :https://golang.org/src/net/net.go
// An Error represents a network error.
type Error interface {
error
Timeout() bool // Is the error a timeout?
Temporary() bool // Is the error temporary?
}
if nerr, ok := err.(net.Error); ok && nerr.Temporary() {
// to do something
}
type temporary interface {
Temporary() bool
}
// You can use it directly without importing packages err Related actions
func IsTemporary(err error) bool {
te, ok := err.(temporary)
return ok && te.Temporary()
}
Any error can be passed to IsTemporary To determine if the error can be retried .
If the error is not implemented temporary Interface ; in other words , It has no Temporary Method , So mistakes are not temporary .
If the error does happen Temporary, So if true return true , The caller can retry the operation .
The key here is , This logic can define the wrong package without importing , Or know anything about err Based on the type of implementation - We're just interested in its behavior .
I suggest , Try to avoid Sentinel Error、error types, To use the Opaque Error, At least in the library file 、 public api in .
3.Handing Error Gracefully
3.1 Try to eliminate errors handle
1) Without destroying the correctness and readability of the program , Try to reduce error Handle
Patients with a
| Bad | Good |
|---|---|
| func AuthRequest() error{ if err:= auth();err!=nil{ return err } return nil } | func AuthRequest(r, *Request) error{ return auth(r.User) } |
Example 2
There are often a lot of if error != nil {...} This code , It can be optimized in some special scenarios
Let's start with a crunchy piece of code .
func parse(r io.Reader) (*Point, error) {
var p Point
if err := binary.Read(r, binary.BigEndian, &p.Longitude); err != nil {
return nil, err
}
if err := binary.Read(r, binary.BigEndian, &p.Latitude); err != nil {
return nil, err
}
if err := binary.Read(r, binary.BigEndian, &p.Distance); err != nil {
return nil, err
}
if err := binary.Read(r, binary.BigEndian, &p.ElevationGain); err != nil {
return nil, err
}
if err := binary.Read(r, binary.BigEndian, &p.ElevationLoss); err != nil {
return nil, err
}
}
We can see that this code is mainly calling binary.Read() Method , We can refer to bufio.Scanner The operation of
scanner := bufio.NewScanner(input)
for scanner.Scan() {
token := scanner.Text()
// process token
}
if err := scanner.Err(); err != nil {
// process the error
}
Errors will be saved to Scanner in , Only make the last judgment . Apply this idea , The optimization code is as follows
type Reader struct {
r io.Reader
err error
}
func (r *Reader) read(data interface{}) {
if r.err == nil {
r.err = binary.Read(r.r, binary.BigEndian, data)
}
}
func parse(input io.Reader) (*Point, error) {
var p Point
r := Reader{r: input}
r.read(&p.Longitude)
r.read(&p.Latitude)
r.read(&p.Distance)
r.read(&p.ElevationGain)
r.read(&p.ElevationLoss)
if r.err != nil {
return nil, r.err
}
return &p, nil
}
But this optimization scenario is limited , It can only be applied to the continuous operation of the same business object , For a variety of business objects and functions, we still have to be honest if err != nil
3.2 Annotating errors
In addition to returning the error directly , We often bring more context information to the error and then return , Because enough information can let us solve the problems quickly . The following will give error The operation of adding context is called error notes
at present error notes This is done as follows :
fmt.ErrorfGet a formatted error, Can carry extra error messagesCustomize error, By implementing Error()MethodWrong packing , adopt pkg/errors".Wrap
How to choose the right error notes The way ? The following points need to be considered :
Whether it needs to be captured by the caller 、 Handle
No need to be captured by the caller 、 Handle ; Use fmt.Errorf
func Open(filePath string) error {
_, err := createfile(filePath)
return fmt.Errorf(“createfile: %s“, err)
}
It needs to be captured by the client 、 Handle
Use fmt.Errorf, It will destroy the equality check , As a result, the error message string can only be used to compare whether the errors are equal , Very unreliable ; We can customize the error type , While carrying the context , Provide the caller with a reliable way to judge ; however , Customization errors are quite tedious , We can use pkg/errors This package simplifies operations . as follows :
// file package
var FileNotExsist = “file path not exsist: %s“
func Open(filePath string) error {
_, err := createfile(filePath)
return errors.warp(err, "createfile fail: ")
}
Here is a brief introduction to this bag github.com/pkg/errors, Using it to package downstream information is almost a golang Standard practice in .
It's very easy to use , If we want to generate a new error , have access to New function , Generated errors , With call stack information .
// fundamental is an error that has a message and a stack, but no caller.
type fundamental struct {
msg string
*stack
}
func New(message string) error {
return &fundamental{
msg: message,
stack: callers(),
}
}
there fundamental Objects are also implemented golang The built-in interface error Of Error Method .
If there is a ready-made error, We need to repack him , There are three functions to choose from .
// Just add new information , It is generally used to replace fmt.Errorf()
func WithMessage(err error, message string) error
// Only call stack information , Generally used for packaging on third-party codes ( Standard library or third-party library ) Call to .
func WithStack(err error) error
// Add stack and information at the same time , Generally used for packaging on third-party codes ( Standard library or third-party library ) Call to .
func Wrap(err error, message string) error
In fact, the package on it , Very similar to Java The abnormal packing of , Packaged error, Is the root cause of this mistake . So this error handling library provides us with Cause Function allows us to get the most primitive error object .
func Cause(err error) error {
type causer interface {
Cause() error
}
for err != nil {
cause, ok := err.(causer)
if !ok {
break
}
err = cause.Cause()
}
return err
}
Use for The cycle has always found the most fundamental ( At the bottom ) the error.
We have packed all the above mistakes , I've also collected , So how to put the stack in them 、 The reason for the error and so on are printed out ? Actually , The error type of this error handling Library , It's all done Formatter Interface , We can go through fmt.Printf The function outputs the corresponding error message .
%s,%v // Function as , Output error message , Does not contain stack %q // The output error message is quoted , Does not contain stack %+v // Output error messages and stacks
️ Don't pack incorrectly many times , Stack information will repeat .
If you use it many times WithStack(err), Will stack Print multiple times ,err The information can be very long . It's human flesh check Is it used in the lower level WithStack(err), If you use the lower layer, you don't use the upper layer . But it would add to the mental burden , It's easy to make mistakes . We can use a wrap function , Judge whether it has been carried out WithStack(err). however github.com/pkg/errors Self defined error type withStack Is private type , How to judge whether it has been implemented WithStack(err) Well ? Fortunately StackTrace It's not a private type , So we can use it interface A little trick for , Define one for yourself interface, If you have StackTrace() Method is no longer executed WithStack(err). like this :
type stackTracer interface {
StackTrace() errors2.StackTrace
}
// once
func WithStack(err error) error {
_, ok := err.(stackTracer)
if ok {
return err
}
return errors2.WithStack(err)
}
3.3 Only handle errors once
Handling an error means inspecting the error value, and making a decision.
I quote dave.cheney This sentence of , intend “ Dealing with errors means that you have checked the errors and made a decision ”.
If you make a decision Less than one , So you didn't check for errors 、 Errors are ignored , as follows :
func Write(w io.Writer, buf []byte) {
w.Write(buf)
}
w.Write(buf) Of error Discarded
If you make a decision about a problem More than one , There are also problems , as follows :
func Write(w io.Writer, buf []byte) error {
_, err := w.Write(buf)
if err != nil {
// annotated error goes to log file
log.Println("unable to write:", err)
// unannotated error returned to caller
return err
}
return nil
}
In the example above We record the error log , The error is returned to the caller , The returned error may be recorded layer by layer , Up to the top .
Finally, we will get a pile of repeated log information , But at the top, you can only get one of the most primitive mistakes .
We can choose according to different needs error notes ( Check the specific selection method [3.2](3.2 Annotating errors)), Back to the caller
func Write(w io.Write, buf []byte) error {
_, err := w.Write(buf)
return errors.Wrap(err, "write failed")
}
4.Golang1.13 error
Combined with community feedback ,Go The team finished at Go 2 A proposal to simplify error handling in . Go Core team members Russ Cox stay xerrors The content of the proposal is partially realized in . It is used with github.com/pkg/errors Solve the same problem with similar ideas , Introduced a new fmt Format verb : %w, Use Is Judge .:
import (
"database/sql"
"fmt"
"golang.org/x/xerrors"
)
func bar() error {
if err := foo(); err != nil {
return xerrors.Errorf("bar failed: %w", foo())
}
return nil
}
func foo() error {
return xerrors.Errorf("foo failed: %w", sql.ErrNoRows)
}
func main() {
err := bar()
if xerrors.Is(err, sql.ErrNoRows) {
fmt.Printf("data not found, %v\n", err)
fmt.Printf("%+v\n", err)
return
}
if err != nil {
// unknown error
}
}
/* Outputs:data not found, bar failed: foo failed: sql: no rows in result set
bar failed:
main.bar
/usr/four/main.go:12
- foo failed:
main.foo
/usr/four/main.go:18
- sql: no rows in result set
*/
And github.com/pkg/errors comparison , It has several shortcomings :
Use : %w Instead of Wrap It seems simplified , But the compile time check is lost . If there is no colon , or : %w Not at the end of the format string , Or there is no space between colon and percent , The package will be invalid without error
More serious , call xerrors.Errorf Parameters need to be nil Judge . This does not actually simplify the work of developers
here we are Go 1.13 ,xerrors Some functions of are integrated into the standard library . It inherited xerrors All the disadvantages of , And contributed an additional : Call stack information output is not supported . According to the official statement , There is no clear schedule for this function . Therefore, its practicability is far lower than github.com/pkg/errors
Therefore, it is not necessary to use it at present
5.go 2 inspection
https://go.googlesource.com/proposal/+/master/design/29934-error-values.md
go2 Add sum type, That is, a type can represent multiple types ; It's kind of similar protoBuffer Of oneof type ;
var int|error result
So this result Or int, Or error type ;
This also solves go There are some problems with generic proxies in ;
6. summary
Last , Let me help you sort out the key points of this article , That is, what we need to master at present
// TODO picture
The above figure is in the business code and library file respectively 、api The code uses error The advice of
panic
When the program starts , If a strongly dependent service fails panic sign out When the program starts , If it is found that the configuration obviously does not meet the requirements , Sure panic sign out ( Defense programming ) In other cases, as long as it is not an unrecoverable program error , Should not directly panic Should return to error At the program entrance , for example gin Middleware needs to use recover The prevention of panic Program exit We should avoid using wild goroutine If you need to execute asynchronous tasks in the request , Asynchronous should be used worker , Message notification , Avoid a large number of requests goroutine establish If needed goroutine when , You should use the same Go Function to create , In this function recover , Avoid wild goroutine panic Cause the main process to exit
func Go(f func()){
go func(){
defer func(){
if err := recover(); err != nil {
log.Printf("panic: %+v", err)
}
}()
f()
}()
}
error
We use it in our applications github.com/pkg/errors Handling application errors , Pay attention to the Public Library , We don't usually use this error It should be the last return value of the function , When error Not for nil when , Other return values of the function are unavailable , You should not expect anything from other return values When dealing with errors, you should first judge the errors , if err != nil If there is an error, return in time , Make the code a smooth straight line , Avoid excessive nesting When an error occurs in the application , Use errors.New perhaps errors.Errorf Returns an error
func (u *usecese) usecase1() error { money := u.repo.getMoney(uid) if money < 10 { errors.Errorf(" The user balance is insufficient , uid: %d, money: %d", uid, money) } // Other logic return nil } 5. If there is an error calling other functions of the application , Please return directly , If you need to carry information , Please use errors.WithMessage
func (u *usecese) usecase2() error {
name, err := u.repo.getUserName(uid)
if err != nil {
return errors.WithMessage(err, " Additional information ")
}
// Other logic
return nil
}
If it is to call other libraries ( Standard library 、 Enterprise public library 、 Open source third-party libraries, etc ) When getting an error , Please use errors.Wrap Add stack information
Bear in mind , Don't use it everywhere errors.Wrap It only needs to be done when the error first occurs errors.Wrap that will do
Judge whether it is necessary to swallow the original errors of other libraries according to the scene , For example, you can put repository Layer database related errors swallow , Return the business error code , Avoid our subsequent segmentation of micro services or replacement ORM You need to modify the upper code when Library
Note that we are in the basic library , Third party libraries introduced in large numbers are generally not used when writing errors.Wrap Avoid stack information duplication
It is forbidden to log every error , Just use it at the beginning of the process %+v Print uniformly , for example http/rpc Service-Oriented Middleware
Wrong judgment use errors.Is Compare
func f() error {
err := A()
if errors.Is(err, io.EOF){
return nil
}
// Other logic
return nil
}
Wrong type judgment , Use errors.As Assign a value
func f() error {
err := A()
if errA := new(errorA) && errors.As(err, &errA){
// ...
}
// Other logic
return nil
}
How to determine whether the wrong information is sufficient , Think about whether your error information can help you locate the problem quickly when there is a problem in your code that needs to be checked , For example, we usually output parameter information in the request , Used to assist in judging errors
For business errors , It is recommended to create an error dictionary in a unified place , The error dictionary should contain the wrong code, And print it as a separate field in the log , It is convenient to judge the business alarm , Errors must be clearly documented
No need to return , The ignored error must output log information
It should only be handled once , Output error log is also considered as processing , Once the function is determined / Method will handle errors , Mistakes are no longer mistakes . If the function / Method still needs to issue a return , Then it cannot return an error value . It should only return zero ( such as Degraded processing in , You returned degraded data , And then you need to return nil).
For the same type of error , Use the same pattern , For example, parameter error , Don't return 404 Some return 200
When dealing with mistakes , Need to deal with allocated resources , Use defer Clean up , For example, file handles
Try to avoid
Sentinel Error、error types, To use theOpaque Error, At least in the library file 、 public api in .
Recently, I am also designing 、 Reconstruction of the company's microservice error 、 Log related infrastructure , If you have the time , I will also write practical articles .
7. Thinking questions
When we operate the database , such as dao When a sql.ErrNoRows When , Whether it should be Wrap This error, Throw it to the top . Why? , Please write the code for what you should do ?
Why is it not allowed to use everywhere errors.Wrap ?
errors.wrap/ WithMessage What's the use , Why not use the standard library fmt.Errorf("%w")
reference
https://blog.csdn.net/u012516914/article/details/110848774 https://zhuanlan.zhihu.com/p/328591249 https://github.com/xxjwxc/uber_go_guide_cn
https://time.geekbang.org/column/article/330207?utm_source=u_nav_web&utm_medium=u_nav_web&utm_term=u_nav_web
https://dave.cheney.net/2012/01/18/why-go-gets-exceptions-right https://dave.cheney.net/2016/04/27/dont-just-check-errors-handle-them-gracefully https://dave.cheney.net/2015/01/26/errors-and-exceptions-redux https://dave.cheney.net/2014/11/04/error-handling-vs-exceptions-redux https://rauljordan.com/2020/07/06/why-go-error-handling-is-awesome.html https://morsmachine.dk/error-handling https://blog.golang.org/error-handling-and-go https://www.ardanlabs.com/blog/2014/10/error-handling-in-go-part-i.html https://www.ardanlabs.com/blog/2014/11/error-handling-in-go-part-ii.html https://dave.cheney.net/2016/04/27/dont-just-check-errors-handle-them-gracefully https://commandcenter.blogspot.com/2017/12/error-handling-in-upspin.html https://blog.golang.org/errors-are-values https://dave.cheney.net/2016/06/12/stack-traces-and-the-errors-package
https://www.ardanlabs.com/blog/2017/05/design-philosophy-on-logging.html https://crawshaw.io/blog/xerrors https://blog.golang.org/go1.13-errors https://medium.com/gett-engineering/error-handling-in-go-53b8a7112d04 https://medium.com/gett-engineering/error-handling-in-go-1-13-5ee6d1e0a55c
边栏推荐
- Is Alipay wallet convenient to use?
- A wonderful time to buy and sell stocks
- C# SelfHost WebAPI (2)
- 斯坦福、Salesforce|MaskViT:蒙面视觉预训练用于视频预测
- R语言使用epiDisplay包的aggregate函数将数值变量基于因子变量拆分为不同的子集,计算每个子集的汇总统计信息
- Image acquisition and playback of coaxpress high speed camera based on pxie interface
- Technology implementation and Architecture Practice
- Create your own NFT collections and publish a Web3 application to show them (Introduction)
- 搭建一個通用監控告警平臺,架構上需要有哪些設計
- R语言使用dplyr包的transmute函数计算dataframe数据中的指定数据列的移动窗口均值、使用ggplot2包可视化移动均值与原始数据的折线图
猜你喜欢

实现一个Prometheus exporter

About enterprise middle office planning and it architecture microservice transformation

3、《创建您自己的NFT集合并发布一个Web3应用程序来展示它们》在本地铸造 NFT

Mysql database of easyclick

用GSConv+Slim Neck改进Yolov5,将性能提升到极致!

Lumiprobe 双功能交联剂丨Sulfo-Cyanine5 双-NHS 酯

Lumiprobe 生物分子定量丨QuDye 蛋白定量试剂盒

AI 训练速度突破摩尔定律;宋舒然团队获得RSS 2022最佳论文奖

用WPF写一款开源方便、快捷的数据库文档查询、生成工具

Case study on comprehensive competitiveness of principal components
随机推荐
Li Kou daily question - Day 32 -589 N × Preorder traversal of tree
11、用户、组和权限(1)
Basic concepts of binary tree
lefse分析
[Chongqing Guangdong education] basic psychology reference materials of Tianjin Normal University
力扣每日一题-第32天-1232. 缀点成线
R language epidisplay package ordinal or. The display function obtains the summary statistical information of the ordered logistic regression model (the odds ratio and its confidence interval correspo
Unity learning fourth week
LeetCode-21合并两个有序链表
Localization through custom services in the shuttle application
力扣每日一题-第32天-589.N×树的前序遍历
如何在自有APP内实现小程序实现连麦直播
How to find the optimal learning rate
2. Create your own NFT collections and publish a Web3 application to show them start and run your local environment
golang 错误处理
主成分计算权重
记一次 .NET 差旅管理后台 CPU 爆高分析
每周推荐短视频:警惕“现象”与“问题”相互混淆
R language uses the aggregate function of epidisplay package to divide numerical variables into different subsets based on factor variables, and calculate the summary statistics of each subset
Privacy sandbox is finally coming