Skip to content

2)Golang 语言编程规范大全

2.1)Go 的特点与生态

2.1.1)Go 的特点

1. 并发模型:Goroutine 与 Channel(核心王牌)

  • Goroutine(Go 程)
    • 本质:是一种用户态的轻量级线程,由 Go 运行时调度和管理,而非操作系统内核。
  • Channel(通道)
    • 本质:是 goroutine 之间进行通信和同步的管道。它遵循 “不要通过共享内存来通信,而应通过通信来共享内存” 的设计哲学。

2. 极致的编译与部署体验

  • 编译速度快:依赖清晰、语法简洁,使得 Go 的编译速度极快,像编译一个脚本一样。
  • 静态链接:编译后生成一个独立的静态二进制文件。这个文件包含了程序运行所需的一切(包括它自己的运行时),不依赖外部的动态库(如 glibc)。
  • 跨平台编译:通过简单的环境变量(如 GOOS, GOARCH)就能轻松编译出运行在任何主流操作系统(Windows, Linux, macOS)和架构(x86, ARM)上的可执行文件。

2.1.2)Go 的生态

Go 自带了一个功能丰富、高质量的标准库,覆盖了网络、加密、I/O、文本处理等常见任务。特别是 net/http 库,使得开发 HTTP 服务器和客户端变得异常简单。

典型应用场景:云原生、API 网关、微服务、 CLI 工具、分布式系统。

待补充...

2.2)Go 的基础语法

2.2.1)Go 的基础语法

程序结构:Hello World。

go
// 1. 声明该文件属于哪个包
// main 包是一个特殊的包,它表示一个可执行的程序
package main

// 2. 导入外部包,fmt 包用于格式化输入输出
import "fmt"

// 3. main 函数是程序的入口点
func main() {
    // 4. 调用 fmt 包的 Println 函数在控制台打印一行文字
    fmt.Println("Hello, World!")
}

2.2.2)Go 的变量与常量

go
// 方式一:完整声明,显式指定类型
var name string = "Alice"

// 方式二:类型推断,编译器根据初始值推断类型
var age = 30 // 编译器推断为 int 类型

// 方式三:短变量声明(最常用!),只能在函数内部使用
isStudent := true

// 方式四:批量声明
var (
    height float64 = 1.75
    weight         = 70.0 // 类型推断为 float64
)

// 方式五:先声明,后赋值(初始化为类型的零值)
var score int
score = 100

// 常量:使用 const 关键字声明,编译时确定其值。
const Pi = 3.14159
const (
    StatusOK = 200
    StatusNotFound = 404
)

// 优雅的枚举实现(iota 是常量计数器,从0开始)
const (
    Monday = iota // 0
    Tuesday       // 1
    Wednesday     // 2
)

2.2.3)Go 的数据类型

基本类型:整数、浮点数、复数、布尔值、字符串。

复合类型:数组、切片、映射、结构体、通道、接口。

2.2.4)Go 的控制结构

条件语句:if、switch。

循环语句:for、range。

跳转语句:break、continue、goto。

if-else 语句的特点:条件不需要括号,但主体必须有大括号。

go
x := 10

// 基本 if
if x > 5 {
    fmt.Println("x is greater than 5")
}

// if 带简短语句(声明的变量作用域仅在 if-else 块内)
if y := calculate(); y > 0 {
    fmt.Println("y is positive:", y)
} else {
    fmt.Println("y is not positive:", y)
}

// 多分支
if score >= 90 {
    fmt.Println("A")
} else if score >= 80 {
    fmt.Println("B")
} else {
    fmt.Println("C")
}

Go 只有 for 一种循环语句,但形式多样。

go
// 1. 传统的 for 循环(类似 C/Java)
for i := 0; i < 5; i++ {
    fmt.Println(i) // 输出 0,1,2,3,4
}

// 2. while 循环(Go 中没有 while 关键字)
n := 0
for n < 5 {
    fmt.Println(n) // 输出 0,1,2,3,4
    n++
}

