简介
一篇关于函数、错误处理、数组、映射、单元测试和编译的短篇介绍。
这里是关于Go语言的一些基本特性介绍的第一部分。如果你是刚开始接触Go,请先看一下这里;这里所跳转的页面会介绍关于go命令、Go模块和一些很简单的Go代码。
在这篇文章里你会创建两个模块。第一个是可以被其他库或者应用程序所导入的库模块。第二个是导入了第一个库的应用程序模块。
下面一系列文本将会介绍七个简短的篇章,而每个篇章分别介绍了一个不同的Go语言的内容。
- 创建一个模块。写一个你可以在别的模块中引用其中函数的小模块。
- 从另一个模块引用你的代码。导入并且使用你的新模块。
- 返回并处理错误。添加一点简单的错误处理逻辑。
- 返回一段随机问候。在切片(slices)中处理数据(切片是Go中的动态且可变长的数组)
- 为不同的人返回对应的问候。在映射(或称字典,map)中存储键值对。
- 测试。使用Go自带的单元测试模块来测试你的代码。
- 编译和安装应用程序。在本地编译和安装你的代码。
创建一个别人能使用的模块
我们从创建一个Go模块开始。在一个模块中,你会在一个函数单独且有实际应用的部分使用一个或多个相关联的包。比如,你创建了一个包含了多个金融分析函数的包的模块,从而当别人可以引用你写过的成果来写金融相关的程序。更多关于开发模块的信息可以点击这里。
Go代码分类组合成包,而包又分类组合成模块。你的模块里需要说明执行你的代码所需要的依赖,其中包括Go的版本和其他所需要的模块。
当你添加或者修改了你的模块功能,这个时候你需要对你的模块发布一个新的版本。这样子的话,当开发者在把自己的代码部署到生产环境的时候,他们可以对引入了你模块函数的部分导入最新的包并执行新版本的测试。
- 打开命令行终端并且进入到home目录下。
如果是Linux和Mac:
cd
如果是Windows:
cd %HOMEPATH%
- 为你的Go模块源码创建一个greetings文件夹。
mkdir greetings
cd greetings
- 通过go mod init命令创建你的模块。
执行go mod init命令来定义你的模块路径:在这里我们使用example.com/greetings这个路径。如果你发布了一个模块,那么这个路径必须是Go的工具链可以下载得到的路径,比如可以是你的代码仓库地址。
go mod init example.com/greetings
go mod init命令创建了一个go.mod文件,从而可以追踪到你代码中的依赖。到目前为止,这个go.mod文件只会包含你的模块名称和你代码所支持的Go的版本。但当你添加依赖的时候,go.mod文件会列出你代码所需要的依赖版本。这样子的话,就能让代码重复编译和直接管理使用需要用到的模块版本。
4. 在你的代码编译器中,在你写代码的文件夹下创建一个greetings.go文件。
5. 粘贴下面代码到greetings.go文件中并保存。
package greetings
import "fmt"
// Hello returns a greeting for the named person.
func Hello(name string) string {
// Return a greeting that embeds the name in a message.
message := fmt.Sprintf("Hi, %v. Welcome!", name)
return message
}
这是你模块里的第一段代码。它会为所需要的调用方返回一段问候。你会在下一步写一段代码来调用这个方法。
在这段代码里,你做了以下工作:
- 创建了一个包含了相关函数的greetings包。
- 创建了一个Hello方法来返回问候。
这函数需要传入一个类型为字符串的name参数,并且返回一个字符串类型的数据。在Go里面,一个以大写字母开头命名的函数可以被不同的包调用。这种关于导出的逻辑的命名方式在Go里是众所周知的。
- 创建一段包含你问候的message变量。
在Go里, :=符号用于吧定义和初始化一个变量的逻辑简写成一行(Go使用在右边的值来定义变量类型)。上面代码里这个符号的逻辑等价于:
var message string
message = fmt.Sprintf("Hi, %v.Welcome!", name)
- 使用fmt包的Sprintf函数去创建一段问候信息。第一个参数是一段格式化字符串,并且Sprintf将会把name参数替换掉占位符%v。插入name参数从而完善好问候的文本。
- 返回格式化后的文本给调用方。
下一步,我们将会从另外一个模块调用这个方法。
从另一个模块引用你的代码
在上面,你已经创建了一个greetings模块。在这个篇章里,你将会写一段代码并引用你刚刚创建的模块里的Hello方法。
- 为你的Go模块源码创建一个go文件夹。这个文件夹将是你写调用代码的地方。
当你创建完这个文件夹后,你应该会有hello和greetings目录在同一级目录下。
比如,如果你当前路径在greetings文件夹里,那么应该使用下面命令。
cd ..
mkdir hello
cd hello
- 为你即将写的代码启用依赖项跟踪。
go mod init example.com/hello
- 在代码编辑器里,hello文件夹下,创建一个hello.go文件来写你的代码。
- 写代码引用Hello方法,然后打印该方法返回的值。
为了完成这件事,我们把下面代码粘贴进hello.go里面。
package main
import (
"fmt"
"example.com/greetings"
)
func main() {
// Get a greeting message and print it.
message := greetings.Hello("Gladys")
fmt.Println(message)
}
在这段代码里,你做了以下工作:
- 定义了一个main包。在Go里,代码一定要在main包里才能作为一个应用程序运行起来。
- 导入两个包:example.com/greetings和fmt包。这可以让你的代码对这些包进行访问。导入example.com/greetings(这个包被包含在你之前创建的模块里)可以让你访问到Hello函数。你也可以导入fmt包,这个包的作用是提供输入输出文本的函数(比如在控制台中打印文本)。
- 通过引用greetings包的Hello函数来获取一句问候。
- 编辑example.com/hello 模块来使用你本地的example.com/greetings模块。
在生产环境中使用的时候,你需要把example.com/greetings模块发布到Go的工具链能找到并下载到的仓库中(模块路径需要反映其发布位置)。现在的话,因为你还没有发布这个模块,你需要调整一下example.com\hello模块从而让它能从你的本地文件系统中找到example.com\greetings代码。
为了实现这个功能,我们使用go mod edit命令去配置example.com/hello模块,使得Go工具链把依赖的路径重定向到本地文件夹。
- 在命令行工具的hello文件夹下,执行下面命令:
go mod edit -replace example.com/greetings=../greetings
为了定位依赖,这个命令把example.com/greetings替换成…/greetings.当你运行这个命令后,hello文件夹里的go.mod文件应该会包含一条替换的描述:
module example.com/hello
go 1.16
replace example.com/greetings => ../greetings
- 在命令行的hello文件夹下,执行go mod tidy命令来同步example.com/hello模块中使用了但没有记录的依赖。
go mod tidy
在命令执行完之后,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
go mod tidy命令找到在本地的greetings文件夹里面的代码,然后添加一条require描述来识别example.com/hello依赖于example.com/greetings。你会在hello.go导入greetings包时创建这个依赖关系。
跟在模块路径后面的数字是一个伪版本号–这是一段并不存在于相关模块里的版本数字。
如果是为了查询一个发布了的模块,go.mod文件会特别地删掉替换描述和在末尾使用带上设置好的版本号的依赖描述。
require example.com/greetings v1.1.0
- 在命令行的hello文件夹下,执行你的代码并确认已成功运行。
恭喜!你已经写了两个功能模块。
在下个篇章,你将会为你的代码加上一些错误处理。
返回并处理错误
错误处理是健壮的代码一项基本特性。在这个篇章里,你将会在greetings模块里添加一点可以返回错误的代码,然后在调用方处理这个错误。
- 把greetings/greetings.go的代码替换成下面的代码:
package greetings
import (
"errors"
"fmt"
)
// Hello returns a greeting for the named person.
func Hello(name string) (string, error) {
// If no name was given, return an error with a message.
if name == "" {
return "", errors.New("empty name")
}
// If a name was received, return a value that embeds the name
// in a greeting message.
message := fmt.Sprintf("Hi, %v. Welcome!", name)
return message, nil
}
在这段代码里,你做了以下工作:
- 修改Hello函数,以使得他可以返回两个值:一个字符串和一个错误。你的调用方会检查上诉第二个值是否有返回,从而判断是否发生了错误。(任何Go程序都可以返回多个值)
- 导入了Go的标准库之一的errors包,让你可以使用它的errors.New方法。
- 添加一个if声明来验证一个非法请求(name参数是一个空字符串)并且如果请求非法就返回一个错误。errors.New函数返回一个包含你写好的信息的错误。
- 在成功的返回中添加nil(意味着没有错误)作为第二个值。这样子的话,调用方就会认为方法成功。
- 在你的hello/hello.go文件里,与Hello函数返回的合法值一起,也对返回的错误进行处理。
把下面代码粘贴进hello.go。
package main
import (
"fmt"
"log"
"example.com/greetings"
)
func main() {
// Set properties of the predefined Logger, including
// the log entry prefix and a flag to disable printing
// the time, source file, and line number.
log.SetPrefix("greetings: ")
log.SetFlags(0)
// Request a greeting message.
message, err := greetings.Hello("")
// If an error was returned, print it to the console and
// exit the program.
if err != nil {
log.Fatal(err)
}
// If no error was returned, print the returned message
// to the console.
fmt.Println(message)
}
在这段代码里,你做了以下工作:
- 配置log包,使其可以在打印的信息前面打印出命令命令名称(“greetings:”),并且不会带上时间戳或者源文件信息。
- 把包括报错内容在内的Hello的两个参数分配给对应的变量
- 把Hello的的参数设为空字符串,从而让我们可以验证我们的错误处理代码
- 识别非空的错误值。在这种情况下没有继续执行的必要。
- 使用标准库里的log包里的函数对error信息进行输出。如果你发现了一个报错,那么就使用log包里的Fatal函数来打印出报错的信息和终止程序。
- 在命令行的hello文件夹下,执行hello.go来确认代码运行正常。
现在你传进去了一个空的name参数,你会得到一个报错:
go run .
greetings: empty name
exit status 1
这是Go里面通用的错误处理方法:把报错作为值回传让调用方可以进行检查。
接下来,你将会使用Go切片返回一段随机选择的问候。
返回一段随机问候
在这个篇章中,你会通过修改你的代码来实现返回我们预先设置后的其中一段问候,而不再是返回单一问候了。
为了做到上述操作,你将会使用一个Go的切片。切片除了当你添加或移除元素的时候他的大小可以动态修改外,其余的特性都很像一个数组。切片是Go最为有用的特性之一。
你将会添加一个包含了三段问候的话语信息小切片,然后让你的代码随机返回其一。
- 在greetings/greeting.go里,把你的代码修改如下:
package greetings
import (
"errors"
"fmt"
"math/rand"
"time"
)
// Hello returns a greeting for the named person.
func Hello(name string) (string, error) {
// If no name was given, return an error with a message.
if name == "" {
return name, errors.New("empty name")
}
// Create a message using a random format.
message := fmt.Sprintf(randomFormat(), name)
return message, nil
}
// init sets initial values for variables used in the function.
func init() {
rand.Seed(time.Now().UnixNano())
}
// randomFormat returns one of a set of greeting messages. The returned
// message is selected at random.
func randomFormat() string {
// A slice of message formats.
formats := []string{
"Hi, %v. Welcome!",
"Great to see you, %v!",
"Hail, %v! Well met!",
}
// Return a randomly selected message format by specifying
// a random index for the slice of formats.
return formats[rand.Intn(len(formats))]
}
在这段代码里,你做了以下事情:
- 添加一个返回一段随机选中的问候信息格式的randomFormat函数。请注意randomFormat函数的名称开头字母是小写,使其只能在它当前的包里使用(即不能被导出)。
- 在randomFormat里,定义一个包含三个信息模版的模版切片。在定义一个切片的时候,你可以忽略在本应该在中括号里定义的数组大小,就像[]string。这表示Go在切片下定义的数组的大小是可以动态修改的。
- 使用math/rand包来生成一个获取切片内容的随机数字下标。
- 添加一个init函数来对rand包进行当前时间的埋点。Go会在程序开始并且全局变量都已经初始化完的时候自动执行init函数。
- 在Hello里,调用randomFormat函数来获取一个返回的信息格式,然后同时使用这个格式和name值来生成这段信息。
- 像之前一样返回对应信息(或者一个报错)
- 在hello/hello.go里,把你的代码做如下修改:
package main
import (
"fmt"
"log"
"example.com/greetings"
)
func main() {
// Set properties of the predefined Logger, including
// the log entry prefix and a flag to disable printing
// the time, source file, and line number.
log.SetPrefix("greetings: ")
log.SetFlags(0)
// Request a greeting message.
message, err := greetings.Hello("Gladys")
// If an error was returned, print it to the console and
// exit the program.
if err != nil {
log.Fatal(err)
}
// If no error was returned, print the returned message
// to the console.
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!
下一节,你将会使用一个slice来问候多个人。