Go

概述

参考:

Go 是一种开源编程语言,可以轻松构建 simple(简单)reliable(可靠)efficient(高效) 的软件。

学习资料

菜鸟教程,Go(快速上手尝试,简单直接)

Go 语言之旅(官方在线教程)

Go 官方 FAQ

Go by Example

GitHub 组织,golang-china(Go 语言中国)

公众号-HelloGitHub,适合 Go 新手学习的开源项目

地鼠文档系列文章

电子书

视频

https://github.com/avelino/awesome-go

其他

Hello World

代码:hello_world.go

package main

import "fmt"

func main() {
 fmt.Println("Hello World")
}

运行

# go run hello_world.go
Hello World

Go 范儿

参考:

gopher 原意地鼠,在 go 语言的世界里解释为地道的 go 程序员。在其他语言的世界里也有 PHPer,Pythonic 的说法,反而 Java 是个例外。虽然也有 Javaer 之类的说法,但似乎并不被认可。而地道或者说道地,说的是 gopher 写的代码无不透露出 go 的独特气息,比如项目结构、命名方式、代码格式、编码风格、构建方式等等。用 gopher 的话说,用 go 编写代码就像是在画一幅中国山水画,成品美不胜收,心旷神怡。

环境变量

gopher 第一条:把东西放对地方。

go 程序的运行,需要依赖于两个基础的环境变量,GOROOT 与 GOPATH。环境变量几乎在各类编程语言中都存在,比如 java 的 JAVA_HOME,其实也就是编译器及相关工具或标准库所在目录。

但 go 除了 GOROOT 之外,还增加了 GOPATH,它指的是 go 程序依赖的第三方库或自有库所在目录,以指示编译器从这些地方找到依赖。GOPATH 支持多个目录,通常一个目录就是一个项目,并且 GOPATH 目录按约定由 src, pkg, bin 三个目录组成。gopher 们的做法是定义 Global GOPATH, Project GOPATH,而更大的项目还会定义 Module GOPATH。当使用 go get 下载依赖时,会选择 GOPATH 环境变量中的第一个目录存放依赖包。

变量含义说明
GOROOTgo 运行环境根目录通常指 go sdk 安装目录,包含编译器、官方工具及标准库
GOPATH工作环境目录列表通常指第三方库

项目结构

参考:

gopher 第二条:按东西放在约定的地方。

不论采用何种编程语言,良好的项目组织结构都至关重要,因为这将直接影响项目内部依赖的复杂程度以及项目对外提供 API 等服务的灵活性等。最好在项目初期便制定好项目结构约定,甚至可以为其开发脚手架之类的工具来生成项目模板,让开发者尽量按照统一的规范参与项目。

一个常见的 Go 应用项目布局,通常有如下结构:

- my-go-project
 - cmd
 - pkg
 - internal
 - go.mod && go.sum
 - Makefile

其中 cmd 与 pkg 目录是最常见的。一个项目如果具有多个功能,比如 kubernetes 项目,具有多个组件,所有组件的入口都在 cmd 目录中,并以组件名命名其下的目录名。而每个组件所调用的各种功能,通常都是放在 pkg 目录下,一个功能一个目录,通常来说,pkg 目录是一个项目中代码量最多的地方。

cmd

cmd 包是项目的主干,是编译构建的入口,main() 所在文件通常放置在此处。一个典型的 cmd 包的目录结构如下所示:

- cmd
   - app1
     - main.go
   - app2
     - main.go

从上述例子可以看出,cmd 下可以允许挂载多个需要编译的应用,只需要在不同的包下编写 main 文件即可。需要注意的是,cmd 中的代码应该尽量「保持简洁」,main() 函数中可能仅仅是参数初始化、配置加载、服务启动的操作。

pkg

pkg 中存放的是可供项目内部/外部所使用的公共性代码,例如:用来连接第三方服务的 client 代码等。也有部分项目将该包命名为 lib,例如:consul 项目 ,所表示的含义其实相同。

internal