// 3. 无限循环
for {
    fmt.Println("Loop forever...")
    // 通常配合 break 使用
    break
}

// 4. for-range 遍历切片、映射等
nums := []int{1, 2, 3}
for index, value := range nums {
    fmt.Printf("index: %d, value: %d\n", index, value)
}

switch 语句: 比其它语言中的 switch 更强大、灵活。

go
fruit := "apple"

// 基本用法(自动 break,不需要显式写)
switch fruit {
case "apple":
    fmt.Println("This is an apple")
case "banana":
    fmt.Println("This is a banana")
default:
    fmt.Println("Unknown fruit")
}

// 条件判断
score := 85
switch {
case score >= 90:
    fmt.Println("A")
case score >= 80:
    fmt.Println("B") // 输出 B
case score >= 70:
    fmt.Println("C")
}

// 多值匹配
switch fruit {
case "apple", "pear":
    fmt.Println("This is a pome fruit")
case "orange", "lemon":
    fmt.Println("This is a citrus fruit")
}

2.2.5)Go 的函数与闭包

go
// 基本函数,无返回值
func sayHello(name string) {
    fmt.Println("Hello,", name)
}

// 单返回值
func add(a int, b int) int {
    return a + b
}

// 多返回值(Go 的特色!)
func divide(a, b float64) (float64, error) {
    if b == 0.0 {
        return 0.0, fmt.Errorf("cannot divide by zero")
    }
    return a / b, nil
}
go
// 命名的返回值(result 在函数体内已声明)
func calculate(a, b int) (result int) {
    result = a * b // 直接使用
    return         // 裸返回,自动返回 result
}
go
// 匿名函数
double := func(x int) int {
    return x * 2
}
fmt.Println(double(5)) // 10

// 闭包:函数可以捕获其所在作用域的变量
func adder() func(int) int {
    sum := 0
    return func(x int) int {
        sum += x
        return sum
    }
}

myAdder := adder()
fmt.Println(myAdder(1)) // 1
fmt.Println(myAdder(2)) // 3
fmt.Println(myAdder(3)) // 6

2.2.6)Go 的错误处理

Go 中的错误本质上是一个内置的接口类型:

go
type error interface {
    Error() string
}

创建错误

go
import (
    "errors"
    "fmt"
)

// 方式一:使用 errors.New 创建简单错误
err1 := errors.New("something went wrong")

// 方式二:使用 fmt.Errorf 创建格式化错误
name := "config.json"
err2 := fmt.Errorf("file %s not found", name)

// 方式三:自定义错误类型(实现 error 接口)
type MyError struct {
    Code    int
    Message string
}

func (e *MyError) Error() string {
    return fmt.Sprintf("Error %d: %s", e.Code, e.Message)
}

func process() error {
    return &MyError{Code: 404, Message: "Resource not found"}
}

检查错误(经典模式)

go
// 多返回值函数的典型调用方式
result, err := someFunction()
if err != nil {
    // 错误处理:记录日志、返回错误、降级处理等
    log.Printf("Operation failed: %v", err)
    return err
}
// 成功时继续使用 result
fmt.Println("Result:", result)

优雅的错误处理模式

错误包装(提供上下文信息)

go
import "github.com/pkg/errors" // 或者 Go 1.13+ 的 fmt.Errorf

func readConfig() error {
    _, err := os.ReadFile("config.json")
    if err != nil {
        // 包装原始错误,添加上下文
        return errors.Wrap(err, "readConfig failed")
        // Go 1.13+ 也可以:return fmt.Errorf("readConfig failed: %w", err)
    }
    return nil
}

