1. Go基础

1.1 核心知识点

数据类型

  • 基本类型:bool, string, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64, complex64, complex128
  • 复合类型:array, slice, map, struct, pointer, function, interface, channel
  • 零值:每种类型都有默认的零值

变量声明

var name string = "hello"
var age int = 25
var isActive bool = true
 
短变量声明(只能在函数内使用):
name := "hello"
age := 25

数组与切片

数组:固定长度
var arr [5]int
arr := [5]int{1, 2, 3, 4, 5}
 
切片:动态长度
var slice []int
slice := []int{1, 2, 3, 4, 5}
slice := make([]int, 5, 10)  // len=5, cap=10

Map

m := make(map[string]int)
m["key1"] = 1
value, ok := m["key1"]  // 检查key是否存在
delete(m, "key1")

函数

func add(a, b int) int {
    return a + b
}
 
多返回值:
func divmod(a, b int) (int, int) {
    return a / b, a % b
}
 
可变参数:
func sum(nums ...int) int {
    total := 0
    for _, num := range nums {
        total += num
    }
    return total
}
 
闭包:
func adder() func(int) int {
    sum := 0
    return func(x int) int {
        sum += x
        return sum
    }
}

错误处理

if err != nil {
    return err
}
 
自定义错误:
type MyError struct {
    Msg string
}
 
func (e *MyError) Error() string {
    return e.Msg
}
 
panic/recover:
func recoverFromPanic() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recovered from panic:", r)
        }
    }()
    panic("something went wrong")
}

1.2 常见面试题

Q1: Go的变量声明有哪些方式?

解题思路

  1. 列举Go中变量声明的几种方式
  2. 说明每种方式的适用场景
  3. 对比不同方式的优缺点

代码实现

package main
 
import "fmt"
 
func main() {
    var name string = "方式1:完整声明"  // 完整声明:显式指定类型 string
    var age = 25                         // var声明省略类型:Go自动推断为 int
    var isActive bool = true             // 完整声明:显式指定类型 bool
    
    city := "方式2:短变量声明"          // 短变量声明:只能在函数内使用,自动推断类型
    
    var (                                // 批量声明:可以省略类型,Go自动推断
        country = "China"                // 自动推断为 string
        province = "Beijing"             // 自动推断为 string
    )
    
    fmt.Println(name)
    fmt.Println(age)
    fmt.Println(isActive)
    fmt.Println(city)
    fmt.Println(country, province)
}

常见错误分析

  • 错误1:在函数外使用短变量声明 :=
  • 错误2:声明变量但不使用(Go不允许未使用的变量)
  • 错误3:重复声明变量

Q2: 数组和切片的区别是什么?

解题思路

  1. 解释数组的基本特性(固定长度、值类型)
  2. 解释切片的基本特性(动态长度、引用类型)
  3. 说明切片的底层实现(指向数组的指针、长度、容量)
  4. 演示切片的操作(append、copy、切片表达式)

代码实现

package main
 
import "fmt"
 
