NOTE

1.3 error

The distinction between exception and error in Go, how errors are used and handled, their implementation, and practical patterns.

GoCreated Updated 1 min readhistorical

This is a historical learning note and may contain outdated or incomplete understanding.

1. exception and error

Golang divides errors into exception and error.

  • exception refers to bugs inside the program or unexpected situations
  • error refers to expected situations that can be checked through a return value

2. Usage

2.1. exception

  • Internal bugs are caught through recovery and converted into errors for handling
  • Unexpected situations terminate the program through panic

2.2. error

  • Construct an err through fmt.ErrorOf or errs.New
func TestError1(t *testing.T) {
	// The passed string should start with a lowercase letter and should not end with punctuation
	fmt.Println(errors.New("network cannot reachable"))
	fmt.Println(fmt.Errorf("value %s too long", "aaaaaaaaaaa"))
}
// output
network cannot reachable
value aaaaaaaaaaa too long
  • How to handle an error
    • Use github.com/pkg/errors
    • Principles
      • Do not handle it repeatedly. For example, the lower layer catches an error and logs it, then throws it upward again
      • Do not only check the error. For example, when the lower layer returns an error upward, the upper layer should not return it unchanged; it should add information
      • Do not determine a specific error from the error string. Although you can determine a concrete type through type conversion, defining error codes is more recommended

2.2.1. Example

  • main.go
package main

import (
	"database/sql"
	"fmt"
	"github.com/pkg/errors"
	"log"
	"test/errors/service"
)



func main() {
	err := service.FindUser(1)
	if errors.Cause(err) == sql.ErrNoRows {
		fmt.Printf("data not found, %v\n", err)
		return
	}
	if err != nil {
		log.Fatalf("%+v\n", err)
		return
	}

	log.Println("---------------------------")
}

// output
// when it is 0
data not found, service finduser failed: dao find user faile
d: sql: no rows in result set

// when it is 1
val cannot be 1
test/errors/dao.SelectOne
        C:<USER_HOME>/code/go/test/errors/dao/dao.go:12
test/errors/service.FindUser
        C:<USER_HOME>/code/go/test/errors/service/service.go:9
main.main
        C:<USER_HOME>/code/go/test/errors/errors.go:32
runtime.main
        C:/software/Go/src/runtime/proc.go:203
runtime.goexit
        C:/software/Go/src/runtime/asm_amd64.s:1357
service finduser failed


// when it is 2
---------------------------
  • service.go
package service

import (
	"github.com/pkg/errors"
	"test/errors/dao"
)

func FindUser(val int) error {
	return errors.WithMessage(dao.SelectOne(val), "service finduser failed")
}
    
  • dao.go
package dao

import (
	"database/sql"
	"github.com/pkg/errors"
)

func SelectOne(val int) error {
	if val == 0 {// wrap the underlying error
		return errors.Wrap(sql.ErrNoRows, "dao find user failed")
	} else if val == 1 {// custom error
		return errors.Errorf("val cannot be %d", val)
	}
	return nil
}

3. Implementation

3.1. Data Structure

type error interface {
    Error() string
}

3.2. Methods

// src/errors/errors.go

func New(text string) error {
    // Create an errorString object
	return &errorString{text}
}

// Contains only the error message
type errorString struct {
	s string
}

func (e *errorString) Error() string {
	return e.s
}


func Errorf(format string, a ...interface{}) error {
    // Format the string first
	p := newPrinter()
	p.wrapErrs = true
	p.doPrintf(format, a)
	s := string(p.buf)
	var err error
	if p.wrappedErr == nil {
	    // Then create it through errors.New
		err = errors.New(s)
	} else {
		err = &wrapError{s, p.wrappedErr}
	}
	p.free()
	return err
}

4. Best Practices

go get -u golang.org/x/xerrors
  • main
func main() {
	err := service.FindUser(1)
	/* 
	2022/10/30 16:08:16 predefined error: query user failed:
		test/errors/service.FindUser
			C:<USER_HOME>/code/go/test/errors/service/service.go:11
	- check val [0] failed:
		test/errors/dao.checkVal
			C:<USER_HOME>/code/go/test/errors/dao/dao.go:23
	- invalid val
	exit status 1
	*/
	if errors.Is(err, dao.ErrorInvalidVal) {
		log.Fatalf("predefined error: %+v\n", err)
	}
	/* 
	2022/10/30 16:09:15 non-predefined error: query user failed:
		test/errors/service.FindUser
			C:<USER_HOME>/code/go/test/errors/service/service.go:11
	- record not found for val [1]:
		test/errors/dao.SelectOne
			C:<USER_HOME>/code/go/test/errors/dao/dao.go:16
	exit status 1
	*/
	if err != nil {
		log.Fatalf("non-predefined error: %+v\n", err)
	}
	log.Println("-------------success--------------")
}
  • service
func FindUser(val int) error {
	if err := dao.SelectOne(val); err != nil {
		return xerrors.Errorf("query user failed: %w", err)
	}
	return nil
}
  • dao
var ErrorInvalidVal = errors.New("invalid val")

func SelectOne(val int) error {
	if err := checkVal(val); err != nil {
		return err
	}
	if val == 1 {
		return xerrors.Errorf("record not found for val [%v]", val)
	}
	return nil
}

func checkVal(val int) error {
	if val == 0 {
		return xerrors.Errorf("check val [%d] failed: %w", val, ErrorInvalidVal)
	}
	return nil
}

5. References

Discussion

Sign in with GitHub to comment. Discussions are stored as GitHub Issues.View on GitHub