func main() {
    err := readConfig()
    if err != nil {
        fmt.Printf("Error: %v\n", err)        // 显示完整错误链
        fmt.Printf("Original: %v\n", errors.Cause(err)) // 获取原始错误
    }
}
  • 最佳实践:
    • 对于可预见的错误(如文件不存在、网络超时),使用 error
    • 只有在遇到不可恢复的程序错误时(如数组越界、空指针),才使用 panic
    • defer 常用于资源清理(文件关闭、锁释放等)

2.2.7)Go 的接口与反射

接口是 Go 实现多态的核心方式,采用 隐式实现(Duck Typing)。

go
// 定义接口
type Writer interface {
    Write([]byte) (int, error)
}

type Closer interface {
    Close() error
}

// 组合接口
type WriteCloser interface {
    Writer
    Closer
}

// 实现接口(隐式实现,无需显式声明)
type File struct {
    name string
}

func (f *File) Write(data []byte) (int, error) {
    fmt.Printf("Writing to %s: %s\n", f.name, string(data))
    return len(data), nil
}

func (f *File) Close() error {
    fmt.Printf("Closing file %s\n", f.name)
    return nil
}

// 使用接口
func process(wc WriteCloser) {
    wc.Write([]byte("hello"))
    wc.Close()
}

func main() {
    file := &File{name: "test.txt"}
    process(file) // File 自动实现了 WriteCloser 接口
}

空接口可以保存任何类型的值,类似于 TypeScript 的 any 或 Java 的 Object。

go
// 空接口作为参数,可以接受任何类型
func printValue(v interface{}) {
    fmt.Printf("Value: %v, Type: %T\n", v, v)
}

func main() {
    printValue(42)        // int
    printValue("hello")   // string
    printValue([]int{1, 2, 3}) // []int
}

反射允许程序在运行时检查类型信息和操作对象,通过 reflect 包实现。

go
import "reflect"

type Person struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}

func inspectValue(v interface{}) {
    value := reflect.ValueOf(v)
    typeOf := reflect.TypeOf(v)

    fmt.Printf("Type: %s, Kind: %s\n", typeOf, value.Kind())

    // 检查是否是结构体
    if value.Kind() == reflect.Struct {
        // 遍历结构体字段
        for i := 0; i < value.NumField(); i++ {
            field := typeOf.Field(i)
            fieldValue := value.Field(i)

            // 获取标签
            jsonTag := field.Tag.Get("json")
            fmt.Printf("  Field: %s, Tag: %s, Value: %v\n",
                field.Name, jsonTag, fieldValue.Interface())
        }
    }
}

func main() {
    p := Person{Name: "Alice", Age: 30}
    inspectValue(p)
}
  • 反射的常见用途:
    • JSON/XML 序列化与反序列化
    • 数据库 ORM 映射
    • 配置解析
    • 通用函数库

2.2.8)Go 的包与模块

包的基本概念

  • 包是 Go 语言中组织代码的基本单位,用于将相关的代码组织在一起。
  • 每个包都有一个唯一的包名,包名通常与包所在的目录名相同。
  • 包中的函数、类型、变量等都可以被其他包引用。
  • main 包是特殊包,用于生成可执行文件

包的导入

  • 使用 import 关键字导入包。例如:import "fmt"
  • 可以使用 import 关键字导入多个包,例如:import "fmt"; import "os"
  • 可以使用 import . 导入包,这样可以直接使用包中的函数和变量,而不需要使用包名前缀。
  • 可以使用 import _ 导入包,这样只会执行包的 init 函数,不会使用包中的其他内容。

包的初始化

  • 每个包都有一个 init 函数,用于包的初始化。init 函数在包被首次引用时自动执行。
  • init 函数没有参数和返回值,不能被显式调用。
  • 一个包可以有多个 init 函数,它们按照文件顺序执行。

包的可见性:在 Go 语言中,包的可见性是通过首字母大小写来控制的。首字母大写的标识符(函数、变量、类型等)是公开的,可以被其他包引用。首字母小写的标识符是私有的,只能在同一个包中使用。