func main() {
    // 数组:固定长度、值类型、内存连续
    // 声明一个长度为5的数组,包含元素 [1, 2, 3, 4, 5]
    arr := [5]int{1, 2, 3, 4, 5}
    
    // 切片:动态长度、引用类型、底层指向数组
    // 切片结构:{指向数组的指针, 长度len, 容量cap}
    // arr[1:3] 表示从索引1开始到索引3(不包含3),即 [2, 3]
    // slice 指向 arr[1] 和 arr[2],容量从索引1到数组末尾,即 cap=4
    slice := arr[1:3]
    
    fmt.Printf("Array: %v, len=%d, cap=%d\n", arr, len(arr), cap(arr))
    fmt.Printf("Slice: %v, len=%d, cap=%d\n", slice, len(slice), cap(slice))
    
    // 修改切片元素:因为切片是引用类型,修改会影响底层数组
    // slice[0] 实际上修改的是 arr[1]
    slice[0] = 10
    fmt.Printf("After modification - Array: %v, Slice: %v\n", arr, slice)
    // 输出:Array: [1 10 3 4 5], Slice: [10 3]
    // 可以看到 arr[1] 被修改为 10
    
    // append操作:如果容量足够,直接在底层数组上追加
    // 如果容量不足,会创建新的底层数组,并复制原有元素
    // 这里 slice 的容量是4,当前长度是2,追加2个元素后长度变为4,容量足够
    newSlice := append(slice, 6, 7)
    fmt.Printf("New slice: %v, len=%d, cap=%d\n", newSlice, len(newSlice), cap(newSlice))
    // 输出:New slice: [10 3 6 7], len=4, cap=4
    // 注意:因为容量足够,newSlice 仍然指向原来的底层数组 arr
    // 所以 arr 也会被修改为 [1 10 3 6 7]
    
    // 演示扩容机制:当 len == cap 时,append 会触发扩容
    fmt.Println("\n--- 扩容机制演示 ---")
    smallSlice := make([]int, 2, 2)  // len=2, cap=2
    fmt.Printf("Before append: slice=%v, len=%d, cap=%d\n", smallSlice, len(smallSlice), cap(smallSlice))
    fmt.Printf("Slice pointer: %p\n", &smallSlice[0])
    
    // 追加一个元素,触发扩容
    expandedSlice := append(smallSlice, 3)
    fmt.Printf("After append: slice=%v, len=%d, cap=%d\n", expandedSlice, len(expandedSlice), cap(expandedSlice))
    fmt.Printf("New slice pointer: %p\n", &expandedSlice[0])
    // 输出可以看到指针地址变了,说明创建了新的底层数组
    
    // 扩容规则(Go 1.18+):
    // 1. 如果新容量 < 256,新容量 = 旧容量 * 2
    // 2. 如果新容量 >= 256,新容量 = 旧容量 + 旧容量/4
    // 3. 还会考虑内存对齐等因素
    
    fmt.Println("\n--- 扩容倍数演示 ---")
    s := make([]int, 0)
    oldCap := 0
    for i := 0; i < 20; i++ {
        s = append(s, i)
        if cap(s) != oldCap {
            fmt.Printf("len=%d, cap=%d (扩容了)\n", len(s), cap(s))
            oldCap = cap(s)
        }
    }
}

常见错误分析

  • 错误1:误以为切片是值拷贝,实际上切片是引用类型
  • 错误2:不理解切片扩容机制,导致性能问题
  • 错误3:切片操作越界

Q3: Go的map是线程安全的吗?如何实现线程安全的map?

解题思路

  1. 说明Go的map不是线程安全的
  2. 解释并发读写map会导致panic
  3. 提供几种线程安全的解决方案:
    • 使用sync.Mutex
    • 使用sync.RWMutex
    • 使用sync.Map(适合读多写少的场景)

代码实现

package main
 
import (
    "fmt"
    "sync"
)
 
type SafeMap struct {
    mu   sync.RWMutex
    data map[string]int
}
 
func NewSafeMap() *SafeMap {
    return &SafeMap{
        data: make(map[string]int),
    }
}
 
func (sm *SafeMap) Get(key string) (int, bool) {
    sm.mu.RLock()
    defer sm.mu.RUnlock()
    val, ok := sm.data[key]
    return val, ok
}
 
func (sm *SafeMap) Set(key string, value int) {
    sm.mu.Lock()
    defer sm.mu.Unlock()
    sm.data[key] = value
}
 
func (sm *SafeMap) Delete(key string) {
    sm.mu.Lock()
    defer sm.mu.Unlock()
    delete(sm.data, key)
}
 
func main() {
    sm := NewSafeMap()
    sm.Set("key1", 100)
    
    if val, ok := sm.Get("key1"); ok {
        fmt.Println("key1:", val)
    }
    
    sm.Delete("key1")
    
    var wg sync.WaitGroup
    for i := 0; i < 100; i++ {
        wg.Add(1)
        go func(n int) {
            defer wg.Done()
            sm.Set(fmt.Sprintf("key%d", n), n)
        }(i)
    }
    wg.Wait()
    
    fmt.Println("Safe map size:", len(sm.data))
}

常见错误分析

  • 错误1:在多个goroutine中并发读写map导致panic
  • 错误2:过度使用锁导致性能下降
  • 错误3:死锁问题(忘记解锁或锁顺序不一致)

方案选择指南

场景对比

