go项目基本要素的个人之见
Table of contents
看到一条推文
hate 所有更新版本后不写 changelog 或者 release note 的软件或者库
催生了本篇博文的产生,结合笔者过往的开发经验,想讨论一下关于在公司内参与开发go(其它项目也基本适合)项目的基本要素。
CHANGELOG.md
人的记忆是不可靠,也不可能每次都去翻代码看看某次版本增加的功能、修复的问题、优化、库升级等,所以changelog是有必要的,至少可以成为项目版本迭代信息的一个参考和指引
一个示例如下
# Change Log
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).
## [0.9.0]
### Changed
- 更新版本号
### Removed
- 删除deadcode
### Fixed
- 修复XX问题
其实并不用写太多,在多人协作的时候,每个人写自己负责的那一部分即可,要做到这一点,其实并难,难的是建立起编写changelog的整体意识。
golangci-lint
golangci-lint
is a fast Go linters runner. It runs linters in parallel, uses caching, supportsyaml
config, has integrations with all major IDE and has dozens of linters included.
笔者曾经跟一个同事说过一句话:lint 是多人协作编程的基础之一,决定项目的下限。现在想想,这句话真的讲的蛮好的。其实笔者也不知何时开始重视静态检查的,印象中大概是早年参与TiDB的十分钟成为contributor活动那会开始的吧,这个开源项目的静态检查可是很棒的,同时也是我参与开源的启蒙(当年因参与贡献而收到的马克杯用到现在)。
下面会举一个静态检查的例子,目录结构如下:
.
├── .golangci.yml
├── go.mod
└── main.go
变量unused
没有地方在用,属于deadcode,也即死代码
package main
var unused int
func main() {
println("deadcode example")
}
检查deadcode的.golangci.yml配置如下
linters:
# Disable all linters.
# Default: false
disable-all: true
# Enable specific linter
# https://golangci-lint.run/usage/linters/#enabled-by-default
enable:
- deadcode
执行lint
golangci-lint run -v
输出
main.go:3:5: `unused` is unused (deadcode)
var unused int
^
INFO File cache stats: 1 entries of total size 75B
INFO Memory: 4 samples, avg is 31.6MB, max is 31.6MB
INFO Execution took 247.852683ms
可以检测出unused变量未被使用,属于deadcode。如果出于一些原因想让unused
变量跳过deadcode检查,只需增加一个注释即可,如下所示
package main
//nolint:deadcode
var unused int
func main() {
println("deadcode example")
}
当然,还有其它用法,可以参与golangci的官方使用文档
Dockerfile
笔者一直坚持Dockerfile应该放置于项目repo中,所以在贵司看到Dockerfile放置在Jenkins的agent节点上时,冲击不可谓不大,这些细节处理不足容易引起混乱。
改造前
jenkins节点上事先安装好需要的golang版本,去到指定的环境目录提前放置好Dockerfile,go build, docker build, 然后docker push
FROM alpine:3.18.0 WORKDIR /workspace COPY app /workspace/app
改造后
Dockerfile放在项目repo中,利用docker的multi-stage,保持了编译环境的纯净,也方便go升级,使得编译不再依赖jenkins节点,以下是一个示例
FROM golang:1.20.5-alpine3.18 as builder WORKDIR /workspace # Copy the Go Modules manifests COPY go.mod go.mod COPY go.sum go.sum RUN go env -w GO111MODULE=on && \ go env -w GOPROXY=https://goproxy.cn,direct && \ go mod download # Copy the go source COPY main.go main.go # Build RUN CGO_ENABLED=0 GOOS=linux go build -a -o app main.go FROM busybox:1.35.0-glibc COPY --from=builder /workspace/app /workspace/app
Makefile
这算是一个可选项吧,通常我会在makefile中封装一些常用命令,比如make lint就可以完成项目的lint,比执行golangci-lint run还是要简短些的,非常方便。另外,笔者在快速部署项目中也使用了makefile,效果也很好,以下是一个简单的示例
.PHONY: lint
lint:
golangci-lint run
.PHONY: build
build:
go build -a -o app main.go
.PHONY: dev
dev:
go run main.go
README.md
公司内的项目,能够讲清楚项目是干什么的,如何启动就已经不错了,倒不用有多复杂 ,因为一般就是给新人看的,如果能描述清楚项目背景、设计、功能、部署及注意事项等,那就更好了。
总结
代码格式化、go mod tidy这些已经不能算是要素了,而应该成为一种本能,但是笔者在从业过程中,也发现好多项目连上述两点也做不到。如果无法决定项目的上限,至少可以遵循一些基本原则守住下限,共勉。
参考: