Go编码规范

命名规范

文件名

  • 小写单词,使用下划线分割,简短有意义
base_test.go

包命名

  • 包名与目录名相同,小写单词,不要使用下划线,不要和标准库冲突
package main

package utils

结构体

  • 驼峰式命名,首字母根据访问控制大小写
type User struct {
    Name   string
    Age    uint32
    Gender uint8
}

接口名

  • 单个方法,接口以er结尾
interface Reader {
    Read(content []byte) (n int, err error)
}

  • 两个方法,接口综合两个方法名
interface ReadWriter {
    Read(content []byte) (n int, err error)
    Write(content []byte) (n int, err error)
}

  • 三个以上的方法,接口命名同结构体
type Car interface {
    Start([]byte)
    Stop() error
    Recover()
}

函数名

  • 函数/方法名称应该直接表明函数的用途

  • 对于Seter和Geter的命名方式,Owner, SetOwner

type Pen struct{
    Length  float64
    owner   string
}


(p *Pen) func Owner() string {
    return p.owner
}

(p *Pen) func SetOwner(owner string) {
    p.owner = owner
}

  • 对于判断类型的函数,以Has, Is, Can, Allow开头
func HasPrefix(name string, prefixes []string) bool { ... }
func IsEntry(name string, entries []string) bool { ... }
func CanManage(name string) bool { ... }
func AllowGitHook() bool { ... }

常量名

  • 全部大写,用下划线分割

const APP_VERSION = "1.0"

  • 枚举类型的常量,需要先创建类型
type Protocol string

const (
    HTTP  Protocol = "http"
    HTTPS Protocol = "https"
)

  • 功能较复杂的情况下,常量名容易混淆,可以使用完整的前缀
type PullRequestStatus int

const (
    PULL_REQUEST_STATUS_CONFLICT PullRequestStatus = iota
    PULL_REQUEST_STATUS_CHECKING
    PULL_REQUEST_STATUS_MERGEABLE
)

变量名

  • 全局变量,驼峰式命名
var (
    ProjectName  string
    ProjectOwner string
)

  • 局部变量,驼峰式命名,使用短变量赋值
projectName  := "venus"
projectOwner := "loda"

  • 变量缩写

    • user可以简写为 u
    • userId可简写为 uid
    • context可简写为 ctx
    • error可以简写为 err
    • ...
  • bool类型变量,以Is, Has, Can, Allow等开头

注释规范

注释风格

  • 全部使用单行注释

  • 单行注释不要太长,不能超过120个字符

包注释

  • 位于package之前

  • 包的基本简介

  • 创建者

  • 创建时间

// util 包含一些公用的函数
// Author: loda
// Date: 20210723

package util

结构体(接口)注释

  • 结构体名称,结构体说明
// User , 用户对象,定义了用户的基础信息
type User struct{
    Username  string // 用户名
    Email     string // 邮箱
}

函数(方法)注释

  • Title, Description, Author, Params, Return
// @Title NewtAttrModel 
// @Description 属性数据层操作类的工厂方法
// @Author loda
// @Param  ctx  上下文信息
// @Return 属性操作类指针
func NewAttrModel(ctx *common.Context) *AttrModel {
}

代码逻辑注释

  • 关键位置的代码逻辑

  • 局部较复杂的逻辑说明

Bug注释

  • 针对代码中出现的Bug
// BUG(astaxie):This divides by zero. 
var i float = 1/0

代码风格

缩进和换行

  • 缩进使用gofmt工具即可

  • 一行不超过120个字符,超过换行显示

控制结构

  • 语句的结尾

    • 不需要分号,默认一行就是一句
    • 可以显示使用分号连接两行,但不建议使用
  • 括号和空格

    • 左大括号不换行
    • 所有的运算符和操作数之间要有空格
  • if else

    • 条件语句不需要小括号
    • 省略不必要的else
    • 可加上合适的初始化语句
result := query()

if err := check(result); err != nil {
    return err
}

  • for

for i:=0; i++; i<10 {
    // do sth
}

  • 遍历
import fmt

for pos, str := range "hello" {
    fmt.Printf("%q: %d\n", str, pos)
}

// 遍历 array, slice, map, chan, string
for k, v := range mapA {
    // do sth
}

  • switch
    • 表达式可以为变量、常量等
    • case可以列举多个条件
    • 可使用break体检结束
func Factory(name string, value interface{}) interface{} {
    var object interface{}

    switch name {
    case "A1", "A2":
        object = NewA()
    case "B":
        object = newB()
        if value == nil {
            break
            }
        object.SetValue(value)
    }

    return object
}

  • switch type
