模块版本编号

本文翻译自《Module version numbering》。

目录

开发过程中

伪版本号

v0版本号

预发布版本号

次版本号

补丁程序版本号

主版本号

模块的开发人员使用模块版本号的每个部分来表示版本的稳定性和向后兼容性。对于每个新版本,模块的版本号具体反映了自上一个版本以来模块更改的性质。

当你开发使用外部模块的代码时,可以在升级时使用版本号来了解外部模块的稳定性。当你开发自己的模块时,你的版本号将向其他开发人员指示模块的稳定性和向后兼容性。

本主题描述模块版本号的含义。

另请参阅

  • 当你在代码中使用外部包时,可以使用Go工具管理这些依赖关系。有关详细信息,请参见管理依赖关系
  • 如果你正在开发供其他人使用的模块,则在发布模块时使用版本号,并在其代码存储库中加标签。有关详细信息,请参见发布一个模块

发布的模块使用语义版本号控制模型给出的版本号,如下图所示:

下表描述了版本号的各个部分如何表示模块的稳定性和向后兼容性。

各阶段的版本号示例给开发者的信息
开发过程中自动给出伪版本号 v0.x.x表明模块仍在开发中且不稳定。此版本没有向后兼容性或稳定性保证。
主版本号(Major version)v1.x.x表示向后不兼容的公共API更改。此版本不保证它将与以前的主要版本向后兼容。
次版本号(Minor version)vx.4.x表示向后兼容的公共API更改。此版本保证了向后兼容性和稳定性。
补丁程序版本号(Patch version)vx.x.1表示不影响模块的公共API或其依赖关系的更改。此版本保证了向后兼容性和稳定性。
预发布版本号(Pre-release version)vx.x.x-beta.2表明这是一个预发布里程碑,例如alphabeta。此版本没有稳定性保证。

开发过程中

表示该模块仍在开发中且不稳定。此版本不提供向后兼容性或稳定性保证。

版本号可以采用以下形式之一:

伪版本号(pseudo-version number)

v0.0.0-20170915032832-14c0d48ead0c

v0版本号

v0.x.x

伪版本号

当模块未在其代码存储库中打标签时,Go工具将生成一个伪版本号,供调用该模块中的函数的代码的go.mod文件使用。

注意:作为最佳实践,始终允许Go工具生成伪版本号而不是创建自己的版本号。

当使用该模块功能的代码开发人员需要针对尚未加语义版本号标签的commit进行开发时,伪版本号就很有用。

伪版本号由破折号分隔的三部分组成,如下表所示:

语法

baseVersionPrefix-timestamp-revisionIdentifier

部分

  • baseVersionPrefix(vX.0.0或vX.Y.Z-0)是从之前的语义版本号标签或从vX.0.0(如果没有语义版本号标签)派生的值。
  • timestamp(yymmddhhmms)是创建修订号的UTC时间。在Git中,这是提交时间,而不是作者的时间。
  • revisionIdentifier(abcdefabdedef)是commit哈希值的12个字符前缀,或者在Subversion中是用0填充的修订号。

v0版本号

使用v0编号发布的模块将具有正式的语义版本号,其中包含主、次和补丁部分,以及可选的预发布标识符。

虽然v0版本可以在生产中使用,但它不能保证稳定性或向后兼容性。此外,版本v1和更高版本允许破坏v0版本的代码的向后兼容性。因此,使用v0模块中的代码的开发人员应该明白将会有不兼容的更改,直到v1发布。

预发布版本号

表明这是一个预发布里程碑,如alpha或beta。此版本没有稳定性保证。

示例

vx.x.x-beta.2

模块的开发人员可以通过添加连字符和预发布标识符,与任何major.minor.patch组合成预发布版本号。

次版本号

表明模块的公共API具有向后兼容的更改。此版本保证向后兼容性和稳定性。

示例

vx.4.x

此版本更改了模块的公共API,但并未以破坏调用代码的方式进行。这可能包括更改模块自身的依赖项或添加新函数、方法、结构体字段或类型。

换句话说,此版本可能包含新功能以进行增强,其他开发人员可能想要使用这些新功能。但是,使用以前的次版本的开发人员无需更改他们的代码。

补丁程序版本号

表示不影响模块的公共API或其依赖项的更改。此版本保证向后兼容性和稳定性。

示例

vx.x.1

增加此数字的版本更新仅适用于很小的更改,如Bug修复。使用该版本的开发人员可以安全地升级到此版本,而无需更改代码。

主版本号

表明模块的公共API有向后不兼容的更改。此版本不保证它与以前的主版本向后兼容。

示例

v1.x.x

v1或更高版本号表示模块可稳定使用(它的预发布版本除外)。

请注意,因为版本0没有稳定性或向后兼容性保证,所以将模块从v0升级到v1的开发人员负责调整破坏向后兼容性的更改。

只有在必要时,模块开发人员才应该将这个数字增加到v1以上,因为版本升级对代码使用该模块中的功能的开发人员来说意味着严重的中断。这种中断包括对公共API向后不兼容的更改,以及从该模块导入包时可能需要更新包路径。

如果主版本更新到高于v1的版本,也会有一个新的模块路径。这是因为模块路径将附加主版本号,如下例所示:

module example.com/mymodule/v2 v2.0.0

主版本更新使其成为一个新模块,其历史与模块的先前版本不同。如果你正在开发要为其他人发布的模块,请参阅模块发布和版本控制工作流中的“发布破坏API的更改”。

有关模块指令的更多信息,请参阅go.mod参考

教程:Go入门

本文翻译自《Tutorial: Get started with Go》。

目录

先决条件

安装Go

写一些代码

在外部包中调用代码

写更多的代码

