Go 学习笔记(四):接口与错误处理

写在前面

本文是 Go 学习笔记系列的第四篇,介绍 Go 的接口和错误处理机制。接口是 Go 实现多态的核心,错误处理是 Go 的设计哲学(没有 try-catch)。前置知识:结构体和方法(第三篇)。


一、接口基础

1.1 定义接口

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// 定义接口
type Speaker interface {
    Speak() string
}

// 实现接口(隐式实现,不需要 implements 关键字)
type Dog struct {
    Name string
}

func (d Dog) Speak() string {
    return d.Name + ":汪汪汪"
}

type Cat struct {
    Name string
}

func (c Cat) Speak() string {
    return c.Name + ":喵喵喵"
}

Go 的接口是隐式实现:只要一个类型实现了接口的所有方法,它就自动实现了该接口。不需要声明 implements

1.2 使用接口

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// 接口作为参数(多态)
func makeSound(s Speaker) {
    fmt.Println(s.Speak())
}

func main() {
    d := Dog{Name: "旺财"}
    c := Cat{Name: "小橘"}

    makeSound(d)   // 旺财:汪汪汪
    makeSound(c)   // 小橘:喵喵喵

    // 接口切片
    animals := []Speaker{d, c}
    for _, a := range animals {
        fmt.Println(a.Speak())
    }
}

1.3 接口变量

1
2
3
4
5
6
7
8
var s Speaker

s = Dog{Name: "旺财"}
fmt.Println(s.Speak())

// 接口变量包含两部分:
// 1. 类型信息(动态类型)
// 2. 值信息(动态值)

二、类型断言与类型选择

2.1 类型断言

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
var s Speaker = Dog{Name: "旺财"}

// 断言(如果类型不对会 panic)
d := s.(Dog)
fmt.Println(d.Name)

// 安全断言(推荐)
d, ok := s.(Dog)
if ok {
    fmt.Println("是 Dog:", d.Name)
}

// 错误的断言
c, ok := s.(Cat)
fmt.Println(ok)   // false

2.2 类型选择(type switch)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
func checkType(s Speaker) {
    switch v := s.(type) {
    case Dog:
        fmt.Println("Dog:", v.Name)
    case Cat:
        fmt.Println("Cat:", v.Name)
    default:
        fmt.Println("未知类型")
    }
}

三、常用接口

3.1 Stringer(类似 Java 的 toString)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
type User struct {
    Name string
    Age  int
}

// 实现 fmt.Stringer 接口
func (u User) String() string {
    return fmt.Sprintf("%s (%d岁)", u.Name, u.Age)
}

user := User{Name: "张三", Age: 25}
fmt.Println(user)   // 张三 (25岁)

3.2 error 接口

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// error 是一个内置接口
type error interface {
    Error() string
}

// 自定义错误(实现 Error() 方法即可)
type NotFoundError struct {
    Resource string
    ID       int
}

func (e NotFoundError) Error() string {
    return fmt.Sprintf("%s #%d 未找到", e.Resource, e.ID)
}

3.3 io.Reader 和 io.Writer

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// io.Reader 接口
type Reader interface {
    Read(p []byte) (n int, err error)
}

// io.Writer 接口
type Writer interface {
    Write(p []byte) (n int, err error)
}

// 文件同时实现了 Reader 和 Writer
// net.Conn 也同时实现了两者
// 这两个接口是 Go I/O 的基础

3.4 空接口 interface

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// 空接口没有任何方法,所有类型都实现了它
// 类似 Java 的 Object 或 Python 的 Any

func printAnything(v interface{}) {
    fmt.Println(v)
}

printAnything(42)
printAnything("hello")
printAnything([]int{1, 2, 3})

// Go 1.18+ 可以用 any 代替 interface{}
func printAnything(v any) {
    fmt.Println(v)
}

空接口要慎用,能确定类型就用具体类型或泛型。


四、错误处理

4.1 基本模式

Go 没有 try-catch,错误通过返回值传递:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// 函数返回错误
func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("除数不能为零")
    }
    return a / b, nil
}

// 调用时检查错误
result, err := divide(10, 0)
if err != nil {
    fmt.Println("出错:", err)
    return
}
fmt.Println("结果:", result)

nil 表示没有错误。Go 的错误处理就是 if err != nil,写多了就习惯了。

4.2 创建错误

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import "errors"
import "fmt"

