跳过正文
  1. 所有文章/

03|项目初始化

·4198 字·9 分钟
目录
从零开始构建Go项目 - 这篇文章属于一个选集。
§ 3: 本文

Go 项目开发的第一步是初始化一个 Go 项目仓库。根据开发能力、项目类别、项目需求等,可以选择不同的项目初始化方式。一般而言有以下几种项目初始化方式,具体如下表所示:

方式方式描述
生成工具借助项目开发脚手架自动生成项目模版,例如 kratos、nirvana、kubebuilder、osctl 等工具
复制已有项目复制一个可以满足功能需求的现有项目,然后重命名项目目录并修改 Go 包导入路径,最后基于修改后的源代码进行开发
从零初始化从头初始化一个 Go 项目,包括设计目录结构、代码结构以及进行源代码开发

在实际开发中,可以根据需要选择合适的项目初始化方法。如果脚手架工具生成的代码,在目录结构设计、开发风格、代码质量、架构设计、功能列表等方面能够满足你的需求,则可以优先考虑使用工具来快速初始化 Go 项目。如果工具不能满足需求,建议基于一个优秀的 Go 项目进行魔改。该系列的项目是为了学习别人的优秀设计而选择从零开始模仿构建。

开始 Go 项目开发的第一步便是初始化一个项目仓库。对于 Go 项目而言,主要包括以下初始化内容:

  1. 创建项目目录;
  2. 初始化目录为 Go 模块;
  3. 初始化目录为 Git 仓库;
  4. 创建需要的目录。

初始化完仓库后,还需要给项目仓库添加基本的初始化文件:

  1. Air 工具配置文件;
  2. 版权声明文件;
  3. Makefile 脚本。

创建项目目录
#

在开始初始化项目之前,需先设计好项目的名称。一个简洁、易懂且合适的项目名是开发高质量 Go 项目的第一步。通常,需要提前确认以下名称:

  • 项目名称:项目名要具有一定语义,说明该项目的功能。建议的格式为纯小写的精短名字。如果项目名字过长,可以按单词用中杠线(-)分割,但最好不要使用。以下是一些合格的名字:api、controllermanager、controller-manager。不建议命名为:controller_manager;
  • 项目名称大小写:还需确认项目名在代码中的大小写格式。统一大小写格式可以使整个代码命名格式保持统一。例如:controller-manager/controllermanager 项目的小写格式为 controllermanager,大写格式为 ControllerManager;
  • 项目名简写格式:有些项目名出于易读性考虑,可能会较长。在编写代码时,如果引用了项目名,可能会导致代码行过长。为了使代码行简短易读,通常会采用简写模式。带中杠线分割的项目名的简短模式一般为每个单词的首字母,例如:controller-manager 为 cm。不带中杠线分割的项目名简写模式需要根据具体名字确定,且没有统一的命名规则,例如:controller 可以简写为 ctrl。

例如该系列的项目名字为iam(ldentity and Access Management),代码中大写格式为IAM,代码中小写格式为iam。

初始化目录为 Go 模块
#

iam 是一个 Go 项目,根据 Go 语法要求,还需要将该项目初始化为一个 Go 模块,并添加到 Go 工作区中。初始化命令如下:

$ go mod init # 初始化当前项目为一个 Go 模块
$ go work init . # 初始化当前目录为一个 Go 工作区
$ go work use . # 添加当前模块到 Go 工作区

初始化目录为 Git 仓库
#

当前项目开发基本上都是使用 Git 来管理项目源码,因此,还需要将项目仓库初始化为一个 Git 仓库。

在提交代码时,有些文件,例如备份文件、临时文件和日志文件不需要提交到项目仓库中,可以通过在项目目录下添加 .gitignore 文件来忽略这些文件。如果你不知道如何配置 .gitignore 文件中的内容,可以借助 .gitignore 文件生成工具来自动生成一个可用的 .gitignore 文件,例如 gitignore.io 就是一个很好用的 .gitignore 文件生成工具。iam 项目使用的 .gitignore 文件就是用上面的工具生成。

可以执行以下命令将 Go 项目仓库初始化为一个 Git 仓库:

$ git init # 初始化当前目录为 Git 仓库
$ git config user.name 'Robin Liu' # 设置仓库级别用户名
$ git config user.email robinliutech@gmail.com # 设置仓库级别邮箱
$ git config --global credential.helper store # 永久保存凭据
$ git add . # 添加所有被 Git 追踪的文件到暂存区
$ git remote add origin https://github.com/robinlg/iam # 将本地仓库与远程仓库相关联
$ git commit -m "feat: 第一次提交" # 将暂存区内容添加到本地仓库中

