以下收錄撰寫golang package可以遵循的內容
Package 概覽
一個Go Pakcage的基本元素包含了如下:
- Package declaration: 基本定義,告訴developer package的功能是什麼
- Documentation: 提供Package內所包含的function是什麼,要如何使用
- Imports: 如何引入pakcage內的function
- var & const blocks: 使用的變數宣告
- Types & interfaces: 定義package內會使用的型態以及介面
- Functions: functions的實作
Package的範圍:一個資料夾內的所有.go
檔案,其他除外
基本上一個Go Package只會包含單一目錄內所有的Go Source.
舉個例子
1 | ---services |
上面events
這個package,只會包含 handlers.go
以及 serve.go
這兩隻檔案的內容,data
資料夾會被排除
Package種類
Library Packages
定義Golang Package的內容,提供相同功能性的function。
1 | // .../services |
- Library Pakcage通常被其他的package所引入進來
- Library Package的名稱必須要跟自己的資料夾名稱一樣
- pakcage內的最好是提供相似的功能
Main Package
定義了Application的進入點,程式要啟動就會是從main package開始,注重在app的設置以及初始化的邏輯。
1 | package main |
- 包含 main() function
- 可以在任何的資料夾
範例
http
package: https://golang.org/pkg/net/http/
- Constants: 定義常數內容
1 | const ( |
- Variables: 定義變數內容,且給予說明
1 | var ( |
- Types: 定義客製化型態,並給予說明
1 | // A Client is an HTTP client. Its zero value (DefaultClient) is a usable client that uses DefaultTransport. ... |
也提供了Clinet形態的初始化變數設置,不用一直自行初始化
1 | // DefaultClient is the default Client and is used by Get, Head, and Post. |
- Function: 定義function內容,並添加說明
1 | // CanonicalHeaderKey returns the canonical format of the header key s. The canonicalization converts the first letter and any letter following a hyphen to upper case; the rest are converted to lowercase. For example, the canonical key for "accept-encoding" is "Accept-Encoding". If s contains a space or invalid header field bytes, it is returned without modifications. |
Pakcage 的生命週期
- Import 所需要的package
- 將initial value設置在variables
- 呼叫
init()
方法,執行初始化該package的動作
注意,無法直接呼叫
init()
,該方法只能在golang的compiler時期被golang呼叫執行。
範例
假如有個目錄如下
1 | --- cmd |
若 main.go
import services
這個package
1 | /* |
可以看到init() 可以多次宣告
此時main.go
呼叫services
pakcage內的startServer
function
1 | // Package main is typically used to define a single, executable command and its associated logic. |
要注意,services import了internal內ports
package的變數,故若呼叫了main.go
中的startServer,會先印出初始化的port = 42
, 再來是透過init()
接收到ports package的 port = 3000
1 | func init() { |
印出如下
1 | serve.go 1 42 |
Pakcage 存取範疇
- Public Scope
- 以大寫宣告
- 可被專案內所有成員存取
- Package Scope
- 以小寫宣告
- 只能被同一個package內存取
- Internal Package
- 可同時使用public-level, package-level 的成員
- 只限於父層與子層的成員存取
Pakcage設計的Best Practice
Pakcage 名稱
- 儘量簡短,使用名詞
- 小寫開頭,不使用
_
,等特殊字元
- 別使用一般使用者會用到的變數名稱
event
,temp
等名詞來命名.
Package 說明
一個公開的pakcage通常會包含兩個主要說明內容
- licensing: 使用的開放協議
- package comment: package的功能說明
- 說明開頭應包含 package名稱,方法的話開頭會function的名稱
helloworld/doc.go
1 | // Copyright 2009 The Go Authors. All rights reserved. |
這時使用 go doc helloworld
,就會顯示 helloworld
package內doc.go
的說明
使用 doc.go
做更複雜的說明
若有太多的package敘述要呈現,通常會在doc.go
說明
Pakcage內容的描述撰寫指引
- 避免冗余
例如,下面的package名稱與function名稱都重複了,所以可以將function名稱與package重複的部分給去掉
1 | http.HTTPServer -> http.server |
- 盡量簡化
例如,下面可以簡化成如下
1 | time.NewTime -> time.Time |
Package內容準則
- 提供直覺的功能
- single reponsibility: 單一功能
- cohesive API: 功能相依的Application Programming Interface
- 提供友善的使用方法
- simple to use
- minimize API: 將功能重點化
- encapsulate changes: 將變動封裝起來,不影響原本使用者的input, output的使用方式
- 最大化可重複利用性
- reduce dependencies: 減少依賴其他dependencies的狀況
- minimize scope: 縮小範疇,避免到處都是public,造成package內部的變數被其他地方所影響。
結構設計
Package Input | Package Output |
---|---|
對於configuration使用 concrete types | 對於configuration & behavior 都使用concrete types |
對於behavior 使用 interface | 對於errors 請避免不發生 panics: 因為錯誤即使發生了,使用者也無法對其做處理 |
透過 http
package來看看其結構設計
Package Input | Package Output |
---|---|
net/http.Request 對於http.Reqeust所要使用的configuration,例如http body要用的參數格式是什麼,使用 concrete types,明確規範使用者要放置什麼給package |
net/http.Response 對於其回傳結果,將configuration & behavior 都使用concrete types定義好,避免使用者混淆 |
net/http.Handler 對於http.Handler, 這個處理http的behavior 使用 interface,方便使用者可以針對他們自己的http Request進來的行為定義自己的型態 |
net/http.Get 正常狀況下 (用戶正確使用下),用戶使用其方法是不會發生panic的 |
Import Package方法
1. Typical Imports
使用fmt
pakcage的 Println
function
1 | package main |
2. Alternative Imports
- Aliases 將pakcege 別名
1 | package main |
可透過 alias ,將其中一個package名稱進行別名
1 | import ( |
3. Import for side effect: 為引入的package,在引入其他的package使其作用
這有點難理解,直接看範例
例如以下有引入兩個packages, 一個是sql
package, 另外一個是 pq
postgres這個資料庫的package.
1 | import ( |
可以看到 pq
是用 _
代表他是一個write only的package, 本身使用者不會去使用到它,而是提供給 sql
做初始化sql內要採用哪個database做使用
可以看到 pg package內 有這一段程式碼,提供init()
給sql做注入使用其postgres dirver做使用
1 | func init() { |
4. Internal packages
主要提供父層與子層的pakcage做存取,目的是要提供跳脫自己之外的存取限制,又不會洩漏自己內部訊息給外部使用者。
只要是宣告為 internal
的package,就可以被父層或子層存取
以下範例為,services
下有宣告 internal
package, 可提供其他package services/events/serve.go
做存取使用
若在超出internal定義的範疇 (上一個層級與下一個層級),則會compiler編譯時會發生錯誤訊息
4. Relative Import
透過 ../../package
的方式import,不過在Production環境下不建議使用; 或者得透過golang最新的 go mod
管理方式。