古老的榕树

Go 语言的包

发表 2016-04-12 21:08 阅读(2177)
包是函数和数据的集合。用 package 关键字定义一个包。文件名不需要与包名一致。包名的约定是使用小写字符。 Go 包可以由多个文件组成,但是使用相同的 package <name> 这一行。让我们在文件 even.go 中定义一个叫做 even 的包。

Listing 3.1. 一个小包

package even ← 开始自定义的包
func Even(i int) bool { ← 可导出函数
return i % 2 == 0
}
func odd(i int) bool { ← 私有函数
return i % 2 == 1
}

名称以大写字母起始的是可导出的,可以在包的外部调用(稍候会对此进行讨论)。现在只需要构建这个包。在 $GOPATH 下建立一个目录,复制 even.go 到这个目录:
% mkdir $GOPATH/src/even
% cp even.go $GOPATH/src/even
% go build
% go install

现在就可以在程序 myeven.go 中使用这个包:

Listing 3.2. even 包的使用

package main

import ( 0.
"even" 1.
"fmt" 2.
)

func main() {
i := 5
fmt.Printf("Is %d even? %v\n", i, even.Even(i)) 3.
}


0 . 导入下面的包;
1 . 本地包 even 在这里导入;
2 . 官方 fmt 包导入;
3 . 调用 even 包中的函数。访问一个包中的函数的语法是 <package>.Function()。

% go build myeven.go
% ./myeven
Is 5 even? false

在 Go 中,当函数的首字母大写的时候,函数会被从包中导出(在包外部可见,或者说公有的),因此函数名是 Even。如果修改 myeven.go 的第 10 行,使用未导出的函数 even.odd:
fmt.Printf("Is %d even? %v\n", i, even.odd(i))

由于使用了私有的函数,会得到一个编译错误:
myeven.go:10: cannot refer to unexported name even.odd

概括来说:
• 公有函数的名字以大写字母开头;
• 私有函数的名字以小写字母开头。
这个规则同样适用于定义在包中的其他名字(新类型、全局变量)。注意, “大写” 的含义并不仅限于 US ASCII,它被扩展到了所有大小写字母表(拉丁文、希腊文、斯拉夫文、亚美尼亚文和埃及古文)。


标识符



像在其他语言中一样, Go 的命名是很重要的。在某些情况下,它们甚至有语义上的作用:例如,在包外是否可见决定于首字母是不是大写。因此有必要花点时间讨论一下 Go 程序的命名规则。

使用的规则是让众所周知的缩写保持原样,而不是去尝试到底哪里应该大写。 Atoi,Getwd, Chmod。

驼峰式对那些有完整单词的会很好: ReadFile, NewWriter, MakeSlice。


包名


当包导入(通过 import)时,包名成为了内容的入口。在
import "bytes"

之后,导入包的可以调用函数 bytes.Buffer。任何使用这个包的人,可以使用同样的名字访问到它的内容,因此这样的包名是好的:短的、简洁的、好记的。根据规则,包名是小写的一个单词;不应当有下划线或混合大小写。保持简洁(由于每个人都可能需要录入这个名字),不要过早考虑命名冲突。

包名是导入的默认名称。可以通过在导入语句指定其他名称来覆盖默认名称:
import bar "bytes"

函数 Buffer 现在可以通过 bar.Buffer 来访问。这意味着,包名无需全局唯一;在少有的冲突中,可以给导入的包选择另一个名字在局部使用。在任何时候,冲突都是很少见的,因为导入的文件名会用来做判断,到底是哪个包使用了。

另一个规则是包名就是代码的根目录名;在src/pkg/compress/gzip 的包,作为 compress/gzip 导入,但名字是 gzip,不是 compress_gzip 也不是 compressGzip。

导入包将使用其名字引用到内容上,所以导入的包可以利用这个避免罗嗦。例如,缓冲类型 bufio 包的读取方法,叫做 Reader,而不是 BufReader,因为用户看到的是 bufio.Reader 这个清晰、简洁的名字。更进一步说,由于导入的实例总是它们包名指向的地址, bufio.Reader 不会与 io.Reader 冲突。类似的, ring.Ring(包 container/ring)创建新实例的函数——在 Go 中定义的构造函数——通常叫做 NewRing,但是由于 Ring 是这个包唯一的一个导出的类型,同时,这个包也叫做 ring,所以它可以只称作 New。包的客户看到的是 ring.New。用包的结构帮助你选择更好的名字。另外一个简短的例子是 once.Do(参看 sync); once.Do(setup) 读起来很不错,并且命名为 once.DoOrWaitUntilDone(setup) 不会有任何帮助。长的名字不会让其变得
容易阅读。如果名字表达了一些复杂并且微妙的内容,更好的办法是编写一些有帮助的注释,而不是将所有信息都放入名字里。