在本教程中,你将简要介绍Go编程。在此过程中,你将:

  • 安装Go(如果尚未安装)。
  • 编写一些简单的“Hello, world”代码。
  • 使用go命令运行你的代码。
  • 使用Go包工具查找可在你的代码中使用的包。
  • 调用外部模块的函数。

注意:有关其他教程,请参阅教程

先决条件

  • 一些编程经验。此处的代码非常简单,但有助于了解一些有关函数的知识。
  • 一个编辑代码的工具。你拥有的任何文本编辑器都可以正常工作。大多数文本编辑器都对Go有很好的支持。最受欢迎的是VSCode(免费)、GoLand(付费)和Vim(免费)。
  • 一个命令行终端。Go在Linux和Mac上的任何终端,以及Windows中的PowerShell或cmd上都能很好地工作。

安装Go

只需使用下载和安装步骤。

写一些代码

从Hello,World开始。

1 打开命令提示符并cd到家目录。

在Linux或Mac上:

cd

在Windows上:

cd %HOMEPATH%

2 为第一个Go源代码创建hello目录。

例如,使用以下命令:

mkdir hello
cd hello

3 为代码启用依赖项跟踪。

当你的代码导入包含在其他模块中的包时,你可以在自己的模块中管理这些依赖关系。该模块由go.mod文件定义,该文件跟踪提供这些包的模块。go.mod文件将保留在源代码中,包括源代码存储库中。

要通过创建go.mod文件来启用代码的依赖项跟踪,请运行go mod init命令,为其指定代码所在模块的名称。该名称会成为模块的模块路径。

在实际开发中,模块路径通常是保存源代码的存储库的位置。例如,模块路径可能是github.com/mymodule。如果你计划发布模块供其他人使用,则模块路径必须是Go工具可以下载到该模块的位置。有关使用模块路径命名模块的详细信息,请参阅管理依赖项

在本教程中,只需使用example/hello

$ go mod init example/hello
go: creating new go.mod: module example/hello

4 在文本编辑器中,创建一个文件hello.go,在其中编写代码。

5 将以下代码粘贴到hello.go文件中并保存该文件。

package main

import "fmt"

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

这是你的Go代码。在此代码中,你:

  • 声明一个main包(包是对函数进行分组的一种方式,它由同一目录中的所有文件组成)。
  • 导入流行的fmt,其中包含格式化文本的功能,包括打印到控制台。此软件包是安装Go时获得的标准库软件包之一。
  • 实现将消息打印到控制台的main函数。运行main包时,默认情况下会执行main函数。

6 运行你的代码以查看这句问候语。

$ go run .
Hello, World!

go run命令是你将使用Go完成任务的众多命令之一。使用以下命令获取其他列表:

$ go help

在外部包中调用代码

当你需要你的代码执行其他人可能已实现的操作时,你可以寻找具有对应功能的第三方包。

1 使用外部模块的功能使你打印的消息更有趣。

1) 访问pkg.go.dev并搜索“quote”包

2) 在搜索结果中找到并单击rsc.io/quote包(如果你看到rsc.io/quote/v3,请暂时忽略它)。

3) 在“文档Documentation”部分的“索引Index”下,记下你可以从代码中调用的函数列表。你将使用Go函数。

4) 在本页顶部,请注意包quote含在rsc.io/quote模块中。

你可以使用pkg.go.dev站点查找已发布的模块,这些模块的包中包含你可以在自己的代码中使用的功能。包在模块中发布,比如rsc.io/quote。随着时间的推移,新版本对模块进行了改进,你可以升级代码以使用改进的版本。

2 在Go代码中,导入rsc.io/quote包并调用其Go函数。

添加高亮显示的行后,代码应包括以下内容:

package main

import "fmt"

import "rsc.io/quote"

func main() {
    fmt.Println(quote.Go())
}

3 添加新的模块需求和go.sum。

Go将根据需要添加rsc.io/quote模块,以及用于验证模块的go.sum文件。有关更多信息,请参阅Go modules Reference中的Authenticating modules

$ go mod tidy
go: finding module for package rsc.io/quote
go: found rsc.io/quote in rsc.io/quote v1.5.2

4 运行代码以查看所调用函数生成的消息。

$ go run .
Don't communicate by sharing memory, share memory by communicating.

请注意,你的代码调用Go函数,打印关于通信的巧妙消息。

当你运行go mod tidy时,它找到并下载了包含你导入的包的rsc.io/quote模块。默认情况下,它下载最新版本——v1.5.2。

写更多的代码

通过这篇快速入门教程,你安装了Go并学习了一些基本知识。要使用其他教程编写更多代码,请查看创建一个Go模块

总结

本文翻译自《Conclusion》。

在本教程中,你编写了封装到两个模块中的函数:一个发送问候语;另一个作为第一个的消费者。

注意:本主题是从《创建一个Go模块》开始的多部分教程的一部分。

有关管理代码中的依赖项的更多信息,请参阅管理依赖项。有关开发供他人使用的模块的更多信息,请参阅开发和发布模块

有关Go语言的更多功能,请查看Go之旅

编译和安装这个应用程序

本文翻译自《Compile and install the application》。

在最后一个主题中,你将学习几个新的go命令。go run命令是在你频繁更改代码时,快捷地编译和运行程序的方式,但它不会生成二进制可执行文件。

本主题介绍了两个用于构建代码的附加命令:

注意:本主题是从《创建一个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的testinggo 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。)
  • 添加一个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/greetingsfmt。这使你的代码可以访问这些包中的函数。导入example.com/greetings(你之前创建的模块中包含的包)可以让你访问Hello函数。你还导入fmt,具有处理输入和输出文本的功能(例如将文本打印到控制台)。
  • 通过调用greetings包的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!

恭喜!你已经编写了两个功能模块。

在下一主题中,你将添加一些错误处理