// 方式1:errors.New
err1 := errors.New("文件不存在")

// 方式2:fmt.Errorf(支持格式化,最常用)
err2 := fmt.Errorf("用户 %s 不存在", name)

// 方式3:自定义错误类型
type ValidationError struct {
    Field   string
    Message string
}

func (e ValidationError) Error() string {
    return fmt.Sprintf("验证失败: %s — %s", e.Field, e.Message)
}

err3 := ValidationError{Field: "email", Message: "格式不正确"}

4.3 errors.Is 和 errors.As(Go 1.13+)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import "errors"

// 定义哨兵错误(Sentinel Error)
var (
    ErrNotFound  = errors.New("未找到")
    ErrForbidden = errors.New("无权限")
)

// errors.Is:判断错误链中是否包含指定错误
err := someFunc()
if errors.Is(err, ErrNotFound) {
    fmt.Println("资源不存在")
}

// errors.As:从错误链中提取指定类型的错误
var valErr *ValidationError
if errors.As(err, &valErr) {
    fmt.Println("字段:", valErr.Field)
}

4.4 错误包装(Wrapping)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// 用 %w 包装错误(保留原始错误信息)
func readConfig(path string) error {
    data, err := os.ReadFile(path)
    if err != nil {
        return fmt.Errorf("读取配置文件失败: %w", err)
    }
    // ...
    return nil
}

// 调用方可以用 errors.Is / errors.As 追溯到原始错误
err := readConfig("config.yaml")
if errors.Is(err, os.ErrNotExist) {
    fmt.Println("配置文件不存在")
}

4.5 常见错误处理模式

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 模式1:逐层返回
func handler() error {
    if err := validate(); err != nil {
        return fmt.Errorf("参数校验失败: %w", err)
    }
    if err := process(); err != nil {
        return fmt.Errorf("处理失败: %w", err)
    }
    return nil
}

// 模式2:错误后清理资源
func copyFile(src, dst string) error {
    in, err := os.Open(src)
    if err != nil {
        return err
    }
    defer in.Close()

    out, err := os.Create(dst)
    if err != nil {
        return err
    }
    defer out.Close()

    _, err = io.Copy(out, in)
    return err
}

// 模式3:记录日志后继续
if err := retryableOp(); err != nil {
    log.Printf("操作失败,稍后重试: %v", err)
    // 不返回,继续执行
}

五、panic 与 recover

5.1 panic

panic 用于不可恢复的严重错误,类似其他语言的异常抛出:

1
2
3
4
5
6
7
func mustParse(s string) int {
    n, err := strconv.Atoi(s)
    if err != nil {
        panic(fmt.Sprintf("无法解析 %q 为整数", s))
    }
    return n
}

正常业务逻辑不要用 panic,应该返回 error。panic 适合程序初始化阶段的致命错误。

5.2 recover

recover 用于捕获 panic,只在 defer 中有效:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
func safeCall() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("捕获到 panic:", r)
        }
    }()

    panic("出了大问题")
}

func main() {
    safeCall()
    fmt.Println("程序继续运行")   // 会正常执行
}

5.3 panic vs error

1
2
3
4
5
6
error — 正常的错误处理方式,调用方决定怎么处理
panic — 不可恢复的错误,程序崩溃(除非 recover)

使用场景:
- 返回 error:文件不存在、网络超时、参数无效等可预期的错误
- panic:数组越界、空指针、初始化失败等程序 bug 或致命错误

六、接口组合

Go 通过接口组合实现类似继承的效果:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
type Reader interface {
    Read(p []byte) (n int, err error)
}

type Writer interface {
    Write(p []byte) (n int, err error)
}

// 接口组合
type ReadWriter interface {
    Reader
    Writer
}

// 一个类型只需同时实现 Read 和 Write 就自动实现 ReadWriter
type File struct{}

func (f File) Read(p []byte) (int, error)  { return 0, nil }
func (f File) Write(p []byte) (int, error) { return 0, nil }

var rw ReadWriter = File{}   // 合法

七、小结

本文学习了 Go 的接口和错误处理:

  • 接口的隐式实现和多态
  • 类型断言和类型选择
  • 常用接口(Stringer、error、Reader/Writer)
  • 错误处理的惯用模式(if err != nil)
  • errors.Is / errors.As / 错误包装
  • panic 与 recover
  • 接口组合

下一篇将学习 Go 的并发编程:goroutine、channel 和 context。