Advertisement

100 Go Mistakes and How to Avoid Them 之代码结构和项目组织(一)

阅读量:

来源《100 Go Mistackes:How to Avoid Them》

一 意外的变量隐藏

变量的有效范围是指其可见程度。换句话说,在程序设计中,名称的有效部分通常受到特定限制。在Go语言中,声明在函数或方法块内的变量可以在内部嵌套的块(如子函数)中再次声明。这种称为'变量隐藏'的原则容易导致逻辑错误。

举例如下:
复制代码
    var client *http.Client  1
    if tracing {
        client, err := createClientWithTracing()  2 
        if err != nil {
                return err
        }
        log.Println(client)
    } else {
        client, err := createDefaultClient() 3 
        if err != nil {
                return err
        }
        log.Println(client)
    }
    // Use client
    
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    AI助手

在第一行先声明了一个名为client的变量。接着,在两个嵌套的代码块内使用了短变量声明运算符::=。这个运算符与上文中使用的名称相同,并生成了一个新的、独立的client变量。它不会为第一行定义的那个client变量赋值。因此,在该示例中HTTP客户端始终会保持为nil值。(注:在Golang中执行时会遇到这个问题)

如何解决这个问题呢:
复制代码
    var client *http.Client
    if tracing {
        c, err := createClientWithTracing()
        if err != nil {
                return err
        }
        client = c
    } else {
        // Same logic
    }
    
    
      
      
      
      
      
      
      
      
      
      
    
    AI助手

为外部命名空间命名为该名称,并将在内部用其他临时变量替代;最后将该临时名称重新指配给外部命名空间中的client。

最基本的做法就是在内部对该变量进行赋值操作即可(无需重新声明其他变量)

复制代码
    var client *http.Client
    var err error
    if tracing {
        client, err = createClientWithTracing()
        if err != nil {
                return err
        }
    } else {
        // Same logic
    }
    
    
      
      
      
      
      
      
      
      
      
      
    
    AI助手
最终这个代码可以优化成如下结构:
复制代码
    if tracing {
        client, err = createClientWithTracing()
    } else {
        client, err = createDefaultClient()
    }
    if err != nil {
        // Common error handling
    }
    
    
      
      
      
      
      
      
      
      
    
    AI助手
二 不必要的嵌套代码
复制代码
    func join(s1, s2 string, max int) (string, error) {
        if s1 == "" {
                return "", errors.New("s1 is empty")
        } else {
                if s2 == "" {
                        return "", errors.New("s2 is empty")
                } else {   -------(1)
                        concat, err := concatenate(s1, s2)
                        if err != nil {
                                return "", err
                        } else {
                                if len(concat) > max {
                                        return concat[:max], nil
                                } else {
                                        return concat, nil
                                }
                        }
                }
        }
    }
    
    func concatenate(s1 string, s2 string) (string, error) {
        // ...
    }
    
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    AI助手
在编号为(1)的这个位置,这个else的判断可以去掉,修改如下:
复制代码
    func join(s1, s2 string, max int) (string, error) {
        if s1 == "" {
                return "", errors.New("s1 is empty")
        }
        if s2 == "" {
                return "", errors.New("s2 is empty")
        }
        concat, err := concatenate(s1, s2)  // 这里避免了else判断
        if err != nil {
                return "", err
        }
        if len(concat) > max {
                return concat[:max], nil
        }
        return concat, nil
    }
    
    func concatenate(s1 string, s2 string) (string, error) {
        // ...
    }
    
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    AI助手
这样的话,在编辑器里面看到的效果可能就如下所示:
在这里插入图片描述
代码结构如下的:
复制代码
    if foo() {
        // ...
        return true
    } else {
        // ...
    }
    
    
      
      
      
      
      
      
    
    AI助手
可以修改成下面这样的:
复制代码
    if foo() {
        return true
    }
    // ...
    
    
      
      
      
      
    
    AI助手
三 误用初始化方法( init functions)

在Go语言中,默认情况下是第一个被调用的方法(函数),其主要作用是初始化相关参数或配置信息。

复制代码
    package main
    
    import "fmt"
    
    var a = func() int {
        fmt.Println("var")
        return 0
    }()
    
    func init() {
        fmt.Println("init")
    }
    
    func main() {
        fmt.Println("main")
    }
    
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    AI助手
得到的结果是:
复制代码
    var
    init
    main
    
    
      
      
      
    
    AI助手
2 init执行顺序问题
main/main.go
复制代码
    package main
    
    import (
        "fmt"
    
        "redis"
    )
    
    func init() {
        // ...
    }
    
    func main() {
        err := redis.Store("foo", "bar")
        // ...
    }
    
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    AI助手
redis/redis.go
复制代码
    package redis
    
    // imports
    
    func init() {
        // ...
    }
    
    func Store(key, value string) error {
        // ...
    }
    
    
      
      
      
      
      
      
      
      
      
      
      
    
    AI助手
在main目录下和redis目录下,均有init方法,执行顺序如下:
在这里插入图片描述
3 init方法不能嵌套在其他方法里面
复制代码
    package main
    
    func init() {}
    
    func main() {
        init()
    }
    
    
      
      
      
      
      
      
      
    
    AI助手
编译后的结果
复制代码
    $ go build .
    ./main.go:6:2: undefined: init
    
    
      
      
    
    AI助手
4 什么时候开始使用
一般用来初始化配置或者数据库链接校验等
复制代码
    var db *sql.DB
    
    func init() {
        dataSourceName := os.Getenv("MYSQL_DATA_SOURCE_NAME")
        d, err := sql.Open("mysql", dataSourceName)
        if err != nil {
                log.Panic(err)
        }
        err = d.Ping()
        if err != nil {
                log.Panic(err)
        }
        db = d
    }
    
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    AI助手

全部评论 (0)

还没有任何评论哟~