古老的榕树

用 Go 开发终端接口服务--Go 语言编码规范

潘军杰 发表于 2019-05-14 15:25 阅读(3330) 评论(1) 赞(0)
初学者使用 Go 语言开发项目的时候,首先需要熟悉一下该语言的编码规范,逐渐养成良好的编码习惯,提高自己的编码质量,对自己,对项目团队都受益匪浅。

**【强制】程序内部命名方式**,一律采用驼峰命名方式。常量、变量、函数名都统一采用驼峰命名,公用对象首字母需大写,私有对象首字母可小写。有些特定名词或缩写名词,建议全部大写,如 HTML、XML、JSON、ID、UID、API、POST 等,但不是特定名词,请不要全部大写,包括常量和变量,避免全部大写,或者全部大写和下划线组合。

正例:

const (
    UserID           = 100001
    DefaultCharset  = "utf-8"
    ApplicationJSON = "application/json"
    TextHTML        = "text/html"
    TextXML         = "text/xml"
)

var (
	Expiration = 15 * time.Minute
)

func init() {
    ...
}

func MyProduct() {
    ...
}


反例:

// 以下都是不规范的命名
const (
    UserId           = 100001
    USERID           = 100001
    DefaultCharset  = "utf-8"
    ApplicationJson = "application/json"
    TextHtml        = "text/html"
    Text_Html       = "text/html"
    TEXTXML         = "text/xml"
    TEXT_XML        = "text/xml"
)

var (
	EXPIRATION = 15 * time.Minute
)

func INIT() {
    ...
}

func My_product() {
    ...
}


**【强制】项目名、包名、文件名命名不采用驼峰命名方式** ,项目名和包名优先采用小写名词命名方式,避免动词和下划线;文件名同理也优先采用小写名词命名方式,有时候可以适当采用下划线,尽量使用单数形式,避免复数名词。都规避采用 Go 关键字和预定的标识符。
正例:

// 项目名
project
// 包名
project/src/controller
project/src/model
project/src/util
// 文件名
article.go
article_test.go


反例:

// 项目名
Project
// 包名
Project/src/Controller
project/src/Models
project/src/utils
// 文件名
Article.go
ArticleTest.go


**【建议】多行方式导入包**,导入包分为: Go 标准包,第三方包,项目内部包,建议按照分类空一行分组导入包,包尽量按照字母排序。
正例:

import (
    "fmt"
    "time"
    
    "github.com/urfave/negroni"

    "project/src/common"
    "project/src/controller"
)


反例:

import "fmt"
import "time"
import "github.com/urfave/negroni"
import "project/src/common"
import "project/src/controller"

import (
    "fmt"
    "time"
    "github.com/urfave/negroni"
    "project/src/common"
    "project/src/controller"
)


**【强制】注释两种方式,可以两个斜杠 // 和 /* ... */**。// 之后应该有个空格,注释如果很长可以分多行,可以带逗号、句号等符号;/* *... */ 开始和结束分别占一行,中间是注释句子。一般双斜杠注释比较常见,代码首先是给人看的,所以配合一些必要的注释是良好的做法,但一些函数即使注释了也于事无补,要尽早重构。
正例:
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// EOF is the error returned by Read when no more input is available.
// If the EOF occurs unexpectedly in a structured data stream,
// the appropriate error is either ErrUnexpectedEOF or some other error
// giving more detail.

/**
 * ErrShortWrite means that a write accepted fewer bytes than requested
 * but failed to return an explicit error.
 */

// Seek whence values.
const (
	SeekStart = 0 // seek relative to the origin of the file
)


反例:

//Copyright 2009 The Go Authors. All rights reserved.
//Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
//EOF is the error returned by Read when no more input is available.
// If the EOF occurs unexpectedly in a structured data stream,the appropriate error is either ErrUnexpectedEOF or some other error
//giving more detail.

/**ErrShortWrite means that a write accepted fewer bytes than requested
 * but failed to return an explicit error.*/

//Seek whence values.
const (
	SeekStart = 0//seek relative to the origin of the file
)


**【建议】函数注释开头写上函数的名称,再进行对函数注释。**
正例:

// SelectOneUser 获取一个用户记录
func SelectOneUser() (*model.User, error) {
    ...
}


反例:

//获取一个用户记录
func SelectOneUser() (*model.User, error) {
    ...
}


**【建议】一行代码长度不要超过120个字符,如果超过,请换行。**
**【建议】循环遍历,不要放在循环体内计算集合的长度。**
正例:
for i, s := 0, len(slice); i < s; i++ {
    ...
}

// 或另外外部定义
s := len(slice)
for i := 0; i < s; i++ {
    ...
}


反例:

for i := 0; i < len(slice); i++ {
    ...
}


**【建议】结构体初始化的时候,按照顺序标出属性名**
正例:

// BaseField 定义基类结构体
type BaseField struct {
	ID      int64
	Created time.Time
}

// BaseField 初始化
u := BaseField {
    ID      : 1,
    Created : time.Now(),
}


