2.打包和工具链

  • 包名惯例
  • main包
  • 导入
  • 远程导入
  • 命名导入
  • 函数init
  • 使用Go的工具
  • 其他Go开发工具
  • go vet
  • Go代码格式化
  • Go语言的文档
  • Go开发协作要点
  • 依赖管理
  • 第三方依赖
  • 对gb的介绍

在Go语言中,

是个非常重要的概念。其设计理念是

使用包来封装不同的语义单元

所有Go语言的程序都会组织成若干组文件,每组文件被称为一个包。这样每个包的代码都可以作为很小的复用单元,被其他项目引用。每个包都可以单独导入和使用,以便开发者可以根据自己的需要导入特定功能。

所有的.go文件,除了空行和注释,都应该在第一行声明自己所属的包。每个包都在一个单独的目录里。不能把多个包放在同一个目录中,也不能把同一个包的文件分拆到多个不同目录中。这意味着,同一个目录下的所有.go文件必须声明同一个包名

包名惯例

给包命名的惯例是使用包所在目录的名字。给包及其目录命名时,应该使用简介、请求且全小写的名字
注意:并不需要所有包的名字都与别的包不同,因为导入包时是使用全路径的,所以可以区分同名的不同包。一般情况下,包被导入后会使用你的包名作为默认的名字,不过这个导入后的名字可以修改。

main包

在Go语言里,命名为main的包具有特殊的含义。Go语言的编译程序会试图把这种名字的包编译为二进制可执行文件。所有用Go语言编译的可执行程序都必须有一个叫main的包。

当编译器发现某个包的名字为main时,一定也会发现名为main()的函数,否则不会创建可执行文件。main()函数是程序的入口,所以如果没有这个函数,程序就没有办法开始执行。**程序编译时,会使用声明main包的代码所在的目录的目录名作为二进制可执行文件的文件名。**在Go语言里,命令是任何可执行程序。

导入

import语句告诉编译器到磁盘哪里去找想要导入的包。如果需要导入多个包,习惯上是将import语句把包装在一个导入块中,例如

import (
	"encoding/xml"
	"errors"
	"fmt"
	"log"
	"net/http"
	"regexp"

	"../search"
)

编译器会使用Go环境变量设置的路径,通过引入的相对路径来查找磁盘上的包。标准库中的包也会在安装Go的位置找到。Go开发者创建的包会在GOPATH环境变量指定的目录里查找。GOPATH指定的这些目录就是开发者的个人工作空间。
例如,Go安装在/usr/local/go,并且环境变量GOPATH设置为/home/myproject:/home/mylibraries,编译器就会按照下面的顺序查找net/http包:

/usr/local/go/src/pkg/net/http
/home/myproject/src/net/http
/home/mylibraries/src/net/http

一旦编译器找到一个满足import语句的包,就停止进一步查找。因此,编译器会首先查找GO的安装目录,然后才会按照顺序查找GOPATH变量里列出的目录。

如果在执行run或者build的时候,由于某些包没有导入而报错,可以使用go get命令来修正这种错误。

远程导入

Go工具链会使用导入路径确定需要获取的代码在网络的什么地方。例如:

import "github.com/spfl3/viper"

用导入路径编译程序时,go build命令会使用GOPATH的设置,在磁盘上搜索这个包。事实上,这个导入路径代表一个URL,指向GITHUB上的代码库。如果路径包含URL,可以使用Go工具链从DVCS获取包,并把包的源代码保存到GOPATH指向的路径里与URL匹配的目录里。这个获取过程使用go get命令完成。go get将获取任意指定的URL的包,或者一个已经导入的包所依赖的其他包。由于go get的递归性,这个命令会扫描某个包的源码树,获取能找到的所有依赖包。

命名导入

如果要导入的多个包具有相同的名字,这种情况下,重名的包可以通过命名导入的方式来导入。命名导入是指,在import语句给出的包路径的左侧定义一个名字,将导入的包命名为新名字。例如:

import (
	"fmt"
	myfmt "mylib/fmt"
)

当导入了一个不在代码里使用的包,Go编译器会编译失败,并输出一个错误。有时候,用户导入一个包,但是不需要引用这个包的标识符。这种情况下,可以使用空白标识符_来重命名这个导入。

函数init

每个包可以包含任意多个init函数,这些函数都会在程序执行开始的时候被调用。所有被编译器发现的init函数都会安排在main函数之前执行。init函数用在设置包、初始化变量或者其他要在程序运行前优先完成的引导工作。
以数据库驱动为例,database下的驱动在启动时执行init函数会将自身注册到sql包里,因为sql包在编译时并不知道这些驱动的存在,等启动之后sql才能调用这些驱动。代码如下:

package postres

import (
	"database/sql"
)

func init(){
	sql.Register("postgres", new(PostgresDriver))
}