场景推荐方案原因
读多写少sync.RWMutex读操作并发,性能最优
读写都多sync.Mutex避免写操作饥饿
读少写多sync.Mutex读写锁反而更慢
类型固定手动锁类型安全,性能好
类型动态sync.Map灵活性高,适合缓存

类型固定场景(90%的业务代码)

// 场景:用户服务、订单服务、配置管理等
type UserService struct {
    users map[int64]*User  // 类型固定:key是int64,value是*User
    mu    sync.RWMutex
}
 
type User struct {
    ID   int64
    Name string
}
 
func (s *UserService) GetUser(id int64) (*User, bool) {
    s.mu.RLock()
    defer s.mu.RUnlock()
    user, ok := s.users[id]
    return user, ok
}
 
// 优点:
// 1. 类型安全,编译时检查
// 2. 性能更好
// 3. 代码清晰,易于维护

类型动态场景(10%的基础设施代码)

// 场景:通用缓存、动态配置、插件系统等
var cache sync.Map
 
// 可以存储任意类型的数据
cache.Store("user:1", User{ID: 1, Name: "John"})
cache.Store("config:timeout", 30)
cache.Store("session:abc", SessionData{...})
cache.Store(123, "numeric key")
 
// 优点:
// 1. 灵活性高,可以存储任意类型
// 2. 适合缓存场景
// 3. 内置并发安全
 
// 缺点:
// 1. 需要类型断言,容易出错
// 2. 可能导致运行时panic
// 3. 性能不如手动锁

快速决策指南

你的map类型固定吗?
├─ 是 → 使用手动锁(sync.Mutex 或 sync.RWMutex)
│  ├─ 读多写少 → sync.RWMutex
│  └─ 读写都多 → sync.Mutex
│
└─ 否 → 使用 sync.Map
   └─ 适合缓存、动态配置等场景

实际应用示例

业务代码(推荐手动锁)

type UserRepository struct {
    users map[int64]*User
    mu    sync.RWMutex
}
 
func (r *UserRepository) GetUser(id int64) (*User, bool) {
    r.mu.RLock()
    defer r.mu.RUnlock()
    user, ok := r.users[id]
    return user, ok
}

缓存代码(推荐sync.Map)

var globalCache sync.Map
 
func GetFromCache(key interface{}) (interface{}, bool) {
    return globalCache.Load(key)
}
 
func SetCache(key, value interface{}) {
    globalCache.Store(key, value)
}

同步map实现通用缓存

方案1:泛型版本(Go 1.18+,类型安全)

package cache
 
import "sync"
 
type Cache[K comparable, V any] struct {
    mu   sync.RWMutex
    data map[K]V
}
 
func New[K comparable, V any]() *Cache[K, V] {
    return &Cache[K, V]{
        data: make(map[K]V),
    }
}
 
func (c *Cache[K, V]) Get(key K) (V, bool) {
    c.mu.RLock()
    defer c.mu.RUnlock()
    val, ok := c.data[key]
    return val, ok
}
 
func (c *Cache[K, V]) Set(key K, value V) {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.data[key] = value
}
 
func (c *Cache[K, V]) Delete(key K) {
    c.mu.Lock()
    defer c.mu.Unlock()
    delete(c.data, key)
}
 
func (c *Cache[K, V]) Size() int {
    c.mu.RLock()
    defer c.mu.RUnlock()
    return len(c.data)
}
 
// 使用示例
func ExampleGenericCache() {
    // 字符串缓存
    stringCache := New[string, string]()
    stringCache.Set("key1", "value1")
    if val, ok := stringCache.Get("key1"); ok {
        fmt.Println(val)  // value1
    }
 
    // 整数缓存
    intCache := New[string, int]()
    intCache.Set("count", 100)
    if val, ok := intCache.Get("count"); ok {
        fmt.Println(val)  // 100
    }
 
    // 结构体缓存
    type User struct {
        ID   int64
        Name string
    }
    userCache := New[int64, *User]()
    userCache.Set(1, &User{ID: 1, Name: "John"})
    if user, ok := userCache.Get(1); ok {
        fmt.Println(user.Name)  // John
    }
}

方案2:interface{}版本(灵活但需要类型断言)

package cache
 
import "sync"
 
type GenericCache struct {
    mu   sync.RWMutex
    data map[interface{}]interface{}
}
 
func NewGenericCache() *GenericCache {
    return &GenericCache{
        data: make(map[interface{}]interface{}),
    }
}
 
