偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

Go開發(fā)命令行程序指南

開發(fā) 前端
你學習了如何設(shè)置Go環(huán)境、設(shè)計命令行接口、處理錯誤和信號、編寫文檔、使用各種工具和軟件包測試和發(fā)布程序。你還看到了一些代碼和配置文件的例子。通過遵循這些準則和最佳實踐,你可以創(chuàng)建一個用戶友好、健壯和可靠的CLI程序。

近期在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 version這個命令應該會打印出你所安裝的Go的版本。比如說:

go version go1.20 darwin/amd64

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)建一個新的項目目錄,我們可以在終端運行以下命令:

$mkdir -p $HOME/go/src/github.com/your-username/your-li-program
$cd $HOME/go/src/github.com/your-username/your-cli-program

注意,我們的項目目錄名使用的是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,我們可以在我們的項目目錄下運行以下命令。

$go mod init github.com/your-username/your-cli-program

這將創(chuàng)建一個名為go.mod的文件,內(nèi)容如下。

module github.com/your-username/your-cli-program

go 1.20

第一行指定了我們的module名稱,這與我們的項目目錄名稱相匹配。第二行指定了構(gòu)建我們的module所需的Go的最低版本。

為了給我們的模塊添加依賴項,我們可以使用go get命令,加上我們想使用的軟件包的導入路徑和可選的版本標簽。例如,如果我們想使用cobra[13]作為我們的CLI框架,我們可以運行如下命令:

$go get github.com/spf13/cobra@v1.3.0

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,我們可以運行下面命令:

$go build github.com/your-username/your-cli-program/cmd/your-cli-program

$go install github.com/your-username/your-cli-program/cmd/your-cli-program@latest

三. 設(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程序的行為或輸出。例如:

$curl -s -o output.txt https://example.com

在這個例子中:

  • “-s”是一個讓curl沉默的標志,即不輸出執(zhí)行日志到控制臺;
  • “-o”是另一個標志,用于指定輸出文件的名稱
  • “output.txt”則是一個參數(shù),是為“-o”標志提供的值。

**參數(shù)(argument)**是不以中劃線(-)開頭的輸入?yún)?shù),為你的CLI程序提供額外的信息或數(shù)據(jù)。例如:

$tar xvf archive.tar.gz

我們看在這個例子中:

  • x是一個指定提取模式的參數(shù)
  • v是一個參數(shù),指定的是輸出內(nèi)容的詳細(verbose)程度
  • f是另一個參數(shù),用于指定采用的是文件模式,即將壓縮結(jié)果輸出到一個文件或從一個壓縮文件讀取數(shù)據(jù)
  • archive.tar.gz是一個參數(shù),提供文件名。

**子命令(subcommand)**是輸入?yún)?shù),作為主命令下的輔助命令。它們通常有自己的一組標志和參數(shù)。比如下面例子:

$git commit -m "Initial commit"

我們看在這個例子中:

  • git是主命令(primary command)
  • commit是一個子命令,用于從staged的修改中創(chuàng)建一個新的提交(commit)
  • “-m”是commit子命令的一個標志,用于指定提交信息
  • "Initial commit"是commit子命令的一個參數(shù),為"-m"標志提供值。

**選項(option)**是輸入?yún)?shù),它可以使用等號(=)將標志和參數(shù)合并為一個參數(shù)。例如:

$docker run --name=my-container ubuntu:latest

我們看在這個例子中“--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包,你需要在你的代碼中導入它:

import "log"

然后你可以使用log.Println、log.Printf、log.Fatal和log.Fatalf等函數(shù)來輸出不同嚴重程度的信息。比如說:

log.Println("Starting the program...") // 打印帶有時間戳的消息
log.Printf("Processing file %s...\n", filename) // 打印一個帶時間戳的格式化信息
log.Fatal("Cannot open file: ", err) // 打印一個帶有時間戳的錯誤信息并退出程序
log.Fatalf("Invalid input: %v\n", input) // 打印一個帶時間戳的格式化錯誤信息,并退出程序。

