首页 > 其他分享 >10 个提高生产力的 Go 小技巧

10 个提高生产力的 Go 小技巧

时间:2024-06-21 22:31:46浏览次数:3  
标签:10 技巧 nil int fmt Println func Go

10 个提高生产力的 Go 小技巧

原创 陈煎鱼 脑子进煎鱼了 2024-06-21 08:42 广东 听全文

最近 Phuong Le 大佬针对日常开发 Go 项目时,总结了一些好用的 Go 小技巧。

看了后,感觉对于刚入门 Go 的同学有一定的学习价值。可以挑好的学。应用到自己项目里。以下内容分享给大家。

在开发 Go 生产项目时,我发现自己经常重复编写代码和使用某些技术,直到后来回顾自己的工作时才意识到这一点。

下面是从总结经验中挑选的一些有用的代码片段,希望对大家有所帮助。

1. 计时技巧

如果你对跟踪函数的执行时间感兴趣,或者在排查问题时需要使用。

可以在 Go 中可以使用 defer 关键字,只需一行代码即可实现一个非常简单、高效的技巧。

你只需要一个 TrackTime 函数:

func TrackTime(pre time.Time) time.Duration {
  elapsed := time.Since(pre)
  fmt.Println("elapsed:", elapsed)

  return elapsed
}

func TestTrackTime(t *testing.T) {
  defer TrackTime(time.Now()) // <-- 就是这里

  time.Sleep(500 * time.Millisecond)
}

// elapsed: 501.11125ms

1.5 两阶段 Defer

Go 的 defer 的强大之处不仅在于任务完成后的清理工作;它也在于为任务做准备。

考虑以下情况场景:

func setupTeardown() func() {
    fmt.Println("Run initialization")
    return func() {
        fmt.Println("Run cleanup")
    }
}

func main() {
    defer setupTeardown()() // <-- 就是这里
    fmt.Println("Main function called")
}

// 输出:
// Run initialization
// Main function called
// Run cleanup

这种模式的美妙之处?只需一行代码,你就可以实现如下任务:

  • 打开数据库连接,稍后关闭它
  • 设置模拟环境,稍后拆除它
  • 获取并稍后释放分布式锁

还记得第一点提到的的计时技巧吗?

我们也可以优化一下程序,这样写:

func TrackTime() func() {
  pre := time.Now()
  return func() {
    elapsed := time.Since(pre)
    fmt.Println("elapsed:", elapsed)
  }
}

func main() {
  defer TrackTime()()

  time.Sleep(500 * time.Millisecond)
}

2. 预先分配切片

我们在编写程序时,可以有意识的预先分配或映射切片,可以显著提高我们的 Go 程序的性能。

如下例子:

// 而不是这样
a := make([]int, 10)
a[0] = 1

// 这样使用
b := make([]int, 0, 10)
b = append(b, 1)

3. 链式调用

链式调用技术可以应用于函数(指针)接收者。

我们考虑一个具有两个函数 AddAge 和 Rename 的 Person 结构体,这两个函数可以用来修改 Person 的字面值。

type Person struct {
  Name string
  Age  int
}

func (p *Person) AddAge() {
  p.Age++
}

func (p *Person) Rename(name string) {
  p.Name = name
}

如果你想给一个人增加年龄,然后重命名他,通常的方法如下:

func main() {
  p := Person{Name: "Aiden", Age: 35}

  p.AddAge()
  p.Rename("煎鱼")
}

或者,我们可以修改 AddAge 和 Rename 函数的接收者,返回修改后的对象本身,即使它们通常不返回任何东西。

func (p *Person) AddAge() *Person {
  p.Age++
  return p
}

func (p *Person) Rename(name string) *Person {
  p.Name = name
  return p
}

通过返回修改后的对象本身,我们可以轻松地将多个函数接收者链接在一起,而无需添加不必要的代码行:

p = p.AddAge().Rename("脑子进煎鱼了")

4. Go 1.20 支持将切片解析为数组或数组指针

当我们需要将切片转换为固定大小的数组时,我们不能像这样直接赋值:

a := []int{0, 1, 2, 3, 4, 5}
var b [3]int = a[0:3]

// cannot use a[0:3] (value of type []int) as [3]int value in variable
// declaration compiler(IncompatibleAssign)

为了将切片转换为数组,Go 团队在 Go 1.17 中更新了这个特性。

