快速優化 Golang 代碼的幾個小 Tips

本文將提供一些代碼優化指南,希望能夠幫助開發者增強其程序性能並簡化開發,實現更高效和健壯的編碼,解鎖 Golang 應用程序的潛力。 下面是我從自己平時開發常用的實用庫中隨機選擇的一些有用且通用的代碼片段。

Tracking Execution Time

如果你想要跟蹤 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()) // <--- THIS

  time.Sleep(500 * time.Millisecond)
}

// elapsed: 501.11125ms

預分配切片

預分配切片或 Map 可以顯著提高 Go 的性能。

我們可以使用預分配的零長度切片,而不指定數組長度,可以使用 append 來操作切片。

// 不推薦
a := make([]int, 10)
a[0] = 1

// 推薦用法
b := make([]int, 0, 10)
b = append(b, 1)

鏈式調用

鏈式調用的技巧可以應用於帶有接收器的函數(指針)。爲了說明這一點,先寫一個 Person 結構體,其中有兩個函數 AddAge 和 Rename,用於修改它。

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

不過我們可以修改 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("Aiden 2")

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

Importing with import .

在理解了下劃線(_)在導入中的用法之後,讓我們看看點(.)運算符是如何更常用的。

作爲開發者,點(.)運算符可以用來從包中導入公開標識符,而無需顯式指定包名。這爲懶惰的開發者提供了一個方便的快捷方式。

當處理項目中的長包名時,比如 externalmodel 或 doingsomethinglonglib 時,這特別有用。

package main

import (
  "fmt"
  . "math"
)

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

多個 Error 的 Join

Go 1.20 引入了 errors 包的新功能,包括支持多個錯誤以及對 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 函數而不是手動管理一個數組。這樣就簡化了錯誤處理過程。

檢查 Interface 是否爲 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
//

我們如何確定一個 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([]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
}

NOTE: 需要注意的是變量 d 不應該爲 nil,因爲這可能會導致錯誤。可以在函數開始時對 d 進行檢查。

函數參數註釋

在處理具有多個參數的函數時,僅通過閱讀其用法來理解每個參數的含義可能會令人困惑。請看以下示例:

printInfo("foo", true, true)

如果你不檢查 printInfo 函數,第一個'true' 和第二個'true' 的含義是什麼?當你有一個帶有多個參數的函數時,僅通過閱讀它們的用法來理解參數的含義可能會令人困惑。

我們可以使用註釋來使代碼更易讀。例如:

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

printInfo("foo"true /* isLocal */, true /* done */)
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/eq8bMaj0X64QpecaaqDfag