[Go 入门] 第十二章 使用包实现代码复用

Go 入门系列参考于互联网资料与 人民邮电出版社 《Go 语言入门经典》 与 《Effective Go》,编写目的在于学习交流,如有侵权,请联系删除

包用于代码编组, 以便在Go程序中导入并使用它们, 本文内容:

导入包

Go 程序以 package 语句打头,main包是一种特殊的包,其特殊之处在于不能导入。对 main 包唯一的要求是,必须声明一个 main 函数,这个函数不接收任何参数且不返回任何值。 main就是程序的入口

1
2
3
4
5
6
7
8
9
10
package main

import (
"fmt"
)

func main() {
fmt.Println("Hello world!")
}

在 main 包中,可使用import声明来导入其他包。导入包后,就可使用其中被导出的(即公有的)标识符。在 Go 语言中,标识符可以是变量、常量、类型、函数或方法。这让包能够通过接口提供各种功能。例如 math 包提供了常量 Pi

1
2
3
4
5
6
7
8
9
10
package main

import (
"fmt"
"math"
)

func main() {
fmt.Println(math.Pi)
}

导入包并使用其中导出的标识符,是重要标准库和其他第三方代码的基本方式。这种方式虽然简单,但您必须理解,灵活性和代码复用在很大程序上都是通过这种方式实现的

理解包的用途

要了解如何使用包,先得知道该使用哪个。在标准库中,Go语言采用了一致的命名约定。Go语言中,包名短小且精悍又含义丰富。 strings 包包含用于处理字符串的函数,bytes 包包含用于处理字节的函数。

假设要在程序中操作字符串,可以通过阅读标准库包清单,会发现存在着 strings 这样一个包,但如何知道它提供了那些功能呢? Go 语言的文档很完善,而对于标准库中的包,都有卓越的文档,对于 stings 包,其文档可在 Go 语言官网找到

在这个文档中,列出了所有被导出的标识符。假设您需要在程序中将字符串转换为小写,通过查看文档可知,有一个名为 ToLower 的函数。文档还指出这个函数将一个字符串作为参数,并返回一个字符串

func ToLower(s string) string

文档简单地描述了这个函数的作用,还包含一些演示其用法的示例代码。对于这些示例代码,可以直接在浏览器执行它们。如果还需要更深入地了解这个函数,还可以查看 strings 的源代码

使用第三方包

标准库提供了很多功能,但Go语言的设计理念是确保核心标准库小巧而稳定,因此标准库没有提供连接到数据库,分析文件格式以及实现身份验证协议的功能。不用多久,标准库就无法满足编写的需求了,这种情况下,通常有两种选择:

  • 自己编写解决问题的代码
  • 寻找能够解决问题的包 (或库代码)

大部分程序员很有可能选择第二种做法。然而,在程序中添加额外的依赖要三思而行,因为这可能影响程序的稳定性和可维护性。考虑使用第三方库时,应该考虑:

  • 是否明白了这些代码是做什么的
  • 这些代码值得信任吗
  • 这些代码的维护情况如何
  • 真的需要这个库吗

回答这些问题时,请注意:

  • 明白包的作用至关重要,优秀的第三方包都有卓越的文档,这些文档通常遵循 Go 语言文档约定,指出了它们导出了那些标识符。通过阅读文档,可确定包是否提供了您所需的功能

  • 确定第三方包值得信任很重要。别忘了,将包导入程序后,它就能访问底层的操作系统。要确定第三方包的可信任程序,可了解还有多少人在使用它、是否有同事推荐,还可阅读其源代码

  • 考虑到软件的特征,第三方包不可避免地存在 bug,不要选择几年都没有更新的包,而应选择开发方积极维护的第三方包,因为这意味着随着时间的推移,这样的包会越来越稳定

  • 导入第三方包会增加程序的复杂性。很多时候导入一个包只为了使用其中一个函数,这种情况下,可以复制这个函数,而不导入整个包

安装第三方包

要使用第三方库,必须向标准库一样 import 语句导入它

在下面的示例中,将使用 Go 小组开发的 stringutil 包。这是一个简单的第三方包,只有一个函数被导出——Reverse。这个函数将一个字符串作为参数,将该字符串反转并返回结果。

要使用第三方包,必须先使用命令 go get 安装它。这个命令默认随 Go 一起安装了,它将指向远程服务器中包的路径作为参数,并在本地安装指定的包

go get github.com/golang/example/stringutil

这个包被安装到环境变量 GOPATH 指定的路径中,因此可在程序中使用它。要查看这个包的源代码,可打开目录src中的文件。包的安装目录如下:

1
2
3
4
5
6
// OSX and Linux
%GOPATH/src/github/golang/example/stringutil

// Windows
%GOPATH%\src\github\golang\example\stringutil

安装这个包后,就可以导入它了

1
2
3
4
5
6
7
8
9
10
11
package main

import (
"fmt"
"github.com/golang/example/stringutil"
)

