Go 开发十种常犯错误(下)

简介: Go 开发十种常犯错误

6、slice 初始化


有时候我们明确的知道一个 slice 切片的最终长度。例如我们想要将一个 Foo 切片 convert 为 Bar 切片,这意味着两个 slice 切片的长度是相同的。


然而有些人却经常初始化 slice 切片如:

var bars []Bar
bars := make([]Bar, 0)

slice 切片并不是一个神奇的结构,当没有更多可用空间时,它会进行扩容,也就是其将会自动创建一个具有更大容量的新数组并复制所有的元素。


现在,让我们想象一下如果切片需要多次扩容,即使时间复杂度保持为 O(1) ,但在实践中,它也会对性能造成影响。尽可能避免这种情况。


7、context 管理


context.Context 经常被开发者误解。官方文档的描述是:

A Context carries a deadline, a cancelation signal, and other values across API boundaries.

这个描述很宽泛,以致于一些人对为什么以及如何使用它感到困惑。


让我们试着详细说明下,一个 context 可以包含:

  • 一个 deadline 。其可以是持续时间(例如 250 毫秒)或者具体某个时间点(例如 2019-01-08 01:00:00),一旦达到 deadline 则所有正在进行的活动都会取消(比如 I/O 请求,等待某个 channel 输入等等)。
  • 一个取消 signal 信号(基本上是 <-chan struct{} )。这里的行为是类似的,一旦收到取消信号则必须停止正在进行中的活动。
  • 一组 key/value(基于 interface{} 类型)。


需要说明的是,一个 context 是可组合的,例如既包含一个 deadline 又包含一组 key/value 。此外,多个 goroutine 可以共享同一个 context ,因此取消信号可能会导致多个 goroutine 中的活动被停止。


例如由同一个 context 引发的连环取消,我们要注意使用父子形式的 context ,以此来区分管理,避免相互影响。



8、未使用 -race


测试时未使用 -race 选项也是常见的,它是有价值的工具,我们应该在测试时始终启动它。



9、使用文件名作为输入


假设我们要实现一个函数去统计文件中的空行数,我们可能这样做:

func count(filename string) (int, error) {
  file, err := os.Open(filename)
  if err != nil {
    return 0, errors.Wrapf(err, "unable to open %s", filename)
  }
  defer file.Close()
  scanner := bufio.NewScanner(file)
  count := 0
  for scanner.Scan() {
    if scanner.Text() == "" {
      count++
    }
  }
  return count, nil
}

这看起来很自然,filename 文件名作为输入,在函数内部打开文件。


然而,如果我们想要对此函数进行单元测试,输入可能是普通文件,或者空文件,或者其它不同编码类型的文件等等,此时则很容易变得难以管理。另外,如果我们想对某个 HTTP body 实现相同的逻辑,那么我们不得不创建一个另外的函数。


Go 提供了两个很棒的抽象:io.Reader 和 io.Writer 。我们可以传递 io.Reader 抽象数据源而不是 filename 。这样不管是文件也好,HTTP body 也好,byte buffer 也好,我们都只需要使用 Read 方法即可。


上述例子中,我们甚至可以缓冲输入以逐行读取,因此我们可以使用 bufio.Reader 和它的 ReadLine 方法:

func count(reader *bufio.Reader) (int, error) {
  count := 0
  for {
    line, _, err := reader.ReadLine()
    if err != nil {
      switch err {
      default:
        return 0, errors.Wrapf(err, "unable to read")
      case io.EOF:
        return count, nil
      }
    }
    if len(line) == 0 {
      count++
    }
  }
}

而打开文件的操作则交由 count 的调用者去完成:

file, err := os.Open(filename)
if err != nil {
  return errors.Wrapf(err, "unable to open %s", filename)
}
defer file.Close()
count, err := count(bufio.NewReader(file))

这样,无论数据源如何我们都可以调用 count 函数,同时这有有利于我们进行单元测试,因为我们可以简单的从字符串中创建一个 bufio.Reader :

count, err := count(bufio.NewReader(strings.NewReader("input")))

10、Goroutines 和 Loop 循环变量


示例:

ints := []int{1, 2, 3}
for _, i := range ints {
  go func() {
    fmt.Printf("%v\n", i)
  }()
}

输出将会是什么?1 2 3 吗?当然不是。


上例中,每个 goroutine 共享同一个变量实例,因此将会输出 3 3 3(最有可能)。


这个问题有两种解决方式。第一种是将 i 变量传递给 closure 闭包( inner function ):

ints := []int{1, 2, 3}
for _, i := range ints {
  go func(i int) {
    fmt.Printf("%v\n", i)
  }(i)
}

第二种方式是在 for 循环范围内创建另一个变量:

ints := []int{1, 2, 3}
for _, i := range ints {
  i := i
  go func() {
    fmt.Printf("%v\n", i)
  }()
}


目录
相关文章
|
6月前
|
缓存 弹性计算 API
用 Go 快速开发一个 RESTful API 服务
用 Go 快速开发一个 RESTful API 服务
|
2月前
|
开发框架 Go 计算机视觉
纯Go语言开发人脸检测、瞳孔/眼睛定位与面部特征检测插件-助力GoFly快速开发框架
开发纯go插件的原因是因为目前 Go 生态系统中几乎所有现有的人脸检测解决方案都是纯粹绑定到一些 C/C++ 库,如 OpenCV 或 dlib,但通过 cgo 调用 C 程序会引入巨大的延迟,并在性能方面产生显著的权衡。此外,在许多情况下,在各种平台上安装 OpenCV 是很麻烦的。使用纯Go开发的插件不仅在开发时方便,在项目部署和项目维护也能省很多时间精力。
|
3月前
|
Go 数据安全/隐私保护 开发者
Go语言开发
【10月更文挑战第26天】Go语言开发
54 3
|
3月前
|
Java 程序员 Go
Go语言的开发
【10月更文挑战第25天】Go语言的开发
47 3
|
6月前
|
JSON 中间件 Go
go语言后端开发学习(四) —— 在go项目中使用Zap日志库
本文详细介绍了如何在Go项目中集成并配置Zap日志库。首先通过`go get -u go.uber.org/zap`命令安装Zap,接着展示了`Logger`与`Sugared Logger`两种日志记录器的基本用法。随后深入探讨了Zap的高级配置,包括如何将日志输出至文件、调整时间格式、记录调用者信息以及日志分割等。最后,文章演示了如何在gin框架中集成Zap,通过自定义中间件实现了日志记录和异常恢复功能。通过这些步骤,读者可以掌握Zap在实际项目中的应用与定制方法
236 1
go语言后端开发学习(四) —— 在go项目中使用Zap日志库
|
6月前
|
算法 NoSQL 中间件
go语言后端开发学习(六) ——基于雪花算法生成用户ID
本文介绍了分布式ID生成中的Snowflake(雪花)算法。为解决用户ID安全性与唯一性问题,Snowflake算法生成的ID具备全局唯一性、递增性、高可用性和高性能性等特点。64位ID由符号位(固定为0)、41位时间戳、10位标识位(含数据中心与机器ID)及12位序列号组成。面对ID重复风险,可通过预分配、动态或统一分配标识位解决。Go语言实现示例展示了如何使用第三方包`sonyflake`生成ID,确保不同节点产生的ID始终唯一。
167 0
go语言后端开发学习(六) ——基于雪花算法生成用户ID
|
6月前
|
JSON 缓存 监控
go语言后端开发学习(五)——如何在项目中使用Viper来配置环境
Viper 是一个强大的 Go 语言配置管理库,适用于各类应用,包括 Twelve-Factor Apps。相比仅支持 `.ini` 格式的 `go-ini`,Viper 支持更多配置格式如 JSON、TOML、YAML
125 0
go语言后端开发学习(五)——如何在项目中使用Viper来配置环境
|
6月前
|
JSON 编解码 中间件
go-zero代码生成器助你高效开发
go-zero代码生成器助你高效开发
|
6月前
|
Java Go API
我用go-zero开发了第一个线上项目
我用go-zero开发了第一个线上项目
|
6月前
|
监控 Serverless Go
Golang 开发函数计算问题之Go 语言中切片扩容时需要拷贝原数组中的数据如何解决
Golang 开发函数计算问题之Go 语言中切片扩容时需要拷贝原数组中的数据如何解决