百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

golang 代码规范

toyiye 2024-05-19 19:35 19 浏览 0 评论

由 无闻 根据自身项目实战经验讨论总结出来的 Go 语言编码规范,在一定程度上与 Go 官方的标准库 Code Review 规范有所不同。所述内容均为参考意见,并不一定属于主流方案或绝对正确。


版权声明

作为开源项目,必须有相应的开源许可证才能算是真正的开源。在选择了一个开源许可证之后,需要在源文件中进行相应的版权声明才能生效。以下分别以 Apache License, Version 2.0 和 MIT 授权许可为例。

Apache License, Version 2.0

该许可证要求在所有的源文件中的头部放置以下内容才能算协议对该文件有效:

// Copyright [yyyy] [name of copyright owner]
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.

其中,[yyyy] 表示该源文件创建的年份。紧随其后的是 [name of copyright owner],即版权所有者。如果为个人项目,就写个人名称;若为团队项目,则宜写团队名称。

MIT License

一般使用 MIT 授权的项目,需在源文件头部增加以下内容:

// Copyright [yyyy] [name of copyright owner]. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

其中,年份和版权所有者的名称填写规则与 Apache License, Version 2.0 的一样。

其它说明

  • 其它类型的开源许可证基本上都可参照以上两种方案。
  • 如果存在不同作者或组织对同个源文件的修改,在协议兼容的情况下,可将首行变为多行,按照先后次序排放:
// Copyright 2011 Gary Burd
// Copyright 2013 Unknwon
  • 在 README 文件最后中需要说明项目所采用的开源许可证:
## 授权许可
?
本项目采用 MIT 开源授权许可证,完整的授权说明已放置在 [LICENSE](LICENSE) 文件中。

项目结构

以下为一般项目结构,根据不同的 Web 框架习惯,可使用括号内的文字替换;根据不同的项目类型和需求,可自由增删某些结构:

- templates (views) # 模板文件
- public (static) # 静态文件
 - css 
 - fonts 
 - img 
 - js 
- routes # 路由逻辑处理
- models # 数据逻辑层
- pkg # 子模块
 - setting # 应用配置存取
- cmd # 命令行程序命令
- conf # 默认配置
 - locale # i18n 本地化文件
- custom # 自定义配置
- data # 应用生成数据文件
- log # 应用生成日志文件

命令行应用

当应用类型为命令行应用时,需要将命令相关文件存放于 /cmd 目录下,并为每个命令创建一个单独的源文件:

/cmd
 dump.go
 fix.go
 serve.go
 update.go
 web.go

导入标准库、第三方或其它包

除标准库外,Go 语言的导入路径基本上依赖代码托管平台上的 URL 路径,因此一个源文件需要导入的包有 4 种分类:标准库、第三方包、组织内其它包和当前包的子包。

基本规则:

  • 如果同时存在 2 种及以上,则需要使用分组来导入。每个分类使用一个分组,采用空行作为分区之间的分割。
  • 在非测试文件(*_test.go)中,禁止使用 . 来简化导入包的对象调用。
  • 禁止使用相对路径导入(./subpackage),所有导入路径必须符合 go get 标准。

下面是一个完整的示例:

import (
 "fmt"
 "html/template"
 "net/http"
 "os"
?
 "github.com/codegangsta/cli"
 "gopkg.in/macaron.v1"
?
 "github.com/gogits/git"
 "github.com/gogits/gfm"
?
 "github.com/gogits/gogs/routers"
 "github.com/gogits/gogs/routers/repo"
 "github.com/gogits/gogs/routers/user"
)

注释规范

  • 所有导出对象都需要注释说明其用途;非导出对象根据情况进行注释。
  • 如果对象可数且无明确指定数量的情况下,一律使用单数形式和一般进行时描述;否则使用复数形式。
  • 包、函数、方法和类型的注释说明都是一个完整的句子。
  • 句子类型的注释首字母均需大写;短语类型的注释首字母需小写。
  • 注释的单行长度不能超过 80 个字符。

包级别

  • 包级别的注释就是对包的介绍,只需在同个包的任一源文件中说明即可有效。
  • 对于 main 包,一般只有一行简短的注释用以说明包的用途,且以项目名称开头:
// Gogs (Go Git Service) is a painless self-hosted Git Service.
package main
  • 对于一个复杂项目的子包,一般情况下不需要包级别注释,除非是代表某个特定功能的模块。
  • 对于简单的非 main 包,也可用一行注释概括。
  • 对于相对功能复杂的非 main 包,一般都会增加一些使用示例或基本说明,且以 Package <name> 开头:
/*
Package regexp implements a simple library for regular expressions.
?
The syntax of the regular expressions accepted is:
?
 regexp:
 concatenation { '|' concatenation }
 concatenation:
 { closure }
 closure:
 term [ '*' | '+' | '?' ]
 term:
 '^'
 '$'
 '.'
 character
 '[' [ '^' ] character-ranges ']'
 '(' regexp ')'
*/
package regexp
  • 特别复杂的包说明,可单独创建 doc.go 文件来加以说明。

结构、接口及其它类型

  • 类型的定义一般都以单数形式描述:
// Request represents a request to run a command.
type Request struct { ...
  • 如果为接口,则一般以以下形式描述:
// FileInfo is the interface that describes a file and is returned by Stat and Lstat.
type FileInfo interface { ...

函数与方法

  • 函数与方法的注释需以函数或方法的名称作为开头:
// Post returns *BeegoHttpRequest with POST method.
  • 如果一句话不足以说明全部问题,则可换行继续进行更加细致的描述:
// Copy copies file from source to target path.
// It returns false and error when error occurs in underlying function calls.
  • 若函数或方法为判断类型(返回值主要为 bool 类型),则以 <name> returns true if 开头:
// HasPrefix returns true if name has any string in given slice as prefix.
func HasPrefix(name string, prefixes []string) bool { ...

其它说明

  • 当某个部分等待完成时,可用 TODO: 开头的注释来提醒维护人员。
  • 当某个部分存在已知问题进行需要修复或改进时,可用 FIXME: 开头的注释来提醒维护人员。
  • 当需要特别说明某个问题时,可用 NOTE: 开头的注释:
// NOTE: os.Chmod and os.Chtimes don't recognize symbolic link,
// which will lead "no such file or directory" error.
return os.Symlink(target, dest)

命名规则

文件名

  • 整个应用或包的主入口文件应当是 main.go 或与应用名称简写相同。例如:Gogs 的主入口文件名为 gogs.go。

函数或方法

  • 若函数或方法为判断类型(返回值主要为 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 { ... }

常量

  • 常量均需使用全部大写字母组成,并使用下划线分词:
const APP_VER = "0.7.0.1110 Beta"
  • 如果是枚举类型的常量,需要先创建相应类型:
type Scheme string
?
const (
 HTTP Scheme = "http"
 HTTPS Scheme = "https"
)
  • 如果模块的功能较为复杂、常量名称容易混淆的情况下,为了更好地区分枚举类型,可以使用完整的前缀:
type PullRequestStatus int
?
const (
 PULL_REQUEST_STATUS_CONFLICT PullRequestStatus = iota
 PULL_REQUEST_STATUS_CHECKING
 PULL_REQUEST_STATUS_MERGEABLE
)

变量

  • 变量命名基本上遵循相应的英文表达或简写。
  • 在相对简单的环境(对象数量少、针对性强)中,可以将一些名称由完整单词简写为单个字母,例如:
  • user 可以简写为 u
  • userID 可以简写 uid
  • 若变量类型为 bool 类型,则名称应以 Has, Is, Can 或 Allow 开头:
var isExist bool
var hasConflict bool
var canManage bool
var allowGitHook bool
  • 上条规则也适用于结构定义:
// Webhook represents a web hook object.
type Webhook struct {
 ID int64 `xorm:"pk autoincr"`
 RepoID int64
 OrgID int64
 URL string `xorm:"url TEXT"`
 ContentType HookContentType
 Secret string `xorm:"TEXT"`
 Events string `xorm:"TEXT"`
 *HookEvent `xorm:"-"`
 IsSSL bool `xorm:"is_ssl"`
 IsActive bool
 HookTaskType HookTaskType
 Meta string `xorm:"TEXT"` // store hook-specific attributes
 LastStatus HookStatus // Last delivery status
 Created time.Time `xorm:"CREATED"`
 Updated time.Time `xorm:"UPDATED"`
}

变量命名惯例

变量名称一般遵循驼峰法,但遇到特有名词时,需要遵循以下规则:

  • 如果变量为私有,且特有名词为首个单词,则使用小写,如 apiClient。
  • 其它情况都应当使用该名词原有的写法,如 APIClient、repoID、UserID。

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

// A GonicMapper that contains a list of common initialisms taken from golang/lint
var LintGonicMapper = GonicMapper{
 "API": true,
 "ASCII": true,
 "CPU": true,
 "CSS": true,
 "DNS": true,
 "EOF": true,
 "GUID": true,
 "HTML": true,
 "HTTP": true,
 "HTTPS": true,
 "ID": true,
 "IP": true,
 "JSON": true,
 "LHS": true,
 "QPS": true,
 "RAM": true,
 "RHS": true,
 "RPC": true,
 "SLA": true,
 "SMTP": true,
 "SSH": true,
 "TLS": true,
 "TTL": true,
 "UI": true,
 "UID": true,
 "UUID": true,
 "URI": true,
 "URL": true,
 "UTF8": true,
 "VM": true,
 "XML": true,
 "XSRF": true,
 "XSS": true,
}

声明语句

函数或方法

函数或方法的参数排列顺序遵循以下几点原则(从左到右):

  1. 参数的重要程度与逻辑顺序
  2. 简单类型优先于复杂类型
  3. 尽可能将同种类型的参数放在相邻位置,则只需写一次类型

示例

以下声明语句,User 类型要复杂于 string 类型,但由于 Repository 是 User 的附属品,首先确定 User 才能继而确定 Repository。因此,User 的顺序要优先于 repoName。

func IsRepositoryExist(user *User, repoName string) (bool, error) { ...

代码指导

基本约束

  • 所有应用的 main 包需要有 APP_VER 常量表示版本,格式为 X.Y.Z.Date [Status],例如:0.7.6.1112 Beta。
  • 单独的库需要有函数 Version 返回库版本号的字符串,格式为 X.Y.Z[.Date]。
  • 当单行代码超过 80 个字符时,就要考虑分行。分行的规则是以参数为单位将从较长的参数开始换行,以此类推直到每行长度合适:
So(z.ExtractTo(
 path.Join(os.TempDir(), "testdata/test2"),
 "dir/", "dir/bar", "readonly"), ShouldBeNil)
  • 当单行声明语句超过 80 个字符时,就要考虑分行。分行的规则是将参数按类型分组,紧接着的声明语句的是一个空行,以便和函数体区别:
// NewNode initializes and returns a new Node representation.
func NewNode(
 importPath, downloadUrl string,
 tp RevisionType, val string,
 isGetDeps bool) *Node {
?
 n := &Node{
 Pkg: Pkg{
 ImportPath: importPath,
 RootPath: GetRootPath(importPath),
 Type: tp,
 Value: val,
 },
 DownloadURL: downloadUrl,
 IsGetDeps: isGetDeps,
 }
 n.InstallPath = path.Join(setting.InstallRepoPath, n.RootPath) + n.ValSuffix()
 return n
}
  • 分组声明一般需要按照功能来区分,而不是将所有类型都分在一组:
const (
 // Default section name.
 DEFAULT_SECTION = "DEFAULT"
 // Maximum allowed depth when recursively substituing variable names.
 _DEPTH_VALUES = 200
)
?
type ParseError int
?
const (
 ERR_SECTION_NOT_FOUND ParseError = iota + 1
 ERR_KEY_NOT_FOUND
 ERR_BLANK_SECTION_NAME
 ERR_COULD_NOT_PARSE
)
  • 当一个源文件中存在多个相对独立的部分时,为方便区分,需使用由 ASCII Generator 提供的句型字符标注(示例:Comment):
// _________ __
// \_ ___ \ ____ _____ _____ ____ _____/ |_
// / \ \/ / _ \ / \ / \_/ __ \ / \ __\
// \ \___( <_> ) Y Y \ Y Y \ ___/| | \ |
// \______ /\____/|__|_| /__|_| /\___ >___| /__|
// \/ \/ \/ \/ \/
  • 函数或方法的顺序一般需要按照依赖关系由浅入深由上至下排序,即最底层的函数出现在最前面。例如,下方的代码,函数 ExecCmdDirBytes 属于最底层的函数,它被 ExecCmdDir 函数调用,而 ExecCmdDir 又被 ExecCmd 调用:
// ExecCmdDirBytes executes system command in given directory
// and return stdout, stderr in bytes type, along with possible error.
func ExecCmdDirBytes(dir, cmdName string, args ...string) ([]byte, []byte, error) {
 ...
}
?
// ExecCmdDir executes system command in given directory
// and return stdout, stderr in string type, along with possible error.
func ExecCmdDir(dir, cmdName string, args ...string) (string, string, error) {
 bufOut, bufErr, err := ExecCmdDirBytes(dir, cmdName, args...)
 return string(bufOut), string(bufErr), err
}
?
// ExecCmd executes system command
// and return stdout, stderr in string type, along with possible error.
func ExecCmd(cmdName string, args ...string) (string, string, error) {
 return ExecCmdDir("", cmdName, args...)
}
  • 结构附带的方法应置于结构定义之后,按照所对应操作的字段顺序摆放方法:
type Webhook struct { ... }
func (w *Webhook) GetEvent() { ... }
func (w *Webhook) SaveEvent() error { ... }
func (w *Webhook) HasPushEvent() bool { ... }
  • 如果一个结构拥有对应操作函数,大体上按照 CRUD 的顺序放置结构定义之后:
func CreateWebhook(w *Webhook) error { ... }
func GetWebhookById(hookId int64) (*Webhook, error) { ... }
func UpdateWebhook(w *Webhook) error { ... }
func DeleteWebhook(hookId int64) error { ... }
  • 如果一个结构拥有以 Has、Is、Can 或 Allow 开头的函数或方法,则应将它们至于所有其它函数及方法之前;这些函数或方法以 Has、Is、Can、Allow 的顺序排序。
  • 变量的定义要放置在相关函数之前:
var CmdDump = cli.Command{
	Name: "dump",
	...
	Action: runDump,
	Flags: []cli.Flag{},
}
func runDump(*cli.Context) { ...
  • 在初始化结构时,尽可能使用一一对应方式:
AddHookTask(&HookTask{
	Type: HTT_WEBHOOK,
	Url: w.Url,
	Payload: p,
	ContentType: w.ContentType,
	IsSsl: w.IsSsl,
})

测试用例

  • 单元测试都必须使用 GoConvey 编写,且辅助包覆盖率必须在 80% 以上。

使用示例

  • 为辅助包书写使用示例的时,文件名均命名为 example_test.go。
  • 测试用例的函数名称必须以 Test_ 开头,例如:Test_Logger。
  • 如果为方法书写测试用例,则需要以 Text_<Struct>_<Method> 的形式命名,例如:Test_Macaron_Run。

https://github.com/unknwon/go-code-convention

相关推荐

为何越来越多的编程语言使用JSON(为什么编程)

JSON是JavascriptObjectNotation的缩写,意思是Javascript对象表示法,是一种易于人类阅读和对编程友好的文本数据传递方法,是JavaScript语言规范定义的一个子...

何时在数据库中使用 JSON(数据库用json格式存储)

在本文中,您将了解何时应考虑将JSON数据类型添加到表中以及何时应避免使用它们。每天?分享?最新?软件?开发?,Devops,敏捷?,测试?以及?项目?管理?最新?,最热门?的?文章?,每天?花?...

MySQL 从零开始:05 数据类型(mysql数据类型有哪些,并举例)

前面的讲解中已经接触到了表的创建,表的创建是对字段的声明,比如:上述语句声明了字段的名称、类型、所占空间、默认值和是否可以为空等信息。其中的int、varchar、char和decimal都...

JSON对象花样进阶(json格式对象)

一、引言在现代Web开发中,JSON(JavaScriptObjectNotation)已经成为数据交换的标准格式。无论是从前端向后端发送数据,还是从后端接收数据,JSON都是不可或缺的一部分。...

深入理解 JSON 和 Form-data(json和formdata提交区别)

在讨论现代网络开发与API设计的语境下,理解客户端和服务器间如何有效且可靠地交换数据变得尤为关键。这里,特别值得关注的是两种主流数据格式:...

JSON 语法(json 语法 priority)

JSON语法是JavaScript语法的子集。JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔花括号保存对象方括号保存数组JS...

JSON语法详解(json的语法规则)

JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔大括号保存对象中括号保存数组注意:json的key是字符串,且必须是双引号,不能是单引号...

MySQL JSON数据类型操作(mysql的json)

概述mysql自5.7.8版本开始,就支持了json结构的数据存储和查询,这表明了mysql也在不断的学习和增加nosql数据库的有点。但mysql毕竟是关系型数据库,在处理json这种非结构化的数据...

JSON的数据模式(json数据格式示例)

像XML模式一样,JSON数据格式也有Schema,这是一个基于JSON格式的规范。JSON模式也以JSON格式编写。它用于验证JSON数据。JSON模式示例以下代码显示了基本的JSON模式。{"...

前端学习——JSON格式详解(后端json格式)

JSON(JavaScriptObjectNotation)是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。它基于JavaScriptProgrammingLa...

什么是 JSON:详解 JSON 及其优势(什么叫json)

现在程序员还有谁不知道JSON吗?无论对于前端还是后端,JSON都是一种常见的数据格式。那么JSON到底是什么呢?JSON的定义...

PostgreSQL JSON 类型:处理结构化数据

PostgreSQL提供JSON类型,以存储结构化数据。JSON是一种开放的数据格式,可用于存储各种类型的值。什么是JSON类型?JSON类型表示JSON(JavaScriptO...

JavaScript:JSON、三种包装类(javascript 包)

JOSN:我们希望可以将一个对象在不同的语言中进行传递,以达到通信的目的,最佳方式就是将一个对象转换为字符串的形式JSON(JavaScriptObjectNotation)-JS的对象表示法...

Python数据分析 只要1分钟 教你玩转JSON 全程干货

Json简介:Json,全名JavaScriptObjectNotation,JSON(JavaScriptObjectNotation(记号、标记))是一种轻量级的数据交换格式。它基于J...

比较一下JSON与XML两种数据格式?(json和xml哪个好)

JSON(JavaScriptObjectNotation)和XML(eXtensibleMarkupLanguage)是在日常开发中比较常用的两种数据格式,它们主要的作用就是用来进行数据的传...

取消回复欢迎 发表评论:

请填写验证码