最后,在 Go 中使用混合大小写 MixedCaps 或者 mixedCaps,而不是下划线区分含有多个单词的名字。


包的文档


每个包都应该有包注释,在 package 前的一个注释块。对于多文件包,包注释只需要出现在一个文件前,任意一个文件都可以。包注释应当对包进行介绍,并提供相关于包的整体信息。这会出现在 go doc 生成的关于包的页面上,并且相关的细节会一并显示。来自官方 regexp 包的例子:

/*
The regexp package implements a simple library for
regular expressions.
The syntax of the regular expressions accepted is:
regexp:
concatenation '|' concatenation
*/

package regexp

每个定义(并且导出)的函数应当有一小段文字描述该函数的行为。来自于 fmt 包的例子:

// Printf formats according to a format specifier and writes to standard
// output. It returns the number of bytes written and any write error
// encountered.
func Printf(format string, a ...interface) (n int, err error)

测试包


在 Go 中为包编写单元测试应当是一种习惯。编写测试需要包含 testing 包和程序 go test。两者都有良好的文档。

go test 程序调用了所有的测试函数。 even 包没有定义任何测试函数,执行 go test,这样:

% go test
? even [no test files]

在测试文件中定义一个测试来修复这个。 测试文件也在包目录中, 被命名为 *_test.go。这些测试文件同 Go 程序中的其他文件一样,但是 go test 只会执行测试函数。每个测试函数都有相同的标识,它的名字以 Test 开头:
func TestXxx(t *testing.T)

编写测试时,需要告诉 go test 测试是失败还是成功。测试成功则直接返回。当测试失败可以用下面的函数标记 [11]。这是非常重要的(参阅 go doc testing 或 go help testfunc 了解更多):
func (t *T) Fail()

Fail 标记测试函数失败,但仍然继续执行。

func (t *T) FailNow()
FailNow 标记测试函数失败,并且中断其执行。当前文件中的其余的测试将被跳过,然后执行下一个文件中的测试。

func (t *T) Log(args ...interface { })
Log 用默认格式对其参数进行格式化,与 Print() 类似,并且记录文本到错误日志。

func (t *T) Fatal(args ...interface { })
Fatal 等价于 Log() 后跟随 FailNow()。

将这些凑到一起,就可以编写测试了。首先,选择名字 even_test.go。然后添加下面的内容:

Listing 3.3. even 包的测试

package even 1
import "testing" 3
func TestEven(t *testing.T) { 5
i f ! Even(2) { 6
t.Log("2 should be even!") 7
t.Fail() 8
} 9
}


注意在第一行使用了 package even,测试使用与被测试的包使用相同的名字空间。这不仅仅是为了方便,也允许了测试未导出的函数和结构。然后导入 testing 包,并且在第 5 行定义了这个文件中唯一的测试函数。展示的 Go 代码应当没有任何惊异的地方:
检查了 Even 函数是否工作正常。现在等待了好久的时刻到了,执行测试:

% go test
ok even 0.001s

测试执行并且报告 ok。成功了!
如果重新定义测试函数,就可以看到一个失败的测试:

// Entering the twilight zone
func TestEven(t *testing.T) {
i f Even(2) {
t.Log("2 should be odd!")
t.Fail()
}
}

然后得到:
FAIL even 0.004s
--- FAIL: TestEven (0.00 seconds)
2 should be odd!
FAIL

然后你可以以此行事(修复测试的实例)在编写包的时候应当一边写代码,一边写(一些)文档和测试函数。这可以让你的
程序更好,并且它展示了你的努力。

The Go test suite also allows you to incorperate example functions which serve as documentation and as tests. These functions need to start with Example.

func ExampleEven() {
if Even(2) {
fmt.Printf("Is even\n")
}
// Output:
// Is even
}

Those last two comments lines are part of the example, go test uses those to check the
generated output with the text in the comments. If there is a mismatch the test fails.


来源《学习 Go 语言》

Donate

如果文章对您有帮助,请使用手机支付宝扫描二维码,捐赠X元,作者离不开读者的支持!