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)
  }()
}


目录
相关文章
|
8天前
|
算法 Java Go
【GoGin】(1)上手Go Gin 基于Go语言开发的Web框架,本文介绍了各种路由的配置信息;包含各场景下请求参数的基本传入接收
gin 框架中采用的路优酷是基于httprouter做的是一个高性能的 HTTP 请求路由器,适用于 Go 语言。它的设计目标是提供高效的路由匹配和低内存占用,特别适合需要高性能和简单路由的应用场景。
74 4
|
2月前
|
数据采集 数据挖掘 测试技术
Go与Python爬虫实战对比:从开发效率到性能瓶颈的深度解析
本文对比了Python与Go在爬虫开发中的特点。Python凭借Scrapy等框架在开发效率和易用性上占优,适合快速开发与中小型项目;而Go凭借高并发和高性能优势,适用于大规模、长期运行的爬虫服务。文章通过代码示例和性能测试,分析了两者在并发能力、错误处理、部署维护等方面的差异,并探讨了未来融合发展的趋势。
188 0
|
8天前
|
JavaScript 前端开发 Java
【GoWails】Go做桌面应用开发?本篇文章带你上手Wails框架!一步步带你玩明白前后端双端的数据绑定!
wails是一个可以让你使用Go和Web技术编写桌面应用的项目 可以将它看作Go的快并且轻量级的Electron替代品。可以使用Go的功能,并结合现代化UI完成桌面应用程序的开发
62 4
|
4月前
|
JSON 中间件 Go
Go 网络编程:HTTP服务与客户端开发
Go 语言的 `net/http` 包功能强大,可快速构建高并发 HTTP 服务。本文从创建简单 HTTP 服务入手,逐步讲解请求与响应对象、URL 参数处理、自定义路由、JSON 接口、静态文件服务、中间件编写及 HTTPS 配置等内容。通过示例代码展示如何使用 `http.HandleFunc`、`http.ServeMux`、`http.Client` 等工具实现常见功能,帮助开发者掌握构建高效 Web 应用的核心技能。
269 61
|
4月前
|
开发框架 安全 前端开发
Go Web开发框架实践:模板渲染与静态资源服务
Gin 是一个功能强大的 Go Web 框架,不仅适用于构建 API 服务,还支持 HTML 模板渲染和静态资源托管。它可以帮助开发者快速搭建中小型网站,并提供灵活的模板语法、自定义函数、静态文件映射等功能,同时兼容 Go 的 html/template 引擎,具备高效且安全的页面渲染能力。
|
5月前
|
人工智能 缓存 安全
Go开发遇见的一次Data Race
本文通过一段 Go 语言代码示例,分析了并发编程中的数据竞争(Data Race)问题。代码实现了一个带缓存的内存存储系统,包含 `LRUCache` 和 `MemoryCache` 两个核心组件。尽管在 `MemoryCache` 的 `Set` 方法中加了锁保护,但由于直接调用 `LRUCache` 的 `GetLength` 方法时未加锁,导致底层数据结构在多 goroutine 环境下被同时读写,从而触发 Data Race。文章详细解析了问题根源,并提出了解决方案:为 `LRUCache` 的 `Add` 方法添加锁保护,确保并发安全。
|
6月前
|
Go API 定位技术
MCP 实战:用 Go 语言开发一个查询 IP 信息的 MCP 服务器
随着 MCP 的快速普及和广泛应用,MCP 服务器也层出不穷。大多数开发者使用的 MCP 服务器开发库是官方提供的 typescript-sdk,而作为 Go 开发者,我们也可以借助优秀的第三方库去开发 MCP 服务器,例如 ThinkInAIXYZ/go-mcp。 本文将详细介绍如何在 Go 语言中使用 go-mcp 库来开发一个查询 IP 信息的 MCP 服务器。
378 0
|
10月前
|
开发框架 Go 计算机视觉
纯Go语言开发人脸检测、瞳孔/眼睛定位与面部特征检测插件-助力GoFly快速开发框架
开发纯go插件的原因是因为目前 Go 生态系统中几乎所有现有的人脸检测解决方案都是纯粹绑定到一些 C/C++ 库,如 OpenCV 或 dlib,但通过 cgo 调用 C 程序会引入巨大的延迟,并在性能方面产生显著的权衡。此外,在许多情况下,在各种平台上安装 OpenCV 是很麻烦的。使用纯Go开发的插件不仅在开发时方便,在项目部署和项目维护也能省很多时间精力。
256 5
|
11月前
|
Go 数据安全/隐私保护 开发者
Go语言开发
【10月更文挑战第26天】Go语言开发
144 3
|
11月前
|
Java 程序员 Go
Go语言的开发
【10月更文挑战第25天】Go语言的开发
123 3

热门文章

最新文章