為了使用fmt包,你需要先在你的代碼中導入它:

import "fmt"

然后你可以使用fmt.Println、fmt.Printf、fmt.Sprintln、fmt.Sprintf等函數(shù)以各種方式格式化字符串和值。比如說:

fmt.Println("Hello world!") // 打印一條信息,后面加一個換行符
fmt.Printf("The answer is %d\n", 42) // 打印一條格式化的信息,后面是換行。
s := fmt.Sprintln("Hello world!") // 返回一個帶有信息和換行符的字符串。
t := fmt.Sprintf("The answer is %d\n", 42) // 返回一個帶有格式化信息和換行的字符串。

要使用錯誤包,你同樣需要在你的代碼中導入它:

import "errors"

然后你可以使用 errors.New、errors.Unwrap、errors.Is等函數(shù)來創(chuàng)建和操作錯誤值。比如說:

err := errors.New("Something went wrong") // 創(chuàng)建一個帶有信息的錯誤值
cause := errors.Unwrap(err) // 返回錯誤值的基本原因(如果沒有則為nil)。
match := errors.Is(err, io.EOF) // 如果一個錯誤值與另一個錯誤值匹配,則返回真(否則返回假)。

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包:

import "os"

然后你可以使用os.Exit函數(shù),它的整數(shù)參數(shù)代表退出狀態(tài)代碼。比如說

os.Exit(0) // 以成功的代碼退出程序
os.Exit(1) // 以失敗代碼退出程序

要使用defer語句,你需要把它寫在你想后續(xù)執(zhí)行的函數(shù)調(diào)用之前。比如說

file, err := os.Open(filename) // 打開一個文件供讀取。
if err != nil {
log.Fatal(err) // 發(fā)生錯誤時退出程序
}
defer file.Close() // 在函數(shù)結(jié)束時關(guān)閉文件。

// 對文件做一些處理...

3. 使用os.signal和context包來實現(xiàn)中斷和取消操作

Go有兩個包可以幫助你實現(xiàn)中斷和取消長期運行的或阻塞的操作,它們是os.signal和context包。os.signal提供了一種從操作系統(tǒng)或其他進程接收信號的方法。context包提供了一種跨越API邊界傳遞取消信號和deadline的方法。

要使用os.signal,你需要先在你的代碼中導入它。

import (
"os"
"os/signal"
)

然后你可以使用signal.Notify函數(shù)針對感興趣的信號(如下面的os.Interrupt信號)注冊一個接收channel(sig)。比如說:

sig := make(chan os.Signal, 1) // 創(chuàng)建一個帶緩沖的信號channel。
signal.Notify(sig, os.Interrupt) // 注冊sig以接收中斷信號(例如Ctrl-C)。

// 做一些事情...

select {
case <-sig: // 等待來自sig channel的信號
fmt.Println("被用戶中斷了")
os.Exit(1) // 以失敗代碼退出程序。
default: //如果沒有收到信號就執(zhí)行
fmt.Println("成功完成")
os.Exit(0) // 以成功代碼退出程序。
}

要使用上下文包,你需要在你的代碼中導入它:

import "context"

然后你可以使用它的函數(shù),如context.Background、context.WithCancel、context.WithTimeout等來創(chuàng)建和管理Context。Context是一個攜帶取消信號和deadline的對象,可以跨越API邊界。比如說:

ctx := context.Background() // 創(chuàng)建一個空的背景上下文(從不取消)。
ctx, cancel := context.WithCancel(ctx) // 創(chuàng)建一個新的上下文,可以通過調(diào)用cancel函數(shù)來取消。
defer cancel() // 在函數(shù)結(jié)束前執(zhí)行ctx的取消動作

// 將ctx傳遞給一些接受它作為參數(shù)的函數(shù)......

select {
case <-ctx.Done(): // 等待來自ctx的取消信號
fmt.Println("Canceled by parent")
return ctx.Err() // 從ctx返回一個錯誤值
default: // 如果沒有收到取消信號就執(zhí)行
fmt.Println("成功完成")
return nil // 不返回錯誤值
}

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就是一個很好的例子:

$git 
usage: git [--version] [--help] [-C <path>] [-c <name>=<value>]
[--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]
[-p | --paginate | -P | --no-pager] [--no-replace-objects] [--bare]
[--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]
<command> [<args>]

結(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)的測試分組。例如:
func TestSum(t *testing.T) {
t.Run("positive numbers", func(t *testing.T) {
// test sum with positive numbers
})
t.Run("negative numbers", func(t *testing.T) {
// test sum with negative numbers
})
}
  • 使用表格驅(qū)動(table-driven)的測試來運行多個測試用例,比如下面的例子:
func TestSum(t *testing.T) {
tests := []struct{
name string
a int
b int
want int
}{
{"positive numbers", 1, 2, 3},
{"negative numbers", -1, -2, -3},
{"zero", 0, 0 ,0},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := Sum(tt.a , tt.b)
if got != tt.want {
t.Errorf("Sum(%d , %d) = %d; want %d", tt.a , tt.b , got , tt.want)
}
})
}
}
  • 使用外部包,如testify/assert或mock來簡化你的斷言或?qū)ν獠康囊蕾囆?。比如說:
import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)

type Calculator interface {
Sum(a int , b int) int
}

type MockCalculator struct {
mock.Mock
}

func (m *MockCalculator) Sum(a int , b int) int {
args := m.Called(a , b)
return args.Int(0)
}

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é)果的輸出。比如說
func BenchmarkSum(b *testing.B) {
for i := 0; i < b.N; i++ {
Sum(1 , 2)
}
}
  • 使用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é)果。比如說。
# Generate CPU profile
go test -cpuprofile cpu.out ./...

# Analyze CPU profile using pprof
go tool pprof cpu.out

# Generate two sets of benchmark results
go test -bench . ./... > old.txt
go test -bench . ./... > new.txt

# Compare benchmark results using benchstat
benchstat old.txt new.txt

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)建和打包你的程序。你可以定制各種選項,如二進制名稱、版本、主文件、輸出格式、目標平臺、壓縮、校驗和、簽名等。例如。
# .goreleaser.yml
project_name: mycli
builds:
- main: ./cmd/mycli/main.go
binary: mycli
goos:
- windows
- darwin
- linux
goarch:
- amd64
- arm64
archives:
- format: zip
name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
files:
- LICENSE.txt
- README.md
checksum:
name_template: "{{ .ProjectName }}_checksums.txt"
algorithm: sha256

運行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)系 白明的贊賞賬戶公眾號。

責任編輯:武曉燕 來源: TonyBai
相關(guān)推薦

2015-07-15 10:32:44

Node.js命令行程序

2019-04-16 06:50:34

2016-03-28 10:00:09

Swift命令程序

2010-07-15 10:58:23

Perl命令行程序

2022-09-27 13:07:41

clickPython命令行

2022-09-23 09:50:45

Python

2023-10-30 01:00:42

Go語言Cobra庫

2020-11-01 20:00:26

命令行ShellLinux

2020-11-05 09:30:59

命令行Linux

2020-12-10 16:16:08

工具代碼開發(fā)

2020-12-11 06:44:16

命令行工具開發(fā)

2010-09-01 14:23:54

Linux命令行開發(fā)

2020-11-22 06:20:53

命令行Linux

2021-01-27 11:53:08

工具Go 開發(fā)

2009-07-14 14:03:56

Swing程序

2018-05-04 09:15:35

PythonPlumbum命令行

2023-12-01 07:06:14

Go命令行性能

2020-02-13 10:57:59

Python數(shù)據(jù)設(shè)計

2009-05-30 09:26:38

AndroidGoogle移動OS

2010-07-26 09:14:22

Perl命令行
點贊
收藏

51CTO技術(shù)棧公眾號