func main() {
s := "Hello"
fmt.Println(stringutil.Reverse(s))
}

执行后,它将反转 Hello :

olleH

通常,第三方包依赖于其他第三方包。命令 go get 会下载依赖的第三方包,而无需手动安装每个包依赖的第三方包

管理第三方依赖

很多语言有包管理器,可简化使用第三方包的工作:

  • Python 有 pip
  • .NET 有 Nuget
  • Ruby 有 RubyGems
  • Node.js 有 npm

在1.5版本时,Go 1.5引入了文件夹vendor,这能够让第三方模块添加到项目目录了下的文件夹vendor中,并将所有的包文件移到这个文件夹中,这样可以不全局地安装包,而仅在项目中安装它。对于前面安装的 stringutil包移到vendor目录后,仅能在项目中使用它, 它并不是全局的,这样的做法有一些优点:

  • 可锁定的版本,为此只需要特定版本赋值到项目目录中
  • 构建服务器无需下载依赖,因为它包含在这些项目中

如果您使用过包管理器,可能发现这种做法会有一些缺点

  • 依赖必须包含在仓库中
  • 使用的是包的哪个版本不明显
  • 没有处理包的依赖
  • 无法在清单文件中准确地指定提交或分支

当前,程序员必须手工管理复杂的依赖,有很多第三方工具使用 vendor 文件夹支持安装特定的包版本

go 1.11 版本后推出了 go module 功能,go module 只需要在本地保存 go.mod 文件以及用作校验的 go.sum 文件即可,并不用在 vendor 中下载保存依赖

关于 go module 的使用在后续的文章中会有单独的介绍,届时会更新本文,插入链接

创建包

除了使用第三方包以外,有时还可能需要创建包。文本将创建一个示例包,并将其发布到 Github 以便与人分享,这是一个处理温度的包,提供了在不同温度格式之间进行转化的函数,这创建一个名为 temperature.go 的文件,并在其中添加

1
2
3
4
5
6
7
8
9
package temperature

func CtoF(c float64) float64 {
return (c * (9 / 5)) + 32
}

func FtoC(c float64) float64 {
return (f - 32) * (9 / 5)
}

导入这个包后,就可使用其中所有以大写字母打头的标识符了。要创建私有标识符(变量,函数等),可让其以小写字母开头

为了测试这个包,可以创建一个 temperature_test.go 的测试文件,目前而言,只需知道这个文件对这个包进行测试就行了,后面的文章中将会单独介绍 测试

temperature.go

1
2
3
4
5
6
7
8
9
10
11
package temperature

type Temperature float64

func CtoF(c float64) Temperature {
return Temperature((c * 9 / 5) + 32)
}

func FtoC(f float64) Temperature {
return Temperature((f - 32) * 5 / 9)
}

temperature_test.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package temperature

import "testing"

type temperatureTest struct {
i float64
expected Temperature
}

var CtoFTests = []temperatureTest{
{4.1, 39.38},
{10, 50},
{-10, 14},
}

var FtoCTests = []temperatureTest{
{32, 0},
{50, 10},
{5, -15},
}


func TestCtoF(t *testing.T) {
for _, tt := range CtoFTests{
actual := CtoF(tt.i)
if actual != tt.expected {
t.Errorf("expected %v, actual %v", tt.expected, actual)
}
}
}

func TestFtoC(t *testing.T) {
for _, tt := range FtoCTests{
actual := FtoC(tt.i)
if actual != tt.expected {
t.Errorf("expected %v, actual %v", tt.expected, actual)
}
}
}

执行后:

1
2
3
4
5
=== RUN   TestCtoF
--- PASS: TestCtoF (0.00s)
=== RUN TestFtoC
--- PASS: TestFtoC (0.00s)
PASS

对于发布到网上的包,从用户的角度考虑问题很重要,因此推荐在包中包含以下文件:

  • 指出用户如何使用代码的 LICENSE 文件(如开源协议)
  • 包含有关包的说明信息的 README 文件
  • 详细说明包经过了哪些修改的 Changelog 文件

作为代码的创始者,如何授权开源由自己决定。有很多的开源方式,有些要求宽松,有的要求严格

在 ReadME 文件中,应包含有关包的信息,如何安装包以及如何使用包。您可能还想在其中包含有关如何参与改进项目的信息。如果将包发布到 Github,需要使用 Markdown 格式编写

在 Changelog 文件中,应列出对包所做的修改,这可能包含功能添加情况和API删除情况。通常,使用 git 标签来指示发布情况,能够让用户轻松地下载特定版本

问题列表

  • 如何将第三方包依赖复制到文件夹 vendor 中

    可以使用govendor作为包的管理工具,但随着 go 的版本更新,推荐使用 go module 作为包管理工具


[Go 入门] 第十二章 使用包实现代码复用
https://blog.forgiveher.cn/posts/1580543626/
Author
Mikey
Posted on
February 1, 2020
Licensed under