func (c *GenericCache) Get(key interface{}) (interface{}, bool) {
    c.mu.RLock()
    defer c.mu.RUnlock()
    val, ok := c.data[key]
    return val, ok
}
 
func (c *GenericCache) Set(key, value interface{}) {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.data[key] = value
}
 
func (c *GenericCache) Delete(key interface{}) {
    c.mu.Lock()
    defer c.mu.Unlock()
    delete(c.data, key)
}
 
func (c *GenericCache) Size() int {
    c.mu.RLock()
    defer c.mu.RUnlock()
    return len(c.data)
}
 
// 使用示例
func ExampleGenericCache() {
    cache := NewGenericCache()
 
    // 可以存储任意类型
    cache.Set("string_key", "string_value")
    cache.Set(123, 456)
    cache.Set(3.14, "pi")
 
    // 获取值(需要类型断言)
    if val, ok := cache.Get("string_key"); ok {
        str := val.(string)
        fmt.Println(str)  // string_value
    }
 
    if val, ok := cache.Get(123); ok {
        num := val.(int)
        fmt.Println(num)  // 456
    }
}

方案3:带过期时间的缓存

package cache
 
import (
    "sync"
    "time"
)
 
type CacheItem struct {
    Value      interface{}
    Expiration int64
}
 
type ExpiringCache struct {
    mu   sync.RWMutex
    data map[string]*CacheItem
}
 
func NewExpiringCache() *ExpiringCache {
    cache := &ExpiringCache{
        data: make(map[string]*CacheItem),
    }
    go cache.cleanupExpired()
    return cache
}
 
func (c *ExpiringCache) Set(key string, value interface{}, ttl time.Duration) {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.data[key] = &CacheItem{
        Value:      value,
        Expiration: time.Now().Add(ttl).UnixNano(),
    }
}
 
func (c *ExpiringCache) Get(key string) (interface{}, bool) {
    c.mu.RLock()
    defer c.mu.RUnlock()
 
    item, ok := c.data[key]
    if !ok {
        return nil, false
    }
 
    // 检查是否过期
    if time.Now().UnixNano() > item.Expiration {
        return nil, false
    }
 
    return item.Value, true
}
 
func (c *ExpiringCache) Delete(key string) {
    c.mu.Lock()
    defer c.mu.Unlock()
    delete(c.data, key)
}
 
func (c *ExpiringCache) cleanupExpired() {
    ticker := time.NewTicker(time.Minute)
    defer ticker.Stop()
 
    for range ticker.C {
        c.mu.Lock()
        now := time.Now().UnixNano()
        for key, item := range c.data {
            if now > item.Expiration {
                delete(c.data, key)
            }
        }
        c.mu.Unlock()
    }
}
 
// 使用示例
func ExampleExpiringCache() {
    cache := NewExpiringCache()
 
    // 设置缓存,5秒后过期
    cache.Set("user:1", User{ID: 1, Name: "John"}, 5*time.Second)
 
    // 获取缓存
    if user, ok := cache.Get("user:1"); ok {
        u := user.(User)
        fmt.Println(u.Name)  // John
    }
 
    // 等待5秒后,缓存已过期
    time.Sleep(5 * time.Second)
    if _, ok := cache.Get("user:1"); !ok {
        fmt.Println("Cache expired")
    }
}

方案4:带统计功能的缓存

package cache
 
import (
    "sync"
    "sync/atomic"
)
 
type CacheStats struct {
    Hits   uint64
    Misses uint64
    Sets   uint64
    Deletes uint64
}
 
type StatsCache struct {
    mu    sync.RWMutex
    data  map[string]interface{}
    stats CacheStats
}
 
func NewStatsCache() *StatsCache {
    return &StatsCache{
        data: make(map[string]interface{}),
    }
}
 
func (c *StatsCache) Get(key string) (interface{}, bool) {
    c.mu.RLock()
    val, ok := c.data[key]
    c.mu.RUnlock()
 
    if ok {
        atomic.AddUint64(&c.stats.Hits, 1)
    } else {
        atomic.AddUint64(&c.stats.Misses, 1)
    }
 
    return val, ok
}
 
func (c *StatsCache) Set(key string, value interface{}) {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.data[key] = value
    atomic.AddUint64(&c.stats.Sets, 1)
}
 
func (c *StatsCache) Delete(key string) {
    c.mu.Lock()
    defer c.mu.Unlock()
    delete(c.data, key)
    atomic.AddUint64(&c.stats.Deletes, 1)
}
 
func (c *StatsCache) GetStats() CacheStats {
    return CacheStats{
        Hits:    atomic.LoadUint64(&c.stats.Hits),
        Misses:  atomic.LoadUint64(&c.stats.Misses),
        Sets:    atomic.LoadUint64(&c.stats.Sets),
        Deletes: atomic.LoadUint64(&c.stats.Deletes),
    }
}
 
func (c *StatsCache) HitRate() float64 {
    stats := c.GetStats()
    total := stats.Hits + stats.Misses
    if total == 0 {
        return 0
    }
    return float64(stats.Hits) / float64(total) * 100
}
 
// 使用示例
func ExampleStatsCache() {
    cache := NewStatsCache()
 
    cache.Set("key1", "value1")
    cache.Set("key2", "value2")
 
    cache.Get("key1")  // Hit
    cache.Get("key2")  // Hit
    cache.Get("key3")  // Miss
 
    stats := cache.GetStats()
    fmt.Printf("Hits: %d, Misses: %d\n", stats.Hits, stats.Misses)
    fmt.Printf("Hit Rate: %.2f%%\n", cache.HitRate())
    // 输出:
    // Hits: 2, Misses: 1
    // Hit Rate: 66.67%
}

方案对比

方案类型安全性能灵活性适用场景
泛型缓存⭐⭐⭐⭐⭐⭐⭐⭐业务代码,类型固定
interface{}缓存⭐⭐⭐⭐⭐⭐⭐⭐⭐通用缓存,类型灵活
过期缓存⭐⭐⭐⭐⭐⭐⭐临时数据,会话管理
统计缓存⭐⭐⭐⭐⭐⭐⭐需要监控缓存的场景

实际应用:用户缓存服务

package service
 
import (
    "time"
    "yourproject/cache"
)
 
type User struct {
    ID       int64
    Name     string
    Email    string
}
 
type UserService struct {
    cache *cache.Cache[int64, *User]  // 泛型缓存
    db    *Database
}
 
func NewUserService(db *Database) *UserService {
    return &UserService{
        cache: cache.New[int64, *User](),
        db:    db,
    }
}
 
func (s *UserService) GetUser(id int64) (*User, error) {
    // 先从缓存获取
    if user, ok := s.cache.Get(id); ok {
        return user, nil
    }
 
    // 缓存未命中,从数据库查询
    user, err := s.db.QueryUser(id)
    if err != nil {
        return nil, err
    }
 
    // 写入缓存
    s.cache.Set(id, user)
 
    return user, nil
}
 
func (s *UserService) UpdateUser(user *User) error {
    // 更新数据库
    if err := s.db.UpdateUser(user); err != nil {
        return err
    }
 
    // 更新缓存
    s.cache.Set(user.ID, user)
 
    return nil
}

Q4: Go的defer有什么特性?

解题思路

  1. 解释defer的基本概念(延迟执行)
  2. 说明defer的执行顺序(LIFO)
  3. 解释defer的参数求值时机(声明时求值)
  4. 演示defer的常见应用场景(资源释放、错误处理)

代码实现

package main
 
import (
    "fmt"
    "os"
)
 
func main() {
    defer fmt.Println("defer 1")
    defer fmt.Println("defer 2")
    defer fmt.Println("defer 3")
    
    fmt.Println("main function")
    
    for i := 0; i < 3; i++ {
        defer fmt.Printf("defer in loop: %d\n", i)
    }
    
    result := calculate()
    fmt.Println("Result:", result)
}
 
func calculate() (result int) {
    defer func() {
        result += 10
    }()
    
    result = 5
    return
}
 
func readFile(filename string) error {
    file, err := os.Open(filename)
    if err != nil {
        return err
    }
    defer file.Close()
    
    return nil
}

常见错误分析

  • 错误1:不理解defer的执行顺序
  • 错误2:在循环中使用defer导致资源泄漏
  • 错误3:defer中修改命名返回值导致逻辑错误

Q5: Go的error处理机制有什么特点?

