golang 项目开发如何创建 Module

发布时间 2023-12-27 13:09:07作者: 技术颜良

golang 项目开发如何创建 Module

golang 项目开发如何创建 Module

img

为什么要创建一个 Module?

我们日常开发程序的时候都会引入第三方的 package,使用第三方的 package 的好处是我们可以快速的开发我们的程序的功能,只需要专注我们自己项目的功能,而且第三方的 package 提供了强大的,丰富的功能模块。比如 web 开发框架 gin, echo,微服务开发框架 go-microgo-zero;权限控制:casbin,jwt: jwt-go, gorm:gorm, 存储:minio编写命令:cobra, 配置文件解析:viper,数据库:mysql 等等。这些包都是以 module 的形式提供的服务。

在自己的团队内部同样可以构建自己的私有化的 module,工公司内部的其他团队使用。比如我们公司有三个项目组,都有一些共同的项目。所以我们有公共的 module 放到 gitlab上面提供不同团队的公共使用。同样这样也遵循了软件设计的高内聚低耦合的设计总则。将独立的功能封转为 module

这样做的好处是我们可以只实现一次共享使用,并不需要每个团队中都各自实现,同样也会带啦有好多弊端。

如何实现我们自己的 Module?

既然我们使用了很多开源的 module为我们的日常开发提供了很多的便捷性,那我们该如何实现自己的 module 来提供给团队中使用,甚至可以开放到 gitlab 上面提供给所有人使用(取之于开源,馈与开源)。

通过一下步骤来实现一个其他人可以使用的 module

  1. 1. 创建模块:编写一个小模块,其中包含可以从另一个模块调用的函数。

  2. 2. 从另一个模块调用您的代码:导入并使用您的新模块。

  3. 3. 返回并处理错误: 添加简单的错误处理。

  4. 4. 处理切片中的数据(Go 的动态大小数组)。

  5. 5. 添加测试程序:使用 Go 的内置单元测试功能来测试您的代码。

创建一个 module 目录

首先创建一个 Go 模块。在模块中,我们可以将一组有用的功能在一个或多个相关包中实现。

Go 代码被分组为 packagepackage 又被分组为modulemodule 中需要指定运行代码所需的依赖项,包括 Go 版本及其所需的其他模块。

 

1、打开控制台并cd转到工作目录中,并创建目录module/greetings

mkdir module
cd module/greetings/

2、go mod init命令初始化模块 。

运行go mod init命令,为其提供模块路径 - 此处使用example.com/greetings. 如果发布模块,则这必须是 Go 工具可以下载您的模块的路径。那将是我们的代码的存储库。

$ go mod init example.com/greetings

go mod init命令创建一个 go.mod 文件来跟踪代码的依赖项。当我们添加依赖项时,go.mod 文件将列出项目的代码所依赖的版本。这可以保持构建的可重复性,并让我们可以直接控制要使用的模块版本。

图片image-20230922143447336

3、打开编辑器在greetings创建目录下创建 文件greetings.go并编写代码:

package greetings

import (
    "fmt"
    "math/rand"
)
func Hello(name string) string {
    return fmt.Sprintf("嗨,%v。欢迎!", name)
}

该函数Hello 接受一个name类型为的参数 string。该函数还返回一个string。在Go中,名称以大写字母开头的函数可以被不在同一包中的函数调用。这在 Go 中称为导出名称。

图片img

从另一个模块调用您的代码

1、我们在 greeting 的同级目录创建 hello 目录,作为调用者。

~/Developer/module
tree -d
.
├── greetings
└── hello

在 hello 目录中我们执行命令 go mod init初始化module 模块

go mod init example.com/hello

2、打开编辑器并创建文件 hello.go 文件。在 hello.go 文件中编写代码:

import (
    "example.com/greetings"
    "log"
)

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

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

导入两个包:example.com/greetingsfmt。 导入 example.com/greetings可以访问该Hello 函数。还可以导入fmt, 以及处理输入和输出文本的函数(例如将文本打印到控制台)。greetings通过调用包的 函数 来获取输出信息。

阿里云ECS11月销量王 99元/年 2核2G 3M固定带宽不限流量,新老同享,活动期间新购、续费同价,开发必备! 

图片

https://www.aliyun.com/lowcode/promotion/allinaliyun/99program?source=5176.11533457&userCode=c0ngnrad

3、编辑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 文件应包含一条replace的指令。

module example.com/hello

go 1.20

replace example.com/greetings => ../greetings

require example.com/greetings v0.0.0-00010101000000-000000000000

然后我们在命令中 cd hello 的目录中,并执行命令 go mod tidy, 执行完成后我们发现新增加一行 require example.com/greetings v0.0.0-00010101000000-000000000000 并制定了一个版本号.

我们在 hello 中执行运行命令查看:

go run hello.go
嗨,demo007。欢迎!

返回并处理错误

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

在 greetings/greetings.go 中,修改代码:

package greetings

import (
    "errors"
    "fmt"
    "math/rand"
)

func Hello(name string) (string, error) {
    // 如果没有提供名称,则返回带有错误消息的错误。
    if name == "" {
        return name, errors.New("empty name")
    }

    // 使用随机格式创建一条消息。
    message := fmt.Sprintf("嗨,%v。欢迎!", name)
    return message, nil
}

我们修改了 Hello 函数添加了返回值 errorif 语句来检测输入的 name 是否是一个有效的值。如果为空则返回name 和 一个 错误 errors.New("empty name")

