对于一个像我这样没有go背景的java程序员来说,使用go开发一个可用的程序的速度是肉眼可见的缓慢。
其难点不在于go语言本身,而是搭建整个工程链路的过程,即所谓的“配环境”。
本文主要讲述如何配出一个适合go开发的环境,以免有同事陷入困境时对我使出“您配吗”之类的大招。
安装brew:/bin/bash-c"$(curl-fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
安装完成后可以执行 brew--version
查看brew是否安装成功。
▐ 安装go编译器
brew install go
这将安装go的最新版本。在发文的这一刻,go的最新版本是1.22。
如果想要安装某个特定的go历史版本,可以使用
brew install go@1.21
这将安装go1.21。
可以使用
brew search go
来查看当前brew仓库中可用的go版本列表。安装完成后可以执行
go version
查看go是否安装成功。 ▐ 安装go调试器
brew install delve
delve可以理解为是go版本的gdb。
brew install gdb
如果遇到delve搞不定的场景,就需要gdb出来救场了。
安装完成后可以执行
dlv version
查看delve是否安装成功,执行 gdb --version
查看gdb是否安装成功。▐ 配置 go 在编译产物中附加上调试信息
export GOFLAGS="-ldflags=-compressdwarf=false"
▐ 配置github gfw代理
export GOPROXY=https://goproxy.io,direct
▐ 配置私有代码仓库
export GOPRIVATE="你的私有gitlab地址.com/*,另外的私有gitlab地址.com/*"
,不要带http或https前缀
其次推荐idea安装go插件,但是要求idea是Ultimate版本。
不想花钱的话可以用vscode。Go
认准Microsoft出品。
your_app_name/
├── cmd/
│ ├── your_app_name/ # 应用程序的入口点,包含main包
│ │ └── main.go # 应用程序的主函数所在
│ └── ... # 如果有多个可执行文件,可以在这里添加
├── internal/ # 可选,存放仅供本应用内部使用的包
│ └── ... # 内部包结构,比如业务逻辑、工具函数等
├── config/ # 配置文件或配置加载逻辑
│ └── ... # 如yaml, toml, env等配置文件
├── scripts/ # 构建、部署脚本等
│ └── ...
├── tests/ # 单元测试、集成测试等
│ └── ...
├── README.md # 项目说明文档
├── go.mod # Go模块定义文件
├── go.sum # Go模块依赖的校验和文件
├── .gitignore # Git忽略规则文件
└── ... # 其他必要的文件,如Dockerfile、Makefile等
your_library_name/
├── internal/ # 可选,用于存放不希望对外暴露的包
│ └── ... # 内部使用的包
├── pkg/ # 存放库的公开对外接口。pkg 这一层可以省略,直接把源代码放在/your_library_name 目录下
│ └── your_library_name/
│ ├── your_library.go
│ └── ... # 库的各个源文件
├── tests/ # 单元测试、集成测试等
│ └── ...
├── README.md # 项目说明文档,包括安装、使用方法等
├── go.mod # Go模块定义文件
├── go.sum # Go模块的校验和文件
├── .gitignore # Git忽略文件列表
└── ... # 其他必要的文件,如Dockerfile、Makefile等
go的依赖项通过 go.mod
文件描述,类似于java的 pom.xml
或py的 requirements.txt
go是源代码依赖,没有制品的概念。可以理解为每次编译时,go都会从git上面把所有的依赖项的源代码全部拉下来,放在一起编译。官方的源代码仓库就是github。
一个典型的go.mod示例如下:
module github.com/dapr/dapr
^^^ 声明该模块的名字,类似于 groupId:artifactId
强烈建议在这里填写这个工程的实际可访问的git地址,否则会带来无穷无尽的麻烦
go 1.21
^^^ 指定要求的最低 go 版本,类似于 <java.version>1.8</java.version>
require (
^^^ 这里列出所有直接依赖项,类似于 <dependencies>
contrib.go.opencensus.io/exporter/prometheus v0.4.2
github.com/PaesslerAG/jsonpath v0.1.1
github.com/PuerkitoBio/purell v1.2.1
)
require (
^^^ 这一大片后面带有 // indirect 的是 go 自动生成的所有间接依赖,类似 mvn dependency:tree 的产出。
千万不要手工修改这部分。
cloud.google.com/go v0.110.10 // indirect
cloud.google.com/go/compute v1.23.3 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect
cloud.google.com/go/datastore v1.15.0 // indirect
cloud.google.com/go/iam v1.1.5 // indirect
)
replace (
^^^ 这一段用于模块的名称和实际 git 地址不匹配的情况。
上面提到的无穷无尽的麻烦就是指,所有引用你的包的项目都需要使用 replace 来将你的包指向实际的 git 地址
github.com/toolkits/concurrent => github.com/niean/gotools v0.0.0-201512
replace github.com/dapr/components-contrib => ../components-contrib
^^^ 也可以将模块的路径指向本地,以便于二方包开发过程中进行调试
)
export GOPRIVATE="你的私有gitlab地址.com/*,另外的私有gitlab地址.com/*"
,这在第1节中已经讲过。
在代码中引用二方包
▐ 时刻注意区分清楚【模块 / module】和【包 / package】的概念
github.com/groupId/artifactId
github.com/apache/dubbo-go
,在域名后面只有两段(groupId 和 artifactId)github.com/apache/dubbo-go/remoting
github.com/apache/dubbo-go
部分是模块名,后面的 /remoting
表示这个包所处的具体git路径。▐ import包时是import包的git地址,但是这个地址和包的名称可以不一致
import (
"github.com/go-playground/validator/v10"
)
...
func foo() {
validator.Xxx()
}
引用这个包时需要依赖包的路径
(”github.com/go-playground/validator/v10″),但是在具体使用的时候使用的包名是validator,这中间看不出任何对应关系,只能去翻源代码。
个人建议:除了标准库的包之外,对所有import的包都自己定义一个别名,例如:
import (
validator "github.com/go-playground/validator/v10"
)
▐ go在import中引入的包在代码中必须被使用,否则编译会报错
所以如果临时注释掉一条语句,再回头把注释去掉时,很有可能因为import语句被删掉了而编译不过。
个人建议:当临时需要禁用掉某些代码时,不要直接注释,要使用if false {} 给包起来。
if false {
validator.Xxx() // 不要直接注掉,免得ide把对应的import语句也给删了
}
▐ 修改go.mod后必须执行一次go mod tidy
go mod tidy
,否则会编译失败。go不会在编译时帮你更新依赖。
顺便,受上一条规则影响,所有的import都必须是实际被使用的。如果代码中去掉了某个import,而这一行import是整个工程中唯一引用某个依赖的位置,那么就意味着这个依赖不再被需要。此时需要从go.mod中去掉该依赖并执行 go mod tidy
,否则会导致编译不通过。
个人建议:直接把go mod tidy命令放在编译脚本中,每次编译时都执行一下。下面【编译】一节会详细解释。
▐ 执行过go mod tidy之后, go.mod文件本身会被修改
go mod tidy
之后都要记得检查一下go.mod文件,以免被go mod tidy命令改坏。以及,再重申一次,千万不要碰go.mod里面标了 // indirect的那一片自动生成的require指令。
gitlab.mycompany.com/${groupId}/${artifactId}
▐ 正式包的发版是通过git的tag机制来实现的
require gitlab.mycompany.com/${groupId}/${artifactId} v$tag
require gitlab.mycompany.com/shop/libaaa v0.3.6-rc9
▐ snapshot包的发版是通过git的branch机制实现的
require gitlab.mycompany.com/${groupId}/${artifactId} v0.0.0-notexist
replace gitlab.mycompany.com/${groupId}/${artifactId} => gitlab.mycompany.com/${groupId}/${artifactId} v0.0.0-${branch}
例如:
require gitlab.mycompany.com/shop/libaaa v0.0.0-notexist
replace gitlab.mycompany.com/shop/libaaa => gitlab.mycompany.com/shop/libaaa v0.0.0-develop2
go get -u
(类似于mvn -U命令)更新所有snapshot。-
二方包的git repo地址,形式为 gitlab.mycompany.com/${groupId}/${artifactId}
-
如果是正式包,需要公布其tag,使用SemVer格式;如果是snapshot,需要公布其branch,没有命名格式要求
沧海月明珠有泪,蓝田日暖玉生烟。
此情可待成追忆,只是当时已惘然。
—— (唐)李商隐
go build
。它有很多参数,非常灵活,但是每次编译都把完整命令全部敲一遍的话,精神方面可能就有点问题了。这个时候我们需要用make来简化构建工作,这又是一个大坑。validate
compile
package
test
deploy
之类的阶段,直接敲 mvn clean build
即可让 maven 完成预先定义的动作。make clean build
之后要执行什么操作都需要在Makefile中手工声明。这份Makefile应当放在工程根目录下,文件名即为 Makefile
八个字母,注意大小写。
# 定义一些常量
APPNAME := your_application_name
BINDIR := /build/bin
GOBIN := $(shell go env GOPATH)/bin
export GO111MODULE := on
# 目标: 清理
clean:
@rm -rf $(BINDIR)/*
@echo "Cleanup completed."
# 目标: 构建
build: go_mod_tidy compile
# 执行go mod tidy
go_mod_tidy:
@go mod tidy
@echo "go mod tidy completed."
# 编译项目
compile:
@mkdir -p $(BINDIR)
@go build -o $(BINDIR)/${APPNAME}
@echo "Build completed."
# 使用帮助信息
help:
@echo "Usage:"
@echo "make build - 整理模块依赖,编译项目,将产出物放到 /build/bin 目录下"
@echo "make clean - 清理构建产物"
.PHONY: clean build go_mod_tidy compile help
make build 整理模块依赖,编译项目,将产出物放到 /build/bin目录下 make clean 清理构建产物 make help 输出上述说明
调试
{
"version": "0.2.0",
"configurations": [
{
"name": "Launch Go Program with Args",
"type": "go",
"request": "launch",
"mode": "debug",
"program": "${fileDirname}", // 当前文件所在的目录作为工作目录,如果你需要指定特定的可执行文件路径,可以改为该路径
"args": ["arg1", "arg2", "--option=value"], // 在这里添加你的命令行参数
"env": {"env1":"env_value1", "env2":"env_value2"}, // 可以在这里添加环境变量,如果需要
"showLog": true, // 是否在调试控制台显示Delve的日志,默认为false
"trace": "verbose" // 设置为"verbose"可以获取更详细的调试信息,根据需要调整
}
]
}
-ldflags=-compressdwarf=false
,这个参数用于在编译产物中保留debug信息。dlv exec xxxx -- 要传给xxx的命令行参数
,要记得它调试的是编译出来的可执行文件。
此时可以敲命令与程序进行交互,常用的命令如下:
b 命令 设置断点 c 命令 执行至下一个断点 n 命令 step over s 命令 step in so 命令 step out args 命令 查看方法参数 p 命令 查看变量的值
dlv debug xxx.go
的语法,可以直接指定源文件,此时它会调用go编译器把这个文件编译出来之后再去调试。对于特别小的工程来说较为方便。
打包
面向ChatGPT老师教程
Q: 我需要以《面向chatgpt老师编程》为题写一篇几百字的短文,向同事们介绍如何借助您的知识迅速掌握go的编程技能,请帮我提供一份草稿
A: 《面向ChatGPT老师编程:快速掌握Go语言技能》
在技术日新月异的今天,Go语言以其简洁的语法、高效的并发模型和卓越的性能,在众多编程语言中脱颖而出,成为众多开发者的新宠。但对于初学者而言,如何高效、系统地学习Go,快速掌握其精髓,却是一个值得探讨的话题。在此,我们引入一位特别的“老师”——ChatGPT,来指导我们如何在编程之旅中乘风破浪,迅速提升Go语言技能。
第一步:基础搭建,从Hello World开始
与ChatGPT老师的对话,不妨就从最经典的“Hello, World!”程序开始。通过编写这个简单的程序,ChatGPT不仅能帮你熟悉Go的基本语法,还能深入浅出地解释背后的工作原理,比如包的导入、函数定义等基础概念。
第二步:深入理解并发模型
Go语言的一大亮点是其内置的goroutine和channel机制,这对于构建高性能的并发服务至关重要。通过向ChatGPT提出具体场景的问题,如“如何使用goroutine处理大量并发请求?”或“如何利用channel实现数据安全共享?”,ChatGPT能以实例结合理论的方式,让你轻松掌握并发编程的核心技巧。
第三步:实战演练,项目驱动学习
理论知识虽好,但没有实践的支撑,就如同空中楼阁。不妨向ChatGPT请教一些小型实战项目,如编写一个简易的Web服务器、实现一个文件上传下载服务等。ChatGPT不仅会提供代码示例,还会引导你思考如何优化代码结构、提高执行效率,让你在实践中深刻理解Go的高级特性。
第四步:调试与优化,进阶之道
遇到bug怎么办?性能瓶颈如何突破?ChatGPT在这些方面同样能给予帮助。你可以询问如何使用Go的调试工具(如delve)进行代码调试,或是如何利用pprof进行性能分析。通过ChatGPT的指导,你将学会如何高效地定位问题并进行性能调优,让代码更加健壮、高效。
第五步:持续学习,紧跟Go生态发展
Go语言的生态系统不断发展壮大,新的库和框架层出不穷。ChatGPT可以帮助你追踪最新的Go语言动态,推荐优秀的开源项目和文章,让你的学习之路永不止步。
总之,ChatGPT不仅是你的编程助手,更是你学习Go语言道路上的良师益友。它以无尽的知识库、耐心的解答态度,帮助你快速成长,从Go语言新手进化为高手。在这个过程中,记得多提问、多实践,与ChatGPT老师一起探索Go语言的无限可能。
原创文章,作者:guozi,如若转载,请注明出处:https://www.sudun.com/ask/78330.html