解题思路

  1. 说明Go的error是一个接口类型
  2. 解释Go的error处理哲学(显式处理、不使用异常)
  3. 演示自定义error的实现
  4. 介绍errors包的使用
  5. 讲解错误包装和错误链
  6. 说明错误处理的最佳实践

核心概念

1. Error的本质

error是一个接口类型,只有一个方法Error() string:

type error interface {
    Error() string
}

2. Go的错误处理哲学

  • 显式处理:错误必须显式检查和处理
  • 不使用异常:没有try-catch-finally机制
  • 错误即值:error是一个普通的值,可以传递、存储、比较
  • 快速失败:遇到错误立即返回,避免继续执行

代码实现

基础错误处理

package main
 
import (
    "errors"
    "fmt"
)
 
func divide(a, b int) (int, error) {
    if b == 0 {
        return 0, errors.New("division by zero")
    }
    return a / b, nil
}
 
func main() {
    result, err := divide(10, 0)
    if err != nil {
        fmt.Println("Error:", err)
    } else {
        fmt.Println("Result:", result)
    }
}

自定义Error类型

type ValidationError struct {
    Field   string
    Message string
}
 
func (ve *ValidationError) Error() string {
    return fmt.Sprintf("validation error on field %s: %s", ve.Field, ve.Message)
}
 
func validateAge(age int) error {
    if age < 0 {
        return &ValidationError{
            Field:   "age",
            Message: "age cannot be negative",
        }
    }
    if age > 150 {
        return &ValidationError{
            Field:   "age",
            Message: "age cannot be greater than 150",
        }
    }
    return nil
}
 
func main() {
    err := validateAge(-5)
    if err != nil {
        if ve, ok := err.(*ValidationError); ok {
            fmt.Printf("Validation failed: %s - %s\n", ve.Field, ve.Message)
        } else {
            fmt.Println("Unknown error:", err)
        }
    }
}

错误包装和错误链(Go 1.13+)

import (
    "errors"
    "fmt"
)
 
func readFile(filename string) ([]byte, error) {
    data, err := os.ReadFile(filename)
    if err != nil {
        return nil, fmt.Errorf("failed to read file %s: %w", filename, err)
    }
    return data, nil
}
 
func main() {
    data, err := readFile("nonexistent.txt")
    if err != nil {
        fmt.Printf("Error: %v\n", err)
        
        if errors.Is(err, os.ErrNotExist) {
            fmt.Println("File does not exist")
        }
        
        var pathErr *os.PathError
        if errors.As(err, &pathErr) {
            fmt.Printf("Path: %s\n", pathErr.Path)
        }
    }
}

定义错误变量

var (
    ErrUserNotFound = errors.New("user not found")
    ErrInvalidInput = errors.New("invalid input")
)
 
func getUser(id int64) (*User, error) {
    user, err := db.QueryUser(id)
    if err != nil {
        if errors.Is(err, sql.ErrNoRows) {
            return nil, ErrUserNotFound
        }
        return nil, err
    }
    return user, nil
}
 
func main() {
    user, err := getUser(1)
    if errors.Is(err, ErrUserNotFound) {
        fmt.Println("User not found")
    }
}

Panic和Recover

func safeExecute() (err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("panic recovered: %v", r)
        }
    }()
    
    panic("something went wrong")
}
 
func main() {
    if err := safeExecute(); err != nil {
        fmt.Println("Recovered:", err)
    }
}

最佳实践

  1. 总是检查错误
file, err := os.Open("file.txt")
if err != nil {
    return err
}
defer file.Close()
  1. 添加上下文信息
return fmt.Errorf("failed to get user %d: %w", id, err)
  1. 定义明确的错误类型
var ErrUserNotFound = errors.New("user not found")
  1. 避免过度包装
return fmt.Errorf("failed: %w", err)

常见错误分析

  • 错误1:忽略error返回值
    file, _ := os.Open("file.txt")  // 错误
  • 错误2:过度使用panic/recover
    panic("division by zero")  // 错误,应该返回error
  • 错误3:错误信息不够详细,难以调试
    return errors.New("error")  // 错误,缺少上下文
  • 错误4:错误包装后丢失原始错误
    return fmt.Errorf("failed: %s", err)  // 错误,应该用%w