包的依赖管理

  • Go 语言的依赖管理是通过 go.mod 文件来实现的。go.mod 文件定义了项目的模块名和依赖的版本。
  • 使用 go mod init 命令初始化 go.mod 文件。
  • 使用 go get 命令下载依赖包。
  • 使用 go mod tidy 命令清理依赖。
bash
go mod tidy          # 添加缺失的依赖,删除未使用的依赖
go mod download      # 下载依赖到本地缓存
go mod vendor        # 创建 vendor 目录(可选)
go list -m all       # 查看所有依赖
go mod graph         # 查看依赖图
text
my-project/
├── go.mod           # 模块定义
├── go.sum           # 依赖校验
├── cmd/
│   └── myapp/
│       └── main.go  # 程序入口
├── internal/        # 内部包(外部无法导入)
│   └── auth/
│       └── auth.go
├── pkg/             # 公共库包
│   └── utils/
│       └── stringutil.go
├── api/             # API 定义
├── configs/         # 配置文件
└── README.md        # 项目文档

2.3)Go 的数据结构

  • 数组(Array)
    • 数组:内存连续,访问速度快,但长度固定
  • 切片(Slice)
    • 切片:基于数组,动态扩容,是 Go 中最常用的数据结构
  • 映射(Map)
    • 映射:哈希表实现,平均 O(1) 时间复杂度,但内存开销较大
  • 结构体(Struct)
    • 结构体:内存紧凑,值语义,适合组织复杂数据
  • 接口(Interface)
  • 函数(Function)
  • 通道(Channel)
  • 并发(Concurrency)

2.4)Go 的并发编程

  • 并发 vs 并行
    • 并发:同时处理多个任务的能力(逻辑上同时)
    • 并行:同时执行多个任务(物理上同时)

Go 的并发模型基于 CSP(Communicating Sequential Processes) 理论,核心思想是:"不要通过共享内存来通信,而应通过通信来共享内存"。

Goroutine 是 Go 的轻量级线程,由 Go 运行时管理。

go
package main

import (
    "fmt"
    "time"
)

// 普通函数
func sayHello(name string) {
    for i := 0; i < 3; i++ {
        fmt.Printf("Hello %s!\n", name)
        time.Sleep(time.Millisecond * 100)
    }
}

// 带延迟的函数
func slowFunction(id int) {
    fmt.Printf("Goroutine %d started\n", id)
    time.Sleep(time.Second * 2)
    fmt.Printf("Goroutine %d finished\n", id)
}

func main() {
    // 顺序执行
    sayHello("Alice")
    sayHello("Bob")

    fmt.Println("--- 使用 Goroutine ---")

    // 并发执行 - 使用 go 关键字
    go sayHello("Charlie")
    go sayHello("David")

    // 启动多个 goroutine
    for i := 1; i <= 3; i++ {
        go slowFunction(i)
    }

    // 主 goroutine 等待,否则程序会退出
    time.Sleep(time.Second * 3)
    fmt.Println("Main function finished")
}
bash
# 打印结果
Hello Alice!
Hello Alice!
Hello Alice!
Hello Bob!
Hello Bob!
Hello Bob!

# 注意下面是乱序的
--- 使用 Goroutine ---
Hello Charlie!
Goroutine 3 started
Hello David!
Goroutine 2 started
Goroutine 1 started
Hello David!
Hello Charlie!
Hello Charlie!
Hello David!
Goroutine 1 finished
Goroutine 3 finished
Goroutine 2 finished

# 这个是固定的打印输出
Main function finished

Channel 是 goroutine 之间的通信管道,提供安全的同步机制。Channel 可以用于 goroutine 之间的数据传递和同步。

创建和使用 Channel

go
// 无缓冲 channel - 同步通信
ch1 := make(chan int)

// 有缓冲 channel - 异步通信
ch2 := make(chan string, 10)  // 缓冲区大小为10

// 发送和接收数据
func main() {
    ch := make(chan int)

    // 生产者 goroutine
    go func() {
        fmt.Println("Sending data...")
        ch <- 42  // 发送数据
        fmt.Println("Data sent")
    }()

    // 消费者(主 goroutine)
    fmt.Println("Waiting for data...")
    data := <-ch  // 接收数据(会阻塞直到有数据)
    fmt.Printf("Received: %d\n", data)
}

Channel 操作模式

go
// 1. 单向 Channel(限制方向)
func producer(ch chan<- int) {  // 只写 channel
    for i := 0; i < 5; i++ {
        ch <- i
    }
    close(ch)  // 关闭 channel
}

func consumer(ch <-chan int) {  // 只读 channel
    for num := range ch {       // 自动检测 channel 关闭
        fmt.Printf("Received: %d\n", num)
    }
}

func main() {
    ch := make(chan int, 3)
    go producer(ch)
    consumer(ch)
}

Channel 的关闭和检测

go
func worker(tasks <-chan string, results chan<- string) {
    for task := range tasks {  // 自动检测 channel 关闭
        fmt.Printf("Processing: %s\n", task)
        results <- task + " done"
    }
    fmt.Println("Worker finished")
}

func main() {
    tasks := make(chan string, 3)
    results := make(chan string, 3)

    // 启动 worker
    go worker(tasks, results)

    // 发送任务
    tasks <- "task1"
    tasks <- "task2"
    tasks <- "task3"
    close(tasks)  // 重要:关闭 channel 通知接收方没有更多数据

    // 接收结果
    for i := 0; i < 3; i++ {
        result := <-results
        fmt.Printf("Result: %s\n", result)
    }
}

其他

  • Select 语句
  • 同步原语
    • sync.WaitGroup
    • WaitGroup
    • Once
  • Mutex(互斥锁)
  • 并发模式
    • Worker Pool 模式
    • Fan-out, Fan-in 模式
  • 上下文(Context)

最佳实践

  • 并发编程最佳实践
    • 优先使用 channel:遵循 "通过通信共享内存" 的原则
    • 使用 context:为所有可能长时间运行的操作提供取消机制
    • 避免 goroutine 泄漏:确保所有 goroutine 都能正常退出
    • 谨慎使用共享内存:如果必须使用,一定要加锁
    • 控制并发数量:使用 worker pool 模式避免资源耗尽
go
// 控制并发数量的示例
func controlledConcurrency(urls []string, maxConcurrency int) {
    semaphore := make(chan struct{}, maxConcurrency)  // 信号量
    var wg sync.WaitGroup

    for _, url := range urls {
        wg.Add(1)
        go func(u string) {
            defer wg.Done()

            semaphore <- struct{}{}        // 获取信号量
            defer func() { <-semaphore }() // 释放信号量

            // 执行任务
            fmt.Printf("Fetching %s\n", u)
            time.Sleep(time.Second * 2)
        }(url)
    }

    wg.Wait()
}

2.5)Go 的网络编程

Go 的 net 包提供了跨平台的网络 I/O 接口,支持 TCP、UDP、HTTP 等协议。

go
import (
    "net"
    "time"
)

// 基本网络操作
func networkBasics() {
    // 解析地址
    addr, err := net.ResolveTCPAddr("tcp", "google.com:80")
    if err != nil {
        panic(err)
    }
    fmt.Printf("Resolved address: %s\n", addr.String())

    // 查找主机
    ips, err := net.LookupIP("google.com")
    if err != nil {
        panic(err)
    }
    for _, ip := range ips {
        fmt.Printf("IP: %s\n", ip.String())
    }
}

2.5.1)TCP/UDP 编程

go
// 简单的 TCP 服务器
func startTCPServer() {
    // 监听 8080 端口
    listener, err := net.Listen("tcp", ":8080")
    if err != nil {
        panic(err)
    }
    defer listener.Close()

    fmt.Println("TCP Server listening on :8080")

    for {
        // 接受客户端连接
        conn, err := listener.Accept()
        if err != nil {
            fmt.Printf("Accept error: %v\n", err)
            continue
        }

        // 为每个客户端启动一个 goroutine
        go handleTCPConnection(conn)
    }
}

