暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Go语言开发规范

拖地先生 2019-09-23
528

1. 代码格式

  • 【强制】采用4个空格缩进,每个 tab 也代表4个空格。

  • 【强制】提交的代码必须经过 gofmt 格式化(配置IDE保存文件时自动格式化)。

  • 【强制】代码最大行宽为120列,超过换行。

2. 命名规范

  • 【强制】命名均不能以特殊字符开始和结束,包含常见的中划线、下划线等。

// 反例:
var _name, -name, _name, $name, %name string

  • 【强制】命名统一采用英文,不能采用拼音等其他方式。

// 正例:
var name string
var address string

// 反例:
var mingzi string
var dizhi string

  • 【强制】参数名、局部变量都统一使用小驼峰风格。

func demo(ctx context.Context, name string) {
var localVar string
// other operations
}

  • 【强制】对于包内非导出的 struct 使用小驼峰风格,对于导出的 struct 采用大驼峰风格。

// 非导出的struct
type user struct {}

// 导出的struct
type User struct {}

  • 【强制】常量命名使用大驼峰风格,不要使用下划线分隔。

// 正例:
var MaxConnectionCount int64

// 反例:
var MAX_CONNECTION_COUNT int64

  • 【强制】包名统一采用小写风格,使用短命名,不能包含特殊字符(下划线、中划线等),建议最好是一个单词。文件夹名称必须与报名保持一致

// 正例:
package client
package clientset

// 反例:
package Demo
package Demo_Case

  • 【强制】对于专有名词在使用时要保证全名统一为大写或小写,不能出现部分大写和小写混用的情况。

// 正例:
func setURL() {
var url string
var URL string
// other operations
}

// 反例:
func setUrl() {
var Url string
}

下面列举了一些常见的特有名词:

"API", "ASCII", "CPU", "CSS", "DNS", "EOF", "GUID", "HTML", "HTTP","HTTPS", "ID", "IP", "JSON", "LHS", "QPS", "RAM", "RHS", "RPC", "SLA", "SMTP", "SSH", "TLS", "TTL", "UI", "UID", "UUID", "URI", "URL", "UTF8", "VM", "XML", "XSRF", "XSS"
  • 【强制】对于多个具有枚举特性的类型,要求定义为类型,并使用以下方式进行枚举。

type OrderStatus int32

var OrderStatusEnum = struct {
WaitPayment OrderStatus
PaySuccess OrderStatus
Cancel OrderStatus
Closed OrderStatus
Deleted OrderStatus
Completed OrderStatus
}{
WaitPayment: 1, // 待付款
PaySuccess: 2, // 已付款
Cancel: 3, // 已取消
Closed: 4, // 已关闭
Deleted: 5, // 已删除
Completed: 6, // 已完成
}

  • 【强制】不允许任何未定义的常量直接在代码中使用。

  • 【强制】:=
     或 =
     左右两侧必须加一个空格。

3. 控制语句

3.1. if

  • 【强制】if 接受一个初始化语句,对于返回参数不需要流入到下一个语句时,通过建立局部变量的方式构建 if 判断语句。

// 正例:
if err := file.Chmod(0664); err != nil {
return err
}

// 反例:
err := file.Chmod(0664)
if err != nil {
return err
}

  • 【强制】在 if 语句中对异常处理等情况,如果成功的控制流是继续往下走,而对于异常处理结束语 return 语句时,不能使用 else 语句。

// 正例:
f, err := os.Open(name)
if err != nil {
return err
}
d, err := f.Stat()
if err != nil {
f.Close()
return err
}
codeUsing(f, d)

// 反例:
f, err := os.Open(name)
if err != nil {
return err
} else {
d, err := f.Stat()
if err != nil {
f.Close()
return err
} else {
codeUsing(f, d)
}
}

3.2. for

  • 【强制】采用简短声明建立局部变量。

// 正例:
for i := 0; i < 5; i ++ {
codeUsing(i)
}

