go编码规范

编码规范

1. true/false求值

  1. 当明确expr为bool类型时,禁止使用==或!=与true/false比较,应该使用expr或!expr

  2. 判断某个整数表达式expr是否为零时,禁止使用!expr,应该使用expr == 0

    • 示例

    	GOOD:
    var isWhiteCat bool
    var num int
    
    if isWhiteCat {
        // ...
    }
    
    if num == 0 {
        // ...
    }
    BAD:
    var isWhiteCat bool
    var num int
    
    if isWhite == true {
        // ...
    }
    
    if !num {
        // ...
    }

2. Receiver

  • receiver是什么就不用说了。

  1. Receiver Type

    • 如果receiver是map、函数或者chan类型,类型不可以是指针

    • 如果receiver是slice,并且方法不会进行reslice或者重新分配slice,类型不可以是指针

    • 如果receiver是struct,且包含sync.Mutex类型字段,则必须使用指针避免拷贝。

    • 如果receiver是比较大的struct/array,建议使用指针,这样会更有效率

    • 如果receiver是struct、array或slice,其中指针元素所指的内容可能在方法内被修改,建议使用指针类型(注意第二条,和这条不冲突)

    • 如果receiver是比较小的struct/array,建议使用value类型

  2. receiver命名

    • 尽量简短并有意义。

    • 禁止使用“this"、”self“等面向对象语言中特定的叫法。

    • receiver的命名要保持一致性

3. embedding的使用

  1. embedding只用于"is a"的语义下,而不用于"has a"的语义下

  2. 一个定义内,多于一个的embedding尽量少用

    • 语义上embedding是一种“继承关系“,而不是”成员关系“

    • 一个定义内有多个embedding,则很难判断某个成员变量或函数是从哪里继承得到的

    • 一个定义内有多个embedding,危害和在python中使用”from xxx import *"是类似的

  3. 示例

4. 枚举类型的使用

  1. go没有自带的枚举类型,通常的用法为:使用自增长常量来自定义一个枚举类型,允许你依靠编译器完成自增设置。

  2. 示例

5. 文件风格规范

  1. Go文件Layout,建议文件按以下顺序进行布局

    • General Documentation: 对整个模块和功能的完整描述注释,写在文件头部。

    • package:当前package定义

    • imports:包含的头文件

    • Constants:常量

    • Typedefs: 类型定义

    • Globals:全局变量定义

    • functions:函数实现

  2. General Documentation Layout

    • 建议每个文件开头部分包括文件copyright说明(copyright)。

    • 建议每个文件开头部分包括文件标题(Title),Title应该在一行内完成。

    • 建议每个文件开头部分包括修改记录(Modification History),记录文件的修改过程,并且只记录最主要的修改。

    • 建议每个文件开头部分包括文件描述(Description),详细描述文件的功能和作用。

    • 示例

  3. import规范

    • 需要按照如下顺序进行头文件import,并且每个import部分内的package需按照字母升序排列

      • 系统package

      • 第三方的package

      • 程序自己的package

    • 每部分import间用单个空行进行分隔

  4. 函数注释,建议包括以下内容

    • Description:对函数的完整描述,主要包括函数功能和使用方法

    • Params:对参数的说明

    • Returns:对返回值的说明

6. 函数返回值

  1. 对于“逻辑判断型”的函数,返回值的意义代表“真”或“假”,返回值类型定义为bool

  2. 对于“操作型”的函数,返回值的意义代表“成功”或“失败”,返回值类型定义为error

    • 如果成功,则返回nil

    • 如果失败,则返回对应的error值

  3. 对于“获取数据型”的函数,返回值的意义代表“有数据”或“无数据/获取数据失败”,返回值类型定义为(data, error)

    • 正常情况下,返回为:(data, nil)

    • 异常情况下,返回为:(data, error)

  4. 函数返回值小于等于3个,大于3个时必须通过struct进行包装

  5. 函数参数不建议超过3个,大于3个时建议通过struct进行包装

7. 程序规模

  1. 每行代码不超过100个字符。

  2. 每行注释不超过100个字符。

  3. 函数不超过100行。

  4. 文件不超过2000行。

8. 命名规范

  1. 文件名

    • 文件名都使用小写字母,如果需要,可以使用下划线分割

    • 文件名的后缀使用小写字母

  2. 函数名/变量名

    • 采用驼峰方式命名,禁止使用下划线命名。首字母是否大写,根据是否需要外部访问来决定

  3. 常量

    • 常量建议使用const枚举类型

    • 禁止直接使用类似0、1、2、str、a、b这种含义不明的数字或字母

    • 建议都使用大写字母,如果需要,可以使用下划线分割

    • 尽量不要在程序中直接写数字,特殊字符串,全部用常量替代

9. 编程实践

  1. error string

    • error string尽量使用小写字母,并且结尾不带标点符号,因为可能error string会用于其它上下文中。

  2. Don't panic

    • 除非出现不可恢复的程序错误,不要使用panic,用多返回值和error。

  3. 关于lock的保护

    • 如果临界区内的逻辑较复杂、无法完全避免panic的发生,则要求适用defer来调用Unlock,即使在临界区过程中发生了panic,也会在函数退出时调用Unlock释放锁

    • go提供了recover,可以对panic进行捕获,但如果panic发生在临界区内,则可能导致对锁的使用没有释放。这种情况下,即使panic不会导致整个程序的奔溃,也会由于”锁不释放“的问题而使临界区无法被后续的调用访问。

      • 上述操作如果造成临界区扩大后,需要建立单独的一个函数访问临界区。

      • 对于如下的代码:

      • 如果改造为defer的方式,变为如下代码,实际上扩大了临界区的范围(step2的操作也被放置在临界区了)

      func doDemo() { lock.Lock() defer lock.Unlock()

      }

    • 需要使用单独的匿名函数,专门用于访问临界区:

最后更新于

这有帮助吗?