本文翻译自《Conclusion》。
在本教程中,你编写了封装到两个模块中的函数:一个发送问候语;另一个作为第一个的消费者。
注意:本主题是从《创建一个Go模块》开始的多部分教程的一部分。
有关管理代码中的依赖项的更多信息,请参阅管理依赖项。有关开发供他人使用的模块的更多信息,请参阅开发和发布模块。
有关Go语言的更多功能,请查看Go之旅。
本文翻译自《Conclusion》。
在本教程中,你编写了封装到两个模块中的函数:一个发送问候语;另一个作为第一个的消费者。
注意:本主题是从《创建一个Go模块》开始的多部分教程的一部分。
有关管理代码中的依赖项的更多信息,请参阅管理依赖项。有关开发供他人使用的模块的更多信息,请参阅开发和发布模块。
有关Go语言的更多功能,请查看Go之旅。
本文翻译自《Compile and install the application》。
在最后一个主题中,你将学习几个新的go命令。go run命令是在你频繁更改代码时,快捷地编译和运行程序的方式,但它不会生成二进制可执行文件。
本主题介绍了两个用于构建代码的附加命令:
go build命令编译包及其依赖项,但不安装结果。go install命令编译并安装包。注意:本主题是从《创建一个Go模块》开始的多部分教程的一部分。
1 从hello目录中的命令行运行go build命令,将代码编译为可执行文件。
$ go build
2 从hello目录中的命令行运行新的hello可执行文件,以确认代码有效。
请注意,你的结果可能会有所不同,具体取决于你是否在测试后更改了greetings.go里的代码。
在Linux或Mac上:
$ ./hello
map[Darrin:Great to see you, Darrin! Gladys:Hail, Gladys! Well met! Samantha:Hail, Samantha! Well met!]
在Windows上:
$ hello.exe
map[Darrin:Great to see you, Darrin! Gladys:Hail, Gladys! Well met! Samantha:Hail, Samantha! Well met!]
你已将应用程序编译为可执行文件,因此你可以运行它。但是当前要运行它,你的命令提示符需要位于可执行文件的目录中,或者指定可执行文件的路径。
接下来,你将安装可执行文件,这样你就可以在不指定路径的情况下运行它。
3 发现Go安装路径,go命令将在其中安装当前包。
你可以通过运行go list命令来发现安装路径,如以下示例所示:
$ go list -f '{{.Target}}'
例如,命令的输出可能是/home/gopher/bin/hello,这意味着二进制文件将安装到/home/gopher/bin。你在下一步中将需要此安装目录。
4 将Go安装目录添加到系统的shell路径中。
这样,你就可以运行程序的可执行文件而无需指出可执行文件的位置。
在Linux或Mac上,运行以下命令:
$ export PATH=$PATH:/path/to/your/install/directory
在Windows上,运行以下命令:
$ set PATH=%PATH%;C:\path\to\your\install\directory
作为替代方案,如果你的shell路径中已经有一个类似$HOME/bin的目录,并且你想在那里安装你的Go程序,你可以通过使用go env命令设置GOBIN环境变量来更改安装目标:
$ go env -w GOBIN=/path/to/your/bin
或
$ go env -w GOBIN=C:\path\to\your\bin
5 更新shell路径后,运行go install命令编译并安装包。
$ go install
6 只需键入名称即可运行你的应用程序。为了让它变得有趣,打开一个新的命令提示符窗口并在其他目录中运行hello可执行文件。
$ hello
map[Darrin:Hail, Darrin! Well met! Gladys:Great to see you, Gladys! Samantha:Hail, Samantha! Well met!]
本Go教程到此结束!(译者注:还有一总结篇)
本文翻译自《Add a test》。
现在你已经将代码放到了一个稳定的位置(顺便说一句,做得很好),接下来添加一个测试。在开发期间测试你的代码,可以发现更改代码造成的Bug。在本文中,你将为Hello函数添加一个测试。
注意:本主题是从《创建一个Go模块》开始的多部分教程的一部分。
Go对单元测试的内置支持使你可以轻松地进行测试。具体来说,使用命名约定、Go的testing和go test命令,你可以快速编写和进行测试。
1 在greetings目录中,创建一个名为greetings_test.go的文件。
以_test.go结尾的文件名告诉go test命令该文件包含测试函数。
2 在greetings_test.go中,粘贴以下代码并保存文件。
package greetings
import (
"testing"
"regexp"
)
// TestHelloName函数使用一个名字name调用greetings.Hello函数,检查返回值是否有效。
func TestHelloName(t *testing.T) {
name := "Gladys"
want := regexp.MustCompile(`\b`+name+`\b`)
msg, err := Hello("Gladys")
if !want.MatchString(msg) || err != nil {
t.Fatalf(`Hello("Gladys") = %q, %v, want match for %#q, nil`, msg, err, want)
}
}
// TestHelloEmpty函数传入空字符串调用greetings.Hello函数,检查是否会发生错误。
func TestHelloEmpty(t *testing.T) {
msg, err := Hello("")
if msg != "" || err == nil {
t.Fatalf(`Hello("") = %q, %v, want "", error`, msg, err)
}
}
在此代码中,你:
greetings.Hello函数。测试函数的名字的形式为TestName,其中Name表示有关特定测试的信息。此外,测试函数将指针指向testing包的testing.T类型作为参数。你可以使用此类型的方法在测试中进行报告和记录各种信息。TestHelloName函数调用Hello函数,传递一个name值,应该能够返回有效的响应消息。如果返回一个错误或一个意外消息(不包含你传入的name值),你可以使用t参数的Fatalf方法将消息打印到控制台并结束执行。
TestHelloEmpty函数使用空字符串调用Hello函数。此测试旨在确认你的错误处理是否有效。如果返回非空字符串或没有错误,则使用t参数的Fatalf方法将消息打印到控制台并结束执行。
3 在greetings目录下命令行运行go test命令执行测试。
go test命令执行测试文件(名称以_test.go结尾)中的测试函数(名称以Test开头)。你可以添加-v标志以获得所有测试及其结果的详细输出。
本测试应该可以通过。
$ go test
PASS
ok example.com/greetings 0.364s
$ go test -v
=== RUN TestHelloName
--- PASS: TestHelloName (0.00s)
=== RUN TestHelloEmpty
--- PASS: TestHelloEmpty (0.00s)
PASS
ok example.com/greetings 0.372s
4 破坏greetings.Hello函数以查看失败的测试结果。
测试TestHelloName函数检查你传递name参数给Hello函的返回值。要查看失败的测试结果,请更改greetings.Hello函数,使返回值中不再包含name参数的值。
在greetings/greetings.go中,粘贴以下代码代替Hello函数。请注意,突出显示的行更改该函数返回的值,就好像name参数被意外删除一样。
// Hello函数返回对指定名字的人员的一句问候语。
func Hello(name string) (string, error) {
// 如果name参数的值为空字符串,返回一个错误信息。
if name == "" {
return name, errors.New("empty name")
}
// 使用一个随机格式创建一个消息。
// message := fmt.Sprintf(randomFormat(), name)
message := fmt.Sprint(randomFormat())
return message, nil
}
5 在greetings目录下命令行运行go test命令执行测试。
这次,在没有-v标志的情况下运行go test。输出将仅包含失败测试的结果,这在你有大量测试时很有用。测试TestHelloName函数应该会失败——测试TestHelloEmpty函数仍然会通过。
$ go test
--- FAIL: TestHelloName (0.00s)
greetings_test.go:15: Hello("Gladys") = "Hail, %v! Well met!", <nil>, want match for `\bGladys\b`, nil
FAIL
exit status 1
FAIL example.com/greetings 0.182s
在下一个(也是最后一个)主题中,你将了解如何编译和安装代码以在本地运行它。
本文翻译自《Return greetings for multiple people》。
在你对模块代码所做的最后更改中,你将添加代码,在一个请求中获取对多个人的问候语。换句话说,你将处理多个值输入,然后对应地输出多个值。为此,你需要将一组名字传递给一个可以为每个名字返回一句问候语的函数。
注意:本主题是从《创建一个Go模块》开始的多部分教程的一部分。
但有一个问题。将Hello函数的参数从单个名字name更改为一组名字将会改变函数的签名。如果你已经发布了example.com/greeting模块,并且用户已经编写了调用Hello函数的代码,那么这种更改将破坏他们的程序。
在这种情况下,更好的选择是编写一个具有不同名称的新函数。新函数将采用多个参数。这保留了旧功能以实现向后兼容性。
1 在greetings/greetings.go中,更改你的代码,使其如下所示。
package greetings
import (
"errors"
"fmt"
"math/rand"
"time"
)
// Hello函数为给定名字的人返回一句问候语。
func Hello(name string) (string, error) {
// 如果name参数的值为空字符串,那么返回一条错误信息。
if name == "" {
return name, errors.New("empty name")
}
// 使用一个随机格式创建一条消息。
message := fmt.Sprintf(randomFormat(), name)
return message, nil
}
// Hellos函数返回一个映射map,用来关联每个给出名字的人和发给他的一句问候语消息。
func Hellos(names []string) (map[string]string, error) {
// 一个映射map,用来关联名字和消息。
messages := make(map[string]string)
// 循环遍历接收到的names切片,为其中每个名字调用Hello函数获取一条消息。
for _, name := range names {
message, err := Hello(name)
if err != nil {
return nil, err
}
//在这个映射map中,关联名字和对应的消息。
messages[name] = message
}
return messages, nil
}
// 初始化随机数种子
func init() {
rand.Seed(time.Now().UnixNano())
}
// randomFormat函数返回多个问候语消息中随机选择的其中一个。
func randomFormat() string {
// 一个消息格式切片。
formats := []string{
"Hi, %v. Welcome!",
"Great to see you, %v!",
"Hail, %v! Well met!",
}
// 返回一个随机选择的消息格式。
return formats[rand.Intn(len(formats))]
}
Hellos函数,其参数是一组名字而不是单个名字。此外,你将其返回类型之一从字符串更改为映射,以便你可以返回名字到问候语消息的映射。Hellos函数调用现有的Hello函数。这有助于减少重复,同时保留这两个函数。messages映射,将每个接收到的名字(作为关键字)与生成的消息(作为值)相关联。在Go中,使用以下语法初始化映射:make(map[key-type]value-type)。让Hellos函数将此映射返回给调用者。有关映射类型map的更多信息,请参阅Go博客上的实践Go map。for循环中,range返回两个值:循环中当前条目的索引和条目值的一个副本。你不需要索引,因此你使用Go空白标识符(一个下划线)来忽略它。有关更多信息,请参阅Effective Go中的空白标识符。2 在你的hello/hello.go的代码调用中,传递一个名字切片,然后打印输出你获得的名字/消息映射的内容。
package main
import (
"fmt"
"log"
"example.com/greetings"
)
func main() {
log.SetPrefix("greetings: ")
log.SetFlags(0)
// 一个名字切片
names := []string{"Gladys", "Samantha", "Darrin"}
// 为names请求响应的问候语消息
messages, err := greetings.Hellos(names)
if err != nil {
log.Fatal(err)
}
fmt.Println(messages)
}
通过这些更改,你可以:
names切片类型变量,包含三个名字。names变量作为参数传递给Hellos函数。3 在命令行中,切换到包含hello/hello.go的目录,然后使用go un运行并确认代码是否有效
输出应该是将名字与消息关联起来的映射的字符串表示,如下所示:
$ go run .
map[Darrin:Hail, Darrin! Well met! Gladys:Hi, Gladys. Welcome! Samantha:Hail, Samantha! Well met!]
本主题介绍了表示名/值对的映射类型。它还引入了通过为模块中的新功能或更改功能来实现新功能并保持向后兼容性的思想。有关向后兼容性的详细信息,请参阅保持模块兼容。
接下来,你将使用Go内置的功能为代码创建一个单元测试。
本文翻译自《Return a random greeting》。
在本节中,你将更改你的代码,以便它不会每次都返回同一句问候语,而是返回预定义的几句问候语之一。
注意:本主题是从《创建一个Go模块》开始的多部分教程的一部分。
为此,你将使用Go切片(slice)。切片类似于数组,不同之处在于它的大小会随着你添加和删除元素条目而动态变化。切片是Go中最有用的类型之一。
你将添加一个小切片以包含三句问候语,然后让你的代码随机返回其中一句。有关切片的更多信息,请参阅Go博客中的Go切片。
1 在greetings/greetings.go中,更改你的代码,使其如下所示。
package greetings
import (
"errors"
"fmt"
"math/rand"
"time"
)
// Hello函数对给出名字name的人员返回一句问候语。
func Hello(name string) (string, error) {
// 如果没有给出名字,返回一条错误消息。
if name == "" {
return name, errors.New("empty name")
}
// 使用一个随机格式创建一条消息。
message := fmt.Sprintf(randomFormat(), name)
return message, nil
}
// 使用时间戳初始化随机数种子。
func init() {
rand.Seed(time.Now().UnixNano())
}
// randomFormat函数返回一组问候语中的一句。返回的问候语是随机选择的。func randomFormat() string {
// 一个存储消息格式的切片。
formats := []string{
"Hi, %v. Welcome!",
"Great to see you, %v!",
"Hail, %v! Well met!",
}
// 通过为消息格式切片指定随机索引,返回随机选择的消息格式。
return formats[rand.Intn(len(formats))]
}
在此代码中,你:
randomFormat函数,该函数返回随机选择的问候语消息格式。请注意,randomFormat以小写字母开头,使其只能在其自身包中的被访问(换句话说,它不会被导出给其他包调用)。randomFormat函数中声明了包含三种消息格式的切片。声明切片时,在方括号中省略其大小,如下所示:[]string。这告诉Go,切片下的数组大小可以动态更改。math/rand包生成一个随机数,用于从切片中选择一个条目。init函数以使用当前时间戳为rand包生成随机数种子。Go在程序启动时,在全局变量初始化之后,自动执行init函数。有关init函数的更多信息,请参阅Effective Go。Hello函数中,调用randomFormat函数来获取你将返回的消息的格式,然后使用该格式和name参数的值一起创建一句问候语消息。2 在hello/hello.go中,更改你的代码,使其如下所示。
你将Gladys的名字(或其他名字,如果你愿意)作为参数添加到hello.go中的Hello函数调用。
package main
import (
"fmt"
"log"
"example.com/greetings"
)
func main() {
// 设置内置的日志记录器的属性,包括设置日志条目的前缀,设置标志0以不输出日志条目的时间、源文件名称和行号。
log.SetPrefix("greetings: ")
log.SetFlags(0)
// 请求一句问候语信息。
message, err := greetings.Hello("Gladys")
// 如果返回一个错误,将其打印到控制台并退出程序。
if err != nil {
log.Fatal(err)
}
// 如果没有返回错误,将返回的消息打印到控制台。
fmt.Println(message)
}
3 在命令行的hello目录中,运行hello.go以确认代码有效。多次运行它,注意问候语发生了变化。
$ go run .
Great to see you, Gladys!
$ go run .
Hi, Gladys. Welcome!
$ go run .
Hail, Gladys! Well met!
接下来,你将使用一个切片来问候多个人。
本文翻译自《Return and handle an error》。
能处理错误是可靠代码的基本特征。在本节中,你将添加一些代码以从greetings模块返回一个错误,然后在调用方处理它。
注意:本主题是从《创建一个Go模块》开始的多部分教程的一部分。
1 在greetings/greetings.go中,添加下面突出显示的代码。
如果你不知道该问候谁,回复一句问候语就没有意义。如果name为空,则向调用者返回一个错误。将以下代码复制到greetings.go并保存文件。
package greetings
import (
"errors"
"fmt"
)
// Hello函数向给出名字的人返回一句问候语。
func Hello(name string) (string, error) {
// 如果名字没有给出,就返回一个错误信息。
if name == "" {
return "", errors.New("empty name")
}
// 如果给出了名字,就返回一句嵌入了该名字的问候语。
// in a greeting message.
message := fmt.Sprintf("Hi, %v. Welcome!", name)
return message, nil
}
在此代码中,你:
Hello函数的代码,使其返回两个值:一个字符串和一个错误。你的调用者将检查第二个值以查看是否发生了错误。(任何Go函数都可以返回多个值。有关更多信息,请参阅Effective Go。)errors包,以便你可以使用其errors.New函数。if语句来检查无效请求(name为空字符串),如果请求无效则返回一个错误。errors.New函数返回一个错误,其中包含错误信息。nil(表示没有错误)作为第二个值。这样,调用者就可以看到函数成功返回了。2 在你的hello/hello.go文件中,处理Hello函数返回的错误以及非错误值。 将以下代码粘贴到hello.go中。
package main
import (
"fmt"
"log"
"example.com/greetings"
)
func main() {
// 设置预定义的日志记录器的属性,包括设置日志条目的前缀,设置标志0以禁用打印时间、源文件和行号。
log.SetPrefix("greetings: ")
log.SetFlags(0)
// 请求一句问候语。
message, err := greetings.Hello("")
// 如果返回错误,将其打印到控制台并退出程序。
if err != nil {
log.Fatal(err)
}
// 如果没有返回错误,将返回的消息打印到控制台。
fmt.Println(message)
}
log包以在其日志消息的开头打印命令名称 (“greetings: “),不带时间戳或源文件信息。Hello函数的两个返回值(包括错误)分配给变量。Hello函数参数从具体的名称更改为空字符串,以便你可以尝试运行错误处理代码。log包中的函数输出错误信息。如果出现错误,则使用log包的Fatal函数打印错误信息并停止程序。3 在hello目录的命令行中,运行hello.go以确认代码有效。
现在你传递的是一个空名称,你将收到一个错误。
$ go run .
greetings: empty name
exit status 1
这是Go中的常见的错误处理方式:将错误作为值返回,以便调用者可以检查它。
接下来,你将使用Go切片随机返回选择的一句问候语。
本文翻译自《Call your code from another module》。
在上一节中,你创建了一个greetings模块。在本节中,你将编写代码来调用刚刚编写的模块中的Hello函数。你将编写可作为应用程序执行的代码,并调用greetings模块中的代码。
注意:本主题是从《创建一个Go模块》开始的多部分教程的一部分。
1 为你的Go模块源代码创建一个hello目录。这是你将编写调用者的地方。
创建此目录后,你应该在目录层次结构中的同一级别拥有hello目录和greetings目录,如下所示:
<home>/
|-- greetings/
|-- hello/
例如,如果你的命令提示符位于greetings目录中,你可以使用以下命令:
cd ..
mkdir hello
cd hello
2 为你将要编写的代码启用依赖项跟踪。
要为你的代码启用依赖项跟踪,请运行go mod init命令,指定你的代码所在模块的名称。
出于本教程的目的,使用example.com/hello作为模块路径。
$ go mod init example.com/hello
go: creating new go.mod: module example.com/hello
3 在你的文本编辑器中,在hello目录中,创建一个用于编写代码的文件,并将其命名为hello.go。
4 编写代码调用Hello函数,然后打印函数的返回值。 为此,将以下代码粘贴到hello.go中。
package main
import (
"fmt"
"example.com/greetings"
)
func main() {
// 获取一句问候语并打印输出它。
message := greetings.Hello("Gladys")
fmt.Println(message)
}
在此代码中,你:
main包。在Go中,作为应用程序执行的代码必须在main包中。example.com/greetings和fmt包。这使你的代码可以访问这些包中的函数。导入example.com/greetings(你之前创建的模块中包含的包)可以让你访问Hello函数。你还导入fmt,具有处理输入和输出文本的功能(例如将文本打印到控制台)。Hello函数获取一句问候语。5 编辑example.com/hello模块以使用本地example.com/greetings模块。
对于生产环境,你将从代码仓库中发布example.com/greetings模块(具有反映其发布位置的模块路径),Go工具可以在其中找到它并进行下载。现在,因为你还没有发布该模块,所以你需要调整example.com/hello模块,以便它可以在你的本地文件系统上找到example.com/greetings代码。
为此,请使用go mod edit命令编辑example.com/hello模块,将Go工具从其模块路径(模块不在的位置)重定向到本地目录(它所在的位置)。
5.1 从hello目录中的命令行提示符运行以下命令:
$ go mod edit -replace example.com/greetings=../greetings
该命令指定example.com/greetings应替换为../greetings以定位依赖项。运行该命令后,hello目录中的go.mod文件应包含一个replace指令:
module example.com/hello
go 1.16
replace example.com/greetings => ../greetings
5.2 从hello目录中的命令行提示符运行go mod tidy命令以同步example.com/hello模块的依赖项,添加代码所需但尚未在模块中跟踪的依赖项。
$ go mod tidy
go: found example.com/greetings in example.com/greetings v0.0.0-00010101000000-000000000000
命令完成后,example.com/hello模块的go.mod文件应该如下所示:
module example.com/hello
go 1.16
replace example.com/greetings => ../greetings
require example.com/greetings v0.0.0-00010101000000-000000000000
该命令在greetings目录中找到本地代码,然后添加require指令以指定example.com/hello需求example.com/greetings。当你在hello.go中导入greetings包时,你就创建了这个依赖项。
模块路径后面的数字是一个伪版本号(pseudo-version number)——一个生成的数字,用来代替语义版本号(该模块目前还没有)。
要引用已发布的模块,go.mod文件通常会省略replace指令并使用末尾带有标签版本号的require指令。
require example.com/greetings v1.1.0
有关版本号的更多信息,请参阅模块版本编号。
6 在hello目录中的命令行提示符下,运行你的代码以确认它是否有效。
$ go run .
Hi, Gladys. Welcome!
恭喜!你已经编写了两个功能模块。
在下一主题中,你将添加一些错误处理。
本文翻译自《Tutorial: Create a Go module》。
目录
这是介绍Go语言的一些基本特性的教程的第一部分。如果你刚刚开始使用Go,请务必查看教程:Go入门,其中介绍了go命令、Go模块和非常简单的Go代码。
在本教程中,你将创建两个模块。第一个是旨在由其他库或应用程序导入的库。第二个是将调用第一个模块的应用程序。
本教程的顺序包括七个简短的主题,每个主题都说明了该语言的不同部分。
1 创建一个模块——编写一个小模块,其中包含可以被另一个模块调用的函数。
2 从另一个模块调用你的代码——导入并使用你的新模块。
3 返回并处理一个错误——添加简单的错误处理程序。
4 返回一个随机问候语——处理切片中的数据(Go的动态大小数组)。
5 回复多个人的问候语——在一个映射(map)中存储键/值对。
6 添加一个测试——使用Go内置的单元测试功能来测试你的代码。
7 编译和安装这个应用程序——在本地编译和安装你的代码。
注意:有关其他教程,请参阅教程。
先决条件
创建一个其他人可以使用的模块
首先创建一个Go模块。在一个模块中,一组离散且有用的功能集合构成一个或多个相关包。例如,你可以创建一个模块,其中的包具有进行财务分析的功能,以便其他编写财务应用程序的人可以使用你的工作。有关开发模块的更多信息,请参阅开发和发布模块。
Go代码被分组到包中,包又被分组到模块中。你的模块指定运行代码所需的依赖项,包括Go版本和它需要的其他模块集。
当你在模块中添加或改进功能时,你发布了该模块的新版本。编写调用模块中函数的代码的开发人员可以导入模块的更新后的包并可以在将其投入生产使用之前使用新版本进行测试。
1 打开命令行提示符并cd到你的家目录。
在Linux或Mac上:
cd
在Windows上:
cd %HOMEPATH%
2 为你的Go模块源代码创建一个greetings目录。
例如,在你的家目录使用以下命令:
mkdir greetings
cd greetings
3 使用go mod init命令启动你的模块。
运行go mod init命令,并传给它你的模块路径作为参数——在这里,使用example.com/greetings。如果你发布一个模块,这必须是该模块可以被Go工具下载的路径的一部分。另一部分路径将是你的代码存储库的URL。
有关使用模块路径命名模块的更多信息,请参阅管理依赖项。
$ go mod init example.com/greetings
go: creating new go.mod: module example.com/greetings
go mod init命令创建一个go.mod文件来跟踪代码的依赖项。到目前为止,该文件仅包含你的模块名称和你的代码支持的Go版本。但是当你添加依赖项时,go.mod文件将列出你的代码所依赖的版本。这使构建可重现,并让你直接控制要使用的模块及其版本。
4 在你的文本编辑器中,创建一个用于编写代码的文件,并将其命名为greetings.go。
5 将以下代码粘贴到你的greetings.go文件中并保存该文件。
package greetings
import "fmt"
// Hello函数返回对名字由name参数给出的人的一句问候语。
func Hello(name string) string {
// 返回一句问候语,把name参数的值嵌入其中。
message := fmt.Sprintf("Hi, %v. Welcome!", name)
return message
}
这是你的模块的第一个代码块。它向任何请求Hello函数的调用者返回一句问候语。你将在下一步中编写调用此函数的代码。
在此代码中,你:
greetings包来收集相关的功能。Hello函数来返回一句问候语。该函数接受一个name参数,其类型为字符串(string)类型。该函数还返回一个字符串。在Go中,一个名字以大写字母开头的函数可以被不在同一个包中的函数调用。这在Go中被称为导出名称(Exported names)。有关导出名称的更多信息,请参阅Go教程中的导出名称。

