博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Go语言规范1 - 统一规范篇
阅读量:5292 次
发布时间:2019-06-14

本文共 6264 字,大约阅读时间需要 20 分钟。

目录

序言

看过很多方面的编码规范,可能每一家公司都有不同的规范,这份编码规范是写给我自己的,同时希望我们公司内部同事也能遵循这个规范来写Go代码。

如果你的代码没有办法找到下面的规范,那么就遵循标准库的规范,多阅读标准库的源码,标准库的代码可以说是我们写代码参考的标杆。

本文中凡是【】内为规则的都是参考的标准库的源码,和【】内为原则的一样都是必须遵守的,【】内为建议的,是仅做建议遵守的。

本文参考了网上流传的众多开发规范帖子和标准款的规范代码,最后梳理而成,因内容过多分为几大篇,可选看,最后感谢那些原帖作者,文末给出了参考帖子的链接

目录

  • 统一规范篇
  • 命名篇
  • 开发篇
  • 优化篇

统一规范篇

本篇主要描述了公司内部同事都必须遵守的一些开发规矩,如统一开发空间,既使用统一的开发工具来保证代码最后的格式的统一,开发中对文件和代码长度的控制,必须经过go语言自带的检测机制等。

1.1 合理规划目录

【原则1.1】合理规划目录,一个目录中只包含一个包(实现一个模块的功能),如果模块功能复杂考虑拆分子模块,或者拆分目录。

说明:在Go中对于模块的划分是基于package这个概念,可以在一个目录中可以实现多个package,但是并不建议这样的实现方式。主要的缺点是模块之间的关系不清晰,另外不利于模块功能扩展。

错误示例:

project│  config.go│  controller.go│  filter.go│  flash.go│  log.go│  memzipfile.go│  mime.go│  namespace.go│  parser.go│  router.go│  staticfile.go│  template.go│  templatefunc.go│  tree.go│  util.go|  validation.go|  validators.go

推荐做法: 

project  ├─cache  │  │  cache.go  │  │  conv.go  │  │        │  └─redis  │          redis.go  ├─config  │  │  config.go  │  │  fake.go  │  │  ini.go  │  └─yaml  │          yaml.go  ├─logs  │      conn.go  │      console.go  │      file.go  │      log.go  │      smtp.go  └─validation          util.go          validation.go          validators.go

1.2 GOPATH设置

【建议1.2】使用单一的 GOPATH

虽说Go语言支持拥有多个 GOPATH,但多个GOPATH的情况并不具有弹性。GOPATH本身就是高度自我完备的(通过导入路径)。有多个 GOPATH 会导致某些副作用,例如可能使用了给定的库的不同的版本。你可能在某个地方升级了它,但是其他地方却没有升级。而且,我还没遇到过任何一个需要使用多个 GOPATH 的情况。所以只使用单一的 GOPATH,这会提升你 Go 的开发进度。

许多人不同意这一观点,接下来我会做一些澄清。像 etcd 或 camlistore 这样的大项目使用了像 godep 这样的工具,将所有依赖保存到某个目录中。也就是说,这些项目自身有一个单一的 GOPATH。它们只能在这个目录里找到对应的版本。除非你的项目很大并且极为重要,否则不要为每个项目使用不同的 GOPAHT。如果你认为项目需要一个自己的 GOPATH 目录,那么就创建它,否则不要尝试使用多个 GOPATH。它只会拖慢你的进度。

 所有项目共用一个workspace,如下图所示:

workspace/  ├── bin  ├── pkg  │   └── linux_amd64  │         └── src      ├── project1      │     └── project2      │         └── project3      │         └── …

优点: 方便发布到github.com, 让第三方通过go get等工具获取。

内部项目,建议采用第一种工程结构。公开项目、提供给第三方集成的项目采用第二种项目结构。

1.3 import 规范

import路径是一个唯一标示的字符串

import在多行的情况下,goimports会自动帮你格式化,但是我们这里还是规范一下import的一些规范,如果你在一个文件里面引入了一个package,还是建议采用如下格式:

import (    "fmt")

如果你的包引入了三种类型的包,标准库包,程序内部包,第三方包,建议采用如下方式进行组织你的包:

import (    "encoding/json"    "strings"    "myproject/models"    "myproject/controller"    "myproject/utils"    "github.com/astaxie/beego"    "github.com/go-sql-driver/mysql")

有顺序的引入包,不同的类型采用空格分离,第一种实标准库,第二是项目包,第三是第三方包。

【规则1.3.1】在非测试文件(*_test.go)中,禁止使用 . 来简化导入包的对象调用。

错误示例:

// 这是不好的导入    import . " pubcode/api/broker"

这种写法不利于阅读,因而不提倡。

【规则1.3.2】禁止使用相对路径导入(./subpackage),所有导入路径必须符合 go get 标准。

错误示例: 

// 这是不好的导入    import "../net"

正确做法:

// 这是正确的做法    import "github.com/repo/proj/src/net"

【建议1.3.3】建议使用goimports工具或者IDE工具来管理多行import

go默认已经有了gofmt工具,但是我们强烈建议使用goimport工具,这个在gofmt的基础上增加了自动删除和引入包.

go get golang.org/x/tools/cmd/goimports

不同的编辑器有不同的配置, sublime的配置教程:

LiteIDE和GoLand默认已经支持了goimports,如果你的不支持请点击属性配置->golangfmt->勾选goimports

保存之前自动fmt你的代码。

好处:import在多行的情况下,goimports工具会自动帮你格式化,自动删除和引入包。很多IDE工具也可以自动检查并纠正import路径

1.4 代码风格

Go语言对代码风格作了很多强制的要求,并提供了工具gofmt, golint, go tool vet等工具检查。

【规则1.4.1】提交代码时,必须使用gofmt对代码进行格式化。

