Steins;Lab

  • 项目
  • 折腾
  • 笔记
  • 图册
  • 杂谈
  • 文章索引 - 博主自荐博文
  • 关于/留言
Steins;Lab
某团的自留研究所
  1. 首页
  2. 学习笔记
  3. 正文

100 Go Mistakes 阅读随记 - 03 Control Structures, Functions and Methods, Error Management

2023年1月18日 2515点热度 0人点赞 0条评论

100 Go Mistakes and How to Avoid Them 阅读笔记。4-Control Structures, 6-Functions and Methods, 7-Error Management

Table of Contents

  • 引言
  • 第 4 章: Control Structures 控制结构
    • #30 忽视在 range 循环中,元素被复制
    • #32 忽视指针迭代陷阱
    • #33 对 map 迭代抱有错误假设
    • #34 忽略了 break 对应的生效位置
  • 第 6 章: Functions and Methods 函数和方法
    • #45: Returning a nil receiver
    • #47: Ignoring how defer arguments and receivers are evaluated
  • 第 7 章:Error management 错误管理
    • #49: Ignoring when to wrap an error
    • #50: Checking an error type inaccurately
    • #51: Checking an error value inaccurately
    • #52: Handling an error twice

引言

最近发现了一本书籍,叫做 100 Go Mistakes and How to Avoid Them。主要描述了使用 Go 语言编程时的常见问题。
其中第一章文中即很有趣地引用了句谚语:

Tell me and I forget.
Teach me and I remember.
Involve me and I learn.

于是博主决定挑出其中自己阅读过程中印象深刻的示例,结合自己粗浅的经验,跑一跑例程,同时也激励自己勤耕不辍写一点点笔记出来。
笔者不想完全 copy 书中的内容,所以随记尽量精简。如果读者能在碎片化时间中对某些 "Mistakes" 有印象,是本系列笔记的荣幸!同时推荐有时间的读者直接阅读原文。

第 4 章: Control Structures 控制结构

本章主要描述循环迭代的技巧和陷阱。

#30 忽视在 range 循环中,元素被复制

该部分大部分 Go 入门时都会着重描述,这篇笔记不再赘述。
开发者在迭代中更改被迭代的对象值时应当额外注意,

  • 牢记在 range 循环中,元素被复制,更改是否生效在我们真正希望的元素上
    // wrong:
    accounts := []account{
    {balance: 100.},
    {balance: 200.},
    {balance: 300.},
    }
    for _, a := range accounts {
    a.balance += 1000
    }
  • 是否如期望改变了 range 迭代的逻辑
    s := []int{0, 1, 2}
    for range s {
    s = append(s, 10)
    }
    // 3 次迭代后将停止

#32 忽视指针迭代陷阱

func (s *Store) storeCustomers(customers []Customer) {
    for _, customer := range customers {
        fmt.Printf("%p\n", &customer)
        s.m[customer.ID] = &customer
    }
}
// output
0xc000096020
0xc000096020
0xc000096020

Extra

迭代的 index value 一直是 Go 开发者最容易犯的错误。扩展阅读:
十多年了,这个最容易犯错的Go语法终于要改了 - 鸟窝的博客

#33 对 map 迭代抱有错误假设

迭代 map 时候,开发者不应该依赖于迭代的顺序。不能假设数据被添加的顺序。
Go 甚至添加了一些随机化来确保开发者不会依赖假设的顺序。

这也意味着,在迭代中,试图通过修改 map 数据来控制迭代逻辑是完全不可预料的。

m := map[int]bool{
    0: true,
    1: false,
    2: true,
}
for k, v := range m {
    if v {
        m[10+k] = true
    }
}
fmt.Println(m)
map[0:true 1:false 2:true 10:true 12:true 20:true 22:true 30:true]
map[0:true 1:false 2:true 10:true 12:true 20:true 22:true 30:true 32:true]
map[0:true 1:false 2:true 10:true 12:true 20:true]

一个可选的方法,是复制一份 map 出来,再进行更改。

#34 忽略了 break 对应的生效位置

break 可以对应 switch,使用时容易弄混。
可以使用 break 行标签的方式,在复杂循环中跳转

readlines:
    for {
        line, err := rw.Body.ReadString('\n')
        switch {
        case err == io.EOF:
            break readlines
        case err != nil:
        t.Fatalf("unexpected error reading from CGI: %v", err)
        }
        // ...
    }

第 6 章: Functions and Methods 函数和方法

#45: Returning a nil receiver

函数返回 interface 时候容易出现的反直觉错误。

引出例

书中给出了如下使用例。
假如我们封装了自己的多 error 结构体 MultiError。 其实现了 Error 接口。

type MultiError struct {
    errs []string
}
func (m *MultiError) Add(err error) {
    m.errs = append(m.errs, err.Error())
}
func (m *MultiError) Error() string {
    return strings.Join(m.errs, ";")
}

实际使用中如下:

func (c Customer) Validate() error {
    var m *MultiError
    if c.Age < 0 {
        m = &MultiError{}
        m.Add(errors.New("age is negative"))
    }
    if c.Name == "" {
        if m == nil {
            m = &MultiError{}
        }
        m.Add(errors.New("name is nil"))
    }
    return m
}
customer := Customer{Age: 33, Name: "John"}
if err := customer.Validate(); err != nil {
    log.Fatalf("customer is invalid: %v", err)
}

将得到一个非常怪异的输出

2021/05/08 13:47:28 customer is invalid: <nil>

pointer receiver 允许为 nil, 以及 interface wrap 机制

虽然我们想要返回的 MultiError 为 nil,但是其实现了 Error 接口。
返回的 Error 不为 nil。

在 Go 中,接口实际上是一个 warpper。我们的空 MultiError被包装成了为非空的 Error。

为了改正这个错误,我们可以在检查后直接返回 nil。

    if c.Age < 0 {
        ...
    }
    if c.Name == "" {
        ...
    }
    return nil

本错误建议在开发者返回 interface 时候留有意识检查。

Extra

参考阅读
Go Doc 关于 nil error 的描述: https://go.dev/doc/faq#nil_error
相关讨论: https://github.com/golang/go/issues/42663

#47: Ignoring how defer arguments and receivers are evaluated

注意 defer 的闭包使用

func f() error {
    var status string
    defer func() {
        notify(status)
        incrementCounter(status)
    }()
    // The rest of the function
}

第 7 章:Error management 错误管理

#49: Ignoring when to wrap an error

#50: Checking an error type inaccurately

#51: Checking an error value inaccurately

3 节主要讲述了 error warp 的由来,和 errors.As() errors.Is()配套使用方法。
应当使用 warp error,让 debug 更清晰。

#52: Handling an error twice

这里主要指在返回 error 的时候,不要多次打印日志,难于排查原因。
一个方式,是在上层调用处打印 error 日志

func GetRoute(srcLat, srcLng, dstLat, dstLng float32) (Route, error) {
    err := validateCoordinates(srcLat, srcLng)
    if err != nil {
        // 此处不必打印日志,在最初调用处打印即可
        return Route{}, err
    }
    err = validateCoordinates(dstLat, dstLng)
    if err != nil {
        // 此处不必打印日志,在最初调用处打印即可
        return Route{}, err
    }
    return getRoute(srcLat, srcLng, dstLat, dstLng)
}

func validateCoordinates(lat, lng float32) error {
    if lat > 90.0 || lat < -90.0 {
        return fmt.Errorf("invalid latitude: %f", lat)
    }
    if lng > 180.0 || lng < -180.0 {
        return fmt.Errorf("invalid longitude: %f", lng)
    }
    return nil
}

笔者自己在 code review 时也曾被指出这个问题。多次打印 error 将在复杂调用中增加排查问题的难度。

但这种处理方式仍然不是最好的,因为我们很难找到具体发生 error 的地方,因此可以套用上述的 wrap 方法,在每层返回都追加信息。如此,在最上层调用处打印的 log 可以反应整个的调用链路。

func GetRoute(srcLat, srcLng, dstLat, dstLng float32) (Route, error) {
err := validateCoordinates(srcLat, srcLng)
    if err != nil {
        return Route{},
            fmt.Errorf("failed to validate source coordinates: %w",
                err)
    }
    err = validateCoordinates(dstLat, dstLng)
    if err != nil {
        return Route{},
            fmt.Errorf("failed to validate target coordinates: %w",
                err)
    }
    return getRoute(srcLat, srcLng, dstLat, dstLng)
}

相关

标签: Go 学习笔记
最后更新:2023年1月18日

SPtuan

SPtuan 是一名普通的工程师,最大的愿望是度过平静的时光。 当前从事网络/CDN/对象存储研发。

点赞
< 上一篇
下一篇 >
0 0 votes
文章评分
Subscribe
Login
提醒
guest

guest

0 评论
最新
最旧 得票最多
Inline Feedbacks
View all comments

SPtuan

