开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第8天,点击查看活动详情
本节介绍 Go 语言实践之创建并使用模块。
1. 准备一个module
Go代码组成了 package,package 组成了 module。module 指定了依赖项(包括Go的版本和其它 module)
- 创建 module
mkdir greetings
cd greetings
go mod init example.com/greetings
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
}
- 声明并初始化一个变量
message := fmt.Sprintf("Hi, %v. Welcome!", name)
或者
var message string
message = fmt.Sprintf("Hi, %v. Welcome!", name)
- 函数的格式:
func 函数名(参数名 参数类型) 返回值类型
2. 调用这个module
- 声明一个
mainpackage。在 Go 中,作为应用程序执行的代码必须在mainpackage中。
cd .. && mkdir hello && cd hello
go mod init example.com/hello
package main
import (
"fmt"
"example.com/greetings"
)
func main() {
// Get a greeting message and print it.
message := greetings.Hello("Gladys")
fmt.Println(message)
}
- 修改 module 的路径。正常 module 的路径可以表示发布的地址,Go工具可以通过这个地址进行下载。而现在我们要使用本地目录中的 module,所以要将 module 路径重定向到本地。
go mod edit -replace example.com/greetings=../greetings
执行这条命令会修改go.mod文件
- 同步
mainmodule 的依赖项
go mod tidy
- 编译并执行
go run .
3. 处理错误
- 从模块中返回一个错误
import "errors"
修改函数,在返回值中增加一项 error
func Hello(name ,string) (string, error)
检查条件,存在错误则返回
errors.New("xx")
不存在错误则返回
nil
- 调用时处理错误
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包打印错误信息log.SetPrefix设置log信息前缀log.SetFlags设置log信息是否显示时间戳和源文件信息log.Fatal打印错误信息并停止程序
4. 返回随机的欢迎语
使用 slice 存放多个欢迎语的格式字符串,然后随机选择一个作为返回值。
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))]
}
init()函数中设置rand包的随机种子。- Go 程序启动后,会在初始化全局变量之后自动执行
init()函数
- Go 程序启动后,会在初始化全局变量之后自动执行
randomFormat()函数返回随机的格式字符串- 函数名小写开头,表示仅在当前包内可访问;
- 定义并初始化了一个 slice 变量,
[]内没有指明大小,说明这是一个可变的数组
5. 处理多值输入和输出
- 增加一个新的函数处理成组的输入
func Hellos(names []string) (map[string]string, error) {
// A map to associate names with messages.
messages := make(map[string]string)
// Loop through the received slice of names, calling
// the Hello function to get a message for each name.
for _, name := range names {
message, err := Hello(name)
if err != nil {
return nil, err
}
// In the map, associate the retrieved message with
// the name.
messages[name] = message
}
return messages, nil
}
- 因为修改函数参数类型会更改函数签名,这会影响到那些已经使用当前包的项目,所以新定义一个函数
- 输入类型采用了 slice
- 返回类型采用 map
- 初始化方法
make(map[_key-type_]_value-type_)
- 初始化方法
- 使用
for循环结构遍历range返回当前项的索引和数据拷贝
- 调用新的函数处理多个名字
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)
// A slice of names.
names := []string{"Gladys", "Samantha", "Darrin"}
// Request greeting messages for the names.
messages, err := greetings.Hellos(names)
if err != nil {
log.Fatal(err)
}
// If no error was returned, print the returned map of
// messages to the console.
fmt.Println(messages)
}
6. 单元测试
Go对单元测试的支持主要包括testing包和go test命令
- 在测试的模块下增加测试代码文件
greetings_test.go,这种命名方式可以告诉test go命令这个文件内有测试函数
package greetings
import (
"testing"
"regexp"
)
// TestHelloName calls greetings.Hello with a name, checking
// for a valid return value.
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 calls greetings.Hello with an empty string,
// checking for an error.
func TestHelloEmpty(t *testing.T) {
msg, err := Hello("")
if msg != "" || err == nil {
t.Fatalf(`Hello("") = %q, %v, want "", error`, msg, err)
}
}
- 测试函数和待测试的代码打包在同一个 package
- 测试函数命名
TestName说明测试的内容,使用testing.T类型的指针为参数,使用这个参数的方法进行报告和记录
- 执行
go test命令
7. 编译并安装应用
go run命令可以编译并执行程序,但是并没有生成可执行文件。
- 编译命令
go build命令会编译程序并生成可执行文件。可以通过指定路径来执行程序。
- 安装命令
- 两种方法可以实现直接运行程序
- 执行
go list -f '{{.Target}}'可以知道 Go 对当前的 package 的安装路径,然后将得到的目录加入到环境变量中; - 修改Go的默认安装目录为环境变量中已包含的目录。执行
go env -w GOBIN=/path/to/your/bin
- 执行
- 完成上一步之后,执行
go install完成安装,可以在上一步的目录中找到可执行文件
本节的目录结构
hello/
├── go.mod
├── go.sum
├── hello
└── hello.go
greetings/
├── go.mod
├── greetings.go
└── greetings_test.go