之后,可以在 iam 目录下进行代码开发,并根据需要提交代码。提交后的源码目录内容如下:

$ ls -AF
.git/  .gitignore  go.mod  go.work  README.md

创建需要的目录
#

初始化代码仓库后,我们可以根据前面设计的目录规范创建一些空目录,例如:

$ mkdir -p cmd configs docs scripts
$ ls -F
cmd/  configs/  docs/  go.mod  go.work  README.md  scripts/

提前创建一些符合目录规范的空目录,可以带来以下好处:

  • 提前规划目录相当于提前规划未来的功能,将未来要实现的功能以目录的形式固化在项目仓库中,起到记录的作用;
  • 提前创建目录有利于后续文件按照功能存放在预先规划好的目录中,从而使项目更加规范。否则,不同开发者可能会根据各自的开发习惯,创建各种各样的目录结构和目录名称。

例如,可以将一些说明文档存放在 docs/devel/zh-CN 目录中。因为 Git 不追踪空目录,为了让 Git 追踪空目录,我们可以在空目录下创建一个空文件 .keep,并在适当的时候执行以下命令以删除这些临时 .keep 文件:

$ find . -name .keep | xargs -i rm {}

热加载 Go 应用
#

在 Go 项目开发过程中,经常需要修改代码、编译代码、重新启动程序,然后测试程序。若每次都手动操作,则效率较低。此时,可以借助程序热加载工具来自动编译并重启程序。在 Go 生态中,有许多此类工具,其中较为流行的是 Air 工具。你可以直接参考 Air 官方文档了解如何使用 Air 工具。热加载并不是每个项目必须具备的功能,因此,你可以根据项目需求决定是否使用该功能。目前iam还未使用该功能。

以下是安装和配置 Air 工具的步骤。

(1) 安装 Air 工具

安装命令如下:

$ go install github.com/air-verse/air@latest

(2) 配置 Air 工具

这里我们使用 Air 官方仓库中给出的示例配置:air_example.tomlair_example.toml 中的示例配置基本能满足绝大部分项目需求,一般只需再配置 cmdbinargs_bin 三个参数即可。

(3) 启动 Air 工具

配置完成后,在项目根目录下运行 air 命令。

# 默认使用当前目录下的 .air.toml 配置,你也可以通过 `-c` 选项指定配置,例如:`air -c .air.toml`
$ air
...
Proxy server listening on http://localhost:8090
building...
running...
Hello World!
Process Exit with Code 0

添加版权声明
#

如果项目是一个开源项目或计划在未来开源,则需要为项目添加版权声明,主要包括以下内容:

  • 存放在项目根目录下的 LICENSE 文件,用于声明项目所遵循的开源协议;
  • 项目源文件中的版权头信息,用于说明文件所遵循的开源协议。

业界当前有上百种开源协议可供选择,常用的有六种,按从严格到宽松的顺序依次为:GPL、MPL、LGPL、Apache、BSD、MIT。iam 项目使用了最宽松的 MIT 协议。

iam 添加 LICENSE 文件
#

一般项目的根目录下会存放一个 LICENSE 文件,用于声明开源项目所遵循的协议,因此我们也需要为 iam 初始化一个 LICENSE 文件。我们可以使用 license 工具来生成 LICENSE 文件,具体操作命令如下:

$ go install github.com/nishanths/license/v5@latest
$ license -list # 查看支持的代码协议
# 在 iam 项目根目录下执行
$ license -n 'Robin Liu' -o LICENSE mit

上述命令将在当前目录下生成一个名为 LICENSE 的文件,该文件包含 MIT 开源协议声明。

给源文件添加版本声明
#

除了添加整个项目的开源协议声明,还可以为每个源文件添加版权头信息,以声明文件所遵循的开源协议。iam 的版权头信息保存在 scripts/boilerplate.txt 文件中。

提示: 版权头信息保存的文件名,通常命名为 boilerplate。

有了版权头信息,在新建文件时需要将这些信息放在文件头中。如果手动添加,不仅容易出错,还容易遗漏文件。最好的方法是通过自动化手段追加版权头信息。追加方法如下。

(1) 安装 addlicense 工具

安装命令如下:

$ go install github.com/marmotedu/addlicense@latest

(2) 运行 addlicense 工具添加版权头信息

运行以下命令添加版权头信息。

$ addlicense -v -f ./scripts/boilerplate.txt --skip-dirs=third_party,_output .

Makefile 项目管理
#

优秀的开发者在开发一个 Go 项目时,会提前考虑应用的扩展性,在开发之初,甚至功能都是按着大规模应用的标准去设计的。

