Go语言工程实践 | 豆包MarsCode AI刷题

24 阅读8分钟

01 高并发

并发vs并行

image.png 并行可以理解为并发的一种手段

go可以充分发挥多核优势,高效运行

image.png

函数前+go来调用协程,快速运行,区别于一般调用

time.Sleep(time.Second)使调用子协程时,主协程不退出

image.png

协程间通信,数据交换的不同方式

image.png

image.png

image.png src无缓冲;dest有缓冲 a协程遍历0-9并发送到channel src b协程遍历src的数据,实现a协程和b协程的通信 通过src和dest的传递能够保证顺序性,并发安全 为什么选择有缓冲:eg消费速度慢于生产,使用缓冲让消费不会影响生产效率

image.png 通过共享内存实现通信 undefer问题 对临界区进行控制 避免多协程抢夺数据

image.png

image.png

02 依赖管理

Go 依赖管理演进 各种开发包

image.png 单体函数,原生sdk 原生SDK(Software Development Kit,软件开发工具包)指的是为特定操作系统或平台量身定制的开发工具包。它通常由操作系统的官方机构或平台提供,以便开发者能够利用系统的底层功能和资源,从而开发出性能更高、兼容性更强的应用程序。使用原生SDK的应用程序通常称为“原生应用(Native App)”。 复杂项目,跨平台sdk

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png commit 时间戳+哈希码

image.png

image.png

image.png

image.png

image.png

image.png Go 依赖管理演进 Go Module 依赖管理方案 image.png Go Module实践 Go 语言的依赖管理经历了几次重要的演进,从最初的 GOPATHGo Modules,这个变化极大地简化了 Go 项目的依赖管理。下面是 Go 依赖管理的演进过程,重点介绍 Go Modules 方案。

1. GOPATH 模式(早期) 最初,Go 使用 GOPATH 来管理依赖。在这种模式下,所有的 Go 代码都必须放置在 GOPATH 目录下。依赖库也必须存放在 GOPATH 内的 src 子目录下,go get 命令会自动将第三方库下载到 GOPATH 中。然而,这种方式有许多问题:

  • 路径固定:项目必须放在 GOPATH 目录中,无法自由选择项目存放位置。
  • 版本管理困难:不同项目可能需要不同版本的相同依赖,GOPATH 模式不能很好地处理依赖的版本冲突。

2. Dep(早期的依赖管理工具) 为了弥补 GOPATH 模式的不足,社区引入了 dep 工具。dep 提供了对依赖版本的管理,它使用 Gopkg.tomlGopkg.lock 文件来指定和锁定项目的依赖版本。虽然 dep 改善了依赖版本控制的问题,但仍然存在如下问题:

  • 没有与 Go 官方集成,且需要额外安装和配置。
  • 依赖的版本控制和管理方式比较复杂,需要手动维护锁文件。

3. Go Modules(正式解决方案) 随着 Go 1.11 的发布,Go 官方引入了 Go Modules(以下简称 Go Mod),从而彻底解决了 GOPATHdep 的问题。Go Mod 是 Go 语言的官方依赖管理工具,支持项目和依赖版本的管理,并且有以下特点:

  • 无需 GOPATH:Go Mod 让项目不再依赖 GOPATH,可以放在任何地方,简化了项目结构。
  • 版本控制:通过 go.modgo.sum 文件来管理依赖版本。go.mod 文件用于定义模块名称、依赖项和 Go 版本,go.sum 文件记录每个依赖的校验和,保证了依赖的一致性。
  • 自动化依赖管理go mod tidy 命令可以自动清理不再使用的依赖,保持 go.modgo.sum 文件的整洁。
  • 支持代理:Go Mod 支持通过 Go Proxy 来获取依赖,避免了直接从 GitHub 等源获取的可能不稳定问题。

Go Mod 的关键文件

  1. go.mod:定义模块名、Go 版本、依赖的版本等信息。

    module example.com/myproject
    
    go 1.16
    
    require (
        github.com/gorilla/mux v1.8.0
        github.com/sirupsen/logrus v1.7.0
    )
    
  2. go.sum:记录每个依赖的校验和,确保依赖的完整性。

    github.com/gorilla/mux v1.8.0 h1:45D1C56E3F0C...
    github.com/sirupsen/logrus v1.7.0 h1:kO5U6FgC5Pz...
    
  3. go.mod 和 go.sum 的操作命令

    • go mod init:初始化模块,生成 go.mod 文件。
    • go mod tidy:自动整理 go.modgo.sum,删除不再使用的依赖。
    • go mod vendor:将依赖复制到本地的 vendor 目录中(在一些特定情况下使用)。
    • go get:添加或升级依赖版本。
    • go mod verify:验证依赖的完整性和一致性。

4. Go 1.16 及以后:Go Modules 默认开启

自 Go 1.16 版本起,Go Modules 成为 Go 的默认依赖管理方式。GOPATH 模式被正式淘汰,Go 项目管理的推荐方式是使用 Go Mod

总结

Go 依赖管理的演进从最初的 GOPATH 到社区工具 dep,最终到官方的 Go Modules,实现了更为灵活、强大和简便的依赖管理方式。Go Modules 的引入极大地简化了 Go 项目的依赖管理,并解决了多个版本冲突和管理不便的问题,成为现代 Go 项目不可或缺的一部分。

03.测试

测试是事故的最后一道屏障

image.png 回归测试:手动通过终端回归主流场景 集成测试:对系统功能维度,对系统接口测试 单元测试:开发过程中对函数测试,一定程度上决定代码质量

单元测试 image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

Mock测试 image.png

image.png

基准测试 image.png

image.png

image.png Go 语言的单元测试、Mock 测试和基准测试是开发过程中非常重要的工具,它们帮助开发者确保代码质量、验证功能以及优化性能。

单元测试(Unit Test) 单元测试是测试代码的最小单元——通常是函数或方法,确保它们按照预期的方式工作。

Go 的单元测试框架 Go 语言内置了一个非常简单的测试框架,使用 testing 包。基本的单元测试包括编写一个以 Test 开头的函数,该函数接受一个指向 testing.T 类型的参数。

示例: 假设你有一个函数 Add,用来返回两个整数的和:

package main

import "fmt"

// Add 返回两个整数的和
func Add(a, b int) int {
    return a + b
}

你可以为这个函数编写单元测试:

package main

import "testing"

// TestAdd 是 Add 函数的单元测试
func TestAdd(t *testing.T) {
    result := Add(2, 3)
    if result != 5 {
        t.Errorf("Expected 5, but got %d", result)
    }
}

运行测试 可以使用 go test 命令来运行单元测试。默认情况下,go test 会查找所有以 Test 开头的函数并执行它们:

go test

测试输出

  • 如果测试通过,不会显示任何输出。
  • 如果测试失败,会显示类似 FAIL 的信息,并且打印错误的详细信息。

Mock 测试(Mock Testing) Mock 测试用于模拟外部依赖,特别是当你的代码依赖于外部服务、数据库、API 或其他模块时,直接进行单元测试会很困难。通过创建 Mock 对象,你可以避免对外部资源的实际调用,而是模拟它们的行为。

Go 中有多个工具可以用来实现 Mock 测试,最常见的库是 github.com/stretchr/testify/mockgithub.com/golang/mock

示例: 假设你有一个依赖于外部数据库的函数,你希望对其进行 Mock 测试。首先定义一个接口:

package main

import "fmt"

// Database 接口模拟数据库操作
type Database interface {
    GetUserByID(id int) (string, error)
}

// 获取用户名称
func GetUserName(db Database, id int) (string, error) {
    name, err := db.GetUserByID(id)
    if err != nil {
        return "", err
    }
    return name, nil
}

然后你可以使用 Mock 来模拟 Database 接口:

package main

import (
    "testing"
    "github.com/stretchr/testify/mock"
)

// MockDatabase 是 Database 接口的 Mock 实现
type MockDatabase struct {
    mock.Mock
}

func (m *MockDatabase) GetUserByID(id int) (string, error) {
    args := m.Called(id)
    return args.String(0), args.Error(1)
}

func TestGetUserName(t *testing.T) {
    // 创建 Mock 对象
    mockDB := new(MockDatabase)
    
    // 设置期望
    mockDB.On("GetUserByID", 1).Return("John", nil)

    // 调用被测试的函数
    name, err := GetUserName(mockDB, 1)
    if err != nil {
        t.Errorf("Expected no error, got %v", err)
    }
    if name != "John" {
        t.Errorf("Expected 'John', got %s", name)
    }

    // 验证期望
    mockDB.AssertExpectations(t)
}

运行 Mock 测试 和普通单元测试一样,Mock 测试也可以通过 go test 来运行。

testify/mock 主要功能:

  • 期望设置:通过 On 方法指定某个方法的调用以及期望的返回值。
  • 断言:使用 AssertExpectations 来确保 Mock 对象的所有预期行为都发生。
  • 模拟行为:通过 Return 方法返回模拟值,模拟方法的行为。

基准测试(Benchmark Test) 基准测试用于衡量代码的性能,帮助你理解某段代码的执行效率。Go 提供了 testing 包中的 Benchmark 函数来进行基准测试。

示例: 假设你想要基准测试一个字符串拼接的性能:

package main

import "testing"

// 拼接字符串
func Concatenate(str1, str2 string) string {
    return str1 + str2
}

// 基准测试
func BenchmarkConcatenate(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Concatenate("Hello", "World")
    }
}

在基准测试中,b.N 代表运行基准测试的次数。testing.B 提供了一个基准测试的计时器,Go 会根据性能分析自动决定执行次数。

运行基准测试 通过 go test-bench 标志来运行基准测试:

go test -bench .

输出结果: 基准测试结果通常会显示每个操作的平均时间以及每秒的操作次数:

BenchmarkConcatenate-8    2000000000    0.35 ns/op
  • BenchmarkConcatenate-8:表示这是 BenchmarkConcatenate 测试,-8 是 CPU 核心数。
  • 2000000000:表示测试运行了多少次。
  • 0.35 ns/op:表示每次操作的平均时间。

总结

  • 单元测试:通过 testing 包对函数进行基本的功能测试,确保代码的正确性。
  • Mock 测试:通过 Mock 对象模拟外部依赖,测试与外部资源交互的代码逻辑。
  • 基准测试:通过 testing.B 来测试代码性能,优化代码的执行效率。

这些测试方法帮助开发者在确保代码质量的同时,也能保证系统在不同情况下的正确性与高效性。

04项目实践

需求设计 代码开发 测试运行

image.png 4.2需求用例 浏览消费用户 topicpage:topic、postlist

image.png

image.png

image.png

image.png

image.png

image.png

image.png 单例减少内存浪费

image.png

image.png

image.png

image.png

image.png

image.png 项目拆解-代码设计-测试运行

没跑通😔