go语言编程习惯

写go的代码我纯凭感觉,通过接触到的源码找些go特有的感觉,然后把代码改到自己看着顺眼为止。不过最近需要输出一份go语言编码规范,我就感觉到,需要读各种go官方给出的文档,从里面抽取出个人用的比较多的东西翻译并组合一下。麻烦,但确实有些技术点是值得注意的。

格式化

最优先,也是最简单的,可以使用go自己的gofmt,google内部使用golint,集成到自己的编译器上即可。

注释

这里有点学问,以package为单位划分,每个package需要有个与包同名的go文件,用于描述当前包是做什么的,可以详细描述包的设计理念。注释使用c语言中常用的block注释/* xxx */

包内部的方法或者变量的注释,使用c++风格的注释// xxx

有这样一些注释的技术点需要注意:

常量或者变量的初始化,最好分组,并添加注释,之前我没有注意到这个问题。

方法的注释,第一个单词最好是方法名本身,因为涉及到godoc导出的问题。

包外可以访问的方法或者变量,一定要有注释,方便godoc,当然即便不使用godoc,这样也对使用你包的开发者有好处。

命名规则

命名这事,看似枯燥,实际开发中你总会发现,别人的命名比你的好,这就是差距。下面就简单介绍下。

包名,简短明确,不要引入下划线或者驼峰,而且不需要在全局是唯一的,所以你大可不必担心,bytes这样的命名已经被golang占用。保证和文件夹的名称相同(你非要搞成不相同的,那你得去看看肛肠科医生了)。

包内部方法名,外部引用时会使用package.F(),所以定义exported方法时,不需要使用报名作为前缀,例如:bufio.Reader不需要搞成bufio.BufReader

《clean code》我记得有一个这样的理念,尽量使用方法名说话,减少注释中的无用内容。但在写go时有个注意的情景,当一个exported方法设计相对复杂时,而上下文又相对简单,那么可以加重注释的分量,不要照本宣科。

java中定义一个对象时,通常会提供get/set方法,这在很多ide中可以省时省力的搞定,go中不行,不过go针对这两个方法还是有使用习惯的。GetXxx直接命名为Xxx即可,SetXxx这样就ok。

控制语句

if,在阅读代码时,发现if通常会包住一大段逻辑,并且在这一大段逻辑中还不返回到上层,继续执行下面的if,例如:

if order["est_out_of_wh_time"] == "0" && order["sales_type"] != "9" && order["sales_type"] != "15" {
    // 搞事情,但就不return
}

if order["order_status"] == "3" || order["order_status"] == "1" {
    // 继续搞事情,还他妈不return
}

// 最后return,都他妈等到天荒地老了

正解起码应该是:

if !order.IsSomeIllegalType() {
   // return
}

// 搞些事

if !order.IsSomeIllegalTypeAgain() {
    // return
}

// 我们起码要面向对象一点

if的原则是,在一个方法中尽量尽早的返回。

for语句就不bb了,switch有一个点是我认为自己学到了新的东西。当switch被包在一个for中时,你break直接跳到下一次循环,如果你想跳到for外就使用label的方式,如下:

Loop:
    for {
        switch {
            case xxx:
                break Loop
        }
    }

defer,这里记录一个npe bug,导致线上服务panic的情况,如下:

rows, err := db.Query()
defer rows.Close() // npe,虽然道理粗浅,但是还有有人会犯错
if err != nil {
    return err
}

defer后的方法,入栈,在函数体执行完毕,会LIFO的顺序执行。而且defer如果需要传入参数,不需要担心传入的参数,在之后的方法执行过程中有变化(排除指针的情况)。

还有一个需要注意的地方,defer中的逻辑不能过重,因为recover和panic的引入,defer是方法的最后一道防线,逻辑过重recover可能失去意义。

数据

array是值传递,可以传递指针,例如:

func Sum(a *[3]float64) (sum float64) {
    for _, v := range *a {
        sum += v
    }
    return
}

slice是指针传递,对array的包装。

断言的时候,尽量使用如下:

if str, ok := value.(string); ok {
    return str
} else if str, ok := value.(Stringer); ok {
    return str.String()
}

如果不获取ok,有可能会抛异常。

panic & recover

如果你开发的方法是被上层使用的,就要尽量避免panic。这里有一个例外,就是在你的lib初始化时如果失败,那么可以panic。有这样一个原则,就是对”输入宽容,但是输出严格“,意思就是没个开发人员都要在自己的code中终结问题。不要利用panic作为程序流转的一个出发点。

Error

你开发的包内部,如果错误可以被上层处理,或者想被上层处理,就实现一些特有的struct实现error,允许上层做判断。

包内部的错误,统一定义在常量文件中,也增加代码可读性,防止同样的错误string写多遍,不好维护。

其他习惯,需要自己慢慢养成了,主要就看golang的源代码,学习使用习惯。同时看《重构》《clean code》找些自己喜欢的编程理念。慢慢实验。

go语言编程习惯
Share this