Go编程中常见错误和不良实践解析

 更新时间:2024年01月07日 14:25:31   作者:俞凡 DeepNoMind  
这篇文章主要为大家介绍了Go编程中常见错误和不良实践解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

Go常见错误和不良实践

Go编程的某些实践容易被误用或忽视,了解这些特性的特点和陷阱,可以帮助我们编写更好的代码。原文: 5+ BAD Practices In Go: Learn From Mistakes[1]

使用Go和使用其他编程语言中一样,需要了解常见错误和不良实践,才能编写既干净又高效的代码。

本文讨论的一些实践并不一定都是不好的,在特定情况下很有用。 然而,我们需要知道可能会有什么问题,为什么应该回避某些习惯,以及如何避开常见的陷阱。

1. 使用init()

Go中的init()函数是一个特殊函数,在main函数之前执行。

"如果初始化对于任何包都很重要,为什么init()在Go中被认为是一个不好的做法?"

是的,虽然init()函数确实有助于在运行核心逻辑之前进行初始化,但其执行顺序可能很难理解,可能导致对初始化顺序的混淆。

// package A
func init() {}
// package B 
func init() {}
// which run first?

想象一下,有两个模块在安装时相互依赖,但位于不同的包中。结果我们最终需要编写更复杂的代码来管理时序,更糟的是,甚至可能陷入死锁情况。

使用init()的另一个缺点是测试会变得复杂。因为这些函数是自动运行的,无法选择何时执行。

缺乏控制使得设置测试用例成为一项挑战。

我曾经遇到过一个问题,我的服务在部署后花了很长时间才准备好。我在main()函数的开始处设置了一个断点,但从未触发。

经过冗长的调试后,我们发现一个成员使用了某个包中的init()函数从一个大文件加载一个大数据集,这让我们花费大量时间去解决这么一个小问题。

2. 使用全局变量

Go中的全局变量可能会带来类似单例的问题,特别是当这些全局变量很复杂时(比如映射、切片或指针)。

"那么,全局变量有什么大不了的?"

竞争条件: 当有多个程序试图同时访问同一个全局变量时,事情可能会变得混乱。

更少的可测试性: 应用程序依赖于全局变量,意味着有状态,从而在单元或集成测试期间,这些全局变量需要与main()函数中的内容或在生产环境中部署的内容保持一致。

模块化程度较低,可重用性较差: 可以从任何地方访问全局变量,很难跟踪其使用方式和位置。

因此,这里的建议是保持对包的封装。

从而使得代码更容易移动,并且不太可能破坏其他东西。通过避免使用全局变量,可以使代码不那么受约束,并且更容易更新或复用。

3. 忽略错误信息

用Go编程时,错误是不可避免的,知道如何处理错误可以让我们避免各种各样的问题。

"忽略错误真的那么糟糕吗?"

是的,完全正确。

一些Go新手可能会用"_"符号将错误撇在一边,但忽略函数返回的错误值,可能会带来麻烦。

如果不对错误进行管理,也许程序会出现panic和crash。

// sample 1
func main() {
  var x interface{} = "hello"
  s := x.(int) // panic: interface conversion: interface {} is string, not int
  fmt.Println(s)
}

// sample 2
func main() {
  var x interface{} = "hello"
  s, _ := x.(int) // safe but DON'T
  fmt.Println(s)
}

跳过错误可能会适得其反,尤其是对于线上生产环境,调试会成为一场噩梦。总是--我的意思是总是--检查错误并采取正确的措施以保持代码顺利运行。

4. 避免GOTO

无论用Go还是其他语言,避免使用"goto"是大家的共识。

使用goto会破坏代码的自然流程。

会破坏我们理解不同代码段之间关系的方式,让我们很难在不弄得乱七八糟的情况下修改代码。

此外,调试也变得更加令人困惑,测试也更加棘手。

从本质上讲,依赖goto往往会产生更多错误,并难以深入了解问题。因此,作为最佳实践,明智的做法是避开它。

5. 跳过Defer和Recover

如果你忽略"defer"和"recover",就失去了对panic的坚实保护。

为什么?

因为当出现panic时,"defer"仍然会起作用,而"recover"会抓住panic,让我们有机会处理不可预见的问题[2]。

看看这个例子,其中'file.Close()'只是放在末尾,这不是一个Go风格的解决方案:

func readFile(filename string) {
    file, err := os.Open(filename)
    if err != nil {
        panic(err)
    }

    // Do something with the file
    file.Close() // <--- DONT
}

相反,像这样使用"defer":

func readFile(filename string) {
    file, err := os.Open(filename)
    if err != nil {
        panic(err)
    }
    defer file.Close()
    // Do something with the file
    ...
}

在打开文件后立即调用defer file.Close()可以确保即使readFile()遇到panic,文件也会被关闭。此外,还可以方便的提醒我们在打开资源后立即进行清理。

6. 过多使用context.Background()

