go语言中经常犯的错误( 二 )

结论就是:
当我们创建一个函数时,我们的默认行为应该是使用值而不是指针 。仅当我们想要共享变量时才应使用指针 。
最后:
如果我们遇到性能问题,一种可能的优化可能是检查指针在某些特定情况下是否有帮助 。使用以下命令可以知道编译器何时将变量转移到堆中:go build -gcflags "-m -m" 。(内存逃逸)
3、中断 for/switch 或 for/select我们看下下面的代码会发生什么:
package mainfunc f() bool { return true}func main() { for {switch f() {case true:breakcase false:// Do something} }}我们将调用 break 语句 。但是,这会破坏 switch 语句,而不是 for 循环 。
相同的情况还会出现在fo/select中,像下面这样:
package mainimport ( "context" "time")func main() { ch := make(chan struct{}) ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() for {select {case <-ch:// Do somethingcase <-ctx.Done():break} }}虽然调用了break,但是还是会陷入死循环 。break 与 select 语句有关,与 for 循环无关 。
打破 for/switch 或 for/select 的,一种方案是直接return结束整个函数,下面如果还有代码不会被执行 。
package mainimport ( "context" "fmt" "time")func main() { ch := make(chan struct{}) ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() for {select {case <-ch:// Do somethingcase <-ctx.Done():return} }// 这里不会执行 fmt.Println("done")}还有一种方案是使用中断标记
package mainimport ( "context" "fmt" "time")func main() { ch := make(chan struct{}) ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel()loop: for {select {case <-ch:// Do somethingcase <-ctx.Done():break loop} }// 会继续往下执行 fmt.Println("done")}4、错误管理一个错误应该只处理一次 。记录错误就是处理错误 。因此,应该记录或传播错误 。
我们可能希望为错误添加一些上下文并具有某种形式的层次结构 。
让我们看一个接口请求数据库的例子,我们分为接口层,service层和类库层 。我们希望返回的层次结构像下面这样:
unable to serve HTTP POST request for id 1 |_ unable to insert customer|_ unable to commit transaction如果我们使用 pkg/errors,我们可以这样做:
package mainimport ( "fmt" "Github.com/pkg/errors")func postHandler(id int) string { err := insert(id) if err != nil {fmt.Printf("unable to serve HTTP POST request for id %dn", id)return `{ok: false}` } return `{ok: true}`}func insert(id int) error { err := dbQuery(id) if err != nil {return errors.Wrapf(err, "unable to insert customer") } return nil}func dbQuery(id int) error { // Do something then fail return errors.New("unable to commit transaction")}func main() { res := postHandler(1) fmt.Println(res)}初始错误(如果不是由外部库返回)可以使用 errors.New 创建 。service层 insert 通过向其添加更多上下文来包装此错误 。然后,接口层通过记录错误来处理错误 。每个级别都返回或处理错误 。
例如,我们可能还想检查错误原因本身以实现重试 。假设我们有一个来自处理数据库访问的外部库的 db 包 。这个库可能会返回一个名为 db.DBError 的暂时(临时)错误 。要确定是否需要重试,我们必须检查错误原因:
package mainimport ( "fmt" "github.com/pkg/errors")type DbError struct { msg string}func (e *DbError) Error() string { return e.msg}func postHandler(id int) string { err := insert(id) if err != nil {errCause := errors.Cause(err)if _, ok := errCause.(*DbError); ok {fmt.Println("retry")} else {fmt.Printf("unable to serve HTTP POST request for id %dn", id)return `{ok: false}`} } return `{ok: true}`}func insert(id int) error { err := dbQuery(id) if err != nil {return errors.Wrapf(err, "unable to insert customer") } return nil}func dbQuery(id int) error { // Do something then fail return &DbError{"unable to commit transaction"}}func main() { res := postHandler(1) fmt.Println(res)}这是使用errors.Cause完成的,它也来自pkg/errors 。(可以通过errors.Cause检查 。errors.Cause 将递归检索没有实现causer 的最顶层错误,这被认为是原始原因 。)
有时候也会有人这么用 。例如,检查错误是这样完成的:
package mainimport ( "fmt" "github.com/pkg/errors")type DbError struct { msg string}func (e *DbError) Error() string { return e.msg}func postHandler(id int) string { err := insert(id) if err != nil {switch err.(type) {default:fmt.Printf("unable to serve HTTP POST request for id %dn", id)return `{ok: false}`case *DbError:fmt.Println("retry")} } return `{ok: true}`}func insert(id int) error { err := dbQuery(id) if err != nil {return errors.Wrapf(err, "unable to insert customer") } return nil}func dbQuery(id int) error { // Do something then fail return &DbError{"unable to commit transaction"}}func main() { res := postHandler(1) fmt.Println(res)}


推荐阅读