go项目基本要素的个人之见

·

2 min read

看到一条推文

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, supports yaml 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 intfunc 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 intfunc 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这些已经不能算是要素了,而应该成为一种本能,但是笔者在从业过程中,也发现好多项目连上述两点也做不到。如果无法决定项目的上限,至少可以遵循一些基本原则守住下限,共勉。

参考: