Golang高级微调技术

news2024/12/29 10:29:09

本文分享了一些小技巧,可以帮助我们写出更简化、高效的Golang代码,从而获得更好的开发体验。原文: Fine-Tuning Golang: Advanced Techniques for Code Optimization

本文是Golang代码优化技术的综合指南,帮助我们释放 Golang 应用程序的全部潜能,提高性能、简化开发,获得更高效、更强大的编码体验。

Aaron Thomas @Unsplash
Aaron Thomas @Unsplash

在项目开发过程中,我发现经常会有重复代码,有时还会忽略某些技术,直到进行工作回顾的时候才会发现。

为了解决这个问题,我开发了一个解决方案。这对我自己很有帮助,相信对其他人也会有用。

以下是从我的实用工具库中随机挑选的一些有用的多功能代码片段,没有具体分类,也没有针对特定系统的技巧:

跟踪执行时间的技术

如果想在 Go 中跟踪函数的执行时间,有一种简单高效的技术,只需一行代码,使用 defer 关键字即可实现。所需要的只是一个 TrackTime 函数:

// Utility
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()) // <--- THIS

  time.Sleep(500 * time.Millisecond)
}

// elapsed: 501.11125ms
两阶段延迟执行

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

注意!连接数据库时出现错误怎么办?

事实上,像 defer TrackTime()defer ConnectDB() 这样的模式可能无法正确处理错误。

这种技术最适合在测试或愿意承担潜在致命错误风险的情况下使用。请考虑以下面向测试的方法:

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

这样就可以在测试过程中处理数据库连接中的错误。

预先分配切片

预先分配切片或映射可以显著提高Go程序性能。

不过,值得注意的是,如果我们不小心使用追加而不是索引(如 a[i]),这种方法有时会出错。

可以在不指定数组长度(零长度)的情况下使用预分配的切片,这样就可以像使用 append 一样使用切片。

// Not Recommended
a := make([]int10)
a[0] = 1

// Recommended
b := make([]int010)
b = append(b, 1)
链式调用

链式调用技术可应用于带有接收器的函数(指针)。为了说明这一点,让我们考虑一个带有 AddAgeRename 两个函数的 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: 30}

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

另一方面,也可以修改 AddAgeRename 函数的接收器,使其返回修改后的对象本身,尽管它们通常不会返回任何内容:

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("Aiden 2")
从 Go 1.20 开始,可以将切片转换为数组或数组指针

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

a := []int{012345}
var b [3]int = a[0:3]

在变量声明中,将 a[0:3][]int 类型的值)赋值给 [3]int 类型的变量是不兼容、不允许的。

Go 团队在 Go 1.17 中更新了将切片转换为数组的功能。随着 Go 1.20 的发布以及更多方便的字面量的加入,转换过程变得更加简单:

// Go 1.20
func Test(t *testing.T) {
   a := []int{012345}
   b := [3]int(a[0:3])

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

// Go 1.17
func TestM2e(t *testing.T) {
  a := []int{012345}
  b := *(*[3]int)(a[0:3])

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

提醒一下:可以用 a[:3] 代替 a[0:3]

软件包初始化,import _

有时在某个库中,可能会遇到包含下划线 (_) 的导入语句,就像下面这样:

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

这将执行软件包的初始化代码(init 函数),而不会为其创建命名引用。通过它,可以在运行代码前初始化软件包、注册连接并执行其他任务。

package underscore

func init() {
  fmt.Println("init called from underscore package")
}
// main
package main

import (
  _ "lab/underscore"
)

func main() {}
// Output:init called from underscore package
通过import .导入

在了解了下划线 (_) 在导入中的使用方法后,让我们来看看更常用的点 (.) 操作符。

作为开发人员,可以使用点(.)操作符从软件包导入导出标识符,而无需明确指定软件包名称。对于懒惰的开发人员来说,这是一种方便的快捷方式。

很酷吧?在处理项目中较长的软件包名称(如 externalmodeldoingsomethinglonglib)时,这一点尤其有用。

package main

import (
  "fmt"
  . "math"
)

func main() {
  fmt.Println(Pi) // 3.141592653589793
  fmt.Println(Sin(Pi / 2)) // 1
}
将多个错误合并为一个错误

Go 1.20 为 errors 包引入了新功能,包括支持多重错误以及对 errors.Iserrors.As 的修改。

Joinserrors的新增功能之一,我们将在下文中详细讨论:

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 函数代替手动管理数组,从而简化错误处理流程。

检查接口是否真正为Nil

即使接口持有的值为 nil,也并不意味着接口本身为 nil,这会导致 Go 程序出现意外错误。因此,了解如何检查接口是否真的为 nil 至关重要。

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

// Output:
// x != nil
// <nil>

如何判断 interface{} 值是否为 nil?幸运的是,有一个简单的工具可以帮助我们做到这一点:

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

  return reflect.ValueOf(x).IsNil()
}
解析 JSON 中的 time.Duration

在解析 JSON 时,使用 time.Duration 可能是个繁琐的过程,因为需要在秒后添加 9 个零(即 1000000000)。为了简化这一过程,我创建了一个名为 Duration 的新类型:

type Duration time.Duration

为了将字符串(如 "1s "或 "20h5m")解析为 int64 类型的持续时间,我还为这种新类型实现了自定义解析逻辑:

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 不能为零,否则会导致 marshaling 错误。另外,也可以在函数开始时对 d 进行检查。

避免裸参数

在处理具有多个参数的函数时,仅通过阅读每个参数的用法来理解其含义可能会造成混乱。请看下面的例子:

printInfo("foo"truetrue)

如果不检查 printInfo 函数,第一个 true 和第二个 true 是什么意思?当一个函数有多个参数时,仅通过阅读参数的用法来理解参数的含义可能会让人感到困惑。

不过,可以通过注释来提高代码可读性。例如:

// func printInfo(name string, isLocal, done bool)

printInfo("foo"true /* isLocal */true /* done */)

你好,我是俞凡,在Motorola做过研发,现在在Mavenir做技术工作,对通信、网络、后端架构、云原生、DevOps、CICD、区块链、AI等技术始终保持着浓厚的兴趣,平时喜欢阅读、思考,相信持续学习、终身成长,欢迎一起交流学习。为了方便大家以后能第一时间看到文章,请朋友们关注公众号"DeepNoMind",并设个星标吧,如果能一键三连(转发、点赞、在看),则能给我带来更多的支持和动力,激励我持续写下去,和大家共同成长进步!

本文由 mdnice 多平台发布

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

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

相关文章

微信小程序(五十)请求拦截器实现携token获取用户信息

注释很详细&#xff0c;直接上代码 上一篇 新增内容&#xff1a; 1.个人信息框基本样式 2.请求拦截器携token获取个人信息进行渲染 源码&#xff1a; utils/http.js import http from "wechat-http"//设置全局默认请求地址 http.baseURL "https://live-api.it…

Mybatis_plus-基础

一、简介 1.概述 文档地址&#xff1a;https://baomidou.com/ 概述&#xff1a;MyBatis-Plus (opens new window)&#xff08;简称 MP&#xff09;是一个 MyBatis (opens new window) 的增强工具&#xff0c;在 MyBatis 的基础上只做增强不做改变&#xff0c;为简化开发、提…

前后端分离项目Docker部署指南(上)

目录 前言 一.搭建局域网 1.搭建net-ry局域网&#xff0c;用于部署若依项目 2.注意点 二.安装redis 创建目录 将容器进行挂载 ​编辑 测试是否安装成功 ​编辑 三. 安装MySQL 创建文件夹 上传配置文件并且修改 .启动MySQL容器服务 充许远程连接 四.部署后端 使用…

RISCV 中断控制器 PLIC APLIC (非MSI部分)

以下包含了 PLIC & APLIC 着重解释 APLIC 部分 参考 github 中 riscv-aia 规范1.0 &#xff0c;第四章 APLIC 注&#xff1a;关于MSI的部分简略&#xff08;等后续搞清楚&#xff09; 本人处于学习阶段&#xff0c;不清晰的地方&#xff0c;请见谅 一、关于 PLIC 配置 1、…

【Python】-----基础知识

注释 定义&#xff1a;让计算机跳过这个代码执行用三个单引号/双引号都表示注释信息&#xff0c;在Python中单引号与双引号没有区别&#xff0c;但必须是成对出现 输出与输入 程序是有开始&#xff0c;有结束的&#xff0c;程序运行规则&#xff1a;从上而下&#xff0c;由内…

(1)预处理

我们需要的文件结构如上 main.cpp add.h add.cpp add.h 这里使用riscv的工具链编译为.i文件&#xff0c;需要使用-E&#xff0c;就是只进行预处理&#xff0c;我们可以得到两个.i文件即main.i和add.i main.i 这里看到main.i里头文件全部替换&#xff0c;然后多了三万多行 所以…

力扣经典题目解析--合并两个有序链表

原题地址: . - 力扣&#xff08;LeetCode&#xff09; 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 示例 1&#xff1a; 输入&#xff1a;l1 [1,2,4], l2 [1,3,4] 输出&#xff1a;[1,1,2,3,4,4] 迭代 链表节点结构…

Day12-【Java SE进阶】JDK8新特性:Lambda表达式、方法引用、常见算法、正则表达式、异常

一、JDK8新特性 1.Lambda表达式 Lambda表达式是JDK 8开始新增的一种语法形式;作用:用于简化名内部类的代码写法。 注意:Lambda表达式并不是说能简化全部匿名内部类的写法&#xff0c;只能简化函数式接口的匿名内部类。 有且仅有一个抽象方法的接口。注意:将来我们见到的大部…

【音视频开发好书推荐】《RTC程序设计:实时音视频权威指南》

1、WebRTC概述 WebRTC&#xff08;Web Real-Time Communication&#xff09;是一个由Google发起的实时音视频通讯C开源库&#xff0c;其提供了音视频采集、编码、网络传输&#xff0c;解码显示等一整套音视频解决方案&#xff0c;我们可以通过该开源库快速地构建出一个音视频通…

【前端寻宝之路】总结学习使用CSS的引入方式

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法|MySQL| ​&#x1f4ab;个人格言:“没有罗马,那就自己创造罗马~” #mermaid-svg-BNJBIEvpN0GHNeJ1 {font-family:"trebuchet ms",verdana,arial,sans-serif;f…

C语言第三十五弹---文件操作(上)

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】 文件操作 1、为什么使用文件&#xff1f; 2、什么是文件&#xff1f; 2.1、程序文件 2.2、数据文件 2.3、文件名 3、二进制文件和文本文件 4、文件的打开和…

集成算法(随机森林,AdaBoost,Xgboost,Stacking模型)

目录 一、前言 二、Bagging模型 三、Boosting模型 四、Stacking模型 五、总结 一、前言 集成算法&#xff08;Enseamable learning&#xff09; 集成算法一般考虑树模型&#xff0c;KNN就不太适合 目的&#xff1a;让机器学习效果更好&#xff0c;单个不好&#xff0c;一起…

性能优化篇(七) UI优化注意事项以及使用Sprite Atlas打包精灵图集

UI优化注意事项 1.尽量避免使用IMGUI(OnGUI)来做游戏时的UI&#xff0c;因为IMGUI的开销比较大。 2.如果一个UGUI的控件不需要进行射线检测&#xff0c;则可以取消勾选Raycast Target 3.尽量避免使用完全透明的图片和UI控件。因为即使完全透明&#xff0c;我们看不见它&#xf…

BUUCTF------[HCTF 2018]WarmUp

开局一个表情&#xff0c;源代码发现source.php <?phphighlight_file(__FILE__);class emmm{public static function checkFile(&$page){$whitelist ["source">"source.php","hint">"hint.php"];if (! isset($page) |…

关于图形学中生成三角形库Triangle.Net的下载及简单使用

背景 Triangle.NET 是生成 2D&#xff08;约束&#xff09;Delaunay 三角剖分和点集或平面直线图的高质量网格。此库是基于 Jonathan Richard Shewchuk 的 Triangle 项目&#xff0c;主要用于图形学像Opengl(或Unity3D)中生成三角形使用。 下载 Triangle.Net是基于C#语言的库…

倾斜三维模型OSGB路径漫游——DasViewer

背景 有时候我们需要查看倾斜三维模型效果的效果&#xff0c;虽然也有很多软件可以用&#xff0c;比如CC、超图等等&#xff0c;但是这些软件都比较大&#xff0c;安装也比较麻烦&#xff0c;DasViewer这个软件就比较轻量&#xff0c;安装也简单&#xff0c;功能强大&#xff…

网络信息安全:nginx漏洞收集(升级至最新版本)

网络&信息安全&#xff1a;nginx漏洞收集&#xff08;升级至最新版本&#xff09; 一、风险详情1.1 nginx 越界写入漏洞(CVE-2022-41742)1.2 nginx 缓冲区错误漏洞(CVE-2022-41741)1.3 nginx 拒绝服务漏洞&#xff08;CNVD-2018-22806&#xff09; 二、nginx升级步骤 &…

使用GRU进行天气变化的时间序列预测

本文基于最适合入门的100个深度学习项目的学习记录&#xff0c;同时在Google clolab上面是实现&#xff0c;文末有资源连接 天气变化的时间序列的难点 天气变化的时间序列预测涉及到了一系列复杂的挑战&#xff0c;主要是因为天气系统的高度动态性和非线性特征。以下是几个主…

jpg图片怎么转成png?一个超实用的jpg转格式方法

Jpg是常见的图片格式之一&#xff0c;有时候需要将常见的jpg格式转换成png格式的图片来应对不同的需求。因为png格式的图片画质更清晰还支持透明度等&#xff0c;能够更好的保存图片质量。接下来&#xff0c;就给大家介绍一下jpg转换png格式的小窍门。只需要使用jpg格式转换器&…

Matlab高光谱遥感、数据处理与混合像元分解

光谱和图像是人们观察世界的两种方式&#xff0c;高光谱遥感通过“图谱合一”的技术创新将两者结合起来&#xff0c;大大提高了人们对客观世界的认知能力&#xff0c;本来在宽波段遥感中不可探测的物质&#xff0c;在高光谱遥感中能被探测。以高光谱遥感为核心&#xff0c;构建…