func handleTCPConnection(conn net.Conn) {
    defer conn.Close()

    // 设置超时
    conn.SetDeadline(time.Now().Add(30 * time.Second))

    clientAddr := conn.RemoteAddr().String()
    fmt.Printf("Client connected: %s\n", clientAddr)

    // 读取客户端数据
    buffer := make([]byte, 1024)
    for {
        n, err := conn.Read(buffer)
        if err != nil {
            if err != io.EOF {
                fmt.Printf("Read error from %s: %v\n", clientAddr, err)
            }
            break
        }

        message := string(buffer[:n])
        fmt.Printf("Received from %s: %s", clientAddr, message)

        // 回显数据
        response := fmt.Sprintf("Echo: %s", message)
        _, err = conn.Write([]byte(response))
        if err != nil {
            fmt.Printf("Write error to %s: %v\n", clientAddr, err)
            break
        }

        // 如果是 "quit" 则断开连接
        if strings.TrimSpace(message) == "quit" {
            conn.Write([]byte("Goodbye!\n"))
            break
        }
    }

    fmt.Printf("Client disconnected: %s\n", clientAddr)
}
go
func startTCPClient() {
    // 连接服务器
    conn, err := net.Dial("tcp", "localhost:8080")
    if err != nil {
        panic(err)
    }
    defer conn.Close()

    fmt.Println("Connected to server")

    // 启动 goroutine 接收服务器响应
    go func() {
        buffer := make([]byte, 1024)
        for {
            n, err := conn.Read(buffer)
            if err != nil {
                if err != io.EOF {
                    fmt.Printf("Read error: %v\n", err)
                }
                return
            }
            fmt.Printf("Server response: %s", string(buffer[:n]))
        }
    }()

    // 读取用户输入并发送
    reader := bufio.NewReader(os.Stdin)
    for {
        fmt.Print("Enter message: ")
        text, _ := reader.ReadString('\n')

        _, err := conn.Write([]byte(text))
        if err != nil {
            fmt.Printf("Write error: %v\n", err)
            break
        }

        if strings.TrimSpace(text) == "quit" {
            break
        }
    }
}

2.5.2)HTTP 编程

Go 的 net/http 包提供了完整的 HTTP 客户端和服务器实现。

go
// 基本 HTTP 处理函数
func helloHandler(w http.ResponseWriter, r *http.Request) {
    name := r.URL.Query().Get("name")
    if name == "" {
        name = "World"
    }

    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(map[string]string{
        "message": fmt.Sprintf("Hello, %s!", name),
        "time":    time.Now().Format(time.RFC3339),
    })
}

// 带参数的路由处理
func userHandler(w http.ResponseWriter, r *http.Request) {
    vars := strings.Split(r.URL.Path, "/")
    if len(vars) < 3 {
        http.Error(w, "User ID required", http.StatusBadRequest)
        return
    }
    userID := vars[2]

    switch r.Method {
    case "GET":
        w.Header().Set("Content-Type", "application/json")
        json.NewEncoder(w).Encode(map[string]string{
            "user_id": userID,
            "name":    "John Doe",
            "email":   "john@example.com",
        })
    case "POST":
        // 处理创建用户逻辑
        var user struct {
            Name  string `json:"name"`
            Email string `json:"email"`
        }
        if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
            http.Error(w, "Invalid JSON", http.StatusBadRequest)
            return
        }
        w.WriteHeader(http.StatusCreated)
        json.NewEncoder(w).Encode(map[string]interface{}{
            "user_id": userID,
            "created": true,
            "user":    user,
        })
    default:
        http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
    }
}

