• GO语言规范


    开发环境

    • Jet Brains 出的专用 IDE:Goland
    • IntelliJIDEAL + GO插件(Go、file watcher)
    • Visual Code 与微软开发的专用 Go 插件:VS Code + Go
    • go版本: go 1.16或以上(latest:go1.17.2),用go mod代替gopath。

    工程模板

    工程应用

    项目名
    - templates (views)          # 模板文件(可选)
    - public (static)            # 静态资源(可选)
    - routers (controllers)      # 路由逻辑处理
    - models                     # 数据逻辑层
    - modules                    # 子模块
    - conf                       # 默认配置
    - init                       # 各类初始化数据 
    - scripts                    # 用于执行各种构建,安装,分析等操作的脚本
    - test                      # 测试数据
    - log                       # 应用生成日志文件
    main.go                     # web入口程序
    go.mod
    README.md                   # markdown文档,工程描述   
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    项目名与文件夹:均小写
    适用于Web项目,根据不同的项目类型和需求,可自由删除某些结构。
    标准工程结构:作为补充,见附录

    命令行应用

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

    /cmd
        dump.go
        fix.go
        serve.go
        update.go
        web.go
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    命名规范

    工程名、目录名:小写单词

    文件名

    • 文件名应一律使用小写, 不同单词之间用下划线分割, 不用驼峰式,命名应尽可能地见名知意。
    • 整个应用或包的主入口文件应当以main.go或与应用名称简写。
    • 测试文件以_test.go结尾lib.go, lib_test.go。

    包名规范

    包名用小写,使用短命名,尽量和标准库不要冲突。包名统一使用单数形式。

    变量命名规范

    变量命名一般采用驼峰式,当遇到特有名词(缩写或简称,如DNS)的时候,特有名词根据是否私有全部大写或小写:

    var apiClient
    var URLString
    
    • 1
    • 2

    在对象数量少、针对性强的应用中,可以将一些名称由完整单词简写为单个字母:
    user 可以简写为 u
    userID 可以简写 uid

    若变量类型为 bool 类型,则名称应以 Has, Is, Can 或 Allow 开头:

     var isExist bool
     var hasConflict bool
     var canManage bool
     var allowGitHook bool
    
    • 1
    • 2
    • 3
    • 4

    特有名词

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

    • 如果变量为私有,且特有名词为首个单词,则使用小写,如 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
    • 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
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    常量命名规范

    命名力求语义表达完整清楚,不要嫌名字长。

    常量

    常量均需使用全部大写字母组成,并使用下划线分词。如下:

    const TODAY_NEWS = "Hello"
    
    //如果超过了一个常量应该用括号的方法来组织
    const (
      SYSTEM_NAME = "What"
      SYSTEM_VAL = "dasdsada"
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    枚举常量类型

    对于枚举类型,遵守变量名命名规则,如下:

     type PullRequestStatus int
     const (
         PULL_REQUEST_STATUS_CONFLICT PullRequestStatus = iota
         PULL_REQUEST_STATUS_CHECKING
         PULL_REQUEST_STATUS_MERGEABLE
     )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    结构体命名规范

    遵循变量名的规则。

    // 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"`
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    结构体名应该是名词或名词短语,如Account,Book,避免使用Manager这样的。
    如果该数据结构需要序列化,如json, 则首字母大写, 包括里面的字段。

    接口命名规范

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

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

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

    type WriteFlusher interface {
        Write([]byte) (int, error)
        Flush() error
    }
    
    • 1
    • 2
    • 3
    • 4

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

    type Car interface {
        Start()
        Stop()
        Drive()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    函数或方法命名规范

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

    func updateById(...) {...}
    func getUserInfo(...) {...}
    
    • 1
    • 2

    判断类型的函数或方法,则名称应以 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 { … }

    receiver命名规范

    即结构体方法。receiver 的名称应该缩写,命名应该尽量保持一致。一般使用一个或者两个字符作为 Receiver 的名称;如果Receiver是指针, 那么可统一使用p;避免this, super等其他语言的一些语义关键字。

    type foo struct{...}
    
    func (f foo) method() {
        ...
    }
    
    func (p *foo) method() {
        ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    对于Receiver命名应该统一, 要么都使用值, 要么都用指针。

    可见性规范

    函数或方式:大写字母开头的方法以为着是可供调用的公共方法,小写字母开头为包内调用。常量、变量名、接口等都遵循相同规则。

    编码规范

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

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

    • 如果同时存在 2 种及以上,则需要使用分区来导入。每个分类使用一个分区,采用空行作为分区之间的分割。
    • 在非测试文件(*_test.go)中,禁止使用 . 来简化导入包的对象调用。
    • 禁止使用相对路径导入(./subpackage),所有导入路径必须符合 go get 标准。
    import (
        "fmt"
        "html/template"
        "net/http"
        "os"
        "github.com/gogits/gogs/routers/repo"
        "github.com/gogits/gogs/routers/user"
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    代码基本约束

    • 一般应用的 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)
    
    • 1
    • 2
    • 3
    • 当单行声明语句超过 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
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 分组声明一般需要按照功能来区分,而不是将所有类型都分在一组。
    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
      )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    函数或方法的顺序一般需要按照依赖关系由浅入深由上至下排序,即最底层的函数出现在最前面。例如,下方的代码,函数 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...)
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 结构附带的方法应置于结构定义之后,按照所对应操作的字段顺序摆放方法:
     type Webhook struct { ... }
     func (w *Webhook) GetEvent() { ... }
     func (w *Webhook) SaveEvent() error { ... }
     func (w *Webhook) HasPushEvent() bool { ... }
    
    • 1
    • 2
    • 3
    • 4
    • 如果一个结构拥有对应操作函数,大体上按照 CRUD 的顺序放置结构定义之后:
      func CreateWebhook(w *Webhook) error { ... }
      func GetWebhookById(hookId int64) (*Webhook, error) { ... }
      func UpdateWebhook(w *Webhook) error { ... }
      func DeleteWebhook(hookId int64) error { ... }
    
    • 1
    • 2
    • 3
    • 4
    • 如果一个结构拥有以 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,
     })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    注释规范

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

    包级别

    • 包级别的注释就是对包的介绍,只需在同个包的任一源文件中说明即可有效。
    • 对于复杂项目的子包,一般情况下不需要包级别注释,除非是代表某个特定功能的模块。
    • 对于简单的 非main 包,也可用一行注释概括。
    • 对于 main 包,一般只有一行简短的注释用以说明包的用途,且以项目名称开头,见例1。
    • 对于相对功能复杂的非 main 包,一般都会增加一些使用示例或基本说明,且以 Package 开头,见例2
      特别复杂的包说明,可单独创建 doc.go 文件来加以说明。

    例1:main包

    // Gogs (Go Git Service) is a painless self-hosted Git Service.
      package main
    
    • 1
    • 2

    例2:功能复杂的非 main

    /*
      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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    结构、接口及其它类型

     // Request represents a request to run a command.
      type Request struct { ...
    
    • 1
    • 2

    命名一般都以单数形式描述

    如果为接口,则一般以以下形式描述

     // FileInfo is the interface that describes a file and is returned by Stat and Lstat.
     type FileInfo interface { ...
    
    • 1
    • 2

    函数与方法

    函数与方法的注释需以函数或方法的名称作为开头。

     // Post returns *BeegoHttpRequest with POST method.
    
    • 1

    如果一句话不足以说明全部问题,则可换行继续进行更加细致的描述:

    // Copy copies file from source to target path.
    // It returns false and error when error occurs in underlying function calls.
    
    • 1
    • 2

    若函数或方法为判断类型(返回值一般为bool),注解以 returns true if开头。

    // HasPrefix returns true if name has any string in given slice as prefix.
      func HasPrefix(name string, prefixes []string) bool { ...
    
    • 1
    • 2

    注释中的一些关键字

    • TODO: 当某个部分等待完成时,可用TODO:开头的注释来提醒维护人员
    • FIXME:当某个部分存在已知问题需要进行修复或改进时, 可用 FIXME:开头的注释来提醒维护人员。
    • NOTE: 当需要特别说明某个问题时,可用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)
    
    • 1
    • 2
    • 3

    函数或方法声明

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

    • 参数的重要程度与逻辑顺序
    • 简单类型优先于复杂类型
    • 尽可能将同种类型的参数放在相邻位置,则只需写一次类型
    func IsRepositoryExist(user *User, repoName string) (bool, error) { ...
    
    • 1

    思路说明:
    User类型要复杂于string类型,但由于Repository是User的附属品,首先确定User才能> 继而确定Repository。因此,User的顺序要优先于repoName

    单元测试

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

    附录

    环境配置

    • GOROOT:go的安装路径
    • GOPATH:go的工作目录,每一个子目录包含如下三个子文件夹
    gXXX
       --bin //存放可执行文件
       --pkg //存放编译好的包文件,主要是*.a文件
       --src //源码路径,每个项目也是一个文件夹
    
    • 1
    • 2
    • 3
    • 4

    Go常见命令

    go run : 编译并直接运行程序,不生成编译文件。
    go build : 用于测试编译包,主要检查是否有编译错误,不会产生结果文件,如果编译的是一个可执行文件的源码(即main包),会在执行目录下生成可执行文件。
    go install : 先编译导入的包文件,包文件编译完后再编译主程序,再将编译后的可执行文件放到bin目录下($GOPATH/bin);如果编译的是某个依赖包,编译后的依赖放到pkg目录下($GOPATH/pkg)。
    go get : git clone+go install (需要翻墙,一般直接clone)。
    
    • 1
    • 2
    • 3
    • 4

    GO开发目录配置 go-mod

    # go env设置
    go env -w GO111MODULE=on
    go env -w GOPROXY=https://goproxy.cn,direct  # https://goproxy.cn 国内镜像
    
    # 查看与验证go env 命令
    go env
    
    # windwos环境变量
    GOPROXY=https://goproxy.cn
    GO111MODULE=on  #注意no是小写
    
    # 或on mac/linux
    $ echo "export GO111MODULE=on" >> ~/.profile
    $ echo "export GOPROXY=https://goproxy.cn" >> ~/.profile
    $ source ~/.profile
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    在ideal下配置go插件

    1、打开File - Settings - Plugins : Marketplace
    2、查询"go"与“file watcher”并安装,
    
    • 1
    • 2

    file watcher:获取在运行环境的信息,保存文件的同时自动运行格式化工具
    go: 让ideal支持go

    GO打包部署

    go build main.go

    在Windows下打包Linux可运行的Go程序

    $ set GOARCH=amd64   #GOARCH指的是目标处理器的架构,支持一下处理器架构
    $ set GOOS=linux     #操作系统
    $ go build           #编译打包
    
    • 1
    • 2
    • 3

    GOOS指的是目标操作系统,支持以下操作系统:
    darwin freebsd linux windows android dragonfly netbsd openbsd plan9 solaris
    arm arm64 386 amd64 ppc64 ppc64le mips64 mips64le s390x

    标准工程结构解说

    go官方并没有标准,golang-standards给出目录标准;此次用于内部工程程目录结构规范的补充:

    go目录
    - cmd
    - internal
    - pkg
    - vendor
    服务端应用目录
    - api: 外部依赖api,比如protobuf-spec的json文件
    - web: web程序特定组件(静态资源、服务端模板、单页面应用(SPA))
    通用项目目录
    - build
    - configs
    - deployments
    - init
    - scripts
    - test
    其他目录
    - assets
    - docs
    - examples
    - githooks
    - third-party
    - tools
    - website
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    GO语言规范完整版(合集)

  • 相关阅读:
    基于JAVA的新闻发布管理系统开发参考【数据库设计、源码、开题报告】
    LeetCode 2511. 最多可以摧毁的敌人城堡数目
    linux apt-get安装Jenkins
    【C/C++】GCC vs. G++:C 与 C++ 编译器的差异和比较
    2023-10-19 node.js-将异步处理修改为同步-使用Promise和async-记录
    活动|探索人工智能与行业应用实践沙龙
    Windows安装单节点Zookeeper
    2022-08-05 粗糙集Rough set
    【Java进阶】学好常用类,code省时省力(一)
    elasticsearch-head离线安装
  • 原文地址:https://blog.csdn.net/ic_xcc/article/details/125537058