随着 Go 1.20 的发布,转换过程变得更加容易,使用更方便的字面值转换:

// go 1.20
func Test(t *testing.T) {
    a := []int{0, 1, 2, 3, 4, 5}
    b := [3]int(a[0:3])

    fmt.Println(b) // [0 1 2]
}

// go 1.17
func TestM2e(t *testing.T) {
  a := []int{0, 1, 2, 3, 4, 5}
  b := *(*[3]int)(a[0:3])

  fmt.Println(b) // [0 1 2]
}

5. 使用 _ import 进行包初始化

在库中,你可能会看到像这样带有下划线 _ 的 import 语句:

import (
  _ "google.golang.org/genproto/googleapis/api/annotations"
)

这将会执行包的初始化代码(init 函数),不会为它创建包的名称引用。

功能上来讲,这允许你在运行代码之前初始化包,注册连接并执行其他任务。

这是一个例子,以便于我们更好地理解它的工作原理:

// 下划线包
package underscore

func init() {
    // 初始化代码
}

这种方式允许我们在不直接使用包的情况下,执行包的初始化代码。

这在需要进行一些设置或注册操作时非常有用

6. 使用点 . 操作符导入包

点(.)操作符可以用来使导入包的导出标识符在不必指定包名的情况下可用,这对懒惰的开发者来说是一个有用的捷径。

这在处理项目中的长包名称(例如 externalmodel 或 doingsomethinglonglib)时特别有用。

为了演示,这里有一个简短的示例:

package main

import (
  "fmt"
  . "math"
)

func main() {
  fmt.Println(Pi) // 3.141592653589793
  fmt.Println(Sin(Pi / 2)) // 1
}

7. Go 1.20 起可以将多个错误包装成一个错误

Go 1.20 引入了错误包的新特性(小修小补),包括对多个错误包装的支持和对 errors.Is 和 errors.As 的特性更改。

添加关联错误的新函数是 errors.Join,我们下面将仔细看看:

var (
  err1 = errors.New("Error 1st")
  err2 = errors.New("Error 2nd")
)

func main() {
  err := err1
  err = errors.Join(err, err2)

  fmt.Println(errors.Is(err, err1)) // true
  fmt.Println(errors.Is(err, err2)) // true
}

如果你有多个任务可能会导致程序出现错误,则可以使用 Join 函数关联追加。

这样就不需要自己手动管理数组。大大简化了错误处理过程。

8. 编译时检查接口的技巧

假设有一个名为 Buffer 的接口,其中包含一个 Write() 函数。此外,还有一个名为 StringBuffer 的结构体实现了这个接口。

但是,如果你打错了字,写的是 Writeee(),而不是 Write() 呢?

type Buffer interface {
  Write(p []byte) (n int, err error)
}

type StringBuffer struct{}

func (s *StringBuffer) Writeee(p []byte) (n int, err error) {
  return 0, nil
}

在运行之前,您无法检查 StringBuffer 是否正确实现了 Buffer 接口。

通过使用下面这个技巧,编译器会通过 IDE 错误信息提醒您:

var _ Buffer = (*StringBuffer)(nil)

// cannot use (*StringBuffer)(nil) (value of type *StringBuffer)
// as Buffer value in variable declaration: *StringBuffer
// does not implement Buffer (missing method Write)

9. 三元运算符

Go 不像许多其他编程语言那样有内置对三元运算符的支持。

Python:

min = a if a < b else b

C#:

min = x < y ? x : y

Go 在 1.18 中引入了泛型功能,现在我们可以创建一个实用工具,只需一行代码即可实现类似于三元表达式的功能:

// our utility
func Ter[T any](cond bool, a, b T "T any") T {
  if cond {
    return a
  }

  return b
}

func main() {
  fmt.Println(Ter(true, 1, 2)) // 1
  fmt.Println(Ter(false, 1, 2)) // 2
}

10. 验证接口是否真的为 nil 的方法

即使接口的值为 nil,也不一定意味着接口本身就是 nil。这可能会导致 Go 程序中出现意想不到的错误。

知道如何检查接口是否为 nil 是很重要的。

func main() {
  var x interface{}
  var y *int = nil
  x = y

  if x != nil {
    fmt.Println("x != nil") // <-- actual
  } else {
    fmt.Println("x == nil")
  }

  fmt.Println(x)
}

// x != nil
// <nil>

我们如何确定 interface{} 值是否为空?

通过下述方法可以实现这一个诉求:

func IsNil(x interface{}) bool {
  if x == nil {
    return true
  }

  return reflect.ValueOf(x).IsNil()
}

总结

这些开发技巧不限具体的分类,对于大家在日常开发中能有一些 tips 的作用。

平时我经常看到有同学为了统计函数执行时间,就一条条打日志,打开始和结束时间。显得比较繁琐。

大家可以结合起来,在平时开发时,也可以及时总结这类方法论。会比较有帮助!

推荐阅读

 

关注和加煎鱼微信,

一手消息和知识,拉你进技术交流群

标签:10,技巧,nil,int,fmt,Println,func,Go
From: https://www.cnblogs.com/cheyunhua/p/18261609

相关文章

  • day10 - 字符串
    目录1.API1.1API概述1.2如何使用API帮助文档2.String类2.1String类概述2.2String类的特点2.3String类的构造方法2.4创建字符串对象两种方式的区别2.5字符串的比较2.5.1==号的作用2.5.2equals方法的作用2.6用户登录案例2.6.1案例需求2.6.2代码实现2.7遍......
  • 金控风控:Python实现基于数据技巧的拒绝推断
    常说的拒绝推断(Inferencemethods),通常是指通过数据分析方法修正模型的参数估计偏差。拒绝推断的主要意义是希望修正建模样本和实际全量样本之间的差异,本质上是为了降低模型参数估计的偏差。拒绝推断场景下有如下三个概念。已知好坏标签(KnowGoodBad,KGB)样本:准入模型允许通过......
  • Goldeneye_v1靶场实操
    Goldeneye靶场实操靶场信息下载靶机后用vm打开即可goldeneye靶机地址:https://www.vulnhub.com/entry/goldeneye-1,240/靶机发布日期:2018年5月4日靶机描述:靶机命名来自詹士邦系列的电影——GoldenEye目标:得到root权限&找到flag.txtkali:192.168.1.131靶机:192.168.1.141......
  • C# GOF 23种设计模式
    《DesignPatterns:ElementsofReusableObject-OrientedSoftware》,由ErichGamma、RichardHelm、RalphJohnson和JohnVlissides合著,这本书列举并描述了23种设计模式。这4位作者常被称为"四人组(GangofFour)"。1、创建型模式:简单工厂模式(SimpleFactoryPattern):通过一......
  • 题解:P10641 BZOJ3252 攻略
    我让cz搬这道题,cz给搬了,于是来写个题解(考虑一个朴素的贪心:每次选择一个到根路径价值和最大的叶子,将价值和累加进答案,并把这条链价值清零。这个贪心的正确性显然(可以交换法证明),很容易用数据结构维护做到\(O(n\logn)\)。但是这样太不优美了,而且数据结构比较难写,于是考虑一个......
  • Dragon Boat Festival
    DragonBoatFestivalTheDragonBoatFestival,atraditionalholidayinChina,fallsonthefifthdayofthefifthlunarmonth,anditsfestivitiesarefilledwithcolor,excitement,anddeepculturalsignificance.Andinmycommunity,weattachgreatimport......
  • 超冷门的社群引流玩法,日引精准粉100+
    在当今的网络环境中,社群有着多种多样的引流方式。今天,我要为大家分享的是一种简便易操作、适用于各个行业的社群引流方法。很多时候,我们都被找不到精准社群这个难题所困扰,自然也就难以进行有效的引流。那么,接下来就让我详细讲讲如何去寻觅这些社群,怎样达成精准引流,以及如......
  • Go 内存模型与分配机制
    ......
  • 消息队列kafka中间件详解:案例解析(第10天)
    系列文章目录1-消息队列(熟悉)2-Kafka的基本介绍(掌握架构,其他了解)3-Kafka的相关使用(掌握kafka常用shell命令)4-Kafka的PythonAPI的操作(熟悉)文章目录系列文章目录前言一、消息队列(熟悉)1、产生背景2、消息队列介绍2.1常见的消息队列产品2.2应用场景2.3消息队列中两......
  • python web框架哪家强?Flask、Django、FastAPI对比
    前言当你掌握了python的基础知识,并且会用和HTML和CSS编写简单的静态网页。现在你只需再掌握一个pythonweb框架的知识,就可以开始编写一个动态的网站了。目前市面比较流程的pythonweb框架有三个flask、Django、FastAPI。接下来我们对比一下。他们三个各自有什么特点。Flas......