在 hello/hello.go 文件中,处理函数现在返回的错误 Hello以及非错误值。

package main

import (
    "example.com/greetings"
    "fmt"
    "log"
)

func main() {
    message, err := greetings.Hello("demo007")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(message)

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

在目录中的命令行中hello,运行 hello.go 以确认代码有效

go run hello.go
Great to see you, demo007!

处理切片中的数据

我们将使用 Go 切片。切片类似于数组,只不过它的大小会随着添加和删除项目而动态变化。切片是 Go 最有用的类型之一。

我们将添加一个小切片来包含三条问候消息,然后让代码随机返回其中一条消息。

在 greetings/greetings.go 中,我们修改代码,如下所示。

package greetings

import (
    "errors"
    "fmt"
    "math/rand"
)

func Hello(name string) (string, error) {
    // 如果没有提供名称,则返回带有错误消息的错误。
    if name == "" {
        return name, errors.New("empty name")
    }

    // 使用随机格式创建一条消息。
    message := fmt.Sprintf(randomFormat(), name)
    return message, nil
}

func randomFormat() string {
    formats := []string{
        "Hi, %v. Welcome!",
        "Great to see you, %v!",
        "Hail, %v! Well met!",
    }
    return formats[rand.Intn(len(formats))]
}

1、添加一个randomFormat函数,返回随机选择的问候消息格式。 randomFormat以小写字母开头,使其只能被其自己的包中的代码访问(换句话说,它不会被导出)。

2、在 中randomFormat,声明formats具有三种消息格式的切片。声明切片时,您可以在括号中省略其大小,如下所示:[]string。这告诉 Go 切片底层数组的大小可以动态更改。

3、使用该 math/rand 包生成一个随机数,用于从切片中选择一个项目。

4、在 中Hello,调用该randomFormat函数来获取您将返回的消息的格式,然后 name一起使用该格式和值来创建消息。

5、像以前一样返回消息以及错误

在 hello/hello.go 中,我们修改代码,如下所示:

package main

import (
    "example.com/greetings"
    "fmt"
    "log"
)

func main() {
    message, err := greetings.Hello("demo007x")
    // 如果返回了错误,请将其打印到控制台并退出程序。
    if err != nil {
        log.Fatal(err)
    }
    //如果没有返回错误,请将返回的消息打印到控制台。
    fmt.Println(message)
}

我们运行代码看看输出的内容:

go run hello.go
Hail, demo007x! Well met!

go run hello.go
Great to see you, demo007x!

go run hello.go
Hi, demo007x. Welcome!

 

阿里云ECS11月销量王 99元/年 2核2G 3M固定带宽不限流量,新老同享,活动期间新购、续费同价,开发必备! 

图片

https://www.aliyun.com/lowcode/promotion/allinaliyun/99program?source=5176.11533457&userCode=c0ngnrad

测试 module

以上我们编写了基本的或者说是最简单的 golang module 的程序项目,如何确保我们的程序是无错误的或者是没有 bug 的?最好的办法就是添加 测试程序

在开发过程中测试代码可能会暴露出在您进行更改时出现的错误。

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

  1. 1. 在greetings目录中,创建一个名为greetings_test.go的文件。以 _test.go 结尾的文件名告诉go test命令该文件包含测试函数。

  2. 2. 在greetings_test.go中,编写以下代码并保存文件:

    package greetings

    import (
        "regexp"
        "testing"
    )

    // TestHelloName 调用 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)
        }
    }
  3. 3. 创建两个测试函数来测试该greetings.Hello函数。测试函数名称的形式为,其中Name表示有关特定测试的信息。此外,测试函数将指向包的指针作为参数。可以使用此参数的方法来报告和记录测试。

  4. 4. 两个测试:

    • • TestHelloName调用该Hello函数,传递一个name值,该函数应该能够返回有效的响应消息。如果调用返回错误或意外响应消息(不包含您传入的名称),则可以使用参数t的方法将消息打印到控制台并结束执行Fatalf

    • • TestHelloEmpty Hello使用空字符串调用该函数。此测试目的在确认错误处理是否有效。如果调用返回非空字符串或没有错误,则可以使用t参数的Fatalf 方法将消息打印到控制台并结束执行。

  5. 5. 在greetings目录下的命令行中,运行命令 go test 执行测试。

    go test -v
    === RUN   TestHelloName
    --- PASS: TestHelloName (0.00s)
    === RUN   TestHelloEmpty
    --- PASS: TestHelloEmpty (0.00s)
    PASS
    ok      example.com/greetings    0.007s

    通过测试输出的结果可以看到我们的程序全部都通过了测试。

发布module

以上我们编写了我们的第一个 go module, 并且编写了对应函数的测试程序,然后我们可以将我们的 module 程序提交到 github等系统。其他人就可以通过执行命令 go get example.com/greetings 将 greeting 包安装到自己的项目中。

总结:

golang module 给我们提供了灵活的本地开发并测试 module 的机制。我们通过修改 go.mod 文件,通过指令 replace example.com/greetings => ../greetings 来实现本地 module 的开发和测试的全部过程。

阿里云ECS11月销量王 99元/年 2核2G 3M固定带宽不限流量,新老同享,活动期间新购、续费同价,开发必备! 

图片

https://www.aliyun.com/lowcode/promotion/allinaliyun/99program?source=5176.11533457&userCode=c0ngnrad

 

阅读 99
 
 
关注公众号后可以给作者发消息