【Go语言成长之路】创建Go模块

文章目录

  • 创建Go模块
    • 一、包、模块、函数的关系
    • 二、创建模块
      • 2.1 创建目录
      • 2.2 跟踪包
      • 2.3 编写模块代码
    • 三、其它模块调用函数
      • 3.1 修改hello.go代码
      • 3.2 修改go.mod文件
      • 3.3 运行程序
    • 四、错误处理
      • 4.1 函数添加错误处理
      • 4.2 调用者获取函数返回值
      • 4.4 执行错误处理代码
    • 五、单元测试
      • 5.1 编写测试文件
      • 5.2 执行测试用例
    • 六、编译并安装应用程序
      • 6.1 编译应用程序
      • 6.1 编译并安装应用程序

创建Go模块

一、包、模块、函数的关系

​ 在开始本文章之前,我们首先需要了解一下Go的包、模块、函数之间的关系,以便更好地进行接下来的操作。

​ 我们还是拿hello目录来讲解,查看其目录树,得到的结果如下所示:

pzs@pzs-ubuntu22:~/go_study$ tree hello/
hello/
├── go.mod
├── go.sum
└── hello.go

​ 在这里,可以看到hello其实就是一个Go包,而hello.go就是一个Go模块,然后查看hello.go里面的内容,如下所示:

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

​ 可以看到main()函数是位于模块下的。因此我们可以大致总结出其包含关系为:包 > 模块 >函数。

​ 注:这里为啥hello.go的package的名称为main呢?是因为Go中规定了,main函数所在的包名称必须为main。若模块中不含main函数的话,那么我们就得使用该模块所在包的名称。

二、创建模块

2.1 创建目录

​ 首先,创建一个greetings 目录, 用于存放模块:

pzs@pzs-ubuntu22:~/go_study$ mkdir greetings/
pzs@pzs-ubuntu22:~/go_study$ cd greetings/
pzs@pzs-ubuntu22:~/go_study/greetings$ 

2.2 跟踪包

之后使用go mod init命令初始化目录:

pzs@pzs-ubuntu22:~/go_study/greetings$ go mod init github/pzs/greetings
go: creating new go.mod: module github/pzs/greetings

2.3 编写模块代码

之后在greetings目录下创建一个greetings.go文件,作为我们的模块:

package greetings // 表明模块所处的包的名称,也就是模块文件所在的目录名称

import "fmt"

// Hello returns a greeting for the named person.
func Hello(name string) string { // 在Go中,名称以大写字母开头的函数可以被不在同一包中的函数调用。而以小写开头的函数,只能在同一个包的不同模块之间调用!!!
	// Return a greeting that embeds the name in a message.
	message := fmt.Sprintf("Hi, %v. Welcome!", name)  // 
	return message
}

在上面这个代码中,有如下几点需要说明:

  1. 在 Go 中,:= 运算符是在一行中声明和初始化变量的快捷方式(Go 使用右侧的值来确定变量的类型)

    message := fmt.Sprintf("Hi, %v. Welcome!", name) 
    // 也等价于如下:
    // var message string   
    // message = fmt.Sprintf("Hi, %v. Welcome!", name)
    
  2. 在Go中,名称以大写字母开头的函数可以被不在同一包中的函数调用。而以小写开头的函数,只能在同一个包的不同模块之间调用!如本例中的Hello函数名为大写开头,所以这个函数可以被其它包引用。

  3. 使用 fmt 包的 Sprintf 函数创建问候消息。第一个参数是格式字符串,Sprintf 将名称参数的值替换为 %v 格式动词。

  4. Go语言中的函数形式:

    在这里插入图片描述

三、其它模块调用函数

3.1 修改hello.go代码

​ 我们选用hello来调用greetings包中greetings.go模块的Hello函数。首先需要修改hello.go代码,修改后的结果如下所示:

package main // 声明一个主包。在 Go 中,作为应用程序执行的代码必须位于主包中。

import (
	"fmt"
	"github.com/pzs/greetings"
)

func main() { 
	message := greetings.Hello("pzs")
	fmt.Println(message)
}

​ 但此时我们运行hello.go文件时候会出现错误的情况,这个是因为我的hello包还找到github.com/pzs/greetings, 因此接下来我们还需要告诉hello包,greetings包在哪。

3.2 修改go.mod文件