// 反例:
var i int
for i = 0; i < 5; i ++ {
codeUsing(i)
}

  • 【强制】对于遍历数据(如 map )的场景,如果只使用第一项,则直接丢弃第二项。

// 正例:
for key := range mapper {
codeUsing(key)
}

// 反例:
for key, _ := range mapper {
codeUsing(key)
}

3.3. switch

  • 【强制】在使用多个 case 时,多个 case 写在一起。

// 正例:
switch type {
case 1, 2:
return "a"
case 3:
return "b"
default:
return "c"
}

// 反例:
switch type {
case 1:
return "a"
case 2:
return "a"
case 3:
return "b"
default:
return "c"
}

  • 【强制】必须添加default默认处理。

4. 函数

  • 【强制】对于少量数据,不要通过指针传递。

  • 【强制】对于大量(>=4)的入参,考虑使用 struct 进行封装,并通过指针传递。

  • 【强制】传参是 map、slice、chan 不要使用指针进行传递,因为这三者是引用类型。

  • 【建议】避免使用函数命名返回值。

// 正例:
func Foo(a, b int) (string, bool) {
var c string = "Hello World"
var ok bool = true
return c, ok
}

// 反例:
func Foo(a, b int) (c string, ok bool) {
c = "Hello World"
ok = true
return
}

  • 【强制】context.Context 不能放在结构体中,且必须为函数的第一个参数。

5. 包引用

  • 【建议】对 import 的包进行分组管理,用换行符分割,而且标准库作为分组的第一组。如果你的包引入了三种类型的包:标准库包、程序内部包、第三方包,建议采用如下方式进行组织你的包,goimports 会自动帮你格式化。

package main

import (
"fmt"
"os"

"kmg/a"
"kmg/b"

"code.google.com/a"
"github.com/b"
)

  • 【强制】引用包使用绝对路径,不能使用相对路径。

// 正例
import "shorturl/model"

// 反例
import "./model"

  • 【强制】引用包不能使用省略包名的方式。

// 正例:
import "fmt"
fmt.Println("Hello World")

// 反例
import . "fmt"
Println("Hello World")

  • 【强制】包别名使用全小写的命名风格,尽量用容易理解的英文表示。

import (
orderpb "gitlab.dailyyoga.com.cn/h2/svr-ecommerce/gen/golang/order"
librarysvr "gitlab.dailyyoga.com.cn/h2/svr-ecommerce/library"
)

6. 结构体

6.1. 定义

  • 【建议】结构体字段加上注释,这样可读性好。

  • 【建议】结构体初始化格式采用多行,定义如下:

// 正例:
u := User{
Username: "test",
Email: "test@gmail.com",
}

// 反例:
u := User{Username: "test", Email: "test@gmail.com"}
u := User{"test","test@gmail.com"}

6.2. 接受者

  • 【建议】名称统一采用1~3个字母,不宜太长。

type User struct {
// 姓名
Name string `json:"name"`
// 邮箱
Email string `jdon:"email"`
}

func (u *User) GetName() string {
return u.Name
}

  • 【建议】对于绝大多数可以使用指针接受者的场景,推荐使用指针接受者会更有效率。

  • 【强制】如果接受者是 map、slice、chan 不能使用指针接受者。

  • 【强制】如果接受者是包含有锁( sync.Mutex 等),必须使用指针接受者。

7. 接口

1)接口(interface)采用大驼峰风格命名,具体细分为以下三种情况:

  • 【建议】单个函数的接口名以 er 作为后缀,如 Reader、Writer,而接口的实现则去掉 er。

type Reader interface {
Read(p []byte) (n int, err error)
}

  • 【建议】两个函数的接口名为两个函数名结合。

type WriteFlusher interface {
Write([]byte) (int, error)
Flush() error
}

  • 【建议】三个以上函数的接口名,类似于结构体名。

type Car interface {
Start([]byte)
Stop() error
Recover()
}

8. 注释

  • 【强制】使用行间注释时,如果注释行与上一行不属于同一层,不用空行。如果属于同行,则空一行再进行注释。