对于一个庞大的应用,可能有众多开发者,每天都在提交代码,每天都在重复执行:静态代码检查、代码编译、镜像构建、单元测试、Protobuf 文件编译等操作。试想如果没有一个统一、高效的项目管理,你很可能会面临以下 2 个核心难题:

  • 执行结果不一致带来一些潜在的问题和沟通成本:这么多开发者,相同的操作每个人执行的命令和结果都是不一样的。不一致的结果,可能会程序运行结果不一致,并带来一些沟通成本;
  • 执行效率低下:例如编译一个 Protobuf 文件需要键入的命令长度就可能超过 200 个字符。如果没有一个高效的编译方法,每次手动输入这么多命令字符,整个操作是非常低效的,而且还可能因为命令执行错误,导致运行失败。

上面 2 个难题最终导致的结果就是:项目管理效率低,或者说开发效率低。

所以为了提高开发效率,在开发 Go 项目时,需要有一个统一、高效的项目管理手段。当前,对于一个 Go 项目,有多种项目管理手段,但最普遍的方法是使用 Makefile 来管理项目。

如何通过 Makefile 管理项目?
#

在 Go 项目开发中,开发者可能会编写各种质量、各种结构的 Makefile 文件。一个简单的 Makefile 如下:

# 变量定义
APP_NAME = myapp
SRC = ./...
OUTPUT_DIR = ./bin
BUILD_FILE = $(OUTPUT_DIR)/$(APP_NAME)

# 默认目标
all: build

# 构建二进制文件
build:
	@echo "Building the application..."
	@mkdir -p $(OUTPUT_DIR)
	go build -o $(BUILD_FILE) ./main.go

# 运行应用程序
run:
	@echo "Running the application..."
	@$(BUILD_FILE)

# 运行测试
test:
	@echo "Running tests..."
	go test $(SRC)

# 格式化代码
fmt:
	@echo "Formatting code..."
	go fmt $(SRC)

# 清理生成的文件
clean:
	@echo "Cleaning build files..."
	@rm -rf $(OUTPUT_DIR)

# 伪目标 (防止文件与目标名称冲突)
.PHONY: all build run test fmt clean

对于一个小型项目而言,上述 Makefile 基本能够满足需求。然而,对于一个大型项目来说,上述 Makefile 并不适用。大型项目在开发过程中通常涉及功能繁多、条件复杂、且命令较为冗长。如果直接将这些功能简单拼凑到一个 Makefile 文件中,虽然能够一定程度上提升开发效率,但效果非常有限。一个冗长、难以阅读且操作复杂的 Makefile 同样会对项目管理造成阻碍。

如果想管理大型项目,还需要升级 Makefile 的实现,通过结构化、灵活、可扩展的方式来编写 Makefile。

结构化 Makefile 设计方式
#

结构化设计 Makefile 文件的方式有很多种,一个经典的结构化 Makefile 设计示例如下:

├─ Makefile
├─ scripts/
│  └─ make-rules/
│     ├─ all.mk
│     ├─ common.mk
│     ├─ gen.mk
│     ├─ golang.mk
│     ├─ swagger.mk
│     └─ tools.mk

上述 Makefile 结构也是 iam 项目采用的结构。项目根目录下的 Makefile 主要用来聚合 Makefile 核心规则,具体的规则实现存放于 scripts/make-rules/ 目录下。all.mk 也是一个 Makefile 文件,该文件导入了 scripts/make-rules/ 目录下的其他 Makefile 文件。

项目根目录下的 Makefile 文件通过导入 scripts/make-rules/all.mk,间接导入 scripts/make-rules/ 目录下的 Makefile 文件,例如:

include scripts/make-rules/common.mk
include scripts/make-rules/tools.mk # include at second order
include scripts/make-rules/golang.mk
include scripts/make-rules/gen.mk
include scripts/make-rules/swagger.mk

这种设计方式能够确保根目录下的 Makefile 文件结构清晰、易于阅读和维护。同时,由于功能按类别拆分到 scripts/make-rules/ 目录中的多个 Makefile 文件,也能保证每个文件代码简洁、功能聚焦,从而提升代码的可读性和可维护性。

对于较复杂的功能实现,可以通过编写 Shell 脚本来完成。这些 Shell 脚本可以放置于 scripts/ 目录中,并在 Makefile 中进行引用。相比直接在 Makefile 中实现功能,使用 Shell 脚本不仅提高了开发效率、降低了实现难度,还能够有效减少 Makefile 文件的代码量。

因此,一个合理的 Makefile 结构如图 13-2 所示。

结构化 Makefile

从零开始构建Go项目 - 这篇文章属于一个选集。
§ 3: 本文