任何業務,都是從簡單向複雜演進的。而在業務演進的過程中,技術是從單體向多模組、多服務演進的。技術的這種演進方式的核心目的是複用程式碼、提高效率。
Go 語言中的包
什麼是包
在業務非常簡單的時候,甚至可以把程式碼寫到一個 Go 檔案中。但隨著業務逐漸複雜,會發現,如果程式碼都放在一個 Go 檔案中,會變得難以維護,這時候就需要抽取程式碼,把相同業務的程式碼放在一個目錄中。在 Go 語言中,這個目錄叫作包。
在 Go 語言中,一個包是通過package 關鍵字定義的,最常見的就是main 包,它的定義如下所示:
package main
此外,經常使用到的 fmt 包,也是通過 package 關鍵字宣告的。
一個包就是一個獨立的空間,可以在這個包裡定義函式、結構體等。這時,認為這些函式、結構體是屬於這個包的。
使用包
如果想使用一個包裡的函式或者結構體,就需要先匯入這個包,才能使用,比如常用的 fmt包,程式碼示例如下所示。
package main
import "fmt"
func main() {
fmt.Println("先匯入fmt包,才能使用")
}
要匯入一個包,需要使用 import 關鍵字;如果需要同時匯入多個包,則可以使用小括號,示例程式碼如下所示。
import (
"fmt"
"os"
)
從以上示例可以看到,該示例匯入了 fmt 和 os 這兩個包,使用了小括號,每一行寫了一個要匯入的包。
作用域
講到了包之間的匯入和使用,就不得不提作用域這個概念,因為只有滿足作用域的函式才可以被呼叫。
- 在Java 語言中,通過 public、private 這些修飾符修飾一個類的作用域;
- 但是在Go 語言中,並沒有這樣的作用域修飾符,它是通過首字母是否大寫來區分的,這同時也體現了 Go 語言的簡潔。
如上述示例中 fmt 包中的Println 函式:
- 它的首字母就是大寫的 P,所以該函式才可以在 main 包中使用;
- 如果 Println 函式的首字母是小寫的 p,那麼它只能在 fmt 包中被使用,不能跨包使用。
這裡總結下 Go 語言的作用域:
- Go 語言中,所有的定義,比如函式、變數、結構體等,如果首字母是大寫,那麼就可以被其他包使用;
- 反之,如果首字母是小寫的,就只能在同一個包內使用。
自定義包
你也可以自定義自己的包,通過包的方式把相同業務、相同職責的程式碼放在一起。比如你有一個 util 包,用於存放一些常用的工具函式,專案結構如下所示:
tool
├── main.go
└── util
└── string.go
在 Go 語言中,一個包對應一個資料夾,上面的專案結構示例也驗證了這一點。在這個示例中,有一個 util 資料夾,它裡面有一個 string.go 檔案,這個 Go 語言檔案就屬於 util 包,它的包定義如下所示:
package util
可以看到,Go 語言中的包是程式碼的一種組織形式,通過包把相同業務或者相同職責的程式碼放在一起。通過包對程式碼進行歸類,便於程式碼維護以及被其他包呼叫,提高團隊協作效率。
init 函式
除了 main 這個特殊的函式外,Go 語言還有一個特殊的函式——init,通過它可以實現包級別的一些初始化操作。
init 函式沒有返回值,也沒有引數,它先於 main 函式執行,程式碼如下所示:
func init() {
fmt.Println("init in main.go ")
}
一個包中可以有多個 init 函式,但是它們的執行順序並不確定,所以如果你定義了多個 init 函式的話,要確保它們是相互獨立的,一定不要有順序上的依賴。
那麼 init 函式作用是什麼呢? 其實就是在匯入一個包時,可以對這個包做一些必要的初始化操作,比如資料庫連線和一些資料的檢查,確保可以正確地使用這個包。
Go 語言中的模組
如果包是比較低階的程式碼組織形式的話,那麼模組就是更高階別的,在 Go 語言中,一個模組可以包含很多個包,所以模組是相關的包的集合。
在 Go 語言中:
- 一個模組通常是一個專案,比如例項中使用的 tools 專案;
- 也可以是一個框架,比如常用的 Web 框架 gin。
go mod
Go 語言提供了 go mod 命令來建立一個模組(專案),比如要建立一個 tools 模組,可以通過如下命令實現:
➜ go mod init tools
go: creating new go.mod: module tools
執行這一命令後,會看到已經建立好一個名字為 tools 的資料夾,裡面有一個 go.mod 檔案,它裡面的內容如下所示:
module tools
go 1.15
- 第一句是該專案的模組名,也就是 tools;
- 第二句表示要編譯該模組至少需要Go 1.15 版本的 SDK。
小提示:模組名最好是以自己的域名開頭,比如 github.com/tools,這樣就可以很大程度上保證模組名的唯一,不至於和其他模組重名。
使用第三方模組
模組化為什麼可以提高開發效率?最重要的原因就是複用了現有的模組,Go 語言也不例外。比如可以把專案中的公共程式碼抽取為一個模組,這樣就可以供其他專案使用,不用再重複開發;同理,在 Github 上也有很多開源的 Go 語言專案,它們都是一個個獨立的模組,也可以被直接使用,開發效率,比如 Web 框架 gin-gonic/gin。
眾所周知,在使用第三方模組之前,需要先設定下 Go 代理,也就是 GOPROXY,這樣就可以獲取到第三方模組了。
在這裡推薦 goproxy.io 這個代理,非常好用,速度也很快。要使用這個代理,需要進行如下程式碼設定:
go env -w GO111MODULE=on
go env -w GOPROXY=https://goproxy.io,direct
開啟終端,輸入這一命令回車即可設定成功。
在實際的專案開發中,除了第三方模組外,還有自己開發的模組,放在了公司的 GitLab上,這時候就要把公司 Git 程式碼庫的域名排除在 Go PROXY 之外,為此 Go 語言提供了GOPRIVATE 這個環境變數幫助我們達到目的。通過如下命令即可設定 GOPRIVATE:
# 設定不走 proxy 的私有倉庫,多個用逗號相隔(可選)
go env -w GOPRIVATE=*.cors.example.com
以上域名只是一個示例,實際使用時要改成自己公司私有倉庫的域名。
一切都準備好就可以使用第三方的模組了,假設要使用 Gin 這個 Web 框架,首先需要安裝它,通過如下命令即可安裝 Gin 這個 Web 框架:
go get -u github.com/gin-gonic/gin
安裝成功後,就可以像 Go 語言的標準包一樣,通過 import 命令匯入程式碼中使用它,程式碼如下所示:
package main
import (
"fmt"
"github.com/gin-gonic/gin"
)
func main() {
fmt.Println("先匯入fmt包,才能使用")
r := gin.Default()
r.Run()
}
以上程式碼現在還無法編譯通過,因為還沒有同步 Gin 這個模組的依賴,也就是沒有把它新增到go.mod 檔案中。通過如下命令可以新增缺失的模組:
go mod tidy
執行這一命令,就可以把缺失的模組新增進來,同時它也可以移除不再需要的模組。這時再檢視 go.mod 檔案,會發現內容已經變成了這樣:
module tools
go 1.15
require (
github.com/gin-gonic/gin v1.6.3
github.com/golang/protobuf v1.4.2 // indirect
github.com/google/go-cmp v0.5.2 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
github.com/stretchr/testify v1.6.1 // indirect
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
gopkg.in/yaml.v2 v2.3.0 // indirect
)
所以不用手動去修改 go.mod 檔案,通過 Go 語言的工具鏈比如 go mod tidy 命令,就可以自動地維護、自動地新增或者修改 go.mod 的內容。
總結
在 Go 語言中,包是同一目錄中,編譯在一起的原始檔的集合。包裡面含有函式、型別、變數和常量,不同包之間的呼叫,必須要首字母大寫才可以。
而模組又是相關的包的集合,它裡面包含了很多為了實現該模組的包,並且還可以通過模組的方式,把已經完成的模組提供給其他專案(模組)使用,達到了程式碼複用、研發效率提高的目的。
所以對於專案(模組)來說,它具有模組 ➡ 包 ➡ 函式型別這樣三層結構,同一個模組中,可以通過包組織程式碼,達到程式碼複用的目的;在不同模組中,就需要通過模組的引入,達到這個目的。
程式設計界有個諺語:不要重複造輪子,使用現成的輪子,可以提高開發效率,降低 Bug 率。Go 語言提供的模組、包這些能力,就可以很好地使用現有的輪子,在多人協作開發中,更好地提高工作效率。
本作品採用《CC 協議》,轉載必須註明作者和本文連結