message变量来保存你的问候语。在Go中,:=运算符是一种在一行中声明和初始化变量的快捷方式(Go使用右侧的值来确定变量的类型)。从长远来看,你可能会这样写:
var message string
message = fmt.Sprintf("Hi, %v. Welcome!", name)
fmt包的Sprintf函数创建一句问候语。第一个参数是格式字符串,Sprintf将name参数的值替换为%v格式的谓词。插入name参数的值即可完成这个问候语文本。在下一步中,你将从另一个模块调用此函数。
Windows安全中心->病毒和威胁防护->威胁历史记录->检查出的威胁->还原
本文翻译自《Managing dependencies》。
目录
当你的代码使用外部包时,这些包(作为模块分发)成为依赖项。随着时间的推移,你可能需要升级或更换它们。Go提供了依赖项管理工具,可帮助你在合并外部依赖项时确保Go应用程序的安全。
本主题描述如何执行任务来管理你在代码中采用的依赖项。你可以使用Go工具执行其中的大部分操作。本主题还描述了如何执行一些你可能会觉得有用的其他依赖相关的任务。
另请参阅
使用和管理依赖项的工作流程
你可以通过Go工具获取和使用有用的包。在pkg.go.dev上,你可以搜索你可能觉得有用的包,然后使用go命令将这些包导入到你自己的代码中以调用它们的功。
下面列出了最常见的依赖管理步骤。有关每个步骤的更多信息,请参阅本主题中的对应小节。
1 在pkg.go.dev上找到有用的包。
2 在代码中导入所需的包。
3 将你的代码添加到模块中以进行依赖跟踪(如果它不在模块中的话)。请参阅启用依赖项跟踪。
4 添加外部包作为依赖项,以便你可以管理它们。
5 随着时间的推移,根据需要升级或降级依赖项的版本。
管理模块依赖项
在Go中,你将依赖项作为模块来管理,在代码中导入这些模块的包。该过程得到以下支持:
查找和导入有用的包
你可以搜索pkg.go.dev来查找包含可能有用的函数的包。
找到要在代码中使用的包后,在页面顶部找到包路径,然后单击复制路径按钮将路径复制到剪贴板。在你自己的代码中,将路径粘贴到import语句中,如下例所示:
import "rsc.io/quote"
在代码导入包后,启用依赖项跟踪并获取要编译的包代码。有关更多信息,请参阅在代码中启用依赖项跟踪和添加依一个赖项。
在代码中启用依赖项跟踪
要跟踪和管理你添加的依赖项,你首先要将代码放入其自己的模块中。这会在源代码树的根目录下创建一个go.mod文件。你添加的依赖项将列在该文件中。 要将你的代码添加到它自己的模块中,请使用go mod init命令。例如,从命令行切换到代码的根目录,然后按以下示例运行命令:
$ go mod init example/mymodule
go mod init命令的参数是模块的模块路径。如果可能,模块路径应该是源代码的存储库的位置。
如果一开始你不知道模块的最终存储库位置,请使用一个安全的替代品。这可以是你拥有的域名或你控制的其他名称(如公司名称),以及模块名称或源代码目录的路径。有关详细信息,请参见给一模块个命名。
当你使用Go工具管理依赖关系时,这些工具会更新go.mod文件,以便它维护你当前的依赖项列表。
添加依赖项时,Go工具也会创建一个go.sum文件,其中包含你所依赖的模块的校验和。Go使用该文件来验证下载的模块文件的完整性,尤其是对其他项目开发人员而言。
把go.mod文件和go.sum文件和代码一起放在代码存储库中。
有关更多信息,请参阅go.mod参考手册。
给一个模块命名
当你运行go mod init以创建用于跟踪依赖项的模块时,你指定一个模块路径作为模块的名称。模块路径是模块中的包的导入路径的前缀。请务必指定一个不会与其他模块的模块路径有冲突的模块路径。
模块路径至少要指出有关其来源的信息,例如公司名称或作者名称或所有者姓名。但是模块路径最好也能描述模块是什么或做什么。 模块路径通常采用以下形式:
<prefix>/<descriptive-text>
前缀prefix通常是部分描述模块的字符串,例如描述模块来源的字符串。这可能是:
github.com/<project-name>/。如果你认为可以发布模块供其他人使用,请使用此最佳实践。有关发布的详细信息,请参阅开发和发布模块。对于描述性文本descriptive-text,最好选择项目名称。请记住,包的名称才是主要描述功能信息的。模块路径为这些包的名称创建了一个命名空间。
保留的模块路径前缀
Go保证不会在包名中使用以下字符串。
go mod init test,然后以某种特定方式安装该模块,以便使用Go源代码分析工具进行测试。请注意,当示例可能是一个已发布的模块时,Go文档也会使用example.com来演示。
添加一个依赖项
从已发布的模块导入包后,可以使用go get命令将该模块添加为依赖项进行管理。
该命令执行以下操作:
下面描述几个例子。
$ go get .$ go get example.com/theirmodule该命令还验证它下载的每个模块。这确保它在模块发布后没有发生改变。如果模块在发布后发生了变化——例如,开发人员更改了提交(commit)的内容——Go工具将显示一个安全错误。此身份验证检查可保护你免受可能已被篡改的模块的侵害。
获取一个指定版本的依赖项
你可以通过在go get命令中指定版本号来获取依赖模块的特定版本。该命令更新go.mod文件中的require指令(当然你也可以手动更新)。
你可能想要那样做,如果:
以下是使用go get命令的示例:
$ go get example.com/[email protected]$ go get example.com/theirmodule@latest以下go.mod文件的require指令示例(有关更多信息,请参阅go.mod参考手册)说明了如何需求特定版本号:
require example.com/theirmodule v1.3.4
发现可用的更新
你可以检查当前模块中是否已经使用了更新版本的依赖项。使用go list命令显示模块的依赖项列表,以及该模块可用的最新版本。发现可用的升级后,你可以使用你的代码进行试用,以决定是否升级到新版本。
有关go list命令的更多信息,请参阅go list -m。
这里有几个例子。
$ go list -m -u all$ go list -m -u example.com/theirmodule升级或降级一个依赖项
你可以通过使用Go工具发现可用版本来升级或降级依赖的模块,然后将不同的版本添加为依赖项。
1 要发现新版本,请使用go list命令,如发现可用更新小节所述。
2 要将特定版本添加为依赖项,请使用获取一个指定版本的依赖项小节所述的go get命令。
同步你代码的依赖项
你可以管理所有导入的包对应的依赖项,同时删除不再使用的依赖项。
当你一直在更改代码和依赖项时,这可能很有用,可能会创建一个你管理的依赖项和下载模块的集合,这些模块不再与代码中导入的包依赖的模块集合相匹配。
要保持依赖项集合整洁,请使用go mod tidy命令。对于代码中导入的包集,此命令编辑你的go.mod文件以添加必需但缺少的模块。它还会删除不提供任何相关包的未使用的模块。
该命令除了一个标志-v外没有其他参数,它打印有关已删除模块的信息。
$ go mod tidy
开发和测试未发布的模块的代码
你可以指定你的代码使用可能还未发布的依赖项模块。这些模块的代码可能在它们各自的存储库中,在这些存储库的分支中,或者在当前依赖它们的模块所在的本地硬盘里。
你可能希望在以下情况下执行此操作:
go get命令访问不到它所在的存储库。需求本地目录中的某个模块的代码
你可以指定所需模块的代码与依赖它的代码位于同一本地硬盘里。当你处于以下情况时,你可能会发现这很有用:
要让Go命令使用模块代码的本地副本,请在go.mod文件中使用replace指令替换require指令中给出的模块路径。有关指令的更多信息,请参阅go.mod参考手册。
在下面的go.mod文件示例中,当前模块依赖外部模块example.com/theirmodule,使用不存在的版本号(v0.0.0-unpublished)来确保替换正常工作。replace指令然后用../theirmodule替换原始模块路径,该目录与当前模块目录处于同一级别。
module example.com/mymodule
go 1.16
require example.com/theirmodule v0.0.0-unpublished
replace example.com/theirmodule v0.0.0-unpublished => ../theirmodule
设置require/replace对时,使用go mod edit和go get命令确保文件描述的依赖项保持一致:
$ go mod edit -replace=example.com/[email protected]=../theirmodule
$ go get example.com/[email protected]
注意:当你使用replace指令时,Go工具不会验证外部模块,如添加一个依赖项小节所述。
有关版本号的更多信息,请参阅模块版本编号。
需求来自你自己的代码仓库分支的外部模块的代码
当你fork一个外部模块的代码仓库时(例如修复模块代码中的问题或添加新功能),你可以让Go工具使用你fork的代码作为模块的源代码。这对于测试你自己的代码的更改很有用。(请注意,你还可以在本地硬盘目录中需求模块代码。有关更多信息,请参阅需求本地目录中的某个模块的代码小节。)
为此,你可以在go.mod文件中使用replace指令,将外部模块的原始模块路径替换为你fork的代码仓库的路径。这会指示Go工具在编译时使用替换路径(fork的代码仓库的路径),同时允许你保持你代码里的import语句里的原始模块路径保持不变。
有关replace指令的更多信息,请参阅go.mod文件参考手册。
在下面的go.mod文件示例中,当前模块需求外部模块example.com/theirmodule。然后replace指令使用example.com/myfork/theirmodule替换原始模块路径,这是模块代码仓库的你的一个分支。
module example.com/mymodule
go 1.16
require example.com/theirmodule v1.2.3
replace example.com/theirmodule v1.2.3 => example.com/myfork/theirmodule v1.2.3-fixed
设置require/replace对时,最好使用Go工具命令,以确保与go.mod文件中描述的需求保持一致。使用go list命令查看当前使用的模块的版本。然后使用go mod edit命令用fork的分支替换所需的模块:
$ go list -m example.com/theirmodule
example.com/theirmodule v1.2.3
$ go mod edit -replace=example.com/[email protected]=example.com/myfork/[email protected]
注意:当你使用replace指令时,Go工具不会对外部模块进行身份验证,如添加一个依赖项小节中所述。
有关版本号的更多信息,请参阅模块版本编号。
使用代码仓库标识符获取特定的某个提交(commit)
你可以使用go get命令从某个模块的代码仓库中,提取指定某个commit里的未发布(译者注:未加版本号标签)的代码。
为此,你可以使用go get命令,并使用@符号指定你想要的代码。当你使用go get时,该命令将向你的go.mod文件添加一个需求外部模块的require指令,使用基于commit的详细信息的伪版本号。
以下示例提供了一些说明。这些基于一个模块,其源代码位于git存储库中。
@commithash(译者注:@commit的哈希值)形式:$ go get example.com/theirmodule@4cf76c2@branchname(译者注:@分支名称)形式:$ go get example.com/theirmodule@bugfixes移除一个依赖项
当你的代码不再使用某个模块中的任何包时,你可以停止将该模块作为依赖项进行追踪。
要停止追踪所有未使用的模块,请运行go mod tidy命令。此命令还可以在构建模块的包时添加所需的缺失依赖项。
$ go mod tidy
要删除特定依赖项,请使用go get命令,指定该模块的模块路径并附加@none,如下例所示:
$ go get example.com/theirmodule@none
go get命令还将降级或删除依赖于已删除模块的其他依赖项。
指定一个模块代理服务器
当你使用Go工具处理模块时,这些工具默认从proxy.golang.org(Google运营的公共模块镜像)或直接从模块的代码仓库下载模块。你可以指定Go工具使用另一个代理服务器来下载和验证模块。例如,一些人设置了一个自己的模块代理服务器,以便更好地控制依赖项的使用方式。
要指定另外的模块代理服务器供Go工具使用,请将GOPROXY环境变量设置为一个或多个另外的模块代理服务器的URL。Go工具将按照你指定的顺序尝试每个URL。默认情况下,GOPROXY首先指定一个Google运营的公共的模块代理,然后直接从模块的存储库中下载模块代码(由其模块路径指出):
GOPROXY="https://proxy.golang.org,direct"
有关GOPROXY环境变量(包括其值支持的他行为)的详细信息,请参阅go命令参考手册。
你可以将变量设置为其他模块代理服务器的URL,用逗号或管道分隔多个URL。
GOPROXY="https://proxy.example.com,https://proxy2.example.com"GOPROXY="https://proxy.example.com|https://proxy2.example.com"Go模块经常在版本控制服务器和模块代理上开发和分发,有些服务器和代理在公共互联网上不可用。此时你可以设置GOPRIVATE环境变量来配置go命令,以便从私有源下载和构建模块。
GOPRIVATE或GONOPROXY环境变量可以设置为与模块前缀匹配的全局模式列表,与这些模式匹配的模块是私有的,不应该从中代理任何请求。例如:
GOPRIVATE=*.corp.example.com,*.research.example.com