func ErrorWrap(e interface{}) *TraceableError {
    var message string

    switch e := e.(type) {
    case TraceableError:
        return &e
    case *TraceableError:
        return e
    case error:
        message = e.Error()
    default:
        message = fmt.Sprintf("%v", e)
    }
    return ErrorNew(message, 2)
}

  • break
    • break在switch中可以提前结束
    • break在循环中需要借助标签提前结束
package main

import (
    "fmt"
)

func main() {
Loop:
    for index := 1; index < 10; index++ {
        switch index % 5 {
        case 1:
            break 
        case 0:
            break Loop
        default:
            fmt.Printf("%v\n", index)
        } 
    }
}

  • select类似switch, 用于多个管道的读取

结构体和接口

  • 结构体初始化
type MyStruct struct {
    Value int
}

type MyStruct2 struct {
    MyStruct
    ID int 
}

p1 := new (MyStruct) // type *SyncedBuffer
p2 := &MyStruct{}   // type *SyncedBuffer

s1 := MyStruct{
    Value: 0, 
}

s2 := MyStruct2{
    ID: 0,
    MyStruct1: MyStruct1 {
        Value: 1, 
    },
}

method receiver

  • map, chan, interface, func: 不要使用指针(它们隐式使用指针)

  • 如果不存在对切片重新分配,则不要使用指针

  • 如果方法会改变receiver,则使用指针

  • 如果receiver中有类似sync.Mutex等用于同步的成员,则必须使用指针

  • 一般接受者都声明为指针类型

defer

  • 打开文件、连接等后,需要defer关闭连接
  • 慎用defer处理锁

chan

  • 创建管道时使用make

  • 只读管道: ch <-chan int

  • 只写管道: ch chan<- int


ch := make(chan int 1)

func handle(readCh <-chan int, writeCh chan<- int) {
    go func() {
        v := <-readCh
        writeCh <- 2 * v
    }()
}

gorouting

  • 线程安全

    • gorouting是在线程池中执行的,在gorouting中访问闭包需要考虑线程安全问题
  • Once

    • 提供一个线程安全的单次执行的接口,用于单例模式或初始化场景
package main

import (
    "sync"
)

type singleton struct {}

var instance *singleton

var once sync.Once

func GetInstance() *singleton {
    once.Do(func() {
        instance = &singleton{}
    })
    return instance
}

  • WaitGroup
    • 初始化 WaitGroup,加上特定的值
    • goroutine结束时记得调用WaitGroup.Done()
    • 主流程执行 WaitGroup.Wait()
package main

import (
    "fmt"
    "sync"
)

func main() {
    var wg sync.WaitGroup

    wg.Add(1)

    go func() {
            fmt.Printf("hello ")
            wg.Done()
        }()

    wg.Wait()
    fmt.Printf("world\n")
}

  • Atomic
    • sync.atomic包提供了一系列同步原语来控制临界资源的访问
package main

import (
    "fmt"
    "sync"
    "sync/atomic"
)

func main() {
    var value int64
    var wg sync.WaitGroup

    wg.Add(2)

    fun := func(count int) {
        for index := 0; index < count; index++ {
            atomic.AddInt64(&value, 1)
        }
        wg.Done()
    }

    go fun(100)
    go fun(100)

    wg.Wait()
    fmt.Printf("%v\n", value)
}

import规范

  • 依次导入标准包、内部包、第三方包

  • 不要使用相对路径

import (
    "encoding/json"  //标准包
    "strings"

    "myproject/models"
    "myproject/controller"   //内部包
    "myproject/utils"

    "github.com/astaxie/beego"   //第三方包
    "github.com/go-sql-driver/mysql"
) 

错误处理

  • 不要使用'_'丢弃任何有返回err的调用

  • 一旦有错误发生,尽早return

  • 采用独立的错误流进行处理

  • 尽量不要使用panic

// 错误写法
if err != nil {
    // error handling
} else {
    // normal code
}

// 正确写法
if err != nil {
    // error handling
    return // or continue, etc.
}
// normal code

参数传递

  • 对于少量数据不要使用指针

  • 对于大量数据的struct可以考虑使用指针

  • 传入参数是map,slice,chan不要传递指针,因为map,slice,chan是引用类型,不需要传递指针的指针

单元测试

  • 单元测试代码的go文件必须以_test.go结尾

  • 单元测试的函数名必须以Test开头,是可导出公开的函数

  • 测试函数的签名必须接收一个指向testing.T类型的指针作为参数,并且该测试函数不能返回任何值

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,406评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,732评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,711评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,380评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,432评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,301评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,145评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,008评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,443评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,649评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,795评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,501评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,119评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,731评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,865评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,899评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,724评论 2 354

推荐阅读更多精彩内容