当前位置:首页 > 科技  > 软件

十个令人惊叹的Go语言技巧,让你的代码更加优雅

来源: 责编: 时间:2023-11-18 09:36:07 409观看
导读在开发生产项目的过程中,我注意到经常会发现自己在重复编写代码,使用某些技巧时没有意识到,直到后来回顾工作时才意识到。为了解决这个问题,我开发了一种解决方案,对我来说非常有帮助,我觉得对其他人也可能有用。以下是一些

在开发生产项目的过程中,我注意到经常会发现自己在重复编写代码,使用某些技巧时没有意识到,直到后来回顾工作时才意识到。ZlV28资讯网——每日最新资讯28at.com

为了解决这个问题,我开发了一种解决方案,对我来说非常有帮助,我觉得对其他人也可能有用。ZlV28资讯网——每日最新资讯28at.com

以下是一些从我的实用程序库中随机挑选的有用且多功能的代码片段,没有特定的分类或特定于系统的技巧。ZlV28资讯网——每日最新资讯28at.com

1. 追踪执行时间的技巧

如果你想追踪 Go 中函数的执行时间,有一个简单高效的技巧可以用一行代码实现,使用 defer 关键字即可。你只需要一个 TrackTime 函数:ZlV28资讯网——每日最新资讯28at.com

// Utilityfunc 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()) // <--- THIS  time.Sleep(500 * time.Millisecond)}// 输出:// elapsed: 501.11125ms

1.5. 两阶段延迟执行

Go 的 defer 不仅仅是用于清理任务,还可以用于准备任务,考虑以下示例:ZlV28资讯网——每日最新资讯28at.com

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

这种模式的美妙之处在于,只需一行代码,你就可以完成诸如以下任务:ZlV28资讯网——每日最新资讯28at.com

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

"嗯,这似乎很聪明,但它在现实中有什么用处呢?"ZlV28资讯网——每日最新资讯28at.com

还记得追踪执行时间的技巧吗?我们也可以这样做:ZlV28资讯网——每日最新资讯28at.com

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

注意!如果我连接到数据库时出现错误怎么办?ZlV28资讯网——每日最新资讯28at.com

确实,像 defer TrackTime() 或 defer ConnectDB() 这样的模式不会妥善处理错误。这种技巧最适合用于测试或者当你愿意冒着致命错误的风险时使用,参考下面这种面向测试的方法:ZlV28资讯网——每日最新资讯28at.com