func demo() {
// This is a start line of a new block, do not need a new line
// with the previous code.
say("knock, knock!")

// This is the same block with the previous code,
// you should insert a new line before you start a comment.
echo("who is there ?")
}

  • 【强制】使用//进行注释时,和注释语句之间必须有一个空格,增加可读性。

// validator is used to validate dns's format.
// should not contains dot, underscore character, etc.
func validator(dns string) error {
// do validate.
}

  • 【强制】不要使用尾注释。

// 反例:
func show(name string) {
display(name) // show a man's information
}

  • 【建议】注释的单行长度最大不能超过120列,超过必须换行,一般以80列换行为宜。

  • 【建议】函数与方法的注释需要以函数或方法的名称作为开头。

// HasPrefix tests whether the string s begins with prefix.
func HasPrefix(s, prefix string) bool {
return len(s) >= len(prefix) && s[0:len(prefix)] == prefix
}

  • 【强制】大段注释采用/**/
    风格进行注释。

9. 测试

  • 【强制】单元测试文件名命名规范:example_test.go 

  • 【强制】测试用例的函数名称必须以 Test 开头,例如:

func TestExample(t *testing.T) {}

10. 异常

10.1. 异常处理

  • 【强制】程序中出现的任何异常都必须处理,不能使用 "_" 忽略。

  • 【强制】程序中尽量避免使用panic来进行异常处理,对于必须要使用panic进行异常处理的场景,要启用recover回收处理异常。

func openFile(fileName string) {
defer func() {
if err := recover(); err != nil {
fmt.Println(err)
}
}()

_, err := os.Open(fileName)
if err != nil {
panic(err)
}
}

11. 工程规范

11.1. 数据库

  • 【建议】数据库定义通过TableName方法指定表名,不推荐使用engine.Table()。

package admin

// User 管理员信息
type User struct {
ID int64 `xorm:"not null pk autoincr int(11) 'id'"`
Email string `xorm:"not null varchar(100) 'email'"`
Name string `xorm:"not null varchar(50) 'name'"`
Password string `xorm:"not null varchar(128) 'password'"`
GroupType library.AdminUserGroup `xorm:"not null tinyint(3) 'group_type'"`
CreateTime int64 `xorm:"not null int(11) 'create_time'"`
UpdateTime int64 `xorm:"not null int(11) 'update_time'"`
}

// TableName 表名
func (User) TableName() string {
return "admin_user"
}

反例:

package admin

type AdminUser struct {
ID int64 `xorm:"not null pk autoincr int(11) 'id'"`
Email string `xorm:"not null varchar(100) 'email'"`
Name string `xorm:"not null varchar(50) 'name'"`
Password string `xorm:"not null varchar(128) 'password'"`
GroupType library.AdminUserGroup `xorm:"not null tinyint(3) 'group_type'"`
CreateTime int64 `xorm:"not null int(11) 'create_time'"`
UpdateTime int64 `xorm:"not null int(11) 'update_time'"`
}

  • 【强制】批量执行增删改时使用事务进行处理。

11.2. 微服务

  • 【强制】proto文件定义请求和返回结果message格式为:PrefixRequest、PrefixResponse的大驼峰命名方式,不推荐使用其他格式。

message CountCartRequest {
int64 UID = 1;
}

message CountCartResponse {
comm.ResultCode ResultCode = 1;
int64 Cnt = 2;
}

  • 【建议】protobuf文件和生成解析的相应代码放在不同的文件夹中,比如:proto文件放在protobuf文件夹中,生成解析的代码放在gen/golang文件夹中,rpc接口实现放在rpc文件夹中。

syntax = "proto3";
option go_package = "xxxx/svr-xxx/gen/golang/proto";
option php_namespace = "SvrXxx\\Proto";
option php_metadata_namespace = "SvrXxx\\GpbMetadata";
package proto;




拖地先生,从事互联网技术工作,在这里每周两篇文章,一起聊聊日常的技术点滴和管理心得。

如果对你有帮助,让大家也看看呗~

文章转载自拖地先生,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论