Go语言编码规范

"编码规范(Go)"规范为日常Go项目开发提供一个统一的规范指导, 方便团队形成统一的代码风格, 提高代码可读性, 规范性和一致性.

同时作为CR的有效指导工具, 如果有变更的, 需要补充的, 可以在文档中(文档下)添加评论说明进行补充.

大部分的格式问题可以通过gofmt解决,gofmt自动格式化代码,保证所有的go代码与官方推荐的格式保持一致,于是所有格式有关问题,都以gofmt的结果为准。

目录

一. 命名规范

1 . 包命名

2.  文件命名

3. 方法命名(typespec - request, response)

4. 结构体命名

5. 接口命名

6. 变量命名

7. 常量命名

8. 关键字

二. 注释

1. 注释风格

2. 包注释

3. 结构体(接口)注释

4. 函数(方法)注释

5. 代码逻辑注释

三. 其他规范

1 . Error命名规范

2 . import 规范

四. 编程建议

1. Get方法

2. New方法

3. 错误处理

4. 空行建议

5. 一致性

6. 减少嵌套

五. 其他编码规范参考


一. 命名规范

命名是代码规范中很重要的一部分,统一的命名规则有利于提高的代码的可读性,好的命名仅仅通过命名就可以获取到足够多的信息。

Go在命名时以字母a到Z或a到Z或下划线开头,后面跟着零或更多的字母、下划线和数字(0到9)。Go不允许在命名时中使用@、$和%等标点符号。Go是一种区分大小写的编程语言。因此,Manpower和manpower是两个不同的命名。

  • 当命名(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头,如:Group1,那么使用这种形式的标识符的对象就可以被外部包的代码所使用(客户端程序需要先导入这个包),这被称为导出(像面向对象语言中的 public);
  • 命名如果以小写字母开头,则对包外是不可见的,但是他们在整个包的内部是可见并且可用的(像面向对象语言中的 private )

1 . 包命名

保持package的名字和目录保持一致,尽量采取有意义的包名,简短,有意义,尽量和标准库不要冲突。包名应该为小写单词,不要使用下划线或者混合大小写。

  • 全部小写。没有大写或下划线。
  • 简短而简洁。
  • 不用复数。例如net/url,而不是net/urlsh
  • 避免“common”,“util”,“shared”或“lib”,这些是不好的、信息量不足的名称。

Bad

Good

package http_client
package User 
package services
package httpclient 
package user 
package service

2.  文件命名

尽量采取有意义的文件名,简短,有意义.

  • 使用下划线分隔各个单词(由于业务代码名称一般偏长, 更清晰的表达, 投票后的结果) 全部小写单词, 不用驼峰式, 
  • 其中测试文件以test.go结尾,除测试文件外,命名不出现

社区惯例是 不使用下划线(除非是测试文件)

但是业务项目,命名往往比较复杂、冗长,不用下划线的话,对代码阅读是个灾难

所以建议我们使用下划线进行分割, 这部分需要最终确认

3. 方法命名(typespec - request, response)

MixedCaps

方法名函数名变量常量等标识符的命名,我们遵循Go社区关于使用 MixedCaps (可导出-大驼峰, 不可导出-小驼峰) 的约定。

有一个例外,为了对相关的测试用例进行分组,函数名可能包含下划线,如:TestMyFunction_WhatIsBeingTested.

关于缩写 名称中的缩写词或首字母缩写词(例如"URL"或"NATO")具有一致的大小写。

例如,"URL"应显示为"URL"或"url"(如在"urlPony"或"URLPony"中),而不应显示为"Url"。

对于包含多个缩写的标识符,也应当保遵守这个规则,例如:"xmlHTTPRequest"或"XMLHTTPRequest"。

https://github.com/golang/go/wiki/CodeReviewComments#mixed-caps

(This applies even when it breaks conventions in other languages. For example an unexported constant is maxLength not MaxLength or MAX_LENGTH.)

方法名应该是动词或动词短语(动词+名词),采用驼峰式。将功能及必要的参数体现在名字中, 不要嫌长, 如updateById,getUserInfo.

Bad

Good

BlackImportRequest
CrontabChangeType()
Count()
ImportBlackRequest
ChangeCrontabType()
CountProduct()
AddProductRequest
GetProductListRequest
GetProduct()
GetProductBySaleProductId()

4. 结构体命名

  • 采用驼峰命名法,首字母根据访问控制大写或者小写
  • struct 申明和初始化格式采用多行,例如下面:
  • 结构体名应该是名词或名词短语,如Account,Book,避免使用Manager这样的

// 多行申明

type User struct{

    Name  string

    Email     string

}

// 多行初始化

u := User{

    Name: "wanglu",

    Email:    "[email protected]",

}

5. 接口命名

单个函数的接口名以 er 为后缀

type Reader interface {

    Read(p []byte) (n int, err error)

}


两个函数的接口名综合两个函数名,如:

type WriteFlusher interface {

    Write([]byte) (int, error)

    Flush() error

}


三个以上函数的接口名类似于结构体名,如:

type Car interface {

    Start()

    Stop()

    Drive()

}

6. 变量命名

和结构体类似,变量名称一般遵循驼峰法,首字母根据访问控制原则大写或者小写,但遇到特有名词时,需要遵循以下规则:

  • 作用域越小,命名应该越简短。如在for循环内部用i表示index
  • 变量名不应该包含类型,变量的名称应描述其内容,而不是内容的类型
  • 如果变量为私有,且特有名词为首个单词,则使用小写,如 apiClient
  • 其它情况都应当使用该名词原有的写法,如 APIClient、repoID、UserID
  • 错误示例:UrlArray,应该写成 urlArray 或者 URLArray
  • 若变量类型为 bool 类型,则名称应以 Has, Is, Can 或 Allow 开头

var isExist bool

var hasConflict bool

var canManage bool

var allowGitHook bool

7. 常量命名

常量均需使用MixedCaps命名方式,并使用下划线分词:

const ProductVersion = "1.0"

如果是枚举类型的常量,需要先创建相应类型

type Scheme string

const (

    HTTP  Scheme = "http"

    HTTPS Scheme = "https"

)

8. 关键字

下面的列表显示了Go中的保留字。这些保留字不能用作常量或变量或任何其他标识符名称

包管理(2个):

    import  package

程序实体声明与定义(8个):

    chan    const   func    interface   map struct  type    var

程序流程控制(15个):

    break   case    continue    default defer   else    fallthrough

    for     go      goto        if      range   return  select      switchtps

二. 注释

注释虽然写起来很痛苦, 但对保证代码可读性至关重要. 下面的规则描述了如何注释以及在哪儿注释.

当然也要记住: 注释固然很重要, 但最好的代码应当本身就是文档.

有意义的类型名和变量名, 要远胜过要用注释解释的含糊不清的名字.

你写的注释是给代码读者看的, 也就是下一个需要理解你的代码的人. 所以慷慨些吧, 下一个读者可能就是你!

  • 必须 - 脚本都需要添加注释: 表明脚本的作用, 参数的含义, 预期的数据, 输入参数的含义
  • 建议 - 方法上建议添加注释, 标注方法的作用
  • 建议 - 有步骤执行顺序的代码, 各个顺序关键节点, 建议添加注释
  • 建议 - if, switch等分支判断的时候, 建议添加注释便于理解

1. 注释风格

  • /**/ 的块注释和 // 的单行注释两种注释风格, 在我们的项目中为了风格的统一,全部使用单行注释,注释的质量决定了生成的文档的质量。

2. 包注释

// 请填写文件描述

package ${GO_PACKAGE_NAME}

3. 结构体(接口)注释

// User   用户对象,定义了用户的基础信息

type User struct{

    Username  string // 用户名

    Email     string // 邮箱

}

4. 函数(方法)注释

// 函数的详细描述

// 函数的详细描述2

func (r *RiskCase) GetRiskCase(ctx context.Context)

5. 代码逻辑注释

// 代码块的执行解释

// 执行解释2

if   userAge < 18 {

}

三. 其他规范

1 . Error命名规范

使用MixedCaps或者mixedCaps的形式, 以Err开头

Bad

Good

var ErrorSomething = errors.New("something went wrong") 
var SomethingErr = errors.New("something went wrong") 
var SomethingError = errors.New("something went wrong")
// Package level exported error. 
var ErrSomething = errors.New("something went wrong")

2 . import 规范

// 单行引入

import  "fmt"

// 多包引入,每包独占一行

// 使用绝对路径,避免相对路径如 ../encoding/json

import (

     "strings"

     "fmt"

)

  • 如果程序包名称与导入路径的最后一个元素不匹配,则必须使用导入别名。

import (

  "net/http"

  client "example.com/client-go"

  trace "example.com/trace/v2"

)

  • 在所有其他情况下,除非导入之间有直接冲突,否则应避免导入别名。

Bad

Good

import ( 
    "fmt" 
    "os" 

    nettrace "golang.net/x/trace" 
)
import ( 
    "fmt"
    "os"
    "runtime/trace"
   
    nettrace "golang.net/x/trace" 
)
  • 标准库包,程序内部包,第三方包

import (

    "fmt"

    "strings"

    "selfproject/model"

    "selfprojec/application"

    "github.com/gin-gonic/gin"

    "github.com/go-sql-driver/mysql"

)

四. 编程建议

1. Get方法

Go不提供对Get方法和Set方法的自动支持。你自己提供Get方法和Set方法是没有错的,通常这么做是合适的。但是,在Get方法的名字中加上Get,是不符合语言习惯的,并且也没有必要。如果你有一个域叫做owner(小写,不被导出),则Get方法应该叫做Owner(大写,被导出),而不是GetOwner。对于要导出的,使用大写名字,提供了区别域和方法的钩子。Set方法,如果需要,则可以叫做SetOwner。这些名字在实际中都很好读:

owner := obj.Owner()

if owner != user {

    obj.SetOwner(user)

}

2. New方法

当包里只有一个对象时,比如gin包,New方法可以直接写成gin.New(),而不是gin.NewEngine()

3. 错误处理

  • 错误处理的原则就是不能丢弃任何有返回err的调用,不要使用 _ 丢弃,必须全部处理。接收到错误,要么返回err,或者使用log记录下来
  • 尽早return:一旦有错误发生,马上返回
  • 尽量不要使用panic,除非你知道你在做什么
  • 错误描述如果是英文必须为小写,不需要标点结尾
  • 采用独立的错误流进行处理

4. 空行建议

空行有什么了不起,值得上升到规范的高度吗?是的,空行是一个小小的细节,但又不仅是一个细节问题

计算机并不需要空行, 都可以根据我们写的代码准确的执行

但由于人类大脑天性所致, 大脑会认为同时发生的任何事物都存在某种联系, 并且将事物按某种逻辑模式组织起来

密密麻麻的一大坨代码会給人整体的观感带来无以名状的压力

一个简单的原则就是将概念相关的代码放在一起:相关性越强,彼此之间的距离应该越短

5. 一致性

本文中概述的一些标准都是客观性的评估,是根据场景、上下文、或者主观性的判断;

但是最重要的是,保持一致.

一致性的代码更容易维护、是更合理的、需要更少的学习成本、并且随着新的约定出现或者出现错误后更容易迁移、更新、修复 bug

相反,在一个代码库中包含多个完全不同或冲突的代码风格会导致维护成本开销、不确定性和认知偏差。所有这些都会直接导致速度降低、代码审查痛苦、而且增加 bug 数量。

将这些标准应用于代码库时,建议在 package(或更大)级别进行更改,子包级别的应用程序通过将多个样式引入到同一代码中,违反了上述关注点。

相似的声明放在一组

Go 语言支持将相似的声明放在一个组内。

Bad

Good

import "a"
import "b"
import (
  "a"
  "b"
)

这同样适用于常量、变量和类型声明:

Bad

Good

const a = 1
const b = 2

var a = 1
var b = 2

type Area float64
type Volume float64
const (
  a = 1
  b = 2
)

var (
  a = 1
  b = 2
)

type (
  Area float64
  Volume float64
)

仅将相关的声明放在一组。不要将不相关的声明放在一组。

Bad

Good

type Operation int

const (
  Add Operation = iota + 1
  Subtract
  Multiply
  EnvVar = "MY_ENV"
)
type Operation int

const (
  Add Operation = iota + 1
  Subtract
  Multiply
)

const EnvVar = "MY_ENV"

分组使用的位置没有限制,例如:你可以在函数内部使用它们:

Bad

Good

func f() string {
  var red = color.New(0xff0000)
  var green = color.New(0x00ff00)
  var blue = color.New(0x0000ff)

  ...
}
func f() string {
  var (
    red   = color.New(0xff0000)
    green = color.New(0x00ff00)
    blue  = color.New(0x0000ff)
  )

  ...
}

6. 减少嵌套

代码应通过尽可能先处理错误情况/特殊情况并尽早返回或继续循环来减少嵌套。减少嵌套多个级别的代码的代码量。

Bad

Good

for _, v := range data {
  if v.F1 == 1 {
    v = process(v)
    if err := v.Call(); err == nil {
      v.Send()
    } else {
      return err
    }
  } else {
    log.Printf("Invalid v: %v", v)
  }
}
for _, v := range data {
  if v.F1 != 1 {
    log.Printf("Invalid v: %v", v)
    continue
  }

  v = process(v)
  if err := v.Call(); err != nil {
    return err
  }
  v.Send()
}

     不必要的 else

如果在 if 的两个分支中都设置了变量,则可以将其替换为单个 if。

Bad

Good

var a int
if b {
  a = 100
} else {
  a = 10
}
a := 10
if b {
  a = 100
}

7. 本地变量声明

如果将变量明确设置为某个值,则应使用短变量声明形式 (:=)。

Bad

Good

var s = "foo"
s := "foo"

但是,在某些情况下,var 使用关键字时默认值会更清晰。例如,声明空切片。

Bad

Good

func f(list []int) {
  filtered := []int{}
  for _, v := range list {
    if v > 10 {
      filtered = append(filtered, v)
    }
  }
}
func f(list []int) {
  var filtered []int
  for _, v := range list {
    if v > 10 {
      filtered = append(filtered, v)
    }
  }
}

五. 其他编码规范参考

我们都是站在巨人的肩膀上学习和成长, 编码规范(Go)参考了:

Uber Go Style Guide: https://github.com/xxjwxc/uber_go_guide_cn

Golang官方编码规范: https://github.com/golang/go/wiki/CodeReviewComments

[代码规范]Go语言编码规范指导: https://zhuanlan.zhihu.com/p/63250689

你可能感兴趣的:(语言规范,golang,代码规范)