func TestSomething(t *testing.T) {  defer handleDBConnection(t)()  // ...}func handleDBConnection(t *testing.T) func() {  conn, err := connectDB()  if err != nil {    t.Fatal(err)  }  return func() {    fmt.Println("Closing connection", conn)  }}

这样,在测试期间可以处理数据库连接的错误。ZlV28资讯网——每日最新资讯28at.com

2. 预分配切片

根据文章《Go 性能提升技巧》中的见解,预分配切片或映射可以显著提高 Go 程序的性能。ZlV28资讯网——每日最新资讯28at.com

但是值得注意的是,如果我们不小心使用 append 而不是索引(如 a[i]),这种方法有时可能导致错误。你知道吗,我们可以在不指定数组长度(为零)的情况下使用预分配的切片,就像在上述文章中解释的那样?这使我们可以像使用 append 一样使用预分配的切片:ZlV28资讯网——每日最新资讯28at.com

// 与其a := make([]int, 10)a[0] = 1// 不如这样使用b := make([]int, 0, 10)b = append(b, 1)

3. 链式调用

链式调用技术可以应用于函数(指针)接收器。为了说明这一点,让我们考虑一个 Person 结构,它有两个函数 AddAge 和 Rename,用于对其进行修改。ZlV28资讯网——每日最新资讯28at.com

type Person struct {  Name string  Age  int}func (p *Person) AddAge() {  p.Age++}func (p *Person) Rename(name string) {  p.Name = name}

如果你想给一个人增加年龄然后给他们改名字,常规的方法是:ZlV28资讯网——每日最新资讯28at.com

func main() {  p := Person{Name: "Aiden", Age: 30}  p.AddAge()  p.Rename("Aiden 2")}

或者,我们可以修改 AddAge 和 Rename 函数接收器,使其返回修改后的对象本身,即使它们通常不返回任何内容。ZlV28资讯网——每日最新资讯28at.com

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

通过返回修改后的对象本身,我们可以轻松地将多个函数接收器链在一起,而无需添加不必要的代码行:ZlV28资讯网——每日最新资讯28at.com

p = p.AddAge().Rename("Aiden 2")

4. Go 1.20 允许将切片解析为数组或数组指针

当我们需要将切片转换为固定大小的数组时,不能直接赋值,例如:ZlV28资讯网——每日最新资讯28at.com

a := []int{0, 1, 2, 3, 4, 5}var b [3]int = a[0:3]// 在变量声明中不能将 a[0:3](类型为 []int 的值)赋值给 [3]int 类型的变量// (不兼容的赋值)

为了将切片转换为数组,Go 团队在 Go 1.17 中更新了这个特性。随着 Go 1.20 的发布,借助更方便的字面量,转换过程变得更加简单:ZlV28资讯网——每日最新资讯28at.com

// Go 1.20func 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.17func TestM2e(t *testing.T) {  a := []int{0, 1, 2, 3, 4, 5}  b := *(*[3]int)(a[0:3])  fmt.Println(b) // [0 1 2]}

只是一个快速提醒:你可以使用 a[:3] 替代 a[0:3]。我提到这一点是为了更清晰地说明。ZlV28资讯网——每日最新资讯28at.com

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

有时,在库中,你可能会遇到结合下划线 (_) 的导入语句,如下所示:ZlV28资讯网——每日最新资讯28at.com

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

这将执行包的初始化代码(init 函数),而无需为其创建名称引用。这允许你在运行代码之前初始化包、注册连接和执行其他任务。ZlV28资讯网——每日最新资讯28at.com

让我们通过一个示例来更好地理解它的工作原理:ZlV28资讯网——每日最新资讯28at.com

// 下划线package underscorefunc init() {  fmt.Println("init called from underscore package")}// mainpackage mainimport (  _ "lab/underscore")func main() {}// 输出:init called from underscore package

6. 使用 "import ." 进行导入

在了解了如何使用下划线进行导入后,让我们看看如何更常见地使用点 (.) 运算符。ZlV28资讯网——每日最新资讯28at.com

作为开发者,点 (.) 运算符可用于在不必指定包名的情况下使用导入包的导出标识符,这对于懒惰的开发者来说是一个有用的快捷方式。ZlV28资讯网——每日最新资讯28at.com

很酷,对吧?这在处理项目中的长包名时特别有用,比如 externalmodel 或 doingsomethinglonglib。ZlV28资讯网——每日最新资讯28at.com

为了演示,这里有一个简单的例子:ZlV28资讯网——每日最新资讯28at.com

package mainimport (  "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 的更改。ZlV28资讯网——每日最新资讯28at.com

在 errors 中添加的一个新函数是 Join,我们将在下面详细讨论它:ZlV28资讯网——每日最新资讯28at.com

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 函数而不是手动管理数组。这简化了错误处理过程。ZlV28资讯网——每日最新资讯28at.com

8. 检查接口是否为真正的 nil

即使接口持有的值为 nil,也不意味着接口本身为 nil。这可能导致 Go 程序中的意外错误。因此,重要的是要知道如何检查接口是否为真正的 nil。ZlV28资讯网——每日最新资讯28at.com

func main() {  var x interface{}  var y *int = nil  x = y  if x != nil {    fmt.Println("x != nil") // <-- 实际输出  } else {    fmt.Println("x == nil")  }  fmt.Println(x)}// 输出:// x != nil// <nil>

我们如何确定 interface{} 值是否为 nil 呢?幸运的是,有一个简单的工具可以帮助我们实现这一点:ZlV28资讯网——每日最新资讯28at.com

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

9. 在 JSON 中解析 time.Duration

当解析 JSON 时,使用 time.Duration 可能是一个繁琐的过程,因为它需要在一秒的后面添加 9 个零(即 1000000000)。为了简化这个过程,我创建了一个名为 Duration 的新类型:ZlV28资讯网——每日最新资讯28at.com

type Duration time.Duration

为了将字符串(如 "1s" 或 "20h5m")解析为 int64 类型的持续时间,我还为这个新类型实现了自定义的解析逻辑:ZlV28资讯网——每日最新资讯28at.com

func (d *Duration) UnmarshalJSON(b []byte) error {  var s string  if err := json.Unmarshal(b, &s); err != nil {    return err  }  dur, err := time.ParseDuration(s)  if err != nil {    return err  }  *d = Duration(dur)  return nil}

但是,需要注意的是,变量 'd' 不应为 nil,否则可能会导致编组错误。或者,你还可以在函数开头对 'd' 进行检查。ZlV28资讯网——每日最新资讯28at.com

10. 避免裸参数

当处理具有多个参数的函数时,仅通过阅读其用法来理解每个参数的含义可能会令人困惑。考虑以下示例:ZlV28资讯网——每日最新资讯28at.com

printInfo("foo", true, true)

如果不检查 printInfo 函数,那么第一个 'true' 和第二个 'true' 的含义是什么呢?当你有一个具有多个参数的函数时,仅通过阅读其用法来理解参数的含义可能会令人困惑。ZlV28资讯网——每日最新资讯28at.com

但是,我们可以使用注释使代码更易读。例如:ZlV28资讯网——每日最新资讯28at.com

// func printInfo(name string, isLocal, done bool)printInfo("foo", true /* isLocal */, true /* done */)

有些 IDE 也支持这个功能,可以在函数调用建议中显示注释,但可能需要在设置中启用。ZlV28资讯网——每日最新资讯28at.com

以上是我分享的一些实用技巧,但我不想让文章过长,难以跟进,因为这些技巧与特定主题无关,涵盖了各种类别。ZlV28资讯网——每日最新资讯28at.com

如果你觉得这些技巧有用,或有自己的见解要分享,请随时留言。我重视你的反馈,并乐于在回应此文章时点赞或推荐你的想法。ZlV28资讯网——每日最新资讯28at.com

本文链接:http://www.28at.com/showinfo-26-28347-0.html十个令人惊叹的Go语言技巧,让你的代码更加优雅

声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com

上一篇: 突发!OpenAI的CEO被开了...

下一篇: 微信支付V3版本集成详解【避坑指南】

标签:
  • 热门焦点
  • 7月安卓手机性价比榜:努比亚+红魔两款新机入榜

    7月登场的新机有努比亚Z50S Pro和红魔8S Pro,除了三星之外目前唯二的两款搭载超频版骁龙8Gen2处理器的产品,而且努比亚和红魔也一贯有着不错的性价比,所以在本次的性价比榜单
  • 0糖0卡0脂 旭日森林仙草乌龙茶优惠:15瓶到手29元

    旭日森林无糖仙草乌龙茶510ml*15瓶平时要卖为79.9元,今日下单领取50元优惠券,到手价为29.9元。产品规格:0糖0卡0脂,添加草本仙草汁,清凉爽口,富含茶多酚,保留
  • 分享六款相见恨晚的PPT模版网站, 祝你做出精美的PPT!

    1、OfficePLUSOfficePLUS网站旨在为全球Office用户提供丰富的高品质原创PPT模板、实用文档、数据图表及个性化定制服务。优点:OfficePLUS是微软官方网站,囊括PPT模板、Word模
  • 19个 JavaScript 单行代码技巧,让你看起来像个专业人士

    今天这篇文章跟大家分享18个JS单行代码,你只需花几分钟时间,即可帮助您了解一些您可能不知道的 JS 知识,如果您已经知道了,就当作复习一下,古人云,温故而知新嘛。现在,我们就开始今
  • 一篇文章带你了解 CSS 属性选择器

    属性选择器对带有指定属性的 HTML 元素设置样式。可以为拥有指定属性的 HTML 元素设置样式,而不仅限于 class 和 id 属性。一、了解属性选择器CSS属性选择器提供了一种简单而
  • 重估百度丨“晚熟”的百度云,能等到春天吗?

    &copy;自象限原创作者|程心排版|王喻可2016年7月13日,百度云计算战略发布会在北京举行,宣告着百度智能云的正式启程。彼时的会场座无虚席,甚至排队排到了门外,在场的所有人几乎都
  • 阿里大调整

    来源:产品刘有媒体报道称,近期淘宝天猫集团启动了近年来最大的人力制度改革,涉及员工绩效、层级体系等多个核心事项,目前已形成一个初步的&ldquo;征求意见版&rdquo;:1、取消P序列
  • 朋友圈可以修改可见范围了 苹果用户可率先体验

    近日,iOS用户迎来微信8.0.27正式版更新,除了可更换二维码背景外,还新增了多项实用功能。在新版微信中,朋友圈终于可以修改可见范围,简单来说就是已发布的朋友圈
  • 微软发布Windows 11新版 引入全新任务栏状态

    近日,微软发布了Windows 11新版,而Build 22563更新主要引入了几周前曝光的平板模式任务栏等,系统更流畅了。更新中,Windows 11加入了专门针对平板优化的任务栏
Top