​ 为此,使用 go mod edit 命令编辑 github.com/pzs/hello 模块,将 Go 工具从其模块路径(模块所在的位置)重定向到本地目录(模块所在的位置)。

pzs@pzs-ubuntu22:~/go_study/hello$ go mod edit -replace github.com/pzs/greetings=../greetings

​ 该命令指定github.com/pzs/greetings应替换为 …/greetings 以查找依赖项。运行命令后,hello 目录中的 go.mod 文件应包含替换指令:

module github.com/pzs/hello

go 1.21.3

replace github.com/pzs/greetings => ../greetings

​ 在 hello 目录中的命令提示符下,运行go mod tidy命令来同步 github.com/pzs/hello 模块的依赖项,添加代码所需但尚未在模块中跟踪的依赖项:

pzs@pzs-ubuntu22:~/go_study/hello$ go mod tidy
go: found github.com/pzs/greetings in github.com/pzs/greetings v0.0.0-00010101000000-000000000000

​ 命令完成后,github.com/pzs/hello 模块的 go.mod 文件应如下所示:

module github.com/pzs/hello

go 1.21.3

replace github.com/pzs/greetings => ../greetings

require github.com/pzs/greetings v0.0.0-00010101000000-000000000000

​ 注:v0.0.0-00010101000000-000000000000代表这个包还没有版本号,若有版本号的话,则会出现如下的声明:

require example.com/greetings v1.1.0

3.3 运行程序

之后,就可以直接运行hello.go文件,调用greetings.go模块的Hello函数了,运行结果如下所示:

pzs@pzs-ubuntu22:~/go_study/hello$ go run hello.go 
Hi, pzs. Welcome!

到此,我们就成功的在一个模块内调用另外一个自己开发的模块了!但是还存在一个问题就是,如果发生了错误,那么该怎么处理呢?所以,接下来,让我们一起来看一下Go语言的错误处理相关的内容。

四、错误处理

4.1 函数添加错误处理

​ 处理错误是可靠代码的一个基本特征。在本节中,将添加一些代码以从greetings模块返回错误,然后在调用者中处理它。

​ 首先,我们需要对greetings.go中的Hello函数进行一些修改,添加错误处理代码:

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
}

在这里,有以下几点需要进行说明:

  1. 更改函数,使其返回两个值:string和error。调用者将检查第二个值以查看是否发生错误。 (任何 Go 函数都可以返回多个值)

  2. 导入Go标准库errors包,这样你就可以使用它的errors.New函数。

  3. 添加 if 语句来检查无效请求(名称应为空字符串),如果请求无效则返回错误。 error.New 函数返回一个错误,其中包含您的消息。

  4. 添加 nil(意味着没有错误)作为成功返回中的第二个值。这样,调用者就可以看到该函数成功了。

4.2 调用者获取函数返回值

​ 在 hello/hello.go 文件中,处理 Hello 函数现在返回的错误以及非错误值。修改后的hello.go文件如下所示:

package main

import (
	"fmt"
	"log"

	"github.com/pzs/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("greeting:")
	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)
}

在这里,有以下几点需要进行说明:

  1. 配置日志包以在其日志消息的开头打印命令名称(“greetings:”),不带时间戳或源文件信息。

  2. 将 Hello 参数从 pzs的名字更改为空字符串,以便您可以尝试错误处理代码。

  3. 将两个 Hello 返回值(包括错误)分配给变量。

  4. 使用标准库的log包中的函数输出错误信息。如果出现错误,可以使用日志包的 Fatal 函数打印错误并停止程序。

4.4 执行错误处理代码

​ 在 hello 目录中的命令行中,运行 hello.go 以确认代码有效,运行结果如下所示:

pzs@pzs-ubuntu22:~/go_study/hello$ go run hello.go 
greeting:empty name
exit status 1

​ 到此,我们就成功地添加了错误处理代码了,此时我们的程序健壮性也将进一步得到保障!但是又有一个问题,需要思考一下,就是我们虽然添加了错误处理代码,但是每次修改完函数都要重新运行整个程序才能知道我们写的对不对,比较麻烦!所以,我们得考虑做一个单元测试来单独测试我们修改后的函数是否正确!

五、单元测试

5.1 编写测试文件

