Go 包管理講解

大部分編程語言都有其代碼組織方式,以方便管理我們所開發的代碼,比如PHP命名空間(namespace),JavapackageJavaScriptmodule

Go 語言也有自己的代碼組織方式:包 (package)。

Go 語言包管理歷史

Go語言包管理歷史主要有以下幾個階段:

包的定義

什麼是包 (package)?

在 Go 語言中,是一個或多個源碼文件的集合,源碼文件以.go爲後綴,在源碼文件中,我們可以聲明常量、變量、函數、自定義類型與方法。

我們開發的程序 (項目或者類庫) 是由一個或多個包構成的。

程序、包、源碼文件之間的關係如下圖所示:

包的聲明

對於Go初學者來說,直接學習Go module這種包管理工具就可以了,爲了後面更好地講解,我們先用go mod init命令初始化一個項目:

//創建項目目錄
mkdir demo
//進入目錄
cd demo
//初始化項目
go mod init github.com/my/demo

go.mod

執行了go mod init命令後,這時候可以看到demo目錄下有一個go.mod的文件,其內容如下:

module github.com/my/demo

go 1.18

go.mod文件主要記錄項目的module路徑、項目依賴包列表 (當前未依賴其包,所以爲空) 和go的版本信息。

go.sum

如果我們要導入外部包,可以執行go get命令:

go get github.com/go-sql-driver/mysql

執行後,會生成go.sum文件,這個文件記錄着當前項目每個依賴包的哈希值,在項目構建時,會計算依賴包的哈希值,並與 go.sum 中對應的哈希值比較,以防止依賴包被竄改。

執行go get 命令後,go.mod文件也會新增了一條依賴記錄:

module github.com/my/demo

go 1.18

require github.com/go-sql-driver/mysql v1.7.0 // indirect

main 包

Go 語言程序的入口main函數,該函數所在的包必須爲main:

package main

func main(){
 //...
}

包的聲明

包的聲明位於源文件的第一行的package語句,後面跟着包名:

package 包名

除了main包外,其他包的名稱必須與對應的目錄同名,同一個目錄下所有源碼文件包名必須相同:

user/user.go

package user

type User struct {
 ID   int
 Name string
}

user/list.go

package user

func (u *User) List() []User {
 return []User{{ID: 1, Name: "test"}}
}

包的導入與使用

包聲明之後,就可以在其他包中導入了,導入包名使用import語句。

導入路徑

標準庫包的導入路徑一般就是標準庫的名稱:

import "fmt"
import "time"
import "net/http"

如果是我們自己聲明的包,其導入路徑爲 module 路徑加上包名,比如我們項目 module 路徑爲github.com/my/demo,那麼要導入user包,其路徑爲:

import "github.com/my/demo/user"

對於有多重目錄的包來說,比如 controller 目錄下有一個 order 包,那麼其路徑需要包含完整的目錄名:

import "github.com/my/demo/controller/order"

兩種導入方式

Go 的包有兩種導入方式,一種是標準導入,一種是匿名導入。

標準導入

普通導入在關鍵字 import 後跟着導入路徑即可,然後就可以通過包名調用包下的變量、函數、常量了:

package main

import "github.com/my/demo/user"

func main() {
  userList := user.List()
  fmt.Println(userList)
}

Go 支持在導入的時候爲包取一個別名 (alias),這樣可以避免因包名相同而無法導入的問題,比如下面我們聲明一個名稱爲time的包:

package time

import "time"

func Now() int64 {
 return time.Now().Unix()
}

之後在 main 包中,調用自定義的time包,同時導入標準庫time包,由於導入時包名相同,因此可以給其中一個包起一個別名來避免無法導入的問題:

package main

import t "github.com/my/demo/time"
import "time"

func main() {
  fmt.Println(time.Now().Unix())
  fmt.Println(t.Now())
}

如果別名是一個點號,那麼此時導入的所有變量、函數、常量都不需要通過包名來訪問了:

package main

import . "github.com/my/demo/time"
import "time"

func main() {
  fmt.Println(time.Now().Unix())
  fmt.Println(Now())
}

如果導入多個包,也可將多個導入寫在一個import語句中,用小括號括起來:

package main

import (
 "time"

 t "github.com/my/demo/time"
)

func main() {
  fmt.Println(time.Now().Unix())
  fmt.Println(Now())
}

匿名導入

通過import語句導入的包後必須使用,否則無法通過編譯。

但有時候我們導入包僅僅只是爲了導入時自動執行包中的init函數而已,在後續代碼中並不會使用該包,此時可以通過在包路徑前面加上空白符_將導入聲明爲匿名導入:

import (
 "database/sql"
 "time"

 _ "github.com/go-sql-driver/mysql"
)

可見性

對於定義在包裏的變量、常量、函數等代碼,如果不想被其他包調用,則其首字母應該設置爲小寫,小寫字母開頭的函數、變量、常量爲可見性爲private,而首字母大寫的話,可見性爲public,在其他包中被調用:

package user

//private
func eat(){
 //....
}

//public
func Say(){
 //....
}

內部包

我們開發的庫可能會分享 (比如通過Github或者Gitlab) 給其他開發者使用,當我們改動到其他開發者依賴的函數或變量時,就會對他們造成影響,破壞到他們的代碼,因此在開發時,要對外暴露通用的,不經常改動的代碼,對於會變動的邏輯,可以放在internal目錄中,Go 語言不允許導入internal包。

比如下面的目錄中,internal 包中所有的代碼,只能在本項目中使用,其他開發者無法導入:

.
├── cmd
│   └── main.go
├── go.mod
├── go.sum
├── internal
│   └── util
│       └── util.go
├── time
│   └── time.go
└── user
    ├── list.go
    └── user.go

小結

包是 Go 語言代碼的組織單位,通過包,我們可以管理我們的代碼,尤其是當開發大型項目時,可以通過包進行模塊劃分,另外,我們也可以封裝自己的包以供其他開發者使用,或者在自己的項目中導入其他開發者的包。

最後,總結一下在這篇文章中的要點:

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/j6-U5rgjmvJgah9btRb6Xw