# Golang语言编码规范
版本号 | 编写团队 | 更新日期 | 备注 |
---|---|---|---|
1.0 | 成都新生泰技术团队 | 2020.2.10 | 正式版 |
# 前言
这是一份关于Go语言开发的参考手册。欲获取更多信息与文档,请访问http://golang.org。
# 1 命名规范
命名是代码规范中很重要的一部分,统一的命名规则有利于提高的代码的可读性,好的命名仅仅通过命名就可以获取到足够多的信息。
Go在命名时以字母a到Z或a到Z
或下划线开头,后面跟着零或更多的字母、下划线和数字(0到9)
。Go不允许在命名时中使用@、$和%
等标点符号。Go是一种区分大小写的编程语言。因此,Manpower
和manpower
是两个不同的命名。
- 1 当命名(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头,如:
Group1
,那么使用这种形式的标识符的对象就可以被外部包的代码所使用(客户端程序需要先导入这个包),这被称为导出(像面向对象语言中的public
) - 2 命名如果以小写字母开头,则对包外是不可见的,但是他们在整个包的内部是可见并且可用的(像面向对象语言中的
private
)
# 1.1 文件名
尽量采取有意义的文件名,简短,有意义,应该为小写单词,使用下划线分隔各个单词。
my_test.go
# 1.2 包命名
保持package的名字和目录保持一致,尽量采取有意义的包名,简短,有意义,尽量和标准库不要冲突。包名应该为小写单词,不要使用下划线或者混合大小写。
package demo
package main
package utils
2
3
4
5
# 1.3 结构体命名
采用驼峰命名法,首字母根据访问控制大写或者小写
struct
申明和初始化格式采用多行,例如下面:// 多行申明 type User struct{ UserName string Email string } // 多行初始化 u := User{ UserName: "astaxie", Email: "astaxie@gmail.com", }
1
2
3
4
5
6
7
8
9
10
11
# 1.4 接口名
- 命名规则基本和上面的结构体类型
- 单个函数的结构名以 “er” 作为后缀,例如
Reader , Writer
。
type Reader interface {
Read(p []byte) (n int, err error)
}
2
3
两个函数的接口名综合两个函数名
type WriteFlusher interface { Write([]byte) (int, error) Flush() error }
1
2
3
4三个以上函数的接口名,类似于结构体名
type Car interface {
Start([]byte)
Stop() error
Recover()
}
2
3
4
5
# 1.5 函数命名
若函数或方法为判断类型(返回值主要为
bool
类型),则名称应以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 { ... }
2
3
4
# 1.6 常量命名
常量包含(布尔常量、符文常量、整数常量、浮点数常量、复数常量和字符串常量)。字符、整数、浮点数和复数常量统称为数值常量。
- 常量均需使用全部大写字母组成,并使用下划线分词:const APP_VER = “1.0”
- 如果是枚举类型的常量,需要先创建相应类型:
type Scheme string
const (
HTTP Scheme = "http"
HTTPS Scheme = "https"
)
2
3
4
5
- 如果模块的功能较为复杂、常量名称容易混淆的情况下,为了更好地区分枚举类型,可以使用完整的前缀:
type PullRequestStatus int
const (
PULL_REQUEST_STATUS_CONFLICT PullRequestStatus = iota
PULL_REQUEST_STATUS_CHECKING
PULL_REQUEST_STATUS_MERGEABLE
)
2
3
4
5
6
# 1.7 变量命名
驼峰命名式。局部变量用小写字母开头。需要在
package
外部使用的全局变量用大写字母开头,否则用小写字母开头。
- 全局变量:采用驼峰命名方式,仅限在包内的全局变量
var ProjectName string
//如多组变量则使用,组和声明或者平行赋值
var(
ProjectName string
)
2
3
4
5
- 局部变量:采用小驼峰命名方式,注意声明局部变量尽量使用 :=
projectName := "name"
在相对简单的环境(对象数量少、针对性强)中,可以将一些名称由完整单词简写为单个字母,例如:
- user 可以简写为 u
- userID 可以简写 uid
- 若变量类型为 bool 类型,则名称应以 Has, Is, Can 或 Allow 开头:
var isExist bool
var hasConflict bool
var canManage bool
var allowGitHook bool
2
3
4
# 2 注释规范
Go提供C风格的
/* */
块注释和C ++风格的//
行注释。行注释是常态;块注释主要显示为包注释,但在表达式中很有用或禁用大量代码。
- 单行注释是最常见的注释形式,你可以在任何地方使用以 // 开头的单行注释
- 多行注释也叫块注释,均已以 /* 开头,并以 */ 结尾,且不可以嵌套使用,多行注释一般用于包的文档描述或注释成块的代码片段
# 2.1 包注释
每个包都应该有一个包注释,一个位于
package
子句之前的块注释或行注释。包如果有多个go文件,只需要出现在一个go文件中(一般是和包同名的文件)即可。 包注释应该包含下面基本信息(请严格按照这个顺序,简介,创建人,创建时间):
- 包的基本简介(包名,简介)
- 创建者,格式: 创建人: rtx 名
- 创建时间,格式:创建时间: yyyyMMdd
// util 包, 该包包含了项目共用的一些常量,封装了项目中一些共用函数。
// 创建人: hanru
// 创建时间: 20190419
2
3
# 2.2 结构体 (接口) 注释
每个自定义的结构体或者接口都应该有注释说明,该注释对结构进行简要介绍,放在结构体定义的前一行,格式为: 结构体名, 结构体说明。同时结构体内的每个成员变量都要有说明,该说明放在成员变量的后面(注意对齐),实例如下:
// User , 用户对象,定义了用户的基础信息
type User struct{
Username string // 用户名
Email string // 邮箱
}
2
3
4
5
# 2.3 函数(方法)注释
每个函数,或者方法(结构体或者接口下的函数称为方法)都应该有注释说明。
# 2.3.1 函数注释
// @Title 标题
// @Description 详细信息
// @Auth 创建时间 创建人
// @Param 参数类型 参数介绍
// @Return 返回类型 "错误信息"
// @Title NewtAttrModel
// @Description 属性数据层操作类的工厂方法
// @Auth 福小林
// @Param ctx 上下文信息
// @Return 属性操作类指针
func NewAttrModel(ctx *common.Context) *AttrModel {
}
2
3
4
5
6
7
# 2.3.2 函数注释
@Title 这个 API 所表达的含义,是一个文本,空格之后的内容全部解析为 title
@Description 这个 API 详细的描述,是一个文本,空格之后的内容全部解析为 Description
@Param 参数,表示需要传递到服务器端的参数,有五列参数,使用空格或者 tab 分割,表示的含义如下
1 参数名
2 参数类型,可以有的值是 formData、query、path、body、header,
3 参数类型
4 是否必须
5 注释
@Success 成功返回给客户端的信息
@Failure 失败返回的信息,包含两个参数,使用空格分隔,第一个表示 status code,第二个表示错误信息
@router 路由信息,包含两个参数,使用空格分隔,第一个是请求的路由地址,支持正则和自定义路由,和之前的路由规则一样,第二个参数是支持的请求方法,放在 [] 之中,如果有多个方法,那么使用 , 分隔。 ``
// @Title Get Product list
// @Description 开发时间 编写人 Get Product list by some info
// @Success 200 {object} models.ZDTProduct.ProductList
// @Param category_id query int false "category id"
// @Param brand_id query int false "brand id"
// @Param query query string false "query of search"
// @Param segment query string false "segment"
// @Param sort query string false "sort option"
// @Param dir query string false "direction asc or desc"
// @Param offset query int false "offset"
// @Param limit query int false "count limit"
// @Param price query float false "price"
// @Param special_price query bool false "whether this is special price"
// @Param size query string false "size filter"
// @Param color query string false "color filter"
// @Param format query bool false "choose return format"
// @Failure 400 no enough input
// @Failure 500 get products common error
// @router /products [get]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 2.4 代码逻辑注释
对于一些关键位置的代码逻辑,或者局部较为复杂的逻辑,需要有相应的逻辑说明,方便其他开发者阅读该段代码,实例如下:
package main
import (
"database/sql"
"fmt"
//执行driver.go文件中的init(),向"database/sql"注册一个mysql的驱动
_ "github.com/go-sql-driver/mysql"
)
func main() {
dsn := "root:admin@tcp(127.0.0.1:3306)/go_test?charset=utf8"
//Open打开一个driverName指定的数据库,dataSourceName指定数据源
//不会校验用户名和密码是否正确,只会对dsn的格式进行检测
db, err := sql.Open("mysql", dsn)
if err != nil { //dsn格式不正确的时候会报错
fmt.Printf("打开数据库失败,err:%v\n", err)
return
}
//尝试连接数据库,Ping方法可检查数据源名称是否合法,账号密码是否正确。
err = db.Ping()
if err != nil {
fmt.Printf("连接数据库失败,err:%v\n", err)
return
}
fmt.Println("连接数据库成功!")
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 2.5 bug注释
针对代码中出现的
bug
,可以使用特殊的注释,在godocs
可以做到注释高亮:
// BUG(astaxie):This divides by zero.
var i float = 1/0
2
# 2.6 注释风格
统一使用中文注释,对于中英文字符之间严格使用空格分隔, 这个不仅仅是中文和英文之间,英文和中文标点之间也都要使用空格分隔,例如:
// 从 Redis 中批量读取属性,对于没有读取到的 id , 记录到一个数组里面,准备从 DB 中读取
上面 Redis 、 id 、 DB 和其他中文字符之间都是用了空格分隔。
建议全部使用单行注释
和代码的规范一样,单行注释不要过长,禁止超过 120 字符。
# 3 代码风格
# 3.1 缩进和折行
- 缩进直接使用
gofmt
工具格式化即可(gofmt
是使用tab
缩进的); - 折行方面,一行最长不超过120个字符,超过的请使用换行展示,尽量保持格式优雅。
# 3.2 控制结构
# 3.2.1 语句的结尾
- Go语言中是不需要类似于
Java
需要冒号结尾,默认一行就是一条数据 - 如果你打算将多个语句写在同一行,它们则必须使用
;
# 3.2.2 括号和空格
括号和空格方面,也可以直接使用
gofmt
工具格式化(go 会强制左大括号不换行,换行会报语法错误),所有的运算符和操作数之间要留空格
// 正确的方式
if a > 0 {
}
// 错误的方式
if a>0 // a ,0 和 > 之间应该空格
{ // 左大括号不可以换行,会报语法错误
}
2
3
4
5
6
7
8
9
10
# 3.2.3 if
- 条件语句不需要加上圆括号
- 省略不必要的 else 语句
- 可以加上合适的初始化语句
result := query()
if err := check(result); err != nil {
return err }
// 不需要 else doSomeThing(result)
2
3
4
# 3.2.4 for
Golang 只有 for 一种循环结构。
for i := 0; i < 10; i++ {
...
}
2
3
# 3.2.5 遍历
//遍历字符串
for pos, str := range "SONY大法好" {
fmt.Printf("%q: %d\n", str, pos)
}
//range可以遍历数组,切片,字典,管道和字符串。
for key, value := range oldMap {
...
}
2
3
4
5
6
7
8
# 3.2.6 switch
- 表达式不限制为常量或整数
case
可以使用逗号来列举多个条件- 无需显式
break
,但使用break
可以提前结束
func Factory(name string, value interface{}) interface{} {
var object interface{}
switch name {
case "A", "AA":
object = NewA()
case "B":
object = newB()
if value == nil {
break
}
object.SetValue(value)
}
return object
}
2
3
4
5
6
7
8
9
10
11
12
13
14
# 3.2.7 类型选择
对于接口变量,可以使用
switch
来判断其实际类型,这是一个很有用的技巧:
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)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
# 3.2.8 退出循环
因为
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)
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 3.2.9 select
select
用法类似于switch
,专用于轮询多个管道的读取。
# 3.3 结构体和接口
# 3.3.1 结构体初始化
p1 := new (MyStruct) // type *SyncedBuffer
p2 := &MyStruct{} // type *SyncedBuffer
var s1 MyStruct // type SyncedBuffer
s2 := MyStruct{} // type SyncedBuffer
2
3
4
初始化时可以指定结构成员的初始值:
type MyStruct1 struct {
Value int
}
type MyStruct2 struct {
MyStruct1
ID int }
// 直接初始化
s1 := MyStruct1{
Value: 0, }
// 嵌套结构
s2 := MyStruct2{
ID: 0,
MyStruct1: MyStruct1 {
Value: 1, }
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 3.4 接收者
方法的接收者既可以声明成值类型,也可以声明成指针类型。调用时,
Golang
可以自动进行转换。但是需要注意的是,声明成值类型时,调用方法时传入的是调用者的拷贝, 而不是调用者本身,因此对接收者的修改将不生效。 接收者类型建议如下:
- map , func , chan :不要使用指针
- 切片类型:如果不存在对切片的重分配,
- 则不要使用指针
- 如果方法会改变接收者,必须使用指针
- 如果接收者结构有类似 sync.Mutex 等用于同步的成员,必须使用
- 一般情况,从实用性的角度出发,建议接收者都声明成指针类型
# 3.5 defer
- 打开文件/连接等后需要 defer 来延时执行关闭
- 慎用 defer 来处理锁
- defer 求值是实时的,因此可以在循环中使用
package main
import "fmt"
func main() {
word := "world"
defer fmt.Printf("%v\n", word)
word = "blueking"
fmt.Printf("hello ")
}
2
3
4
5
6
7
8
# 3.5 chan
Golang
的并发模型基于CSP
,并发实体(goroutine
)通过管道(channel
)进行通信。 管道本质上是一个结构体,维护发送和队列两个队列,创建管道时使用make
:
ch := make(chan int, 0)
不要直接声明,这样可能会导致goroutine
死锁:
var ch chan int
# 3.5.1 单向管道
管道可以机上只读和只写声明,这种用法一般用在函数声明中:
- 只读管道: ch <-chan int
- 只写管道: ch chan<- int
func handle(readCh <-chan int, writeCh chan<- int) {
go func() {
v := <-readCh
writeCh <- 2 * v
}()
}
2
3
4
5
6
# 3.6 goroutine
goroutine
是Glolang
提供的一种并发模型,可以通过关键字来启动轻量级线程来执行指定的逻辑。但需要注意的是,goroutine
并不是协程,底层实现是个线程池,一个goroutine
在执行的过程中可能会跑在不同的线程和CPU
上。
# 3.6.1 线程安全
因为
goroutine
是在线程池中执行,因此我们在goroutine
中访问闭包需要考虑线程安全的问题。
# 3.6.2 Once
sync.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
}
2
3
4
5
6
7
8
9
10
11
12
13
# 3.6.3 WaitGroup
Golang
没有提供类似thread.join
等待goroutine
结束的接口,我们可以用sync.WaitGroup
来实现:
- 初始化
WaitGroup
,加上特定的值- 激活
goroutine,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")
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 3.6.4 Atomic
goroutine
可以使用闭包特性访问外部变量,或者多个goroutine
共同修改同一个变量, 很容易陷入了变量并发访问的陷阱。这个时候需要借助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) // not value++
}
wg.Done() }
go fun(100)
go fun(100)
wg.Wait()
fmt.Printf("%v\n", value)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 3.7 import 规范
import
在多行的情况下,goimports
会自动帮你格式化,但是我们这里还是规范一下import
的一些规范,如果你在一个文件里面引入了一个package
,还是建议采用如下格式:
import (
"fmt"
)
2
3
如果你的包引入了三种类型的包,标准库包,程序内部包,第三方包,建议采用如下方式进行组织你的包:
import (
"encoding/json" //标准包
"strings"
"myproject/models"
"myproject/controller" //内部包
"myproject/utils"
"github.com/astaxie/beego" //第三方包
"github.com/go-sql-driver/mysql"
)
2
3
4
5
6
7
8
9
10
11
有顺序的引入包,不同的类型采用空格分离,第一种实标准库,第二是项目包,第三是第三方包。
在项目中不要使用相对路径引入包:
// 这是不好的导入
import “../net”
// 这是正确的做法
import “github.com/repo/proj/src/net”
2
3
4
5
但是如果是引入本项目中的其他包,最好使用相对路径。
# 3.8 错误处理
- 错误处理的原则就是不能丢弃任何有返回
err
的调用,不要使用 _ 丢弃,必须全部处理。接收到错误,要么返回err
,或者使用log
记录下来 - 尽早
return
:一旦有错误发生,马上返回 - 尽量不要使用
panic
,除非你知道你在做什么 - 错误描述如果是英文必须为小写,不需要标点结尾
- 采用独立的错误流进行处理
// 错误写法
if err != nil {
// error handling
} else {
// normal code
}
// 正确写法
if err != nil {
// error handling
return // or continue, etc.
}
// normal code
2
3
4
5
6
7
8
9
10
11
12
13
# 3.9 参数传递
- 对于少量数据,不要传递指针
- 对于大量数据的
struct
可以考虑使用指针 - 传入参数是
map,slice,chan
不要传递指针,因为map,slice,chan
是引用类型,不需要传递指针的指针
# 3.10 单元测试
- 1.单元测试代码的go文件必须以
_test.go
结尾,Go语言测试工具只认符合这个规则的文件 - 2.单元测试的函数名必须以
Test
开头,是可导出公开的函数。备注:函数名最好是Test+要测试的方法函数名 - 3.测试函数的签名必须接收一个指向
testing.T类型的指针
作为参数,并且该测试函数不能返回任何值 - 4.更多参考示例
# 4 数据库规范
# 4.1 总命名规范
- 1、不得使用数据库保留关键字,以及
golang/php/java
等常用语言的保留关键字,或者可能成为关键字的单词作为完整命名。 - 2、如无特殊说明,名称必须用英文字母开头,采用有特征含义的单词或缩写,单词中间用“_”分割,且只能由英文字母、数字和下划线组成,不能用双引号包含。
- 3、除数据库名称长度为1至8个字符,其余(包括表、字段、索引等)不超过30个字符,
Database link
名称也不要超过30个字符。(30并不是凭空想象出来的,而是参考了Oracle的限制)
# 4.2 建表规范
# 4.2.1 表名
(建议以2-3字项目名称为前缀开头),紧跟2-5个字符(英文字母或数字,但不得全是数字)的模块名(必须), 最后跟上当前表的含义的单词(1-3个单词,用下划线连接),
例如:SQ_SYS_CAR,SQ是项目名称的缩写,SYS是模块名称的缩写,CAR表示当前表的具体含义。
特别强调:项目名称和模块名用简写(建议长度为2-5个字符),而表含义的名称,可简写、也可以不简写,但是都不能超过3个单词,
例如下面两个反面例子:
ABF_SUPERVISION_USER,问题:模块名称似乎比较长,建议控制在2-5个字符,缩写为 ABF_SUPV_USER;
ABF_SYS_USER_MANAGE_ORG_ROLE,问题:除去前缀ABF_SYS_,表含义(USER_MANAGE_ORG_ROLE)超过了3个单词。
2
3
4
5
# 4.2.2 字段名
- a) 表的字段数不超过50个。
- b) 类型:各表之间相同含义的字段,类型定义要完全相同(包括精度、默认值等);
- c) 命名:
- 字段名无单词数的限制,但是名字的字符长度应该符合上面的“总命名规范”。
- 字段命名及其注释,要做到清楚、无歧义。
1 表达是与否概念的字段,必须使用is_xxx的方式命名,数据类型是unsignedtinyint
2 字段名必须使用小写字母或数字;禁止出现数字开头,禁止两个下划线中间只出现数字。数据库字段名的修改代价很大,因为无法进行预发布,所以字段名称需要慎重考虑。
3 小数类型为decimal,禁止使用float和double。
4 如果存储的字符串长度几乎相等,使用char定长字符串类型。
5 表必备三字段:id, create_time, modified_time
6 修改字段含义或对字段表示的状态追加时,需要及时更新字段注释。
7 字段允许适当冗余,以提高性能,但是必须考虑数据同步的情况。
8 合适的字符存储长度,不但节约数据库表空间、节约索引存储,更重要的是提升检索速度。
2
3
4
5
6
7
8
# 4.3 索引规约
1 【强制】业务上具有唯一特性的字段,即使是组合字段,也必须建成唯一索引
说明:不要以为唯一索引影响了insert速度,这个速度损耗可以忽略,但提高查找速度是明显的;
另外,即使在应用层做了非常完善的校验和控制,只要没有唯一索引,根据墨菲定律,必然有脏数据产生
2 【强制】超过三个表禁止join。需要join的字段,数据类型保持绝对一致;多表关联查询时,保证被关联的字段需要有索引
说明:即使双表join也要注意表索引、SQL性能。
3.【强制】在varchar字段上建立索引时,必须指定索引长度,没必要对全字段建立索引,根据实际文本区分度决定索引长度。
说明:索引的长度与区分度是一对矛盾体,一般对字符串类型数据,长度为20的索引,区分度会高达90%以上,可以使用count(distinctleft(列名, 索引长度))/count(*)的区分度来确定。
4 【推荐】如果有orderby的场景,请注意利用索引的有序性。orderby最后的字段是组合索引的一部分,并且放在索引组合顺序的最后,避免出现file_sort的情况,影响查询性能。
说明: 正例:wherea=? andb=? orderbyc;索引:a_b_c反例:索引中有范围查找,那么索引有序性无法利用,
如:WHEREa>10 ORDERBYb;索引a_b无法排序。
2
3
4
5
6
7
8
9
10
# 4.4 SQL规约
1 【强制】不要使用count(列名)或count(常量)来替代count(*),count(*)就是SQL92定义的标准统计行数的语法,跟数据库无关,跟NULL和非NULL无关。
说明:count(*)会统计值为NULL的行,而count(列名)不会统计此列为NULL值的行。
2.【强制】count(distinctcol)计算该列除NULL之外的不重复数量。注意count(distinct col1, col2)如果其中一列全为NULL,那么即使另一列有不同的值,也返回为0。
3.【强制】当某一列的值全是NULL时,count(col)的返回结果为0,但sum(col)的返回结果为NULL,因此使用sum()时需注意NPE问题。
正例:可以使用如下方式来避免sum的NPE问题:SELECT IF(ISNULL(SUM(g)),0,SUM(g)) FROM table;
4.【强制】使用ISNULL()来判断是否为NULL值。注意:NULL与任何值的直接比较都为NULL。
说明:1)NULL<>NULL的返回结果是NULL,而不是false。2)NULL=NULL的返回结果是NULL,而不是true 3)NULL<>1的返回结果是NULL,而不是true
5.【强制】在代码中写分页查询逻辑时,若count为0应直接返回,避免执行后面的分页语句
6.【强制】不得使用外键与级联,一切外键概念必须在应用层解决。
说明:(概念解释)学生表中的student_id是主键,那么成绩表中的student_id则为外键。
如果更新学生表中的student_id,同时触发成绩表中的student_id更新,则为级联更新。
外键与级联更新适用于单机低并发,不适合分布式、高并发集群;级联更新是强阻塞,存在数据库更新风暴的风险;外键影响数据库的插入速度。
7.【强制】禁止使用存储过程,存储过程难以调试和扩展,更没有移植性。
8.【强制】数据订正时,删除和修改记录时,要先select,避免出现误删除,确认无误才能执行更新语句。
9.【推荐】in操作能避免则避免,若实在避免不了,需要仔细评估in后边的集合元素数量,控制在1000个之内。
10.【参考】如果有全球化需要,所有的字符存储与表示,均以utf-8编码,那么字符计数方法
说明:SELECTLENGTH("轻松工作");返回为12 SELECTCHARACTER_LENGTH("轻松工作");返回为4
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 4.5 ORM规约
1.【强制】在表查询中,一律不要使用* 作为查询的字段列表,需要哪些字段必须明确写明。
说明:1)增加查询分析器解析成本。2)增减字段容易与resultMap配置不一致。
2【强制】xml配置中参数注意使用:#{},#param# 不要使用${} 此种方式容易出现SQL注入。
2
3
# 5 项目开发规范
# 5.1 项目目录结构
+--bin 编译后的文件
+--pkg 本项目或其他项目使用的包
项目根目录
+--api 接口规范目录
+--cmd
+--swagger 自动化API文档
+--test 该目录放的是临时的测试方法
+--config 所有的配置文件目录
+--internal 只在本项目使用的包
+-- doc 说明文档(含go-bindata和mysql文件)
+-- exec_package 可执行的打包文件(目前只有win 64bit的打包)
+-- inits 所有需初始化的目录
| +-- parse 所有配置文件的初始化目录
| +-- init.go 用于初始化系统root用户,并注入所有service
+-- middleware 包含的中间件目录
| +-- casbins 用于rbac权限的中间件的目录
| +-- jwts jwt中间件目录
+-- resources 打包的前端静态资源文件
| +--img 静态图片
| +--html 网页资源
| +--file 文件资源
+-- utils 工具包目录
+--plugin 插件,扩展码的包
+-- web
| +-- db 数据库dao层目录
| +-- models models 存放实体类
| +--service 业务逻辑
| +-- controller 所有分发出来的路由的目录
| +-- supports 提供辅助方法的目录(可以无)
+-- main.go 入口
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 5.2 包管理器
统一使用
Go Model
进行包管理,对标准包,程序内部包,第三方包进行分组。 更多参考,
import (
"encoding/json" //标准包
"strings"
"myproject/models" //内部包
"myproject/utils"
"github.com/go-sql-driver/mysql" //第三方包
)
2
3
4
5
6
7
8
9
常用的go mod
命令如下:
go mod命令 | 描述 |
---|---|
go mod download | 下载依赖的module到本地cache(默认为$GOPATH/pkg/mod目录) |
go mod edit | 编辑go.mod文件 |
go mod graph | 打印模块依赖图 |
go mod init | 初始化当前文件夹, 创建go.mod文件 |
go mod tidy | 增加缺少的module,删除无用的module |
go mod vendor | 将依赖复制到vendor下 |
go mod verify | 校验依赖 |
go mod why | 解释为什么需要依赖 |
# 5.3 自动格式化
gofmt(goimports)
大部分的格式问题可以通过gofmt
解决,gofmt
自动格式化代码,保证所有的go代码与官方推荐的格式保持一致,于是所有格式有关问题,都以gofmt
的结果为准
# 5.4 常用中间件
Middleware | 描述 | Example |
---|---|---|
jwt | 检查请求的Authorization头,进行JWT检查和解析 | jwt_example |
cors | HTTP 跨域请求。 | cors_example |
secure | 一些快速安全实现的中间件。 | secure_example |
tollbooth | 用于验证 HTTP 请求速率的通用中间件 | tollbooth_example |
cloudwatch | AWS cloudwatch 指标中间件。 | cloudwatch_example |
new relic | 官方 New Relic Go Agent. | newrelic_example |
prometheus | 轻松为 prometheus 检测工具创建指标端点 | prometheus_example |
casbin | 支持各种权限模型的授权库,例如ACL,RBAC,ABAC | casbin_example |
gorm | gorm能够简化操作,提高开发效率。特别是对结构体的应用 | gorm |
go-redis | go操作redis的中间件 | go-redis |
viper | Viper是Go应用程序的完整配置解决方案,包括12-Factor应用程序。 | viper |
json-iterator | json-iterator是一款快且灵活的JSON解析器,同时提供Java和Go两个版本。 | json-iterator |
# 6 安全规约
# 6.1 权限控制校验
系统所有页面必须进行权限控制校验。 防止没有做水平权限校验就可随意访问、操作别人的数据,比如查看、修改别人的数据。
# 6.2 数据脱敏
用户敏感数据禁止直接展示,必须对展示数据脱敏。 如查看个人手机号码会显示成:158****9119,隐藏中间4位,防止隐私泄露。 身份证脱敏等等
# 6.3 防止SQL注入
用户输入的SQL参数严格使用参数绑定或者METADATA字段值限定,防止SQL注入, 禁止字符串拼接SQL访问数据库。
# 6.4 参数有效性验证
用户请求传入的任何参数必须做有效性验证。 忽略参数校验可能导致如下异常:
- pagesize过大导致内存溢出
- 恶意orderby导致数据库慢查询
- 任意重定向
- SQL注入
- 反序列化注入
- 正则输入源串拒绝服务ReDoS
# 6.5 CSRF安全过滤
表单、AJAX提交必须执行CSRF安全过滤。 说明:CSRF(Cross-siterequestforgery)跨站请求伪造是一类常见编程漏洞。对于存在CSRF漏洞的应用/网站,攻击者可以事先构造好URL,只要受害者用户一访问,后台便在用户不知情情况下对数据库中用户参数进行相应修改。
# 7 参考资料
https://github.com/golang/go/wiki/CodeReviewComments
← MVC包的使用 go get命令详解 →