[开发语言 | Go] 实践笔记 02

50 阅读5分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第8天,点击查看活动详情

本节介绍 Go 语言实践之创建并使用模块。

1. 准备一个module

Go代码组成了 package,package 组成了 module。module 指定了依赖项(包括Go的版本和其它 module)

  1. 创建 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

  1. 声明一个main package。在 Go 中,作为应用程序执行的代码必须在main package中。

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)
}
  1. 修改 module 的路径。正常 module 的路径可以表示发布的地址,Go工具可以通过这个地址进行下载。而现在我们要使用本地目录中的 module,所以要将 module 路径重定向到本地。

go mod edit -replace example.com/greetings=../greetings 执行这条命令会修改go.mod文件

  1. 同步main module 的依赖项

go mod tidy

  1. 编译并执行

go run .

3. 处理错误

  1. 从模块中返回一个错误

import "errors" 修改函数,在返回值中增加一项 error func Hello(name ,string) (string, error) 检查条件,存在错误则返回 errors.New("xx") 不存在错误则返回 nil

  1. 调用时处理错误
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()函数
  • randomFormat()函数返回随机的格式字符串
    • 函数名小写开头,表示仅在当前包内可访问;
    • 定义并初始化了一个 slice 变量,[]内没有指明大小,说明这是一个可变的数组

5. 处理多值输入和输出

  1. 增加一个新的函数处理成组的输入
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 返回当前项的索引和数据拷贝
  1. 调用新的函数处理多个名字
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命令

  1. 在测试的模块下增加测试代码文件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类型的指针为参数,使用这个参数的方法进行报告和记录
  1. 执行go test命令

7. 编译并安装应用

go run命令可以编译并执行程序,但是并没有生成可执行文件。

  1. 编译命令

go build命令会编译程序并生成可执行文件。可以通过指定路径来执行程序。

  1. 安装命令
  • 两种方法可以实现直接运行程序
    • 执行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