internal 包主要用处在于提供一个项目级别的代码保护方式,存放在其中的代码仅供项目内部使用。具体使用的规则是:…/a/b/c/internal/d/e/f 仅仅可以被…/a/b/c 下的目录导入,…/a/b/g 则不允许。internal 是 Go 1.4 版本中引入的特性,更多信息可以参考这里

在 internal 内部可以继续通过命名对目录的共享范围做区分,例如 internal/myapp 表示该目录下的代码是供 myapp 应用使用的;internal/pkg 表示该目录下的代码是可以供项目内多个应用使用的。

go.mod && go.sum

go.mod 与 go.sum 是采用 Go Module 进行依赖管理所生成的配置文件。Go Modules 是 Go 1.11 版本中引入的版本管理功能,目前已经是 go 依赖管理的主流方式,所以此处不再讨论 vendor,dep 等依赖管理方式所生成的目录。

Makefile

Makefile 文件通常存放项目的编译部署脚本。Go 的编译命令虽然简单,但总是手写命令还是效率低下,因此使用 Makefile 写编译部署脚本是工程实践中常见的方式。

服务端应用程序目录

api

项目对外提供和依赖的 API 文件。比如:OpenAPI/Swagger specs, JSON schema 文件, protocol 定义文件等。 比如,Kubernetes 项目的 api 目录结构如下:

api
├── api-rules
    └── xxx.plist
├── openapi-spec
    └── swagger.json

因此,在 go 中用的比较多的 gRPC proto 文件,也比较适合放在 api 目录下。

api
└── protobuf-spec
    └── test
        ├── test.pb.go
        └── test.proto

Web 应用程序目录

web

Web 应用程序特定的组件,比如 静态资源、服务器端模板和单页应用

通用应用程序目录

build

打包和持续集成所需的文件。

  • build/ci:存放持续集成的配置和脚本,如果持续集成平台对配置文件有路径要求,则可将其 link 到指定位置。
  • build/package:存放 AMI、Docker、系统包(deb、rpm、pkg)的配置和脚本等。

例子:

configs

配置文件模板或默认配置。

deployments

IaaS,PaaS,系统和容器编排部署配置和模板(docker-compose,kubernetes/helm,mesos,terraform,bosh)。请注意,在某些存储库中(尤其是使用 kubernetes 部署的应用程序),该目录的名字是 /deploy。

init

系统初始化(systemd、upstart、sysv)和进程管理(runit、supervisord)配置。

scripts

用于执行各种构建,安装,分析等操作的脚本。

这些脚本使根级别的 Makefile 变得更小更简单,例如:https://github.com/hashicorp/terraform/blob/master/Makefile

test

外部测试应用程序和测试数据。随时根据需要构建 /test 目录。对于较大的项目,有一个数据子目录更好一些。例如,如果需要 Go 忽略目录中的内容,则可以使用 /test/data 或 /test/testdata 这样的目录名字。请注意,Go 还将忽略以“.”或“_”开头的目录或文件,因此可以更具灵活性的来命名测试数据目录。

其他目录

assets

项目中使用的其他资源(图像、logo 等)。

docs

设计和用户文档(除了 godoc 生成的文档)。

examples

应用程序或公共库的示例程序。

githooks

Git 钩子。

third_party

外部辅助工具,fork 的代码和其他第三方工具(例如:Swagger UI)。

tools

此项目的支持工具。请注意,这些工具可以从 /pkg 和 /internal 目录导入代码。 例子:

website

如果不使用 Github pages,则在这里放置项目的网站数据。 例子:

不应该包含的目录

项目中不应该包含 src 目录

在 Java 项目中,会常见 src 目录,但在 Go 项目中,并不推荐这么做。在 Go 1.11 之前,Go 项目是放在 $GOPATH/src 下,如果项目中再包含 src 目录,那么代码结构就会类似: $GOPATH/src/my-project/src/app.go的结构,容易造成混淆。在 Go 引入 modules 之后,项目可以不用写在 $GOPATH 下,但是依然不推荐项目中采用src 来命名目录。