如果程序导入了这个包,就会调用init函数,促使PostgresSQL的启动最终注册到Go的sql包里,成为一个可用的驱动。在使用这个新的数据库驱动写程序时,使用空白标识符来导入包,以便新的驱动会包含到sql包。代码如下:

package main
import(
	"database/sql"
	_ "github.co,/goinaction/code/chapter3/dbdriver/postgres"
)

func main(){
	sql.Open("postgres", "mydb") // 调用sql包提供的Open方法,该方法能工作的关键在于postgres驱动通过自己的init函数将自身注册到了sql包
}

使用Go的工具

在命令行提示符下,输入go这个命令:

go语言开发区块链教程 go语言公链开发实战_打包


通过输出的列表可以看到,这个命令包含一个编译器,这个编译器可以通过build命令启动。其中build和clean命令会执行编译和清理的工作。例如:

go build hello.go
go clean hello.go

调用clean后会删除编译生成的可执行文件。
注意:大部分Go工具的命名都会接受一个包名作为参数。在不包含文件名时,go工具会默认使用当前目录来编译。另外也可以指定包名的时候使用通配符。3个点表示匹配所有的字符串,例如:

go build github.com/goinaction/code/chapter3/...

除了指定包,大部分Go命名使用短路径作为参数。例如:

go build wordcount.go
go build .

要执行程序,需要首先编译,然后执行编译创建的wordcound或者wordcount.exe程序。
但是go run命令可以在一次调用中完成这两个操作:

go run wordcount.go

其他Go开发工具

go vet

vet命令会帮开发人员检测代码的常见错误。其可以捕获的错误类型有:

  • Printf类函数调用时,类型匹配错误的参数。
  • 定义常用的方法时,方法签名的错误。
  • 错误的结构标签
  • 没有指定字段名的结构字面量。

例如:

package LearnGo

import "fmt"

func main() {
	fmt.Printf("The quick for dogs", 3.14)
}

go语言开发区块链教程 go语言公链开发实战_Go语言_02


注意:每次对代码先执行go vet再将其签入源代码库是一个很好的习惯。

Go代码格式化

fmt工具会将开发人员的代码布局成和Go源代码类似的风格,使用go fmt后面跟文件名或者包名,就可以调用这个代码格式化工具。fmt命令会自动格式化并保存。

Go语言的文档

Go语言有两种方法为开发者生成文档:

  • 命令行提示符模式:在终端上执行使用go doc命令来打印文档。此时无需离开终端即可浏览
  • go语言开发区块链教程 go语言公链开发实战_go语言开发区块链教程_03

  • 浏览器界面模式:使用godoc程序启动一个Web服务器,通过点击的方式查看包的文档
godoc -http=:6060 // 该命令通知godoc在端口6060启动Web服务器。可以通过http://localhost:6060查看

补充:开发人员需要用下面的规则来写代码和注释:

  1. 用户需要在标识符之前,把自己想要的文档作为注释加入到代码中。这个规则对包、函数、类型和全局变量都使用。注释可以以双斜线开头、也可以用斜线和星号风格。
  2. 如果想给包写一段文字量比较大的文档,可以在工程里包含一个叫作doc.go的文件,使用同样的包名,并把包的介绍使用注释加包名声明之前。

Go开发协作要点

以分享为目的创建代码库

  1. 包应该在代码库的根目录中
  2. 包可以非常小
  3. 对代码执行go fmt
  4. 给代码写文档

依赖管理

第三方依赖

像godep和vender这种社区工具已经使用第三方(verdoring)导入路径重写这种特性解决了依赖问题。其思想是把所有的依赖包复制到工程代码库中的目录中,然后使用工程内部的依赖包所在目录来重写所有的导入路径

对gb的介绍

gb是一个由Go社区成员开发的全新的构建工具。背后的原理源自理解到Go语言的import语句并没有提供可重复构建的能力。import语句可以驱动go get,但是import本身并没有包含足够的信息来决定到底要获取包的哪个修改的版本。因此,go get无法定位待获取代码的问题,导致Go工具在解决重复构建时,不得不使用复杂的方法。

gb既不包装Go工具链,也不使用GOPATH。gb基于工程将Go工具链工作空间的元信息做替换。一个gb工程就是磁盘上一个包含src/子目录的目录。符号$PROJECT导入了工程的根目录中,其下有一个src/的子目录中。这个符号只是一个简写,用来描述工程在磁盘上的位置。事实上,gb根本不需要设置任何环境变量。
gb工程会区分开发人员写的代码和开发人员需要依赖的代码。例如:

$PROJECT/src/	// 工程中存放开发人员携带的代码的位置
$PROJECT/vendor/src/	//存放第三方代码的位置

另外,gb的一个最好的特点是:不需要重写导入路径,且提供了插件系统,可以让社区扩展支持的功能。
注意:gb工程与Go官方工具链(包括go get)并不兼容。

参考资料:

  1. 《GO语言实战》