Go语言编码规范

"编码规范(Go)"规范为日常Go项目开发提供一个统一的规范指导, 方便团队形成统一的代码风格, 提高代码可读性, 规范性和一致性.

同时作为CR的有效指导工具, 如果有变更的, 需要补充的, 可以在文档中(文档下)添加评论说明进行补充.

大部分的格式问题可以通过gofmt解决,gofmt自动格式化代码,保证所有的go代码与官方推荐的格式保持一致,于是所有格式有关问题,都以gofmt的结果为准。

目录

一. 命名规范

二. 注释

三. 其他规范

四. 编程建议

五. 其他编码规范参考


一. 命名规范

命名是代码规范中很重要的一部分,统一的命名规则有利于提高的代码的可读性,好的命名仅仅通过命名就可以获取到足够多的信息。

Go在命名时以字母a到Z或a到Z或下划线开头,后面跟着零或更多的字母、下划线和数字(0到9)。Go不允许在命名时中使用@、$和%等标点符号。Go是一种区分大小写的编程语言。因此,Manpower和manpower是两个不同的命名。

  • 当命名(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头,如:Group1,那么使用这种形式的标识符的对象就可以被外部包的代码所使用(客户端程序需要先导入这个包),这被称为导出(像面向对象语言中的 public);
  • 命名如果以小写字母开头,则对包外是不可见的,但是他们在整个包的内部是可见并且可用的(像面向对象语言中的 private )

1 . 包命名

保持package的名字和目录保持一致,尽量采取有意义的包名,简短,有意义,尽量和标准库不要冲突。包名应该为小写单词,不要使用下划线或者混合大小写。

  • 全部小写。没有大写或下划线。
  • 简短而简洁。
  • 不用复数。例如net/url,而不是net/urlsh
  • 避免“common”,“util”,“shared”或“lib”,这些是不好的、信息量不足的名称。

Bad

Good

package http_client
package User 
package services
package httpclient 
package user 
package service

2.  文件命名

尽量采取有意义的文件名,简短,有意义.

  • 使用下划线分隔各个单词(由于业务代码名称一般偏长, 更清晰的表达, 投票后的结果) 全部小写单词, 不用驼峰式, 
  • 其中测试文件以test.go结尾,除测试文件外,命名不出现

社区惯例是 不使用下划线(除非是测试文件)

但是业务项目,命名往往比较复杂、冗长,不用下划线的话,对代码阅读是个灾难

所以建议我们使用下划线进行分割, 这部分需要最终确认

3. 方法命名(typespec - request, response)

MixedCaps

方法名函数名变量常量等标识符的命名,我们遵循Go社区关于使用 MixedCaps (可导出-大驼峰, 不可导出-小驼峰) 的约定。

有一个例外,为了对相关的测试用例进行分组,函数名可能包含下划线,如:TestMyFunction_WhatIsBeingTested.

关于缩写 名称中的缩写词或首字母缩写词(例如"URL"或"NATO")具有一致的大小写。

例如,"URL"应显示为"URL"或"url"(如在"urlPony"或"URLPony"中),而不应显示为"Url"。

对于包含多个缩写的标识符,也应当保遵守这个规则,例如:"xmlHTTPRequest"或"XMLHTTPRequest"。

https://github.com/golang/go/wiki/CodeReviewComments#mixed-caps

(This applies even when it breaks conventions in other languages. For example an unexported constant is maxLength not MaxLength or MAX_LENGTH.)

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

Bad

Good

BlackImportRequest
CrontabChangeType()
Count()
ImportBlackRequest
ChangeCrontabType()
CountProduct()
AddProductRequest
GetProductListRequest
GetProduct()
GetProductBySaleProductId()

4. 结构体命名

  • 采用驼峰命名法,首字母根据访问控制大写或者小写
  • struct 申明和初始化格式采用多行,例如下面:
  • 结构体名应该是名词或名词短语,如Account,Book,避免使用Manager这样的

// 多行申明

type User struct{

    Name  string

    Email     string

}

// 多行初始化

u := User{

    Name: "wanglu",

    Email:    "wanglu@xiaozhu.com",

}

5. 接口命名

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

type Reader interface {

    Read(p []byte) (n int, err error)

}


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

type WriteFlusher interface {

    Write([]byte) (int, error)

    Flush() error

}


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

type Car interface {

    Start()

    Stop()

    Drive()

}

6. 变量命名

和结构体类似,变量名称一般遵循驼峰法,首字母根据访问控制原则大写或者小写,但遇到特有名词时,需要遵循以下规则:

  • 作用域越小,命名应该越简短。如在for循环内部用i表示index
  • 变量名不应该包含类型,变量的名称应描述其内容,而不是内容的类型
  • 如果变量为私有,且特有名词为首个单词,则使用小写,如 apiClient
  • 其它情况都应当使用该名词原有的写法,如 APIClient、repoID、UserID
  • 错误示例:UrlArray,应该写成 urlArray 或者 URLArray
  • 若变量类型为 bool 类型,则名称应以 Has, Is, Can 或 Allow 开头

var isExist bool

var hasConflict bool

var canManage bool

var allowGitHook bool

7. 常量命名

常量均需使用MixedCaps命名方式,并使用下划线分词:

const ProductVersion = "1.0"

如果是枚举类型的常量,需要先创建相应类型

type Scheme string

const (

    HTTP  Scheme = "http"

    HTTPS Scheme = "https"

)

8. 关键字

下面的列表显示了Go中的保留字。这些保留字不能用作常量或变量或任何其他标识符名称

包管理(2个):

    import  package

程序实体声明与定义(8个):

    chan    const   func    interface   map struct  type    var

程序流程控制(15个):

    break   case    continue    default defer   else    fallthrough

    for     go      goto        if      range   return  select      switchtps

二. 注释

注释虽然写起来很痛苦, 但对保证代码可读性至关重要. 下面的规则描述了如何注释以及在哪儿注释.

当然也要记住: 注释固然很重要, 但最好的代码应当本身就是文档.

有意义的类型名和变量名, 要远胜过要用注释解释的含糊不清的名字.

你写的注释是给代码读者看的, 也就是下一个需要理解你的代码的人. 所以慷慨些吧, 下一个读者可能就是你!

  • 必须 - 脚本都需要添加注释: 表明脚本的作用, 参数的含义, 预期的数据, 输入参数的含义
  • 建议 - 方法上建议添加注释, 标注方法的作用
  • 建议 - 有步骤执行顺序的代码, 各个顺序关键节点, 建议添加注释
  • 建议 - if, switch等分支判断的时候, 建议添加注释便于理解

1. 注释风格

  • /**/ 的块注释和 // 的单行注释两种注释风格, 在我们的项目中为了风格的统一,全部使用单行注释,注释的质量决定了生成的文档的质量。

2. 包注释

// 请填写文件描述

package ${GO_PACKAGE_NAME}

3. 结构体(接口)注释

// User   用户对象,定义了用户的基础信息

type User struct{

    Username  string // 用户名

    Email     string // 邮箱

}

4. 函数(方法)注释

// 函数的详细描述

// 函数的详细描述2

func (r *RiskCase) GetRiskCase(ctx context.Context)

5. 代码逻辑注释

// 代码块的执行解释

// 执行解释2

if   userAge < 18 {

}

三. 其他规范

1 . Error命名规范

使用MixedCaps或者mixedCaps的形式, 以Err开头

Bad

Good

var ErrorSomething = errors.New("something went wrong") 
var SomethingErr = errors.New("something went wrong") 
var SomethingError = errors.New("something went wrong")
// Package level exported error. 
var ErrSomething = errors.New("something went wrong")

2 . import 规范

// 单行引入

import  "fmt"

// 多包引入,每包独占一行

// 使用绝对路径,避免相对路径如 ../encoding/json

import (

     "strings"

     "fmt"

)

  • 如果程序包名称与导入路径的最后一个元素不匹配,则必须使用导入别名。

import (

  "net/http"

  client "example.com/client-go"

  trace "example.com/trace/v2"

)

  • 在所有其他情况下,除非导入之间有直接冲突,否则应避免导入别名。

Bad

Good

import ( 
    "fmt" 
    "os" 

    nettrace "golang.net/x/trace" 
)
import ( 
    "fmt"
    "os"
    "runtime/trace"
   
    nettrace "golang.net/x/trace" 
)
  • 标准库包,程序内部包,第三方包

import (

    "fmt"

    "strings"

    "selfproject/model"

    "selfprojec/application"

    "github.com/gin-gonic/gin"

    "github.com/go-sql-driver/mysql"

)

四. 编程建议

1. Get方法

Go不提供对Get方法和Set方法的自动支持。你自己提供Get方法和Set方法是没有错的,通常这么做是合适的。但是,在Get方法的名字中加上Get,是不符合语言习惯的,并且也没有必要。如果你有一个域叫做owner(小写,不被导出),则Get方法应该叫做Owner(大写,被导出),而不是GetOwner。对于要导出的,使用大写名字,提供了区别域和方法的钩子。Set方法,如果需要,则可以叫做SetOwner。这些名字在实际中都很好读:

owner := obj.Owner()

if owner != user {

    obj.SetOwner(user)

}

2. New方法

当包里只有一个对象时,比如gin包,New方法可以直接写成gin.New(),而不是gin.NewEngine()

3. 错误处理

  • 错误处理的原则就是不能丢弃任何有返回err的调用,不要使用 _ 丢弃,必须全部处理。接收到错误,要么返回err,或者使用log记录下来
  • 尽早return:一旦有错误发生,马上返回
  • 尽量不要使用panic,除非你知道你在做什么
  • 错误描述如果是英文必须为小写,不需要标点结尾
  • 采用独立的错误流进行处理

4. 空行建议

空行有什么了不起,值得上升到规范的高度吗?是的,空行是一个小小的细节,但又不仅是一个细节问题

计算机并不需要空行, 都可以根据我们写的代码准确的执行

但由于人类大脑天性所致, 大脑会认为同时发生的任何事物都存在某种联系, 并且将事物按某种逻辑模式组织起来

密密麻麻的一大坨代码会給人整体的观感带来无以名状的压力

一个简单的原则就是将概念相关的代码放在一起:相关性越强,彼此之间的距离应该越短

5. 一致性

本文中概述的一些标准都是客观性的评估,是根据场景、上下文、或者主观性的判断;

但是最重要的是,保持一致.

一致性的代码更容易维护、是更合理的、需要更少的学习成本、并且随着新的约定出现或者出现错误后更容易迁移、更新、修复 bug

相反,在一个代码库中包含多个完全不同或冲突的代码风格会导致维护成本开销、不确定性和认知偏差。所有这些都会直接导致速度降低、代码审查痛苦、而且增加 bug 数量。

将这些标准应用于代码库时,建议在 package(或更大)级别进行更改,子包级别的应用程序通过将多个样式引入到同一代码中,违反了上述关注点。

相似的声明放在一组

Go 语言支持将相似的声明放在一个组内。

Bad

Good

import "a"
import "b"
import (
  "a"
  "b"
)

这同样适用于常量、变量和类型声明:

Bad

Good

const a = 1
const b = 2

var a = 1
var b = 2

type Area float64
type Volume float64
const (
  a = 1
  b = 2
)

var (
  a = 1
  b = 2
)

type (
  Area float64
  Volume float64
)

仅将相关的声明放在一组。不要将不相关的声明放在一组。

Bad

Good

type Operation int

const (
  Add Operation = iota + 1
  Subtract
  Multiply
  EnvVar = "MY_ENV"
)
type Operation int

const (
  Add Operation = iota + 1
  Subtract
  Multiply
)

const EnvVar = "MY_ENV"

分组使用的位置没有限制,例如:你可以在函数内部使用它们:

Bad

Good

func f() string {
  var red = color.New(0xff0000)
  var green = color.New(0x00ff00)
  var blue = color.New(0x0000ff)

  ...
}
func f() string {
  var (
    red   = color.New(0xff0000)
    green = color.New(0x00ff00)
    blue  = color.New(0x0000ff)
  )

  ...
}

6. 减少嵌套

代码应通过尽可能先处理错误情况/特殊情况并尽早返回或继续循环来减少嵌套。减少嵌套多个级别的代码的代码量。

Bad

Good

for _, v := range data {
  if v.F1 == 1 {
    v = process(v)
    if err := v.Call(); err == nil {
      v.Send()
    } else {
      return err
    }
  } else {
    log.Printf("Invalid v: %v", v)
  }
}
for _, v := range data {
  if v.F1 != 1 {
    log.Printf("Invalid v: %v", v)
    continue
  }

  v = process(v)
  if err := v.Call(); err != nil {
    return err
  }
  v.Send()
}

可以参考: if-else过多代码优化 -> 策略模式应用

     不必要的 else

如果在 if 的两个分支中都设置了变量,则可以将其替换为单个 if。

Bad

Good

var a int
if b {
  a = 100
} else {
  a = 10
}
a := 10
if b {
  a = 100
}

7. 本地变量声明

如果将变量明确设置为某个值,则应使用短变量声明形式 (:=)。

Bad

Good

var s = "foo"
s := "foo"

但是,在某些情况下,var 使用关键字时默认值会更清晰。例如,声明空切片。

Bad

Good

func f(list []int) {
  filtered := []int{}
  for _, v := range list {
    if v > 10 {
      filtered = append(filtered, v)
    }
  }
}
func f(list []int) {
  var filtered []int
  for _, v := range list {
    if v > 10 {
      filtered = append(filtered, v)
    }
  }
}

五. 其他编码规范参考

我们都是站在巨人的肩膀上学习和成长, 编码规范(Go)参考了:

Uber Go Style Guide: https://github.com/xxjwxc/uber_go_guide_cn

Golang官方编码规范: https://github.com/golang/go/wiki/CodeReviewComments

[代码规范]Go语言编码规范指导: https://zhuanlan.zhihu.com/p/63250689

热门文章

暂无图片
编程学习 ·

Java输出数组的内容

Java输出数组的内容_一万个小时-CSDN博客_java打印数组内容1. 输出内容最常见的方式// List<String>类型的列表List<String> list new ArrayList<String>();list.add("First");list.add("Second");list.add("Third");list.ad…
暂无图片
编程学习 ·

母螳螂的“魅惑之术”

在它们对大蝗虫发起进攻的时候&#xff0c;我认认真真地观察了一次&#xff0c;因为它们突然像触电一样浑身痉挛起来&#xff0c;警觉地面对限前这个大家伙&#xff0c;然后放下自己优雅的身段和祈祷的双手&#xff0c;摆出了一个可怕的姿势。我被眼前的一幕吓到了&#xff0c;…
暂无图片
编程学习 ·

疯狂填词 mad_libs 第9章9.9.2

#win7 python3.7.0 import os,reos.chdir(d:\documents\program_language) file1open(.\疯狂填词_d9z9d2_r.txt) file2open(.\疯狂填词_d9z9d2_w.txt,w) words[ADJECTIVE,NOUN,VERB,NOUN] str1file1.read()#方法1 for word in words :word_replaceinput(fEnter a {word} :)str1…
暂无图片
编程学习 ·

HBASE 高可用

为了保证HBASE是高可用的,所依赖的HDFS和zookeeper也要是高可用的. 通过参数hbase.rootdir指定了连接到Hadoop的地址,mycluster表示为Hadoop的集群. HBASE本身的高可用很简单,只要在一个健康的集群其他节点通过命令 hbase-daemon.sh start master启动一个Hmaster进程,这个Hmast…
暂无图片
编程学习 ·

js事件操作语法

一、事件的绑定语法 语法形式1 事件监听 标签对象.addEventListener(click,function(){}); 语法形式2 on语法绑定 标签对象.onclick function(){} on语法是通过 等于赋值绑定的事件处理函数 , 等于赋值本质上执行的是覆盖赋值,后赋值的数据会覆盖之前存储的数据,也就是on…
暂无图片
编程学习 ·

Photoshop插件--晕影动态--选区--脚本开发--PS插件

文章目录1.插件界面2.关键代码2.1 选区2.2 动态晕影3.作者寄语PS是一款栅格图像编辑软件&#xff0c;具有许多强大的功能&#xff0c;本文演示如何通过脚本实现晕影动态和选区相关功能&#xff0c;展示从互联网收集而来的一个小插件&#xff0c;供大家学习交流&#xff0c;请勿…
暂无图片
编程学习 ·

vs LNK1104 无法打开文件“xxx.obj”

写在前面&#xff1a; 向大家推荐两本新书&#xff0c;《深度学习计算机视觉实战》和《学习OpenCV4&#xff1a;基于Python的算法实战》。 《深度学习计算机视觉实战》讲了计算机视觉理论基础&#xff0c;讲了案例项目&#xff0c;讲了模型部署&#xff0c;这些项目学会之后可以…
暂无图片
编程学习 ·

工业元宇宙的定义与实施路线图

工业元宇宙的定义与实施路线图 李正海 1 工业元宇宙 给大家做一个关于工业元宇宙的定义。对于工业&#xff0c;从设计的角度来讲&#xff0c;现在的设计人员已经做到了普遍的三维设计&#xff0c;但是进入元宇宙时代&#xff0c;就不仅仅只是三维设计了&#xff0c;我们的目…
暂无图片
编程学习 ·

【leectode 2022.1.15】完成一半题目

有 N 位扣友参加了微软与力扣举办了「以扣会友」线下活动。主办方提供了 2*N 道题目&#xff0c;整型数组 questions 中每个数字对应了每道题目所涉及的知识点类型。 若每位扣友选择不同的一题&#xff0c;请返回被选的 N 道题目至少包含多少种知识点类型。 示例 1&#xff1a…
暂无图片
编程学习 ·

js 面试题总结

一、js原型与原型链 1. prototype 每个函数都有一个prototype属性&#xff0c;被称为显示原型 2._ _proto_ _ 每个实例对象都会有_ _proto_ _属性,其被称为隐式原型 每一个实例对象的隐式原型_ _proto_ _属性指向自身构造函数的显式原型prototype 3. constructor 每个prot…
暂无图片
编程学习 ·

java练习代码

打印自定义行数的空心菱形练习代码如下 import java.util.Scanner; public class daYinLengXing{public static void main(String[] args) {System.out.println("请输入行数");Scanner myScanner new Scanner(System.in);int g myScanner.nextInt();int num g%2;//…
暂无图片
编程学习 ·

RocketMQ-什么是死信队列?怎么解决

目录 什么是死信队列 死信队列的特征 死信消息的处理 什么是死信队列 当一条消息初次消费失败&#xff0c;消息队列会自动进行消费重试&#xff1b;达到最大重试次数后&#xff0c;若消费依然失败&#xff0c;则表明消费者在正常情况下无法正确地消费该消息&#xff0c;此时…
暂无图片
编程学习 ·

项目 cg day04

第4章 lua、Canal实现广告缓存 学习目标 Lua介绍 Lua语法 输出、变量定义、数据类型、流程控制(if..)、循环操作、函数、表(数组)、模块OpenResty介绍(理解配置) 封装了Nginx&#xff0c;并且提供了Lua扩展&#xff0c;大大提升了Nginx对并发处理的能&#xff0c;10K-1000K Lu…
暂无图片
编程学习 ·

输出三角形

#include <stdio.h> int main() { int i,j; for(i0;i<5;i) { for(j0;j<i;j) { printf("*"); } printf("\n"); } }
暂无图片
编程学习 ·

stm32的BOOTLOADER学习1

序言 最近计划学习stm32的BOOTLOADER学习,把学习过程记录下来 因为现在网上STM32C8T6还是比较贵的,根据我的需求flash空间小一些也可以,所以我决定使用stm32c6t6.这个芯片的空间是32kb的。 #熟悉芯片内部的空间地址 1、flash ROM&#xff1a; 大小32KB&#xff0c;范围&#xf…
暂无图片
编程学习 ·

通过awk和shell来限制IP多次访问之学不会你打死我

学不会你打死我 今天我们用shell脚本&#xff0c;awk工具来分析日志来判断是否存在扫描器来进行破解网站密码——限制访问次数过多的IP地址&#xff0c;通过Iptables来进行限制。代码在末尾 首先我们要先查看日志的格式&#xff0c;分析出我们需要筛选的内容&#xff0c;日志…
暂无图片
编程学习 ·

Python - 如何像程序员一样思考

在为计算机编写程序之前&#xff0c;您必须学会如何像程序员一样思考。学习像程序员一样思考对任何学生都很有价值。以下步骤可帮助任何人学习编码并了解计算机科学的价值——即使他们不打算成为计算机科学家。 顾名思义&#xff0c;Python经常被想要学习编程的人用作第一语言…
暂无图片
编程学习 ·

蓝桥杯python-数字三角形

问题描述 虽然我前后用了三种做法&#xff0c;但是我发现只有“优化思路_1”可以通过蓝桥杯官网中的测评&#xff0c;但是如果用c/c的话&#xff0c;每个都通得过&#xff0c;足以可见python的效率之低&#xff08;但耐不住人家好用啊&#xff08;哭笑&#xff09;&#xff09…