命名规范

gopher 第三条:把名字起得 go 一点。

go 语言的命名与其他语言最大的不同在于首字母的大小写。

  • 大写代表公开(导出,可以在其他包内访问)
  • 小写代表私有(不导出,只能在包内访问)。

除此之外,与其他语言并无二致,比如不能以数字开头。而由于关键字、保留字的减少,因而减少了一些命名上的忌讳。更为突出的是,go 语言有一些建议性的命名规范,这也是 gophers 的圣经,理应严格遵守。

约定范围说明示例
驼峰命名法全局统一使用驼峰命名法var isLive = false
大小写一致缩写短语,惯用词如 HTML,CSS, HTTP 等htmlEscape,HTMLEscape
简短命名法局部变量方法内、循环等使用的局部变量可以使用简短命名比如 for 循环中的 i,buf 代表 buffer 等
参数命名法函数参数、返回值、方法接收者如果参数类型能说明含义,则参数名可以简短,否则应该采用有文档说明能力的命名比如 d Duration,t Time
通用命名法作用域越大或者使用的地方离声明的地方太远,则应采用清晰有意义的命名-
导出命名法导出变量、函数、结构等包名与导出名意义不要重复,同时包的命名要与导出的内容相关,不要使用宽泛的名字,如 common,utilbytes.Buffer 比 bytes.ByteBuffer 要好
文件命名go 文件,单元测试文件go 文件名尽量以一个单词来命名,多个单词使用下线划分隔,单元测试文件以对应 go 文件名加_test 结尾proto_test
包命名包的一级名称应是顶级域名,二级名称则应是项目名称,项目名称单词间以-分隔github.com/mysql

代码格式

gopher 第四条:按统一的格式来。

在多人协作团队中,统一的代码格式化模板是第一要义。在 Java 语言中,检验新人经验的一大法宝就是他有没有主动索要代码模板。而在 go 语言中,则没有这个必要了。因为 go 已经有默认的代码格式化工具了,而且代码格式化在 go 语言中是强制规范。所以这使得所有 go 程序员写出来的代码格式都是一样的。

go 默认的代码格式化工具是 gofmt。另外还有一个增强工具 goimport,在 gofmt 的基础上增加了自动删除和引入依赖包。而行长则以不超过 80 个字符为佳,超过请主动以多行展示。

编码风格

gopher 第五条:请学会约定

约定说明
import按标准库、内部包、第三方包的顺序导入包只引一个包时使用单行模式,否则使用多行模式
变量声明如果连续声明多个变量,应放在一起参见例子
错误处理不要忽略每一个 error,即使只是打一行日志go 的 error 处理方式与 C 同出一辙,通过返回值来标明错误或异常,引来的争议也很多,甚至官方已经开始酝酿在 go2 解决这个问题
长语句打印使用格式化方式打印-
注释规范变量、方法、结构等的注释直接加上声明前,并且不要加空行。废弃方法加 Deprecated:即可其中的第一行注释会被 godoc 识别为简短介绍,第二行开始则被认为是注释详情。注释对 godoc 的生成至关重要,因此关于注释会有一些技巧,我将在后面用专门的章节探讨

多变量声明

var (
    name string
    age int
)

注释规范

// Add 两数相加
// 两个整数相加,并返回和。
func Add(n1, n2 int)int{
    return n1 + n2
}

依赖管理

gopher 第六条:使用依赖管理工具管理自有依赖与第三方依赖

一个语言的生态是否完善甚至是否强大,除了 github 上面的开源项目数量之外,还有一大特征就是是否有优秀的依赖管理工具。依赖管理工具在业界已经是无处不在,yum、maven、gradle、pip、npm、cargo 这些工具的大名如雷贯耳。那么 go 有什么呢?

