入门
开发环境
推荐使用module组织golang代码,使用vscode编码,具体可参考官方文档
安装
- Linux
- Mac
下载二进制包
sudo wget https://go.dev/dl/go{最新版本号}.linux-amd64.tar.gz -P /usr/local/src
解压到库目录
cd /usr/local/src && sudo tar -zxvf go{最新版本号}.linux-amd64.tar.gz -C /usr/local
添加环境变量
根据你使用的shell环境添加,如果是
bash
则添加到.profile
或者.bashrc
,如果是zsh
则添加到.zshrc
export PATH=/usr/local/go/bin:$PATH
加载环境变量
source ~/.zshrc
升级golang版本
sudo rm -rf /usr/local/go
然后用上面的方法安装最新版本即可
查看版本
go version
设置仓库代理
除了以下方法,你也可以通过socket进行本地代理
# 设置 Go Proxy 代理
go env -w GOPROXY=https://goproxy.io,direct
编译
通过
go help
可以查看所有go支持的命令
示例代码
初始化一个golang模块
$ cd ~/Code
$ mkdir go-guide && cd $_
$ go mod init github.com/pincman/go-guide
$ touch hello.go && vi hello.go
在hello.go
中写入以下代码
package main
import "fmt"
func main() {
fmt.Println("go语言开发指南.")
}
基本命令
go build
用于编译go代码并在当前目录产出二进制可执行文件go run
用于直接运行go源代码而不生成二进制文件go install
用于编译并生成二进制文件放入指定目录中
运行安装命令
go install github.com/pincman/go-guide
或者通过当前目录安装
go install .
或者
go install
以上命令会编译出一个go-guide
二进制可执行文件,此文件的文件名根据go.mod
中指定的模块名生成,此文件的位置由GOPATH
和GOPATH
控制,通过以下顺序来确定go-guide
的位置
- 如果设置了
GOBIN
,则直接放入GOBIN
路径下 - 如果设置了
GOPATH
,则放入GOPATH
列表的第一个路径下的bin
目录下 - 如果都没有设置,则放入
GOPATH
的默认路径$HOME/go
下的bin
目录下
配置以上环境变量可以通过~/.zhsrc
,~/.bashrc
等方式进行本地化配置,也可以使用以下命令直接配置(推荐)
go env -w GOBIN=/somewhere/else/bin
查看所有golang的环境变量
go env
取消一个环境变量的设置
go env -u GOBIN
在shell
下尝试执行一下go-guide
命令
如果只是临时执行一下编译后的文件只需要在shell中使用
export PATH=$PATH:$(dirname $(go list -f '{{.Target}}' .))
设置一下环境变量即可
go-guide
# 得到如下输出
go语言开发指南.
使用go build
或者go build.
命令会在当前目录下直接构建出一个go-guide
的二进制文件,可以用来测试编译是否通过,并且此二进制文件就是用于部署的文件
构建之后通过./go-guide
命令即可执行并且得到的结果与go install
之后通过环境变量执行的结果是一样的
如果使用go run .
命令可直接运行源代码而不生成编译文件,结果与go install
和go build
后运行可执行文件一样
vscode
使用vscode打开hello.go
文件后会自动下载一些开发工具到GOBIN
中,更改一下format工具,打开设置面板,搜索format
,把go
选项的Format Tool
改成goformat
打开一个hello.go
后按F5
创建调试文件后即可进行调试
代码组织
golang项目的层次为 repository(仓库)->module(模块)->package(包)->source file(源代码文件)
模块
模块是一个独立的库,它的作用与nodejs的npm和php的composer类似,约定俗成的命名规则是{供应商地址}:{包名称},例如: github.com/pincman/go-guide
模块通过init
初始化,模块可以是一堆包的集合也可以是一个主应用程序,也就是说任何go应用本身就是一个模块
示例代码
注意: 如果使用vscode,则需要在不同的窗口打开下面两个模块,否则会报错,这个问题在下面的"多工作空间"部分解决
清空刚才的go-guide
目录,并创建一个子模块
rm -rf goguide/* && mkdir goguide/greetings && cd $_
go mod init github.com/pincman/greetings
在greetings
目录下随意创建一个go文件,比如greetings.go
,然后放入以下代码
package greetings
import "fmt"
// Hello returns a greeting for the named person.
func Hello(name string) string {
// Return a greeting that embeds the name in a message.
message := fmt.Sprintf("你好, %v.,欢迎学习go开发指南!", name)
return message
}
然后创建一个主模块
cd .. && mkdir hello && cd $_
go mod init github.com/pincman/hello
在hello
目录下创建一个go文件,比如hello.go
,在代码中导入并调用greetings
模块的greetings
包的Hello
方法
package main
import (
"fmt"
"github.com/pincman/greetings"
)
func main() {
message := greetings.Hello("pincman")
fmt.Println(message)
}
模块映射
在导入第三方远程模块或者我们自己发布到远程的模块时,运行go get
,go会自动根据模块的地址下载这些模块,比如go get github.com/pincman/greetings/v1
,但是我们的子模块还在本地并没有发布,这时候要导入这个模块,需要做一下本地的模块映射,否则go将不知道在哪里找到它
有两种方法可以添加模块映射,效果是一样的:
直接使用命令
go mod edit -replace github.com/pincman/greetings=../greetings
或者手动编辑go.mod
文件,在尾部追加(上面的命令会自动在go.mod
中生成以下内容)
replace github.com/pincman/greetings => ../greetings
添加映射后可以使用tidy
命令同步(下载)依赖到缓存目录{GOPATH}/pkg/mod
此命令会自动同步代码中导入但本地缓存中缺失的包
go mod tidy
此命令会在go.mod
中自动添加以下内容
require github.com/pincman/greetings v0.0.0-00010101000000-000000000000
其中v0.0.0-00010101000000-000000000000
是一个伪版本号,这是因为本地依赖的模块没有指定版本号,当我们发布这个子模块到它的仓库地址并打上标签后就可以在依赖中指定一个标签作为版本号了,例如
更多关于版本号的规则请查看官方文档
require github.com/pincman/greetings v1.1.0
现在尝试运行一下代码
go run .
# 输出
你好, pincman.,欢迎学习go开发指南!
清除缓存
模块的依赖(包括本地模块)会自动下载到GOPATH
目录下的pkg/mod
子目录,这些文件被标记成只读,可以使用下面的命令进行清除
go clean -modcache
包
包是一堆go源文件的归类,每个包在一个模块中代表了不同功能的集合,我们可以理解为包是模块的子集,和模块是多对一的关系
与typescript一样,本地模块包和第三方远程模块的包均可通过import
关键字导入,也同样支持通过模块名为前缀的绝对路径导入,如果是处于同一模块的包也可以相对路径导入
虽然包名不是必须和目录名一样,甚至可以在模块的根目录下(参考上面示例的greetings
模块),但是一般的情况下,为了代码结构更清晰,可以通过文件夹形式来命名包,比如在项目中创建一个hello
目录,同时把hello
包下的所有源文件的go文件都放置到此目录中.
包名使用package
关键字定义
需要注意的是一些go底层包,如
fmt
等是不需要带模块前缀来导入的
同模块包
示例代码
在上面的hello
模块中创建一个morestrings
目录作为morestrings
包.添加一个go文件reverse.go
,代码如下
package morestrings
// 反转字符串
func ReverseRunes(s string) string {
r := []rune(s)
for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
r[i], r[j] = r[j], r[i]
}
return string(r)
}
写好代码后编译一下,看看是否有错误(这一步不是必需的)
go build # 这不会产生输出文件,是将已编译的程序包保存在本地构建缓存中。
编辑hello.go
,在main
包中导入并在main
函数中调用
package main
import (
"fmt"
"github.com/pincman/hello/morestrings"
// 因为在同一个模块下,所以可以也通过相对路径 ./morestrings 来导入
)
func main() {
fmt.Println(morestrings.ReverseRunes("!南指发开og习学迎欢"))
}
查看效果
go run .
# 输出
欢迎学习go开发指南!
异模块包
与调用程序处于不同模块的包需要先导入添加这个包所处的模块作为依赖.
导入不同模块的包(无论本地或远程包)必须使用绝对路径,使用异模块包的方式有两种
使用预下载的方式在一开始导入时会报错,因为这时候远程模块还没有下载,不用去管它
- 预下载: 先在代码中导入,然后使用
go mod tidy
同步下来即可 - 自动下载: 通过
go get
命令自动添加所需依赖并下载,依赖会自动写入go.mod
,运行go命令即可(推荐)
示例代码
以使用gofiber启动一个web服务为例
下载gofiber
go get -u github.com/gofiber/fiber/v2
编辑hello.go
package main
import "github.com/gofiber/fiber/v2"
func main() {
app := fiber.New()
app.Get("/", func(c *fiber.Ctx) error {
return c.SendString("Hello, World 👋!")
})
app.Listen(":3000")
}
直接启动
go run .
打开https://localhost:3000
即可看到效果
工作空间
前面提到vscode在一个窗口打开多个go模块时会报错,不仅如此,在每次导入一个本地模块时必须先映射这是非常麻烦的事,但是go1.18之后解决了这个问题
使用workspaces
特性就可以在一个项目中同时开发多个模块,go workspaces
通过go.work
文件来配置,其配置写法与go.mod
类似
虽然整体概念上go workspace类似于monorepo,但有根本区别--monorepo的目的是把多个组件或应用包含在仓库里一起发布,而go workspace是每个模块独立发布,go.work
只在本地使用
接下来我们把go工程和pnpm开发monorepo工程做一个形象的类比,这样就比较好理解了
- go.mod -> package.json 用于指定依赖
- go.sum -> pnpm-lock.yaml 用于锁定依赖
- go的workspaces -> pnpm的workspace 一个多库同时开发的概念
- go.work -> pnpm-workspace.yaml 用于配置工作空间
痛点解决
再次提醒,请把每个模块设置成单个仓库,不要提交整个工作空间和
go.work
工作空间解决的最大的问题就是多模块协作工程.以前每次因为本地模块映射的原因,所以每次提交go.mod
都需要修改一下依赖,并且要同时写多个模块的代码必须开多个vscode窗口,有了工作空间以上的问题迎刃而解
使用方法
到父目录创建一个工作空间
cd ..
go work init ./hello
这时候多了一个go.work
文件
下载gofiber
源码
git clone https://github.com/gofiber/fiber
把fiber
目录添加到工作空间
go work use ./fiber
编辑fiber/app.go
,修改New
函数,在顶部添加一个打印
func New(config ...Config) *App {
fmt.Println("start fiber")
// Create a new app
app := &App{
// ...
通过模块名运行应用
go run github.com/pincman/hello
这时候可以看到我们的实时修改生效了,控制台打印出了start fiber
里面指定了这个工程用到的模块,因为我们需要用到greetings
模块,所以加入
go work use ./greetings
现在go.work
的内容如下
go {版本}
use (
./greetings
./hello
)
通过go run 模块名
命令就可以运行hello
模块了
go run github.com/pincman/hello
最佳实践
1, 在本地开发的模块还没有提交到仓库并打上标签时不要把它添加到使用它的模块的go.mod
依赖中,直接添加到go.work
的use
中即可使用
比如go work ./greetings
就可以,如果在go.mod
中添加依赖就会在go mod tidy
或者go work sync
时自动去下载这个模块,因为没提交,所以下载地址不存在就运行不起来了
2.本地模块提交仓库后,请立即在调用它的模块中添加它作为依赖,因为已经在workspace中加载了,所以等需要修改模块代码时修改本地代码即可生效,因为这时候不再运行下载的旧代码了
例如
在hello/go.mod
添加
require github.com/pincman/greetings v1.0.0
执行go mod tidy
在go.work
文件中这样设置
use (
./greetings
./hello
)
修改本地的greetings
模块会实时生效
命令解析
go work init {...dirs}
: 初始化时指定工作空间中需要用到的模块目录go work use {...dirs}
: 用于添加工作空间中需要用到的模块目录go work edit
: 类似于go mod edit
,用于编辑go.work
文件,一般用于开发工具go work sync
: 同步构建列表中的依赖到go.mod
中,防止go.mod
提交到仓库后缺少一些依赖