反例:

// BaseField 初始化
u := BaseField {1, time.Now()}
// BaseField 初始化
u := BaseField {
    1, 
    time.Now(),
}


**【建议】函数如果返回一个结构体实例,建议返回指针对象,因为指针可以是空指针,上层调用时可以进一步判断数据的完整性。**
正例:

func SelectOneUser() (*model.User, error) {
    ...
}


反例:

func SelectOneUser() (model.User, error) {
    ...
}


**【建议】空字符串判断,直接使用 ==,而不是 len 或 nil**
正例:

if name == "" {
    ...
}


反例:

if len(name) == 0 {
    ...
}

if s == nil || s == "" {
    ...
}


**【建议】空 slice 判断,直接使用 len,而不是  nil**
正例:

if len(slc) > 0 {
    ...
}



反例:

if slc != nil && len(slc) > 0 {
    ...
}




**【建议】布朗值判断,直接判断,而不是 ==**
正例:
if b {
    ...
}

if !b {
    ...
}



反例

if b == true {
    ...
}

if b == false {
    ...
}



**【建议】缩短 if 判断语句,省略没必要的分支或代码**
正例:
a, c := 1, 3
return a > c

if b {
    return true
}
return false



反例:

a, c := 1, 3
if a > c {
    return true
} else {
    return false
}

if b {
    return true
} else {
    return false
}


**【建议】append 两个 slice ,避免循环遍历**
正例:
a := [1,3,5,7,9]
b := [2,4,6,8.10]
a = append(b, a...)


反例:

a := [1,3,5,7,9]
b := [2,4,6,8.10]
for _,v := a {
    append(b, v)
}


**【建议】涉及到 IO 文件流打开,数据库游标等操作,使用后一定记得 Close 关闭**
正例:
// 打开 IO 文件流
file, err := os.Open("/path/file_name.txt")
if err != nil {
    ...
}
defer file.Close()

// 数据库游标操作
stmt, err := db.Prepare(`select * from user where status=?`)
if err != nil {
    return err
}
defer stmt.Close()

rows, err := stmt.Query(1)
if err != nil {
    return err
}
defer rows.Close()


反例:

// 打开 IO 文件流
_, err := os.Open("/path/file_name.txt")
if err != nil {
    ...
}

// 数据库游标操作
stmt, err := db.Prepare(`select * from user where status=?`)
if err != nil {
    return err
}

rows, err := stmt.Query(1)
if err != nil {
    return err
}



**【建议】错误字符串不要大写,请全部小写,尽量说清楚错误特征,结尾不带结束符**
正例:
file, err := os.Open("/path/file_name.txt")
if err != nil {
    return errors.New("open file_name.txt fail")
}
...



反例:

file, err := os.Open("/path/file_name.txt")
if err != nil {
    return errors.New("Open file_name.txt Fail.")
    // or
    return err
}
...


**【建议】不要忽略 error ,在底层返回 error,在上层打印 error 日志**
正例:
// 底层返回 error
var name string
_, err := stmt.QueryRow("select name from user where id=? limit 1",id).Scan(&name)
if err != nil {
    return err
}

// 上层打印 error 日志
name, err := getUserName(id)
if err != nil {
    log.Println(err)
}


反例:

// 底层忽略了 error
var name string
stmt.QueryRow("select name from user where id=? limit 1",id).Scan(&name)

// 上层未打印 error 日志
name, _ := getUserName(id)


**【建议】方法接收器命名勿用 this、self  这些词,可以用结构体的缩写名,或首字母小写的全名**
正例:
func (foo *Foo) sayHello() {
    ...
}

func (upf *UserProfile) sayHello() {
    ...
}

func (userProfile *UserProfile) sayHello() {
    ...
}


反例:

func (this *Foo) sayHello() {
...
}

func (self *Foo) sayHello() {
...
}



**【建议】字符串和文件的处理,尽量使用 strings,io/ioutil 内置工具包的函数,减少造轮子**
**【建议】if 语句尽量避免多层嵌套,及时返回 **
正例:
userId, err := selectUserId()
if err != nil {
    return 0, err
} 

if userId <= 0 {
    return 0, errors.New("userId le zero")
}

return userId, nil


反例:

userId, err := selectUserId()
if err == nil {
    if userId > 0 {
    return userId, nil
    } else {
        return 0, errors.New("userId le zero")
    }
} 

return 0, err


小结
Go 语言编码规范需尽早去学习,越早越好,否则开发项目之后,回头再看,自己肯定走了很多弯路。另外优秀的 IDE 工具有时候也会提示你按照规范去写,一边开发一边积累这些有用的规范。以上编码规范若有问题或有不详尽之处,请另行补充通知我。



《用 Go 开发终端接口服务》 目录


1 条网友评论

1 楼: 噜啦 发表于 2021-11-22 20:54:43   回复 TA

总结的很好,比较体系了!
称呼*
邮箱*
内容*
验证码*
验证码 看不清换张