早期 go 的依赖是混乱的,因为没有一个工具能得到普遍认可,而官方又迟迟不出来解决问题。历数存在的工具包括 godep、glide、govender 等等。甚至早期还需要使用 GOPATH 来管理依赖,即项目的所有依赖都通过 go get 下载到指定的 GOPATH 中去。当然这种方案还可以撑大多数时间,但随着时间的流逝,随着开发人员的变动,这种管理依赖的弊端就慢慢显现出来。其实这些老路早期的 java 也走过,曾几何时,每个 java 项目里面都会有一个叫 lib 或 libs 的目录,这里放的就是当前项目依赖的包。当 GO 采用 GOPATH 来管理依赖时,开发人员只能被倒逼着用 java 的方式在源码库中自行管理依赖。这样相当于给依赖包做了隔离,同时又具备了版本管理(因为放在源码库)。

后来在 go1.5 的时候,官方引入了 vender 的概念,其实这也没改变多少,只是官方让大家存放依赖包的目录名称不要乱起了,统一叫 vender 吧。这个方案我觉得比依赖 GOPATH 还糟糕,因为 vendor 目录脱离了版本管理,导致更换依赖包版本很困难,在当前项目对依赖包的版本更新可能会影响其他项目的使用(如果新版本的依赖包有较大变动的话),同时如何将依赖包放到 vendor 下呢?等等。当然官方做出的这些变动可能是想像 maven 那样,推动社区来完成这件事,因而直接推动了上文提到的基于 vendor 的依赖管理工具的诞生。直至后来官方默认的社区做出来 dep,这下安静了,尽管刚开始时也不怎么好用,但有总比没有好。

go1.11 在 vgo 的基础上,官方推出了 go module。在发布前,官方与社区的大神们还为此开吵,认为官方太不厚道且独断专行。完全忽视 dep 社区的存在,无视 dep 在 go 语言中的地位与贡献。喜欢八卦的朋友们,可搜索《关于 Go Module 的争吵》一览大神是怎么吵架的,也可从中学习他们的思想。

相对于 java 的依赖管理工具 maven 或 gradle 来说,gradle 是 maven 的升级版,同时带来了 DSL 与元编程的特性,这无疑使得 gradle 异常地强大。但 gradle.io 在国内的可达情况也不尽如人意,好就好在其与 maven 仓库标准的兼容,F使得从 maven 转到 gradle 几乎没有额外的成本及阻力。

扯了这么多,依赖管理对于一门语言是必不可少的。c 有 cmake,java 有 maven、gradle,rust 有 cargo,那么 go 的 dep 或者 module 就用起来吧,看完大神吵架之后,喜欢哪个就选哪个。是不可能产生一个能满足所有人要求的依赖管理工具的,就连号称最牛逼的 cargo 也不例外。在一般的项目中,能用到的依赖管理功能也就那常用的几个而已,对大多数项目来说,适用好用就行。

构建方式

gopher 第七条:按需构建

构建的目标是让代码成为可运行程序。构建的过程应该是低成本并且让人愉悦的,显然 C 在这一方面让人抓狂,而 go 确实做得不错。并且能在任何平台下编译出另外一个平台的可执行程序。不管你的 go 程序是 CLI、GUI、WEB 或者其他形式的网络通讯程序,在 go 的世界里都只需要一个命令构建成可执行程序(依赖也一并被打包),即可在目标系统上运行。在这一点上,java 是望尘莫及了。 下面是用来构建 go 程序常用的参数,其他参数可通过 go help environment 命令查看。

参数说明
CGO_ENABLED0 or 1是否支持 cgo 命令,如果 go 代码中有 c 代码,需要设置为 1
GOOSdarwin, freebsd, linux, windows可执行程序运行的目标操作系统
GOARCH386, amd64, arm可执行程序运行的目标操作系统架构
# Linux下编译Mac 64位可执行程序
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build main.go
# Linux下编译windows 64位可执行程序
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build main.go
# 如果想减少二进制程序的大小,可以加上-ldflags "-s -w",但同时会丢掉调试信息,即不能用gdb调试了。
# 如果想更进一步减少程序大小,可以使用加壳工具,比如upx

最后修改 February 15, 2026: AI Prompt (8b1f8f10)