Go開發(fā)命令行程序指南
近期在Twitter上看到一個名為“Command Line Interface Guidelines”的站點[1],這個站點匯聚了幫助大家編寫出更好命令行程序的哲學與指南。這份指南基于傳統(tǒng)的Unix編程原則[2],又結(jié)合現(xiàn)代的情況進行了“與時俱進”的更新。之前我還真未就如何編寫命令行交互程序做系統(tǒng)的梳理,在這篇文章中,我們就來結(jié)合clig這份指南[3],(可能不會全面覆蓋)整理出一份使用Go語言編寫CLI程序的指南,供大家參考。
一. 命令行程序簡介
命令行接口(Command Line Interface, 簡稱CLI)程序是一種允許用戶使用文本命令和參數(shù)與計算機系統(tǒng)互動的軟件。開發(fā)人員編寫CLI程序通常用在自動化腳本、數(shù)據(jù)處理、系統(tǒng)管理和其他需要低級控制和靈活性的任務上。命令行程序也是Linux/Unix管理員以及后端開發(fā)人員的最愛。
2022年Q2 Go官方用戶調(diào)查結(jié)果[4]顯示(如下圖):在使用Go開發(fā)的程序類別上,CLI類程序排行第二,得票率60%。
之所以這樣,得益于Go語言為CLI開發(fā)提供的諸多便利,比如:
- Go語法簡單而富有表現(xiàn)力;
- Go擁有一個強大的標準庫,并內(nèi)置的并發(fā)支持;
- Go擁有幾乎最好的跨平臺兼容性和快速的編譯速度;
- Go還有一個豐富的第三方軟件包和工具的生態(tài)系統(tǒng)。
這些都讓開發(fā)者使用Go創(chuàng)建強大和用戶友好的CLI程序變得容易。
容易歸容易,但要用Go編寫出優(yōu)秀的CLI程序,我們還需要遵循一些原則,獲得一些關(guān)于Go CLI程序開發(fā)的最佳實踐和慣例。這些原則和慣例涉及交互界面設(shè)計、錯誤處理、文檔、測試和發(fā)布等主題。此外,借助于一些流行的Go CLI程序開發(fā)庫和框架,比如:cobra[5]、Kingpin[6]和Goreleaser[7]等,我們可以又好又快地完成CLI程序的開發(fā)。在本文結(jié)束時,你將學會如何創(chuàng)建一個易于使用、可靠和可維護的Go CLI程序,你還將獲得一些關(guān)于CLI開發(fā)的最佳實踐和慣例的見解。
二. 建立Go開發(fā)環(huán)境
如果你讀過《十分鐘入門Go語言》[8]或訂閱學習過我的極客時間《Go語言第一課》專欄[9],你大可忽略這一節(jié)的內(nèi)容。
在我們開始編寫Go CLI程序之前,我們需要確保我們的系統(tǒng)中已經(jīng)安裝和配置了必要的Go工具和依賴。在本節(jié)中,我們將向你展示如何安裝Go和設(shè)置你的工作空間,如何使用go mod進行依賴管理[10],以及如何使用go build和go install來編譯和安裝你的程序。
1. 安裝Go
要在你的系統(tǒng)上安裝Go,你可以遵循你所用操作系統(tǒng)的官方安裝說明。你也可以使用軟件包管理器,如homebrew[11](用于macOS)、chocolatey(用于Windows)或snap/apt(用于Linux)來更容易地安裝Go。
一旦你安裝了Go,你可以通過在終端運行以下命令來驗證它是否可以正常工作。
如果安裝成功,go version這個命令應該會打印出你所安裝的Go的版本。比如說:
2. 設(shè)置你的工作區(qū)(workspace)
Go以前有一個慣例,即在工作區(qū)目錄中(組織你的代碼和依賴關(guān)系。默認工作空間目錄位于HOME/go,但你可以通過設(shè)置GOPATH環(huán)境變量來改變它的路徑。工作區(qū)目錄包含三個子目錄:src、pkg和bin。src目錄包含了你的源代碼文件和目錄。pkg目錄包含被你的代碼導入的已編譯好的包。bin目錄包含由你的代碼生成的可執(zhí)行二進制文件。
Go 1.11引入Go module[12]后,這種在下組織代碼和尋找依賴關(guān)系的要求被徹底取消。在這篇文章中,我依舊按照我的習慣在HOME/go/src下放置我的代碼示例。
為了給我們的CLI程序創(chuàng)建一個新的項目目錄,我們可以在終端運行以下命令:
注意,我們的項目目錄名使用的是github的URL格式。這在Go項目中是一種常見的做法,因為它使得使用go get導入和管理依賴關(guān)系更加容易。go module成為構(gòu)建標準后,這種對項目目錄名的要求已經(jīng)取消,但很多Gopher依舊保留了這種作法。
3. 使用go mod進行依賴管理
1.11版本后Go推薦開發(fā)者使用module來管理包的依賴關(guān)系。一個module是共享一個共同版本號和導入路徑前綴的相關(guān)包的集合。一個module是由一個叫做go.mod的文件定義的,它指定了模塊的名稱、版本和依賴關(guān)系。
為了給我們的CLI程序創(chuàng)建一個新的module,我們可以在我們的項目目錄下運行以下命令。
這將創(chuàng)建一個名為go.mod的文件,內(nèi)容如下。
第一行指定了我們的module名稱,這與我們的項目目錄名稱相匹配。第二行指定了構(gòu)建我們的module所需的Go的最低版本。
為了給我們的模塊添加依賴項,我們可以使用go get命令,加上我們想使用的軟件包的導入路徑和可選的版本標簽。例如,如果我們想使用cobra[13]作為我們的CLI框架,我們可以運行如下命令:
go get將從github下載cobra,并在我們的go.mod文件中把它作為一個依賴項添加進去。它還將創(chuàng)建或更新一個名為go.sum的文件,記錄所有下載的module的校驗和,以供后續(xù)驗證使用。
我們還可以使用其他命令,如go list、go mod tidy、go mod graph等,以更方便地檢查和管理我們的依賴關(guān)系。
4. 使用go build和go install來編譯和安裝你的程序
Go有兩個命令允許你編譯和安裝你的程序:go build和go install。這兩個命令都以一個或多個包名或?qū)肼窂阶鳛閰?shù),并從中產(chǎn)生可執(zhí)行的二進制文件。
它們之間的主要區(qū)別在于它們將生成的二進制文件存儲在哪里。
- go build將它們存儲在當前工作目錄中。
- go install將它們存儲在或GOBIN(如果設(shè)置了)。
例如,如果我們想把CLI程序的main包(應該位于github.com/your-username/your-cli-program/cmd/your-cli-program)編譯成一個可執(zhí)行的二進制文件,稱為your-cli-program,我們可以運行下面命令:
或
三. 設(shè)計用戶接口(interface)
要編寫出一個好的CLI程序,最重要的環(huán)節(jié)之一是**設(shè)計一個用戶友好的接口[14]。好的命令行用戶接口應該是一致的、直觀的和富有表現(xiàn)力的**。在本節(jié)中,我將說明如何為命令行程序命名和選擇命令結(jié)構(gòu)(command structure),如何使用標志(flag)、參數(shù)(argument)、子命令(subcommand)和選項(option)作為輸入?yún)?shù),如何使用cobra或Kingpin等來解析和驗證用戶輸入,以及如何遵循POSIX慣例和GNU擴展的CLI語法。
1. 命令行程序命名和命令結(jié)構(gòu)選擇
你的CLI程序的名字應該是**簡短、易記、描述性的和易輸入的[15]**。它應該避免與目標平臺中現(xiàn)有的命令或關(guān)鍵字發(fā)生沖突。例如,如果你正在編寫一個在不同格式之間轉(zhuǎn)換圖像的程序,你可以把它命名為imgconv、imago、picto等,但不能叫image、convert或format。
你的CLI程序的命令結(jié)構(gòu)應該反映你想提供給用戶的主要功能特性。你可以選擇使用下面命令結(jié)構(gòu)模式中的一種:
- 一個帶有多個標志(flag)和參數(shù)(argument)的單一命令(例如:curl、tar、grep等)
- 帶有多個子命令(subcommand)的單一命令(例如:git、docker、kubectl等)
- 具有共同前綴的多個命令(例如:aws s3、gcloud compute、az vm等)
命令結(jié)構(gòu)模式的選擇取決于你的程序的復雜性和使用范圍,一般來說:
- 如果你的程序只有一個主要功能或操作模式(operation mode),你可以使用帶有多個標志和參數(shù)的單一命令。
- 如果你的程序有多個相關(guān)但又不同的功能或操作模式,你可以使用一個帶有多個子命令的單一命令。
- 如果你的程序有多個不相關(guān)或獨立的功能或操作模式,你可以使用具有共同前綴的多個命令。
例如,如果你正在編寫一個對文件進行各種操作的程序(如復制、移動、刪除),你可以任選下面命令結(jié)構(gòu)模式中的一種:
- 帶有多個標志和參數(shù)的單一命令(例如,fileop -c src dst -m src dst -d src)
- 帶有多個子命令的單個命令(例如,fileop copy src dst, fileop move src dst, fileop delete src)
2. 使用標志、參數(shù)、子命令和選項
**標志(flag)**是以一個或多個(通常是2個)中劃線(-)開頭的輸入?yún)?shù),它可以修改CLI程序的行為或輸出。例如:
在這個例子中:
- “-s”是一個讓curl沉默的標志,即不輸出執(zhí)行日志到控制臺;
- “-o”是另一個標志,用于指定輸出文件的名稱
- “output.txt”則是一個參數(shù),是為“-o”標志提供的值。
**參數(shù)(argument)**是不以中劃線(-)開頭的輸入?yún)?shù),為你的CLI程序提供額外的信息或數(shù)據(jù)。例如:
我們看在這個例子中:
- x是一個指定提取模式的參數(shù)
- v是一個參數(shù),指定的是輸出內(nèi)容的詳細(verbose)程度
- f是另一個參數(shù),用于指定采用的是文件模式,即將壓縮結(jié)果輸出到一個文件或從一個壓縮文件讀取數(shù)據(jù)
- archive.tar.gz是一個參數(shù),提供文件名。
**子命令(subcommand)**是輸入?yún)?shù),作為主命令下的輔助命令。它們通常有自己的一組標志和參數(shù)。比如下面例子:
我們看在這個例子中:
- git是主命令(primary command)
- commit是一個子命令,用于從staged的修改中創(chuàng)建一個新的提交(commit)
- “-m”是commit子命令的一個標志,用于指定提交信息
- "Initial commit"是commit子命令的一個參數(shù),為"-m"標志提供值。
**選項(option)**是輸入?yún)?shù),它可以使用等號(=)將標志和參數(shù)合并為一個參數(shù)。例如:
我們看在這個例子中“--name=my-container”是一個選項,它將容器的名稱設(shè)為my-container。該選項前面的部分“--name”是一個標志,后面的部分“my-container”是參數(shù)。
3. 使用cobra包等來解析和驗證用戶輸入的信息
如果手工來解析和驗證用戶輸入的信息,既繁瑣又容易出錯。幸運的是,有許多庫和框架可以幫助你在Go中解析和驗證用戶輸入。其中最流行的是cobra[16]。
cobra是一個Go包,它提供了簡單的接口來創(chuàng)建強大的CLI程序。它支持子命令、標志、參數(shù)、選項、環(huán)境變量和配置文件。它還能很好地與其他庫集成,比如:viper[17](用于配置管理)、pflag[18](用于POSIX/GNU風格的標志)和Docopt[19](用于生成文檔)。
另一個不那么流行但卻提供了一種聲明式的方法來創(chuàng)建優(yōu)雅的CLI程序的包是Kingpin[20],它支持標志、參數(shù)、選項、環(huán)境變量和配置文件。它還具有自動幫助生成、命令完成、錯誤處理和類型轉(zhuǎn)換等功能。
cobra和Kingpin在其官方網(wǎng)站上都有大量的文檔和例子,你可以根據(jù)你的偏好和需要選擇任選其一。
4. 遵循POSIX慣例和GNU擴展的CLI語法
POSIX(Portable Operating System Interface)[21]是一套標準,定義了軟件應該如何與操作系統(tǒng)進行交互。其中一個標準定義了CLI程序的語法和語義。GNU(GNU's Not Unix)是一個旨在創(chuàng)建一個與UNIX兼容的自由軟件操作系統(tǒng)的項目。GNU下的一個子項目是GNU Coreutils[22],它提供了許多常見的CLI程序,如ls、cp、mv等。
POSIX和GNU都為CLI語法建立了一些約定和擴展,許多CLI程序都采用了這些約定與擴展。下面列舉了這些約定和擴展中的一些主要內(nèi)容:
- 單字母標志(single-letter flag)以一個中劃線(-)開始,可以組合在一起(例如:-a -b -c 或 -abc )
- 長標志(long flag)以兩個中劃線(--)開頭,但不能組合在一起(例如:--all、--backup、--color )
- 選項使用等號(=)來分隔標志名和參數(shù)值(例如:--name=my-container )
- 參數(shù)跟在標志或選項之后,沒有任何分隔符(例如:curl -o output.txt https://example.com )。
- 子命令跟在主命令之后,沒有任何分隔符(例如:git commit -m "Initial commit" )
- 一個雙中劃線(--)表示標志或選項的結(jié)束和參數(shù)的開始(例如:rm -- -f 表示要刪除“-f”這個文件,由于雙破折線的存在,這里的“-f”不再是標志)
遵循這些約定和擴展可以使你的CLI程序更加一致、直觀,并與其他CLI程序兼容。然而,它們并不是強制性的,如果你有充分的理由,你也大可不必完全遵守它們。例如,一些CLI程序使用斜線(/)而不是中劃線(-)表示標志(例如, robocopy /S /E src dst )。
四. 處理錯誤和信號
編寫好的CLI程序的一個重要環(huán)節(jié)就是**優(yōu)雅地處理錯誤和信號[23]**。
錯誤是指你的程序由于某些內(nèi)部或外部因素而無法執(zhí)行其預定功能的情況。信號是由操作系統(tǒng)或其他進程向你的程序發(fā)送的事件,以通知它一些變化或請求。在這一節(jié)中,我將說明一下如何使用log、fmt和errors包進行日志輸出和錯誤處理,如何使用os.Exit和defer語句進行優(yōu)雅的終止,如何使用os.Signal和context包進行中斷和取消操作,以及如何遵循CLI程序的退出狀態(tài)代碼慣例。
1. 使用log、fmt和errors包進行日志記錄和錯誤處理
Go標準庫中有三個包log、fmt和errors可以幫助你進行日志和錯誤處理。log包提供了一個簡單的接口,可以將格式化的信息寫到標準輸出或文件中。fmt包則提供了各種格式化字符串和值的函數(shù)。errors包提供了創(chuàng)建和操作錯誤值的函數(shù)。
要使用log包,你需要在你的代碼中導入它:
然后你可以使用log.Println、log.Printf、log.Fatal和log.Fatalf等函數(shù)來輸出不同嚴重程度的信息。比如說:
為了使用fmt包,你需要先在你的代碼中導入它:
然后你可以使用fmt.Println、fmt.Printf、fmt.Sprintln、fmt.Sprintf等函數(shù)以各種方式格式化字符串和值。比如說:
要使用錯誤包,你同樣需要在你的代碼中導入它:
然后你可以使用 errors.New、errors.Unwrap、errors.Is等函數(shù)來創(chuàng)建和操作錯誤值。比如說:
2. 使用os.Exit和defer語句實現(xiàn)CLI程序的優(yōu)雅終止
Go有兩個功能可以幫助你優(yōu)雅地終止CLI程序:os.Exit和defer。os.Exit函數(shù)立即退出程序,并給出退出狀態(tài)代碼。defer語句則會在當前函數(shù)退出前執(zhí)行一個函數(shù)調(diào)用,它常用來執(zhí)行清理收尾動作,如關(guān)閉文件或釋放資源。
要使用os.Exit函數(shù),你需要在你的代碼中導入os包:
然后你可以使用os.Exit函數(shù),它的整數(shù)參數(shù)代表退出狀態(tài)代碼。比如說
要使用defer語句,你需要把它寫在你想后續(xù)執(zhí)行的函數(shù)調(diào)用之前。比如說
3. 使用os.signal和context包來實現(xiàn)中斷和取消操作
Go有兩個包可以幫助你實現(xiàn)中斷和取消長期運行的或阻塞的操作,它們是os.signal和context包。os.signal提供了一種從操作系統(tǒng)或其他進程接收信號的方法。context包提供了一種跨越API邊界傳遞取消信號和deadline的方法。
要使用os.signal,你需要先在你的代碼中導入它。
然后你可以使用signal.Notify函數(shù)針對感興趣的信號(如下面的os.Interrupt信號)注冊一個接收channel(sig)。比如說:
要使用上下文包,你需要在你的代碼中導入它:
然后你可以使用它的函數(shù),如context.Background、context.WithCancel、context.WithTimeout等來創(chuàng)建和管理Context。Context是一個攜帶取消信號和deadline的對象,可以跨越API邊界。比如說:
4. CLI程序的退出狀態(tài)代碼慣例
退出狀態(tài)代碼是一個整數(shù),表示CLI程序是否成功執(zhí)行完成。CLI程序通過調(diào)用os.Exit或從main返回的方式返回退出狀態(tài)值。其他CLI程序或腳本可以可以檢查這些退出狀態(tài)碼,并根據(jù)狀態(tài)碼值的不同執(zhí)行不同的處理操作。
業(yè)界有一些關(guān)于退出狀態(tài)代碼的約定和擴展,這些約定被許多CLI程序廣泛采用。其中一些主要的約定和擴展如下:。
- 退出狀態(tài)代碼為0表示程序執(zhí)行成功(例如:os.Exit(0) )
- 非零的退出狀態(tài)代碼表示失敗(例如:os.Exit(1) )。
- 不同的非零退出狀態(tài)代碼可能表示不同的失敗類型或原因(例如:os.Exit(2)表示使用錯誤,os.Exit(3)表示權(quán)限錯誤等等)。
- 大于125的退出狀態(tài)代碼可能表示被外部信號終止(例如,os.Exit(130)為被信號中斷)。
遵循這些約定和擴展可以使你的CLI程序表現(xiàn)的更加一致、可靠并與其他CLI程序兼容。然而,它們不是強制性的,你可以使用任何對你的程序有意義的退出狀態(tài)代碼。例如,一些CLI程序使用高于200的退出狀態(tài)代碼來表示自定義或特定應用的錯誤(例如,os.Exit(255)表示未知錯誤)。
五. 編寫文檔
編寫優(yōu)秀CLI程序的另一個重要環(huán)節(jié)是編寫清晰簡潔的文檔,解釋你的程序做什么以及如何使用它。文檔可以采取各種形式,如README文件、usage信息、help flag等。在本節(jié)中,我們將告訴你如何為你的程序?qū)懸粋€README文件,如何為你的程序?qū)懸粋€有用的usage和help flag等。
1. 為你的CLI程序?qū)懸粋€清晰簡潔的README文件
README文件是一個文本文件,它提供了關(guān)于你的程序的基本信息,如它的名稱、描述、用法、安裝、依賴性、許可證和聯(lián)系細節(jié)等。它通常是用戶或開發(fā)者在源代碼庫或軟件包管理器上首次使用你的程序時會看到的內(nèi)容。
如果你要為Go CLI程序編寫一個優(yōu)秀的README文件,你應該遵循一些最佳實踐,比如:
- 使用一個描述性的、醒目的標題,反映你的程序的目的和功能。
- 提供一個簡短的介紹,解釋你的程序是做什么的,為什么它是有用的或獨特的。
- 包括一個usage部分,說明如何用不同的標志、參數(shù)、子命令和選項來調(diào)用你的程序。你可以使用代碼塊或屏幕截圖來說明這些例子。
- 包括一個安裝(install)部分,解釋如何在不同的平臺上下載和安裝你的程序。你可以使用go install、go get、goreleaser[24]或其他工具來簡化這一過程。
- 指定你的程序的發(fā)行許可,并提供一個許可全文的鏈接。你可以使用SPDX標識符[25]來表示許可證類型。
- 為想要報告問題、請求新功能、貢獻代碼或提問的用戶或開發(fā)者提供聯(lián)系信息。你可以使用github issue、pr、discussion、電子郵件或其他渠道來達到這個目的。
以下是一個Go CLI程序的README文件的示例供參考:
2. 為你的CLI程序編寫有用的usage和help標志
usage信息是一段簡短的文字,總結(jié)了如何使用你的程序及其可用的標志、參數(shù)、子命令和選項。它通常在你的程序在沒有參數(shù)或輸入無效的情況下運行時顯示。
help標志是一個特殊的標志(通常是-h或--help),它可以觸發(fā)顯示使用信息和一些關(guān)于你的程序的額外信息。
為了給你的Go CLI程序?qū)懹杏玫膗sage信息和help標志,你應該遵循一些準則,比如說:
- 使用一致而簡潔的語法來描述標志、參數(shù)、子命令和選項。你可以用方括號“[ ]”表示可選元素,使用角括號“< >”表示必需元素,使用省略號“...”表示重復元素,使用管道“|”表示備選,使用中劃線“-”表示標志(flag),使用等號“=”表示標志的值等等。
- 對標志、參數(shù)、子命令和選項應使用描述性的名稱,以反映其含義和功能。避免使用單字母名稱,除非它們非常常見或非常直觀(如-v按慣例表示verbose模式)。
- 為每個標志、參數(shù)、子命令和選項提供簡短而清晰的描述,解釋它們的作用以及它們?nèi)绾斡绊懩愕某绦虻男袨?。你可以用圓括號“( )”來表達額外的細節(jié)或例子。
- 使用標題或縮進將相關(guān)的標志、參數(shù)、子命令和選項組合在一起。你也可以用空行或水平線(---)來分隔usage的不同部分。
- 在每組中按名稱的字母順序排列標志。在每組中按重要性或邏輯順序排列參數(shù)。在每組中按使用頻率排列子命令。
git的usage就是一個很好的例子:
結(jié)合上面的準則,大家可以細心體會一下。
六. 測試和發(fā)布你的CLI程序
編寫優(yōu)秀CLI程序的最后一個環(huán)節(jié)是測試和發(fā)布你的程序。測試確保你的程序可以按預期工作,并符合質(zhì)量標準。發(fā)布可以使你的程序可供用戶使用和訪問。
在本節(jié)中,我將說明如何使用testing、testify/assert、mock包對你的代碼進行單元測試,如何使用go test、coverage、benchmark工具來運行測試和測量程序性能以及如何使用goreleaser包來構(gòu)建跨平臺的二進制文件。
1. 使用testing、testify的assert及mock包對你的代碼進行單元測試
單元測試是一種驗證單個代碼單元(如函數(shù)、方法或類型)的正確性和功能的技術(shù)。單元測試可以幫助你盡早發(fā)現(xiàn)錯誤,提高代碼質(zhì)量和可維護性,并促進重構(gòu)和調(diào)試。
要為你的Go CLI程序編寫單元測試,你應該遵循一些最佳實踐:
- 使用內(nèi)置的測試包來創(chuàng)建測試函數(shù),以Test開頭,后面是被測試的函數(shù)或方法的名稱。例如:func TestSum(t *testing.T) { ... };
- 使用*testing.T類型的t參數(shù),使用t.Error、t.Errorf、t.Fatal或t.Fatalf這樣的方法報告測試失敗。你也可以使用t.Log、t.Logf、t.Skip或t.Skipf這樣的方法來提供額外的信息或有條件地跳過測試。
- 使用Go子測試(sub test)[26],通過t.Run方法將相關(guān)的測試分組。例如:
- 使用表格驅(qū)動(table-driven)的測試來運行多個測試用例,比如下面的例子:
- 使用外部包,如testify/assert或mock來簡化你的斷言或?qū)ν獠康囊蕾囆?。比如說:
2. 使用Go的測試、覆蓋率、性能基準工具來運行測試和測量性能
Go提供了一套工具來運行測試和測量你的代碼的性能。你可以使用這些工具來確保你的代碼按預期工作,檢測錯誤或bug,并優(yōu)化你的代碼以提高速度和效率。
要使用go test、coverage、benchmark工具來運行測試和測量你的Go CLI程序的性能,你應該遵循一些步驟,比如說。
- 將以_test.go結(jié)尾的測試文件寫在與被測試代碼相同的包中。例如:sum_test.go用于測試sum.go。
- 使用go測試命令來運行一個包中的所有測試或某個特定的測試文件。你也可以使用一些標志,如-v,用于顯示verbose的輸出,-run用于按名字過濾測試用例,-cover用于顯示代碼覆蓋率,等等。例如:go test -v -cover ./...
- 使用go工具cover命令來生成代碼覆蓋率的HTML報告,并高亮顯示代碼行。你也可以使用-func這樣的標志來顯示函數(shù)的代碼覆蓋率,用-html還可以在瀏覽器中打開覆蓋率結(jié)果報告等等。例如:go tool cover -html=coverage.out
- 編寫性能基準函數(shù),以Benchmark開頭,后面是被測試的函數(shù)或方法的名稱。使用類型為*testing.B的參數(shù)b來控制迭代次數(shù),并使用b.N、b.ReportAllocs等方法控制報告結(jié)果的輸出。比如說
- 使用go test -bench命令來運行一個包中的所有性能基準測試或某個特定的基準文件。你也可以使用-benchmem這樣的標志來顯示內(nèi)存分配的統(tǒng)計數(shù)據(jù),-cpuprofile或-memprofile來生成CPU或內(nèi)存profile文件等等。例如:go test -bench . -benchmem ./...
- 使用pprof或benchstat等工具來分析和比較CPU或內(nèi)存profile文件或基準測試結(jié)果。比如說。
3. 使用goreleaser包構(gòu)建跨平臺的二進制文件
構(gòu)建跨平臺二進制文件意味著將你的代碼編譯成可執(zhí)行文件,可以在不同的操作系統(tǒng)和架構(gòu)上運行,如Windows、Linux、Mac OS、ARM等。這可以幫助你向更多的人分發(fā)你的程序,使用戶更容易安裝和運行你的程序而不需要任何依賴或配置。
為了給你的Go CLI程序建立跨平臺的二進制文件,你可以使用外部軟件包,比如goreleaser等 ,它們可以自動完成程序的構(gòu)建、打包和發(fā)布過程。下面是使用goreleaser包構(gòu)建程序的一些步驟。
- 使用go get或go install命令安裝goreleaser。例如: go install github.com/goreleaser/goreleaser@latest
- 創(chuàng)建一個配置文件(通常是.goreleaser.yml),指定如何構(gòu)建和打包你的程序。你可以定制各種選項,如二進制名稱、版本、主文件、輸出格式、目標平臺、壓縮、校驗和、簽名等。例如。
運行g(shù)oreleaser命令,根據(jù)配置文件構(gòu)建和打包你的程序。你也可以使用-snapshot用于測試,-release-notes用于從提交信息中生成發(fā)布說明,-rm-dist用于刪除之前的構(gòu)建,等等。例如:goreleaser --snapshot --rm-dist。
檢查輸出文件夾(通常是dist)中生成的二進制文件和其他文件。你也可以使用goreleaser的發(fā)布功能將它們上傳到源代碼庫或軟件包管理器中。
七. clig.dev指南要點
通過上述的系統(tǒng)說明,你現(xiàn)在應該可以設(shè)計并使用Go實現(xiàn)出一個CLI程序了。不過本文并非覆蓋了clig.dev指南的所有要點,因此,在結(jié)束本文之前,我們再來回顧一下clig.dev指南中的要點,大家再體會一下。
前面說過,clig.dev上的cli指南是一個開源指南,可以幫助你寫出更好的命令行程序,它采用了傳統(tǒng)的UNIX原則,并針對現(xiàn)代的情況進行了更新。
遵循cli準則的一些好處是:
- 你可以創(chuàng)建易于使用、理解和記憶的CLI程序。
- 你可以設(shè)計出能與其他程序進行很好配合的CLI程序,并遵循共同的慣例。
- 你可以避免讓用戶和開發(fā)者感到沮喪的常見陷阱和錯誤。
- 你可以從其他CLI設(shè)計者和用戶的經(jīng)驗和智慧中學習。
下面是該指南的一些要點:
- 理念
這一部分解釋了好的CLI設(shè)計背后的核心原則,如人本設(shè)計、可組合性、可發(fā)現(xiàn)性、對話性等。例如,以人為本的設(shè)計意味著CLI程序?qū)θ祟悂碚f應該易于使用和理解,而不僅僅是機器??山M合性意味著CLI程序應該通過遵循共同的慣例和標準與其他程序很好地協(xié)作。
- 參數(shù)和標志
這一部分講述了如何在你的CLI程序中使用位置參數(shù)(positional arguments )和標志。它還解釋了如何處理默認值、必傳參數(shù)、布爾標志、多值等。例如,你應該對命令的主要對象或動作使用位置參數(shù),對修改或可選參數(shù)使用標志。你還應該使用長短兩種形式的標志(如-v或-verbose),并遵循常見的命名模式(如--help或--version)。
- 配置
這部分介紹了如何使用配置文件和環(huán)境變量來為你的CLI程序存儲持久的設(shè)置。它還解釋了如何處理配置選項的優(yōu)先級、驗證、文檔等。例如,你應該使用配置文件來處理用戶很少改變的設(shè)置,或者是針對某個項目或環(huán)境的設(shè)置。對于特定于環(huán)境或會話的設(shè)置(如憑證或路徑),你也應該使用環(huán)境變量。
- 輸出
這部分介紹了如何格式化和展示你的CLI程序的輸出。它還解釋了如何處理輸出verbose級別、進度指示器、顏色、表格等。例如,你應該使用標準輸出(stdout)進行正常的輸出,這樣輸出的信息可以通過管道輸送到其他程序或文件。你還應該使用標準錯誤(stderr)來處理不屬于正常輸出流的錯誤或警告。
- 錯誤
這部分介紹了如何在你的CLI程序中優(yōu)雅地處理錯誤。它還解釋了如何使用退出狀態(tài)碼、錯誤信息、堆棧跟蹤等。例如,你應該使用表明錯誤類型的退出代碼(如0代表成功,1代表一般錯誤)。你還應該使用簡潔明了的錯誤信息,解釋出錯的原因以及如何解決。
- 子命令
這部分介紹了當CLI程序有多種操作或操作模式時,如何在CLI程序中使用子命令。它還解釋了如何分層構(gòu)建子命令,組織幫助文本,以及處理常見的子命令(如help或version)。例如,當你的程序有不同的功能,需要不同的參數(shù)或標志時(如git clone或git commit),你應該使用子命令。你還應該提供一個默認的子命令,或者在沒有給出子命令時提供一個可用的子命令列表。
業(yè)界有許多精心設(shè)計的CLI工具的例子,它們都遵循cli準則,大家可以通過使用來深刻體會一下這些準則。下面是一些這樣的CLI工具的例子:
- httpie:一個命令行HTTP客戶端,具有直觀的UI,支持JSON,語法高亮,類似wget的下載,插件等功能。例如,Httpie使用清晰簡潔的語法進行HTTP請求,支持多種輸出格式和顏色,優(yōu)雅地處理錯誤并提供有用的文檔。
- git:一個分布式的版本控制系統(tǒng),讓你管理你的源代碼并與其他開發(fā)者合作。例如,Git使用子命令進行不同的操作(如git clone或git commit),遵循通用的標志(如-v或-verbose),提供有用的反饋和建議(如git status或git help),并支持配置文件和環(huán)境變量。
- npm:一個JavaScript的包管理器,讓你為你的項目安裝和管理依賴性。例如,NPM使用一個簡單的命令結(jié)構(gòu)(npm [args]),提供一個簡潔的初始幫助信息,有更詳細的選項(npm help npm),支持標簽完成和合理的默認值,并允許你通過配置文件(.npmrc)自定義設(shè)置。
八. 小結(jié)
在這篇文章中,我們系統(tǒng)說明了如何編寫出遵循命令行接口指南的Go CLI程序。
你學習了如何設(shè)置Go環(huán)境、設(shè)計命令行接口、處理錯誤和信號、編寫文檔、使用各種工具和軟件包測試和發(fā)布程序。你還看到了一些代碼和配置文件的例子。通過遵循這些準則和最佳實踐,你可以創(chuàng)建一個用戶友好、健壯和可靠的CLI程序。
最后我們回顧了clig.dev的指南要點,希望你能更深刻理解這些要點的含義。
我希望你喜歡這篇文章并認為它很有用。如果你有任何問題或反饋,請隨時聯(lián)系我。編碼愉快!
注:本文系與New Bing Chat聯(lián)合完成,旨在驗證如何基于AIGC能力構(gòu)思和編寫長篇文章。文章內(nèi)容的正確性經(jīng)過筆者全面審校,可放心閱讀。
本文轉(zhuǎn)載自微信公眾號「 白明的贊賞賬戶」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請聯(lián)系 白明的贊賞賬戶公眾號。