SPtuan 是一名普通的工程师,最大的愿望是度过平静的时光。
当前从事网络/CDN/对象存储研发。

  • 引言
  • 第 4 章: Control Structures 控制结构
    • #30 忽视在 range 循环中,元素被复制
    • #32 忽视指针迭代陷阱
    • #33 对 map 迭代抱有错误假设
    • #34 忽略了 break 对应的生效位置
  • 第 6 章: Functions and Methods 函数和方法
    • #45: Returning a nil receiver
    • #47: Ignoring how defer arguments and receivers are evaluated
  • 第 7 章:Error management 错误管理
    • #49: Ignoring when to wrap an error
    • #50: Checking an error type inaccurately
    • #51: Checking an error value inaccurately
    • #52: Handling an error twice
分类
  • Uncategorized
  • 图册
  • 学习笔记
  • 库
  • 折腾
  • 杂谈
  • 瞎**扯
  • 碎碎念
  • 项目跟踪
最近评论
SPtuan 发布于 2 个月前(03月22日) 书签: 关于 disk-io 的经验, 异步/同步 io 系统设计的经验 https://you...
SPtuan 发布于 2 个月前(03月21日) 如果公司不是对外提供这些服务的,这种岗位都是 infra 部门,平均年龄确实会大一些。尤其构建和维护...
HUA 发布于 2 个月前(03月19日) 想请问博主对于国内CDN行业,以及CDN调度、DNS托管类服务相关岗位的看法,以及是否还推荐校招新人...
SPtuan 发布于 3 个月前(02月03日) 2025 注: 长辈对于只身去深圳的担忧,更多地来自于 80s/90s 治安情况。近几年了解了严打...
SPtuan 发布于 4 个月前(01月16日) 哈哈,100就100吧,新年快乐!
热门主题 & 页面
  • 全球互联网拓扑探索 (1) : 互联网是如何工作的
  • 使用 WSL2 + X11 转发 - 在 Windows10 中打造 GNU/Linux 学习生产环境
  • PYNQ上手体验:以目标检测应用为例
  • Intel Movidius Neural Compute Stick - 英特尔Movidius神经计算棒上手体验
  • iowait 到底是什么?
归档
  • 2025 年 5 月
  • 2025 年 3 月
  • 2024 年 12 月
  • 2024 年 9 月
  • 2024 年 8 月
  • 2024 年 5 月
  • 2024 年 3 月
  • 2024 年 2 月
  • 2023 年 12 月
  • 2023 年 11 月
  • 2023 年 9 月
  • 2023 年 8 月
  • 2023 年 4 月
  • 2023 年 1 月
  • 2022 年 12 月
  • 2022 年 10 月
  • 2022 年 9 月
  • 2022 年 7 月
  • 2022 年 6 月
  • 2022 年 2 月
  • 2021 年 12 月
  • 2021 年 11 月
  • 2021 年 2 月
  • 2021 年 1 月
  • 2020 年 9 月
  • 2020 年 4 月
  • 2020 年 3 月
  • 2020 年 1 月
  • 2019 年 8 月
  • 2019 年 7 月
  • 2019 年 5 月
  • 2019 年 4 月
  • 2019 年 3 月
  • 2019 年 2 月
  • 2018 年 12 月
  • 2018 年 10 月
  • 2018 年 9 月
  • 2018 年 8 月
  • 2018 年 5 月
  • 2018 年 2 月
  • 2018 年 1 月
  • 2017 年 11 月
  • 2017 年 9 月
  • 2017 年 7 月
  • 2017 年 6 月
  • 2017 年 5 月
  • 2017 年 4 月
  • 2017 年 3 月
  • 2017 年 2 月
  • 2017 年 1 月
  • 2016 年 12 月
  • 2016 年 11 月
  • 2016 年 10 月
  • 2016 年 9 月
  • 2016 年 8 月
  • 2016 年 7 月
  • 2016 年 6 月
  • 2016 年 5 月
  • 2016 年 4 月
  • 2016 年 3 月
  • 2016 年 2 月
  • 2016 年 1 月
  • 2015 年 12 月
  • 2015 年 11 月
  • 2015 年 9 月

友情链接:

Blessing Studio hahaschool 绘枫和畅 魔法少女Fandy monsterx Clarke的博客 Luminous’ Home Shintaku's Blog
蓝黑的博客 haruhi.club Yida的博客 Bo2SS 涛叔 TangBao 同和君Hocassian

Steins;Lab 团子神社 zdfmc.net

steinslab.io built with ❤. Thanks for all 2015-2025.

Theme Kratos Made By Seaton Jiang

wpDiscuz