零基础入门(二):Go 项目基本工程管理示例
工程管理概述
在上一篇教程中,学院君带着大家搭建起了本地开发环境,并且编写了第一个 Go 程序,向世界问好。不过在实际开发过程中,直接调用编译器进行编译和链接(调用 go build
或 go run
命令)的场景很少,因为在项目工程中不会简单到只有一个源文件,往往有很多源文件并且源文件之间会有相互依赖的关系,如果这样一个文件一个文件逐步编译,那简直无法想象。
Go 语言的设计者作为行业老将,自然不会忽略这一点。说到这里,我们来看看 Go 语言的主要设计者都是何方神圣:
个顶个的都是大神级人物。早期 Go 语言使用 makefile
作为临时方案,到了 Go 1 发布时引入了强大无比的 Go 命令行工具,Go 命令行工具彻底消除了工程文件的概念,完全用目录结构和包名来推导工程结构和构建顺序。想想 C 语言编译、链接和安装可执行程序的繁琐步骤,首先要通过 configure
脚本对特定平台进行检测,比如是否包含 GCC 等编译工具,然后生成 Makefile 文件,该文件定义了编译、链接规则,为下一步编译工作做准备,接下来就是执行 make
命令进行编译工作,最后再执行 make install
命令完成应用程序的安装,经历过这些步骤才可以实现将 C 程序源代码编译为可执行程序。
与之相比,Go 语言则要简单的多,针对只有一个源文件的情况(如上篇教程所示),引入工程管理看起来比较多余,因为直接用 go run
和 go build
搞定,下面我们通过 Go 语言构建一个简单的计算器项目来演示 Go 语言的基本工程管理方法。
编写计算器工程源码
我们假设这个工程被划分为两个部分:
- 可执行程序,名为
calc
,内部只包含一个calc.go
文件,该文件是计算器程序的入口文件; - 算法库,名为
simplemath
,每个计算操作对应于一个同名的 Go 文件,比如add.go
用于计算加法运算。
这样,对应的工程目录组织应该如下所示:
然后我们来编写每个 Go 文件的源代码,src/calc/calc.go
的源码如下:
package main
// 引入其它包
import (
"fmt"
"os"
"simplemath"
"strconv"
)
// 定义一个用于打印程序使用指南的函数
var Usage = func() {
fmt.Println("USAGE: calc command [arguments] ...")
fmt.Println("\nThe commands are:\n\tadd\t计算两个数值相加\n\tsqrt\t计算一个非负数的平方根")
}
// 程序入口函数
func main () {
/*
* 用于获取命令行参数,注意程序名本身是第一个参数,
* 比如 calc add 1 2 这条指令,第一个参数是 calc
*/
args := os.Args
// 除程序名本身外,至少需要传入两个其它参数,否则退出
if args == nil || len(args) < 3 {
Usage()
return
}
// 第二个参数表示计算方法
switch args[1] {
// 如果是加法的话
case "add":
// 至少需要包含四个参数
if len(args) != 4 {
fmt.Println("USAGE: calc add <integer1><integer2>")
return
}
// 获取待相加的数值,并将类型转化为整型
v1, err1 := strconv.Atoi(args[2])
v2, err2 := strconv.Atoi(args[3])
// 获取参数出错,则退出
if err1 != nil || err2 != nil {
fmt.Println("USAGE: calc add <integer1><integer2>")
return
}
// 从 simplemath 包引入 Add 方法进行加法计算
ret := simplemath.Add(v1, v2)
// 打印计算结果
fmt.Println("Result: ", ret)
// 如果是计算平方根的话
case "sqrt":
// 至少需要包含三个参数
if len(args) != 3 {
fmt.Println("USAGE: calc sqrt <integer>")
return
}
// 获取待计算平方根的数值,并将类型转化为整型
v, err := strconv.Atoi(args[2])
// 获取参数出错,则退出
if err != nil {
fmt.Println("USAGE: calc sqrt <integer>")
return
}
// 从 simplemath 包引入 Sqrt 方法进行平方根计算
ret := simplemath.Sqrt(v)
// 打印计算结果
fmt.Println("Result: ", ret)
// 如果计算方法不支持,打印程序使用指南
default:
Usage()
}
}
这段代码相信对于有 PHP 编程基础的人来说,都不困难,需要注意的是在 Go 语言中,通过 var
来声明变量,并且可以将匿名函数赋值给变量以便后续使用,此外,还可以通过 :=
运算符来声明并初始化变量,这个时候,不需要通过 var
声明该变量,比如 args := os.Args
就是如此,需要明确的是,虽然看起来有点和动态语言声明变量类似,但与 PHP 不同,Go 是强类型语言,只不过底层会自动根据赋值判断对应变量的类型,如果你试图声明一个没有初始化值的变量,就会报错,关于 Go 语言变量声明和初始化我们后面还会详细介绍,这里简单了解下即可。
接下来,我们编辑 src/simplemath/add.go
代码如下:
package simplemath
func Add(a int, b int) int {
return a + b
}
以及 src/simplemath/sqrt.go
代码如下:
package simplemath
import "math"
func Sqrt(i int) int {
v := math.Sqrt(float64(i))
return int(v)
}
配置 GOPATH 环境变量
注:对于最新版本(Go 1.11 以上版本),可以使用 Go Modules 来管理项目,更加方便。
为了能够构建这个工程,需要先把这个工程的根目录加入到环境变量 GOPATH
中,上篇教程中,我们介绍过 GOPATH
,并且提到其默认值是 ~/go
(其中 ~
表示用户家目录),除此之外,我们还可以为 GOPATH
配置多个路径值,以便我们在任意地方组织编写 Go 项目代码。假设计算器项目根目录 calc
目录位于 ~/go
下,如果是基于 GoLand 进行开发的话,可以通过「首选项(Preferences)->Go->GOPATH」(Windows 的话对应的路径是 File->Settings->Go->GOPATH)界面来进行设置:
在 Project GOPATH 中新增一条路径即可,然后点击「Apply」按钮,很简单。此外,在 Unix-Like 系统中,还可以通过编辑 ~/.bashrc
文件,并添加下面这行代码:
export GOPATH=~/go/calc
然后执行以下命令应用该设置来完成 GOPATH
的自定义设置:
$ source ~/.bashrc
在 Windows 中,则可以通过配置环境变量 GOPATH
来实现,将你的项目根目录完整路径拷贝过去就好
了。和 PATH
环境变量一样,GOPATH
也可以支持一次配置多个路径,并且路径和路径之间用冒号分隔。
GOPATH
的用处是 Go 语言在编译程序时,会从 GOPATH
配置的路径里面去查找源文件并完成构建。
构建 Go 工程
设置完 GOPATH
后,现在我们开始构建工程。假设我们希望把生成的可执行文件放到 calc/bin
目录中,需要执行的一系列指令如下:
cd ~/go/calc
mkdir bin
cd bin
go build calc
这样就会在 calc/bin
目录下生成可执行文件 calc
:
然后我们就可以执行 calc
程序了:
如果在构建过程中报错:
go build calc can't load package: package calc: cannot find package "calc" ...
则可能是配置的 $GOPATH
环境变量尚未生效,需要关闭当前 Terminal 窗口重新打开让配置生效,再运行 go build
命令。
从上面的构建过程中可以看到,真正的构建命令就一句:
go build calc
这就是为什么说 Go 命令行工具是非常强大的。我们不需要写 makefile
,因为这个工具会替我们分析,知道目标代码的编译结果应该是一个包还是一个可执行文件,并分析 import
语句以了解包的依赖关系,从而在编译 calc.go
之前先把依赖的 simplemath
编译打包好。Go 命令行程序制定的目录结构规则让代码管理变得非常简单。
当然,这里只是一个最简单、最基本工程管理的构建示例,后面我们还会就更复杂的工程项目管理进行介绍,比如工程组织、文档与代码风格、跨平台开发、单元测试、打包分发等。学习过程中有任何问题,欢迎通过下面的评论与我讨论。
使用 Go Modules 管理项目
从 Go 1.11 版本开始,官方提供了 Go Modules 管理项目和依赖,从 1.13 版本开始,更是默认开启了对 Go Modules 的支持,使用 Go Modules 的好处是显而易见的 —— 不需要再依赖 GOPATH
,你可以在任何位置创建 Go 项目,并且在国内,可以通过 GOPROXY 配置镜像源加速依赖包的下载。
通过 Go Modules 管理项目非常简单,我们可以在任意位置创建项目,不过为了方便统一管理,我们可以创建一个 ~/Developmemt/go
存放所有 Go 项目,然后在该目录下创建一个 src
目录存放项目源码(在 Windows 中,可以在 D
盘下可以创建一个 /Projects/go
目录管理 Go 项目)。以最新版本的 Go 为例(目前是 1.14),在 GoLand 中可以直接基于 Go Modules 创建 calc
项目:
同时可以在这个创建新项目的面板中可以设置该项目的环境变量(点击上图红色箭头指示位置设置),我们设置 GOPROXY 作为依赖镜像源。当然你也可以设置系统路径/环境变量来设置全局 GOPROXY。
创建完成后,即可跳转到项目管理页面,并且在项目根目录下生成一个 go.mod
,其作用类似于 PHP 项目中的 composer.json
:
然后,我们将前面基于 GOPATH
管理的 calc
项目 src/simplemath
目录下的源码拷贝过来,再将 calc/calc.go
源码拷贝到新的 calc 项目根目录,并重命名为 main.go
:
由于我们当前的项目是通过 Go Modules 管理的,并且对应的模块名是 calc
,所以需要在 main.go
中通过 calc/simplemath
引入之前的 simplemath
包,否则找不到。
接下来,我们就可以在当前 calc
目录下运行构建指令 go build
构建该项目了:
默认会在项目根目录下生成与项目目录同名的可执行文件(Mac 系统下是 calc
,由于我的 Mac 系统是 Go 1.13 版本,所以这里统一使用 Windows 系统进行演示)。
然后就可以运行 calc
通过命令行代码与 Go 应用交互了:
非常简单,是吧。
29 Comments
打卡成功
已打卡
set GOPATH=D:\Go\calc; can't load package: package calc is not in GOROOT (E:\Go\src\calc) 我运行go build calc的时候怎么还是跑到安装目录下了 看了评论解决了 还需要加一个环境变量GO111MODULE=off
Wrong title:"使用 Go Moudles 管理项目".
感谢反馈 已修正
使用
Go Modules
搞了半天,怎么都读不到calc\simplemath
, 最后把GO111MODULE
设为on
搞定了为什么可以直接 calc 调用呢 , 不管是mac还是windows 都应该./calc吧
如果在bin目录在系统路径中就可以直接执行 calc
这里要介绍一下golang的文件目录不然初学者会懵逼的。 不管是goPath好事goModule,他们的文件目录都是 GOROOT or GOPATH 下 src 文件目录内存放工程文件。
/bin /pkg /src