大部分的格式问题可以通过 gofmt 来解决,gofmt 自动格式化代码,保证所有的 go 代码与官方推荐的格式保持一致,所有格式有关问题,都以gofmt的结果为准。所以,建议在提交代码库之前先运行一下这个命令。

gofmt(也可以用go fmt,其操作于程序包的级别,而不是源文件级别),读入Go的源代码,然后输出按照标准风格缩进和垂直对齐的源码,并且保留了根据需要进行重新格式化的注释。如果你想知道如何处理某种新的布局情况,可以运行gofmt;如果结果看起来不正确,则需要重新组织你的程序,不要把问题绕过去。标准程序包中的所有Go代码,都已经使用gofmt进行了格式化。

不需要花费时间对结构体中每个域的注释进行排列,如下面的代码, 

type T struct {        name string     // name of the object        value int     // its value    }

gofmt将会按列进行排列:

type T struct {        name string   // name of the object        value int     // its value    }

【规则1.4.2】提交代码时,必须使用golint对代码进行检查。

golint 会检测的方面:

  • 变量名规范
  • 变量的声明,像var str string = "test",会有警告,应该var str = "test"
  • 大小写问题,大写导出包的要有注释
  • x += 1 应该 x++

等等

详细可以看官方库示例,

想速成的可以看自行学习使用

【建议1.4.3】提交代码前,必须使用go vet对代码进行检查。

如果说golint是检查我们的代码规范的话,那么vet工具则是可以帮我们静态分析我们的源码存在的各种问题,例如多余的代码,提前return的逻辑,struct的tag是否符合标准等。

go get golang.org/x/tools/cmd/vet

使用如下:

go vet .

1.5 大小约定

【建议1.5.1】单个文件长度不超过500行。

对开源引入代码可以降低约束,新增代码必须遵循。

【建议1.5.2】单个函数长度不超过50行。

函数两个要求:单一职责、要短小

【规则1.5.3】单个函数圈复杂度最好不要超过10,禁止超过15。

说明:圈复杂度越高,代码越复杂,就越难以测试和维护,同时也说明函数职责不单一。

【规则1.5.4】单行语句不能过长,如不能拆分需要分行写。一行最多120个字符。

换行时有如下建议:

换行时要增加一级缩进,使代码可读性更好;
低优先级操作符处划分新行;换行时操作符应保留在行尾; 
换行时建议一个完整的语句放在一行,不要根据字符数断行

示例:

if ((tempFlag == TestFlag) &&    (((counterVar - constTestBegin) % constTestModules) >= constTestThreshold)) {         // process code     }

【建议1.5.5】函数中缩进嵌套必须小于等于3层。

举例,禁止出现以下这种锯齿形的函数:

func testUpdateOpts PushUpdateOptions) (err error) {        isNewRef := opts.OldCommitID == git.EMPTY_SHA        isDelRef := opts.NewCommitID == git.EMPTY_SHA        if isNewRef && isDelRef {            if isDelRef {                repo, err := GetRepositoryByName(owner.ID, opts.RepoName)                if err != nil {                    if strings.HasPrefix(opts.RefFullName, git.TAG_PREFIX) {                        if err := CommitRepoAction(CommitRepoActionOptions{                            PusherName:  opts.PusherName,                            RepoOwnerID: owner.ID,                            RepoName:    repo.Name,                            RefFullName: opts.RefFullName,                            OldCommitID: opts.OldCommitID,                            NewCommitID: opts.NewCommitID,                            Commits:     &PushCommits{},                        }); err != nil {                            return fmt.Errorf("CommitRepoAction (tag): %v", err)                        }                        return nil                    }                }                else {                    owner, err := GetUserByName(opts.RepoUserName)                    if err != nil {                        return fmt.Errorf("GetUserByName: %v", err)                    }                        return nil                }            }        }            // other code    }

提示:如果发现锯齿状函数,应通过尽早通过return等方法重构。

【原则1.5.6】保持函数内部实现的组织粒度是相近的。

举例,不应该出现如下函数:

func main() {        initLog()            //这一段代码的组织粒度,明显与其他的不均衡        orm.DefaultTimeLoc = time.UTC        sqlDriver := beego.AppConfig.String("sqldriver")        dataSource := beego.AppConfig.String("datasource")        modelregister.InitDataBase(sqlDriver, dataSource)            Run()    }

应该改为:

func main() {        initLog()            initORM()  //修改后,函数的组织粒度保持一致             Run()    }

参考链接

转载于:https://www.cnblogs.com/Survivalist/articles/10596110.html

你可能感兴趣的文章
OnContextMenu事件(转)
查看>>
Comparación para 2019 Nueva Lonsdor K518S y K518ISE
查看>>
论文笔记——MobileNets(Efficient Convolutional Neural Networks for Mobile Vision Applications)
查看>>
从今天开始
查看>>
Attribute(特性)与AOP
查看>>
第三次作业
查看>>
Codeforces 962 /2错误 相间位置排列 堆模拟 X轴距离最小值 前向星点双连通分量求只存在在一个简单环中的边...
查看>>
Matrix快速幂 模板
查看>>
laravel command调用方法命令
查看>>
20162302 - 20162319 结对编程项目-四则运算(第一周)
查看>>
用python2和python3伪装浏览器爬取网页
查看>>
MySQL开启远程连接权限
查看>>
tomcat7.0.27的bio,nio.apr高级运行模式
查看>>
SAP HANA 三大特点
查看>>
C#预处理器命令
查看>>
苹果手表:大方向和谷歌一样,硬件分道扬镳
查看>>
ccf 出现次数最多的数
查看>>
单例模式
查看>>
Competing Consumers Pattern (竞争消费者模式)
查看>>
HDUOJ ------1398
查看>>