Go的context功能非常有用,当代码与数据库或网站对话时,有助于管理时间限制等事情。

如果没有设定截止时间,应用可能会陷入阻塞,被数以百万计的请求淹没。

通过一个特殊功能,可以很容易的设置时间限制。

该函数有三种时间选择: Fast(0.5秒)、Medium(3秒)和Slow(10秒)。这样就不用一直使用context.Background(),而且可以为每个任务选择合适的时间限制。

以下是Fast的一些示例代码:

const FastTimeout = 500 * time.Millisecond
func WrapCustomContext(ctx context.Context, dur time.Duration) (context.Context, context.CancelFunc) {
  return context.WithTimeout(ctx, dur)
}
func GenFastContext() (context.Context, context.CancelFunc) {
  return WrapCustomContext(context.Background(), FastTimeout)
}
func WrapFastContext(ctx context.Context) (context.Context, context.CancelFunc) {
  return WrapCustomContext(ctx, FastTimeout)
}

有了这些函数,就可以选择正确的时间限制,应用也因此运行得更好。

好还是不好,只是一些概念,我们可以决定其真正含义。

所以,明智的使用"不好"的特性,它就能变成"最好"的方案。

参考资料

[1]

5+ BAD Practices In Go: Learn From Mistakes: 

https://levelup.gitconnected.com/5-bad-practices-in-go-learn-from-mistakes-13afb4d303b3 

[2]

What you know about defer in Go is not enough!: 

https://medium.com/@func25/what-you-know-about-defer-in-go-is-not-enough-2681d4b128c3 

以上就是Go编程中常见错误和不良实践解析的详细内容,更多关于Go错误解析的资料请关注脚本之家其它相关文章!

相关文章

  • Golang pprof监控之cpu占用率统计原理详解

    Golang pprof监控之cpu占用率统计原理详解

    经过前面的几节对pprof的介绍,对pprof统计的原理算是掌握了七八十了,但唯独还没有分析pprof 工具是如何统计cpu使用情况的,今天我们来分析下这部分
    2023-04-04
  • 一文带你了解Golang中select的实现原理

    一文带你了解Golang中select的实现原理

    select是go提供的一种跟并发相关的语法,非常有用。本文将介绍 Go 语言中的 select 的实现原理,包括 select 的结构和常见问题、编译期间的多种优化以及运行时的执行过程
    2023-02-02
  • Go语言切片常考的面试真题解析

    Go语言切片常考的面试真题解析

    了解最新的Go语言面试题型,让面试不再是难事,下面这篇文章主要给大家介绍了关于Go语言切片面试常考的一些问题,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2022-02-02
  • Go语言制作svg格式树形图的示例代码

    Go语言制作svg格式树形图的示例代码

    SVG是可伸缩矢量图形 (Scalable Vector Graphics),于2003年1月14日成为 W3C 推荐标准。本文将利用Go语言实现制作svg格式树形图,感兴趣的可以了解一下
    2022-09-09
  • golang判断文本文件是否是BOM格式的方法详解

    golang判断文本文件是否是BOM格式的方法详解

    在Go语言中,我们可以通过读取文本文件的前几个字节来识别它是否是BOM格式的文件,BOM(Byte Order Mark)是UTF编码标准中的一部分,用于标示文本文件的编码顺序,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2023-10-10
  • 浅析Go语言中的栈和先进先出原则

    浅析Go语言中的栈和先进先出原则

    这篇文章主要来和大家探讨一样如何在Go语言中实现和使用堆栈,以及堆栈如何遵循先进先出 (FIFO) 原则,文中的示例代码简洁易懂,需要的可以参考一下
    2023-07-07
  • GO语言gin框架实现管理员认证登陆接口

    GO语言gin框架实现管理员认证登陆接口

    这篇文章主要介绍了GO语言gin框架实现管理员认证登陆接口,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-10-10
  • 浅谈go中defer的一个隐藏功能

    浅谈go中defer的一个隐藏功能

    这篇文章主要介绍了浅谈go中defer的一个隐藏功能,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-12-12
  • gorm整合进go-zero的实现方法

    gorm整合进go-zero的实现方法

    go-zero提供的代码生成器里面,没有提供orm框架操作,但是提供了遍历的缓存操作,所以可以利用gorm当作一个sql语句的生成器,把生成后的sql语句放到go-zero生成的模板中去执行,对gorm整合进go-zero的实现方法感兴趣的朋友一起看看吧
    2022-03-03
  • golang监听ip数据包的实现步骤(golang纯享版)

    golang监听ip数据包的实现步骤(golang纯享版)

    这篇文章主要给大家介绍了golang监听ip数据包的实现步骤,本文以ip4 作为案例进行包抓取示范,ip6抓取与ip4方式异曲同工,可自行举一反三得出,文中通过图文结合给大家介绍的非常详细,需要的朋友可以参考下
    2024-02-02

最新评论