// 中间件:日志记录
func loggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()

        // 调用下一个处理器
        next.ServeHTTP(w, r)

        // 记录日志
        fmt.Printf("%s %s %s %v\n",
            r.Method, r.URL.Path, r.RemoteAddr, time.Since(start))
    })
}

// 中间件:认证检查
func authMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        token := r.Header.Get("Authorization")
        if token != "Bearer secret-token" {
            http.Error(w, "Unauthorized", http.StatusUnauthorized)
            return
        }
        next.ServeHTTP(w, r)
    })
}

func startHTTPServer() {
    // 创建多路复用器
    mux := http.NewServeMux()

    // 注册路由
    mux.HandleFunc("/", helloHandler)
    mux.HandleFunc("/user/", userHandler)

    // 静态文件服务
    mux.Handle("/static/",
        http.StripPrefix("/static/",
            http.FileServer(http.Dir("./static"))))

    // 应用中间件
    handler := loggingMiddleware(mux)

    // 启动服务器
    fmt.Println("HTTP Server listening on :8080")
    err := http.ListenAndServe(":8080", handler)
    if err != nil {
        panic(err)
    }
}
go
func httpClientExamples() {
    // 创建自定义 HTTP 客户端
    client := &http.Client{
        Timeout: 10 * time.Second,
    }

    // GET 请求
    resp, err := client.Get("https://httpbin.org/get")
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()

    body, _ := io.ReadAll(resp.Body)
    fmt.Printf("GET Response: %s\n", string(body))

    // POST 请求(JSON)
    data := map[string]interface{}{
        "name":  "John",
        "email": "john@example.com",
    }
    jsonData, _ := json.Marshal(data)

    req, err := http.NewRequest("POST", "https://httpbin.org/post",
        bytes.NewBuffer(jsonData))
    if err != nil {
        panic(err)
    }

    req.Header.Set("Content-Type", "application/json")
    req.Header.Set("Authorization", "Bearer token123")

    resp, err = client.Do(req)
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()

    body, _ = io.ReadAll(resp.Body)
    fmt.Printf("POST Response: %s\n", string(body))
}

2.5.3)其他

  • WebSocket 编程
  • RPC 编程
  • 高级网络模式:负载均衡、反向代理、服务发现等

2.6)Go 的测试与调试

2.6.1 单元测试

Go 的测试框架非常简单易用,测试文件以 _test.go 结尾。

基本测试结构如下:

go
// math.go
package math

func Add(a, b int) int {
    return a + b
}

func Multiply(a, b int) int {
    return a * b
}

func Divide(a, b int) (int, error) {
    if b == 0 {
        return 0, fmt.Errorf("division by zero")
    }
    return a / b, nil
}
go
// math_test.go
package math

import (
    "testing"
)

// 普通测试函数
func TestAdd(t *testing.T) {
    result := Add(2, 3)
    expected := 5

    if result != expected {
        t.Errorf("Add(2, 3) = %d; expected %d", result, expected)
    }
}

// 表格驱动测试(推荐)
func TestMultiply(t *testing.T) {
    testCases := []struct {
        name     string
        a        int
        b        int
        expected int
    }{
        {"positive numbers", 2, 3, 6},
        {"negative numbers", -2, -3, 6},
        {"mixed signs", -2, 3, -6},
        {"zero", 0, 5, 0},
    }

    for _, tc := range testCases {
        t.Run(tc.name, func(t *testing.T) {
            result := Multiply(tc.a, tc.b)
            if result != tc.expected {
                t.Errorf("Multiply(%d, %d) = %d; expected %d",
                    tc.a, tc.b, result, tc.expected)
            }
        })
    }
}

