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

news/2024/9/24 18:05:55

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

最近 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 的作用。

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

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

推荐阅读

  • Go1.0 到 1.22 的性能表现,提高了多少倍?
  • Google 如果把 Go 团队给裁了会怎么样?
  • Go 未来方向:标准库 v2 改进的指导原则

 

关注和加煎鱼微信,

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

图片


图片

 

你好,我是煎鱼,出版过 Go 畅销书《Go 语言编程之旅》,再到获得 GOP(Go 领域最有观点专家)荣誉,点击蓝字查看我的出书之路

日常分享高质量文章,输出 Go 面试、工作经验、架构设计,加微信拉读者交流群,和大家交流!

Go · 目录
上一篇为什么 Go 不学 Rust 用 ? 做错误处理?
阅读原文
阅读 3422
写留言
 
 
 
 
 
 
 
 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.ryyt.cn/news/46278.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

【unity开发】 C#接口使用小结(持续更新)

C#的接口(interface) 早些时候我认识的接口仅仅只是作为一个方法签名来使用 但是随着学习的深入,就我感觉而言,我所认识的接口又越来越像一个抽象类了 1.最基本的使用 作为一个接口提供公共方法 用玩家的交互判断来举一个例子吧!接口也支持使用泛型 再举一个手动实现拷贝方…

14-vertical-aligin

vertical-aligin01 行盒的理解 作用: 将当前行里的所有内容包裹起来 <!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0&…

6/17 死神永生服周报第五期

1.死神永生治理新闻 2.浅海公园[下] 3.死神永生TNT研究基地目录死神永生新闻 浅海公园[下] 死神永生服TNT军事基地[一]死神永生新闻前一周的治理新闻时间 人 行为 处罚方案6.14 Random748126323 火球炸服 踢+设为观察者浅海公园[下]死神永生服浅海生态公园已于6月12日建造完成,…

Goldeneye_v1靶场实操

本文章是对于来自詹士邦系列的电影——GoldenEye所命名的靶机的实操(如有错误,将及时修改)Goldeneye靶场实操 靶场信息下载靶机后用vm打开即可goldeneye靶机地址:https://www.vulnhub.com/entry/goldeneye-1,240/ 靶机发布日期:2018年5月4日 靶机描述:靶机命名来自詹士邦…

c语言程序实验————实验报告十三

c语言程序实验————实验报告十三实验项目名称: 实验报告十三 结构体运用程序设计 实验项目类型:验证性 实验日期:2024 年 5 月 30 日一、实验目的 1.掌握结构体类型变量的定义和使用 2.掌握结构体类型数组的概念和应用 3.掌握结构体类型指针的概念和应用 4.掌握共用体的概…

AWX+gitlab

目录AWX+gitlab1. Awx配置1.1 添加机构1.2 添加团队1.3 添加主机1.4 测试主机连通性2. 对接gitlab2.1 添加凭证2.2 添加项目2.3 上传playbook2.3.1 克隆仓库2.3.2 创建分支2.3.3 编写playbook并上传2.3.4 上传ansible.cfg(可选)2.3.5 创建作业模板2.4 测试 AWX+gitlab 我们可…

硬件开发笔记(二十一):外部搜索不到的元器件封装可尝试使用AD21软件的“ManufacturerPart Search”功能

前言这是一个AD的一个强大的新功能,能招到元器件的原理图、3D模型还有价格厂家,但是不一定都有,有了也不一定有其3D模型。ManufacturerPart Search在设计工具中选择即用型元件直接搜索,搜索到需要使用的元器件。在Altium Designer中,直接选中设备元件。无需使用第三方服务…

Unity 编辑器中获取选中的文件夹、文件路径

编辑器中获取选中的文件夹、文件路径 using UnityEditor; using UnityEngine; using Object = UnityEngine.Object;public class MyEditorScript {[MenuItem("Assets/PrintSelectedFolderPath")]static void PrintSelectedFolderPath(){// 第一种方式// 只能访问选中…