​ Go 对单元测试的内置支持使您可以更轻松地进行测试。具体来说,使用命名约定、Go 的测试包和 go test 命令,可以快速编写和执行测试。

​ 在Go语言中以 _test.go 结尾的文件名告诉 go test 命令该文件包含测试函数。

​ 这里,我们需要对greetings.go模块进行单元测试,因此首先需要创建一个greetings_test.go文件,内容如下所示:

package greetings

import (
	"regexp"
	"testing"
)

// TestHelloName calls greetings.Hello with a name, checking
// for a valid return value.
func TestHelloName(t *testing.T) {
	name := "pzs"
	want := regexp.MustCompile(`\b` + name + `\b`)
	msg, err := Hello("pzs")
	if !want.MatchString(msg) || err != nil {
		t.Fatalf(`Hello("pzs") = %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)
	}
}

在这里,有以下几点需要进行说明:

  1. 在与您正在测试的代码相同的包中实现测试功能。

  2. 创建两个测试函数来测试greetings.Hello 函数。测试函数名称的形式为 TestName,其中 Name 表示有关特定测试的信息。此外,测试函数将指向测试包的testing.T 类型的指针作为参数。您可以使用此参数的方法来报告和记录测试。

  3. 实施两个测试:

    • TestHelloName 调用 Hello 函数,传递一个名称值,该函数应该能够返回有效的响应消息。如果调用返回错误或意外响应消息(不包含您传入的名称的消息),则可以使用 t 参数的 Fatalf 方法将消息打印到控制台并结束本测试函数。
    • TestHelloEmpty 使用空字符串调用 Hello 函数。**此测试旨在确认您的错误处理是否有效。**如果调用返回非空字符串或没有错误,则可以使用 t 参数的 Fatalf 方法将消息打印到控制台并结束本测试函数。

5.2 执行测试用例

在greetings目录下的命令行中,运行go test命令来执行测试。go test 命令执行测试文件(名称以 _test.go 结尾)中的测试函数(名称以 Test 开头)。您可以添加 -v 标志来获取列出所有测试及其结果的详细输出。

pzs@pzs-ubuntu22:~/go_study/greetings$ go test -v
=== RUN   TestHelloName
--- PASS: TestHelloName (0.00s)
=== RUN   TestHelloEmpty
--- PASS: TestHelloEmpty (0.00s)
PASS
ok      github/pzs/greetings    0.002s

注:执行测试用例的时候,是并发执行的,也就是一个测试用例的失败之后,t.Fatalf只会终止所在的测试函数的执行,而不会终止其它测试用例的执行!

​ 到此,我们也就对我们编写的函数进行了单元测试,那么一般到这个情况下,当所有的测试用例都通过的时候,就可以打包分发上线啦!接下来就让我们一起来看看是如何打包的。

六、编译并安装应用程序

虽然 go run 命令是在频繁更改时编译和运行程序的有用快捷方式,但它不会生成二进制可执行文件。

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

  1. go build 命令编译包及其依赖项,但不会安装结果。
  2. go install 命令编译并安装软件包。

6.1 编译应用程序

​ 从 hello 目录中的命令行运行 go build 命令将代码编译为可执行文件:

pzs@pzs-ubuntu22:~/go_study/hello$ go build
pzs@pzs-ubuntu22:~/go_study/hello$ ls
go.mod  go.sum  hello  hello.go

​ 可以看到多出来了一个hello可执行文件!我们可以检验一下这个文件是否有效,直接通过命令行的方式运行该程序:

pzs@pzs-ubuntu22:~/go_study/hello$ ./hello 
greeting:empty name

​ 可以看到该文件是有效的,并且成功运行了

6.1 编译并安装应用程序

​ 首先我们需要找到go的安装路径,go install会将可执行文件安装到go的安装路径内。

pzs@pzs-ubuntu22:~/go_study/hello$  go list -f '{{.Target}}'
/home/pzs/go/bin/hello

注:使用该命令之前,hello命令下必须要有通过go build编译生成的hello文件。命令执行的结果表明:二进制文件将会被安装到该位置下。

​ 之后需要将 Go 安装目录添加到系统的 shell 路径。

$ export PATH=$PATH:/home/pzs/go/bin

注:添加到/etc/profile或者$HOME/.profile内,然后使用source命令生效即可!

​ 更新 shell 路径后,运行 go install 命令来编译并安装包。

$ go install

​ 此时,二进制文件就被移动到指定位置了,之后在任何终端内只需键入应用程序的名称即可运行您的应用程序,其结果如下所示:

pzs@pzs-ubuntu22:~/go_study/greetings$ hello
greeting:empty name
pzs@pzs-ubuntu22:~/go_study/greetings$ ls
go.mod  greetings.go  greetings_test.go

​ 可以看到,我们greetings目录下没有hello文件,但是也能成功地运行hello可执行文件。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/367656.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

R语言分析任务:

有需要实验报告的可CSDN 主页个人私信 《大数据统计分析软件(R语言)》 实 验 报 告 指导教师: 专 业: 班 级: 姓 名: 学 …

【Pg数据库】删除数据库失败,提示有session正在连接

目录 问题现象原因分析处理方法1.断开所有连接2. 查找相关连接数据库的主机信息3. 再次删除 总结 问题现象 Navicat 删除 PostgreSQL 数据库时失败,提示:正在被其他用户访问(有session正在连接)如何处理? 如下所示&am…

【七】【C++】模版初阶

泛型编程 C中的泛型编程是一种编程范式&#xff0c;它强调代码的重用性和类型独立性。通过泛型编程&#xff0c;你可以编写与特定数据类型无关的代码&#xff0c;使得相同的代码可以用于多种数据类型。 利用重载实现泛型编程 /*利用重载实现泛型编程*/ #include<iostream&…

python中的可变与不可变、深拷贝和浅拷贝

个人猜想&#xff08;很遗憾失败了&#xff09; 在硬盘或者系统中存在一个字符集 如果存在硬盘中&#xff0c;那么硬盘出厂的时候他的字符集所占用的空间就已经确定了。 如果存在于系统的话&#xff0c;硬盘应该在出厂的时候为系统设置一个存储系统字符集的地方。在安装系统…

js获取文件名或文件后缀名(扩展名)的几种方法

有时候我们需要通过含有文件名和后缀名的一个字符串中提取出该文件的文件名或文件后缀名&#xff08;扩展名&#xff09;&#xff0c;可以通过如下几种方式进行截取。 例如文件名为: var fileName"12345.txt"; 方式一&#xff1a;subtring() 用法参考博文 【js截取字…

灵伴科技(Rokid)借助 Knative 实现 AI 应用云原生 Serverless 化

作者&#xff1a;朱炜栋、元毅、子白 公司介绍 Rokid 创立于 2014 年&#xff0c;是一家专注于人机交互技术的产品平台公司&#xff0c;2018 年即被评为国家高新技术企业。Rokid 作为行业的探索者、领跑者&#xff0c;目前致力于 AR 眼镜等软硬件产品的研发及以 YodaOS 操作系…

K8s 集群可观测性-数据分流最佳实践

简介 在微服务架构下&#xff0c;一个 k8s 集群中经常会部署多套业务&#xff0c;同时也意味着不同团队、不同角色、不同的业务会在同一集群中&#xff0c;需要将不同业务的数据在不同的空间进行管理和查看。 在传统的主机环境下&#xff0c;这个是可以通过不同的主机部署 Da…

力扣每日一题 ---- 1906. 查询差绝对值的最小值

本题中&#xff0c;我们的题目求的是差值的最小值&#xff0c;我们考虑一个因素&#xff0c;当前题目中给出的数组是没有排序过的&#xff0c;那么想要求的差值&#xff0c;是不是要两两配对进行判断差值最小值。这里我们就很费时间了&#xff0c; O(N^2)的时间复杂度&#xf…

学习笔记:超详解换根法(换根DP)(匠心之作)

一.换根DP的概念 1.换根DP是什么&#xff1f; 换根DP&#xff0c;又叫二次扫描&#xff0c;是树形DP的一种。 2.换根DP能解决什么问题&#xff1f; 换根DP能解决不指定根结点&#xff0c;并且根节点的变化会对一些值产生影响的问题。例如子结点深度和、点权和等。如果要 暴力…

【数据库】分区的优点和缺点

​ &#x1f34e;个人博客&#xff1a;个人主页 &#x1f3c6;个人专栏&#xff1a;数据库 ⛳️ 功不唐捐&#xff0c;玉汝于成 目录 前言 正文 优点&#xff1a; 缺点&#xff1a; 结语 我的其他博客 ​ 前言 数据库中的分区技术为处理大规模数据提供了一种有效的手段…

C++:输入流/输出流

C流类库简介 C为了克服C语言中的scanf和printf存在的缺点。&#xff0c;使用cin/cout控制输入/输出。 cin&#xff1a;表示标准输入的istream类对象&#xff0c;cin从终端读入数据。cout&#xff1a;表示标准输出的ostream类对象&#xff0c;cout向终端写数据。cerr&#xff…

如何远程操控vm虚拟机(finalshell版)

你是否因为虚拟机命令行操作不便而头疼&#xff1f;是否因为难以复制粘贴而烦恼&#xff1f;是否因为无法快速上传文件而烦躁&#xff1f; 别急&#xff01;现在有一个简单便捷的软件能够实现上述你所述说的所有烦恼&#xff0c;请听我细细道来~ 一、查看虚拟机的ip地址 a.首…

Unity C#高级特性 Partial 详细使用案例

文章目录 实例 1&#xff1a;分隔UI逻辑实例 2&#xff1a;Unity编辑器自动生成代码实例 3&#xff1a;数据模型分割实例 4&#xff1a;序列化扩展实例 5&#xff1a;多视图架构实例 6&#xff1a;Unity编辑器自定义 inspectors 在Unity中&#xff0c;部分类&#xff08;Partia…

Java多线程--生产者与消费者问题

文章目录 一、生产者与消费者问题&#xff08;1&#xff09;概要&#xff08;2&#xff09;案例1、案例描述及需要明确的问题2、整体框架构思3、生产者和消费者的数据共享问题4、对Clerk类里面方法的设计5、测试6、唤醒机制7、两个消费者 二、是否释放锁的操作&#xff08;1&am…

PMP备考的三个阶段及学习方法分享

PMP证书是项目管理必备的关键技能证书&#xff0c;是具备进行项目管理的重要技能体现。无论升职加薪&#xff0c;还是从事项目管理工作&#xff0c;都非常重要。 个人主要从事产品开发工作&#xff0c;开始逐渐承担一些项目经理角色&#xff0c;但目前项目管理知识薄弱&#x…

探讨深浅拷贝在js加密中的运用

深浅拷贝是JavaScript中常用的概念&#xff0c;用于复制对象或数组。它们在处理数据时有不同的用途&#xff0c;适用于不同的场景。在本文中&#xff0c;我们将详细介绍深浅拷贝的概念&#xff0c;提供案例代码&#xff0c;并探讨它们在JavaScript中的应用场景&#xff0c;以及…

@JsonFormat 和 @@DateTimeFormat 时间格式化注解详解(一篇带你解决问题)

前后数据交互过程中&#xff0c;Date类型的数据经常会出现类型映射转换的错误&#xff0c;为了达到业务的目标时间格式&#xff0c;通常会使用JsonFormat 和 DateTimeFormat&#xff0c;但是这两者有什么区别呢&#xff1f; 一、示例代码 先准备一个POJO&#xff0c;拥有Date类…

PPT录屏功能在哪?一键快速找到它!

在现代办公环境中&#xff0c;ppt的录屏功能日益受到关注&#xff0c;它不仅能帮助我们记录演示文稿的播放过程&#xff0c;还能将操作过程、游戏等内容完美录制下来。可是很多人不知道ppt录屏功能在哪&#xff0c;本文将为您介绍ppt录屏的打开方法&#xff0c;以帮助读者更好地…

本体论(ontology)在工业4.0中的应用

信息技术中的本体与哲学的本体论是不同的&#xff0c;它代表了某个专业领域的基本概念&#xff0c;它们在智能制造和工业4.0 中具有不可或缺的作用&#xff0c;为了实现人与机器&#xff0c;机器与机器之间的确定性操作。一个标准化的&#xff0c;精确定义的本体服务是非常重要…

【XR806开发板试用】xr806使用tcp socket与手机通信

本文为极术社区XR806开发板活动试用文章。 参考&#xff1a;基于星辰处理器的全志XR806开源鸿蒙开发板上手体验 搭建环境。并成功编译。 项目源码 &#xff1a; https://gitee.com/kingwho/smart-home 在同一个局域网中&#xff0c;手机与xr806连接后&#xff0c;手机 APP 每隔…