// 错误测试
func TestDivide(t *testing.T) {
    t.Run("successful division", func(t *testing.T) {
        result, err := Divide(10, 2)
        if err != nil {
            t.Fatalf("Unexpected error: %v", err)
        }
        if result != 5 {
            t.Errorf("Expected 5, got %d", result)
        }
    })

    t.Run("division by zero", func(t *testing.T) {
        _, err := Divide(10, 0)
        if err == nil {
            t.Error("Expected error for division by zero, but got none")
        }
        expectedError := "division by zero"
        if err.Error() != expectedError {
            t.Errorf("Expected error '%s', got '%s'", expectedError, err.Error())
        }
    })
}

运行测试

bash
# 运行当前包的所有测试
go test

# 显示详细输出
go test -v

# 运行特定测试函数
go test -v -run TestAdd

# 运行匹配模式的测试
go test -v -run "TestDivide.*zero"

# 显示测试覆盖率
go test -cover

# 生成覆盖率报告
go test -coverprofile=coverage.out
go tool cover -html=coverage.out

2.6.2 其他测试

  • 基准测试
  • 示例测试
  • 测试辅助工具
    • testify 断言库
  • 调试技术
    • 使用 fmt 调试
    • 使用 log 调试
    • 使用 delve 调试器

测试组织结构

text
project/
├── main.go
├── service/
│   ├── service.go
│   └── service_test.go
├── repository/
│   ├── repository.go
│   └── repository_test.go
└── internal/
    └── utils/
        ├── utils.go
        └── utils_test.go

常用的测试命令总结

bash
# 基本测试
go test ./...              # 运行所有测试
go test -v ./...           # 详细输出

# 基准测试
go test -bench=. -benchmem # 运行基准测试并显示内存分配

# 覆盖率
go test -coverprofile=coverage.out
go tool cover -html=coverage.out

# 特定测试
go test -run TestName      # 运行特定测试
go test -bench BenchmarkName # 运行特定基准测试

# 竞态检测
go test -race              # 检测数据竞争

# 跳过缓存
go test -count=1           # 跳过测试缓存

2.7)Go 的性能优化

Go 内置了强大的性能分析工具。

2.7.1 CPU 分析

go
import (
    "os"
    "runtime/pprof"
)

func cpuIntensiveTask() {
    // 启动 CPU 分析
    f, err := os.Create("cpu.prof")
    if err != nil {
        panic(err)
    }
    defer f.Close()

    pprof.StartCPUProfile(f)
    defer pprof.StopCPUProfile()

    // 执行需要分析的代码
    for i := 0; i < 1000000; i++ {
        _ = Multiply(i, i+1)
    }
}

2.7.2 内存分析

go
func memoryIntensiveTask() {
    var data [][]int

    for i := 0; i < 1000; i++ {
        slice := make([]int, 1000)
        data = append(data, slice)
    }

    // 写入内存分析数据
    f, err := os.Create("memory.prof")
    if err != nil {
        panic(err)
    }
    defer f.Close()

    pprof.WriteHeapProfile(f)
}

2.7.3 HTTP 性能分析

go
import _ "net/http/pprof"

func startProfilingServer() {
    // 在另一个端口启动性能分析端点
    go func() {
        log.Println(http.ListenAndServe("localhost:6060", nil))
    }()

    // 然后可以通过以下方式访问:
    // http://localhost:6060/debug/pprof/
}

2.7.4 性能分析工具

  • pprof:Go 内置的性能分析工具,可以生成 CPU、内存、阻塞等性能分析数据。
  • go-torch:基于火焰图的性能分析工具,可以将 pprof 生成的数据转换为火焰图,更直观地展示性能瓶颈。
  • gops:一个用于诊断 Go 程序的工具,可以查看程序的 CPU、内存、goroutine 等信息。

使用 pprof 工具

bash
# 查看 CPU 分析结果
go tool pprof cpu.prof

# 查看内存分析结果
go tool pprof memory.prof

# 实时分析运行中的程序
go tool pprof http://localhost:6060/debug/pprof/profile
go tool pprof http://localhost:6060/debug/pprof/heap

# 生成火焰图
go tool pprof -http=:8080 cpu.prof

2.8)Go 的项目实战

详见下一章节。