成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Go開發命令行程序指南

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

近期在Twitter上看到一個名為“Command Line Interface Guidelines”的站點[1],這個站點匯聚了幫助大家編寫出更好命令行程序的哲學與指南。這份指南基于傳統的Unix編程原則[2],又結合現代的情況進行了“與時俱進”的更新。之前我還真未就如何編寫命令行交互程序做系統的梳理,在這篇文章中,我們就來結合clig這份指南[3],(可能不會全面覆蓋)整理出一份使用Go語言編寫CLI程序的指南,供大家參考。

一. 命令行程序簡介

命令行接口(Command Line Interface, 簡稱CLI)程序是一種允許用戶使用文本命令和參數與計算機系統互動的軟件。開發人員編寫CLI程序通常用在自動化腳本、數據處理、系統管理和其他需要低級控制和靈活性的任務上。命令行程序也是Linux/Unix管理員以及后端開發人員的最愛。

2022年Q2 Go官方用戶調查結果[4]顯示(如下圖):在使用Go開發的程序類別上,CLI類程序排行第二,得票率60%。

圖片

之所以這樣,得益于Go語言為CLI開發提供的諸多便利,比如:

  • Go語法簡單而富有表現力;
  • Go擁有一個強大的標準庫,并內置的并發支持;
  • Go擁有幾乎最好的跨平臺兼容性和快速的編譯速度;
  • Go還有一個豐富的第三方軟件包和工具的生態系統。

這些都讓開發者使用Go創建強大和用戶友好的CLI程序變得容易。

容易歸容易,但要用Go編寫出優秀的CLI程序,我們還需要遵循一些原則,獲得一些關于Go CLI程序開發的最佳實踐和慣例。這些原則和慣例涉及交互界面設計、錯誤處理、文檔、測試和發布等主題。此外,借助于一些流行的Go CLI程序開發庫和框架,比如:cobra[5]、Kingpin[6]和Goreleaser[7]等,我們可以又好又快地完成CLI程序的開發。在本文結束時,你將學會如何創建一個易于使用、可靠和可維護的Go CLI程序,你還將獲得一些關于CLI開發的最佳實踐和慣例的見解。

二. 建立Go開發環境

如果你讀過《十分鐘入門Go語言》[8]或訂閱學習過我的極客時間《Go語言第一課》專欄[9],你大可忽略這一節的內容。

在我們開始編寫Go CLI程序之前,我們需要確保我們的系統中已經安裝和配置了必要的Go工具和依賴。在本節中,我們將向你展示如何安裝Go和設置你的工作空間,如何使用go mod進行依賴管理[10],以及如何使用go build和go install來編譯和安裝你的程序。

1. 安裝Go

要在你的系統上安裝Go,你可以遵循你所用操作系統的官方安裝說明。你也可以使用軟件包管理器,如homebrew[11](用于macOS)、chocolatey(用于Windows)或snap/apt(用于Linux)來更容易地安裝Go。

一旦你安裝了Go,你可以通過在終端運行以下命令來驗證它是否可以正常工作。

$go version

如果安裝成功,go version這個命令應該會打印出你所安裝的Go的版本。比如說:

go version go1.20 darwin/amd64

2. 設置你的工作區(workspace)

Go以前有一個慣例,即在工作區目錄中(組織你的代碼和依賴關系。默認工作空間目錄位于HOME/go,但你可以通過設置GOPATH環境變量來改變它的路徑。工作區目錄包含三個子目錄:src、pkg和bin。src目錄包含了你的源代碼文件和目錄。pkg目錄包含被你的代碼導入的已編譯好的包。bin目錄包含由你的代碼生成的可執行二進制文件。

Go 1.11引入Go module[12]后,這種在下組織代碼和尋找依賴關系的要求被徹底取消。在這篇文章中,我依舊按照我的習慣在HOME/go/src下放置我的代碼示例。

為了給我們的CLI程序創建一個新的項目目錄,我們可以在終端運行以下命令:

$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導入和管理依賴關系更加容易。go module成為構建標準后,這種對項目目錄名的要求已經取消,但很多Gopher依舊保留了這種作法。

3. 使用go mod進行依賴管理

1.11版本后Go推薦開發者使用module來管理包的依賴關系。一個module是共享一個共同版本號和導入路徑前綴的相關包的集合。一個module是由一個叫做go.mod的文件定義的,它指定了模塊的名稱、版本和依賴關系。

為了給我們的CLI程序創建一個新的module,我們可以在我們的項目目錄下運行以下命令。

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

這將創建一個名為go.mod的文件,內容如下。

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

go 1.20

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

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

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

go get將從github下載cobra,并在我們的go.mod文件中把它作為一個依賴項添加進去。它還將創建或更新一個名為go.sum的文件,記錄所有下載的module的校驗和,以供后續驗證使用。

我們還可以使用其他命令,如go list、go mod tidy、go mod graph等,以更方便地檢查和管理我們的依賴關系。

4. 使用go build和go install來編譯和安裝你的程序

Go有兩個命令允許你編譯和安裝你的程序:go build和go install。這兩個命令都以一個或多個包名或導入路徑作為參數,并從中產生可執行的二進制文件。

它們之間的主要區別在于它們將生成的二進制文件存儲在哪里。

  • go build將它們存儲在當前工作目錄中。
  • go install將它們存儲在或GOBIN(如果設置了)。

例如,如果我們想把CLI程序的main包(應該位于github.com/your-username/your-cli-program/cmd/your-cli-program)編譯成一個可執行的二進制文件,稱為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

三. 設計用戶接口(interface)

要編寫出一個好的CLI程序,最重要的環節之一是**設計一個用戶友好的接口[14]。好的命令行用戶接口應該是一致的、直觀的和富有表現力的**。在本節中,我將說明如何為命令行程序命名和選擇命令結構(command structure),如何使用標志(flag)、參數(argument)、子命令(subcommand)和選項(option)作為輸入參數,如何使用cobra或Kingpin等來解析和驗證用戶輸入,以及如何遵循POSIX慣例和GNU擴展的CLI語法。

1. 命令行程序命名和命令結構選擇

你的CLI程序的名字應該是**簡短、易記、描述性的和易輸入的[15]**。它應該避免與目標平臺中現有的命令或關鍵字發生沖突。例如,如果你正在編寫一個在不同格式之間轉換圖像的程序,你可以把它命名為imgconv、imago、picto等,但不能叫image、convert或format。

你的CLI程序的命令結構應該反映你想提供給用戶的主要功能特性。你可以選擇使用下面命令結構模式中的一種:

  • 一個帶有多個標志(flag)和參數(argument)的單一命令(例如:curl、tar、grep等)
  • 帶有多個子命令(subcommand)的單一命令(例如:git、docker、kubectl等)
  • 具有共同前綴的多個命令(例如:aws s3、gcloud compute、az vm等)

命令結構模式的選擇取決于你的程序的復雜性和使用范圍,一般來說:

  • 如果你的程序只有一個主要功能或操作模式(operation mode),你可以使用帶有多個標志和參數的單一命令。
  • 如果你的程序有多個相關但又不同的功能或操作模式,你可以使用一個帶有多個子命令的單一命令。
  • 如果你的程序有多個不相關或獨立的功能或操作模式,你可以使用具有共同前綴的多個命令。

例如,如果你正在編寫一個對文件進行各種操作的程序(如復制、移動、刪除),你可以任選下面命令結構模式中的一種:

  • 帶有多個標志和參數的單一命令(例如,fileop -c src dst -m src dst -d src)
  • 帶有多個子命令的單個命令(例如,fileop copy src dst, fileop move src dst, fileop delete src)

2. 使用標志、參數、子命令和選項

**標志(flag)**是以一個或多個(通常是2個)中劃線(-)開頭的輸入參數,它可以修改CLI程序的行為或輸出。例如:

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

在這個例子中:

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

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

$tar xvf archive.tar.gz

我們看在這個例子中:

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

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

$git commit -m "Initial commit"

我們看在這個例子中:

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

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

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

我們看在這個例子中“--name=my-container”是一個選項,它將容器的名稱設為my-container。該選項前面的部分“--name”是一個標志,后面的部分“my-container”是參數。

3. 使用cobra包等來解析和驗證用戶輸入的信息

如果手工來解析和驗證用戶輸入的信息,既繁瑣又容易出錯。幸運的是,有許多庫和框架可以幫助你在Go中解析和驗證用戶輸入。其中最流行的是cobra[16]。

cobra是一個Go包,它提供了簡單的接口來創建強大的CLI程序。它支持子命令、標志、參數、選項、環境變量和配置文件。它還能很好地與其他庫集成,比如:viper[17](用于配置管理)、pflag[18](用于POSIX/GNU風格的標志)和Docopt[19](用于生成文檔)。

另一個不那么流行但卻提供了一種聲明式的方法來創建優雅的CLI程序的包是Kingpin[20],它支持標志、參數、選項、環境變量和配置文件。它還具有自動幫助生成、命令完成、錯誤處理和類型轉換等功能。

cobra和Kingpin在其官方網站上都有大量的文檔和例子,你可以根據你的偏好和需要選擇任選其一。

4. 遵循POSIX慣例和GNU擴展的CLI語法

POSIX(Portable Operating System Interface)[21]是一套標準,定義了軟件應該如何與操作系統進行交互。其中一個標準定義了CLI程序的語法和語義。GNU(GNU's Not Unix)是一個旨在創建一個與UNIX兼容的自由軟件操作系統的項目。GNU下的一個子項目是GNU Coreutils[22],它提供了許多常見的CLI程序,如ls、cp、mv等。

POSIX和GNU都為CLI語法建立了一些約定和擴展,許多CLI程序都采用了這些約定與擴展。下面列舉了這些約定和擴展中的一些主要內容:

  • 單字母標志(single-letter flag)以一個中劃線(-)開始,可以組合在一起(例如:-a -b -c 或 -abc )
  • 長標志(long flag)以兩個中劃線(--)開頭,但不能組合在一起(例如:--all、--backup、--color )
  • 選項使用等號(=)來分隔標志名和參數值(例如:--name=my-container )
  • 參數跟在標志或選項之后,沒有任何分隔符(例如:curl -o output.txt https://example.com )。
  • 子命令跟在主命令之后,沒有任何分隔符(例如:git commit -m "Initial commit" )
  • 一個雙中劃線(--)表示標志或選項的結束和參數的開始(例如:rm -- -f 表示要刪除“-f”這個文件,由于雙破折線的存在,這里的“-f”不再是標志)

遵循這些約定和擴展可以使你的CLI程序更加一致、直觀,并與其他CLI程序兼容。然而,它們并不是強制性的,如果你有充分的理由,你也大可不必完全遵守它們。例如,一些CLI程序使用斜線(/)而不是中劃線(-)表示標志(例如, robocopy /S /E src dst )。

四. 處理錯誤和信號

編寫好的CLI程序的一個重要環節就是**優雅地處理錯誤和信號[23]**。

錯誤是指你的程序由于某些內部或外部因素而無法執行其預定功能的情況。信號是由操作系統或其他進程向你的程序發送的事件,以通知它一些變化或請求。在這一節中,我將說明一下如何使用log、fmt和errors包進行日志輸出和錯誤處理,如何使用os.Exit和defer語句進行優雅的終止,如何使用os.Signal和context包進行中斷和取消操作,以及如何遵循CLI程序的退出狀態代碼慣例。

1. 使用log、fmt和errors包進行日志記錄和錯誤處理

Go標準庫中有三個包log、fmt和errors可以幫助你進行日志和錯誤處理。log包提供了一個簡單的接口,可以將格式化的信息寫到標準輸出或文件中。fmt包則提供了各種格式化字符串和值的函數。errors包提供了創建和操作錯誤值的函數。

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

import "log"

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

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等函數以各種方式格式化字符串和值。比如說:

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等函數來創建和操作錯誤值。比如說:

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

2. 使用os.Exit和defer語句實現CLI程序的優雅終止

Go有兩個功能可以幫助你優雅地終止CLI程序:os.Exit和defer。os.Exit函數立即退出程序,并給出退出狀態代碼。defer語句則會在當前函數退出前執行一個函數調用,它常用來執行清理收尾動作,如關閉文件或釋放資源。

要使用os.Exit函數,你需要在你的代碼中導入os包:

import "os"

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

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

要使用defer語句,你需要把它寫在你想后續執行的函數調用之前。比如說

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

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

3. 使用os.signal和context包來實現中斷和取消操作

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

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

import (
"os"
"os/signal"
)

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

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

// 做一些事情...

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

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

import "context"

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

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

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

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

4. CLI程序的退出狀態代碼慣例

退出狀態代碼是一個整數,表示CLI程序是否成功執行完成。CLI程序通過調用os.Exit或從main返回的方式返回退出狀態值。其他CLI程序或腳本可以可以檢查這些退出狀態碼,并根據狀態碼值的不同執行不同的處理操作。

業界有一些關于退出狀態代碼的約定和擴展,這些約定被許多CLI程序廣泛采用。其中一些主要的約定和擴展如下:。

  • 退出狀態代碼為0表示程序執行成功(例如:os.Exit(0) )
  • 非零的退出狀態代碼表示失敗(例如:os.Exit(1) )。
  • 不同的非零退出狀態代碼可能表示不同的失敗類型或原因(例如:os.Exit(2)表示使用錯誤,os.Exit(3)表示權限錯誤等等)。
  • 大于125的退出狀態代碼可能表示被外部信號終止(例如,os.Exit(130)為被信號中斷)。

遵循這些約定和擴展可以使你的CLI程序表現的更加一致、可靠并與其他CLI程序兼容。然而,它們不是強制性的,你可以使用任何對你的程序有意義的退出狀態代碼。例如,一些CLI程序使用高于200的退出狀態代碼來表示自定義或特定應用的錯誤(例如,os.Exit(255)表示未知錯誤)。

五. 編寫文檔

編寫優秀CLI程序的另一個重要環節是編寫清晰簡潔的文檔,解釋你的程序做什么以及如何使用它。文檔可以采取各種形式,如README文件、usage信息、help flag等。在本節中,我們將告訴你如何為你的程序寫一個README文件,如何為你的程序寫一個有用的usage和help flag等。

1. 為你的CLI程序寫一個清晰簡潔的README文件

README文件是一個文本文件,它提供了關于你的程序的基本信息,如它的名稱、描述、用法、安裝、依賴性、許可證和聯系細節等。它通常是用戶或開發者在源代碼庫或軟件包管理器上首次使用你的程序時會看到的內容。

如果你要為Go CLI程序編寫一個優秀的README文件,你應該遵循一些最佳實踐,比如:

  • 使用一個描述性的、醒目的標題,反映你的程序的目的和功能。
  • 提供一個簡短的介紹,解釋你的程序是做什么的,為什么它是有用的或獨特的。
  • 包括一個usage部分,說明如何用不同的標志、參數、子命令和選項來調用你的程序。你可以使用代碼塊或屏幕截圖來說明這些例子。
  • 包括一個安裝(install)部分,解釋如何在不同的平臺上下載和安裝你的程序。你可以使用go install、go get、goreleaser[24]或其他工具來簡化這一過程。
  • 指定你的程序的發行許可,并提供一個許可全文的鏈接。你可以使用SPDX標識符[25]來表示許可證類型。
  • 為想要報告問題、請求新功能、貢獻代碼或提問的用戶或開發者提供聯系信息。你可以使用github issue、pr、discussion、電子郵件或其他渠道來達到這個目的。

以下是一個Go CLI程序的README文件的示例供參考:

2. 為你的CLI程序編寫有用的usage和help標志

usage信息是一段簡短的文字,總結了如何使用你的程序及其可用的標志、參數、子命令和選項。它通常在你的程序在沒有參數或輸入無效的情況下運行時顯示。

help標志是一個特殊的標志(通常是-h或--help),它可以觸發顯示使用信息和一些關于你的程序的額外信息。

為了給你的Go CLI程序寫有用的usage信息和help標志,你應該遵循一些準則,比如說:

  • 使用一致而簡潔的語法來描述標志、參數、子命令和選項。你可以用方括號“[ ]”表示可選元素,使用角括號“< >”表示必需元素,使用省略號“...”表示重復元素,使用管道“|”表示備選,使用中劃線“-”表示標志(flag),使用等號“=”表示標志的值等等。
  • 對標志、參數、子命令和選項應使用描述性的名稱,以反映其含義和功能。避免使用單字母名稱,除非它們非常常見或非常直觀(如-v按慣例表示verbose模式)。
  • 為每個標志、參數、子命令和選項提供簡短而清晰的描述,解釋它們的作用以及它們如何影響你的程序的行為。你可以用圓括號“( )”來表達額外的細節或例子。
  • 使用標題或縮進將相關的標志、參數、子命令和選項組合在一起。你也可以用空行或水平線(---)來分隔usage的不同部分。
  • 在每組中按名稱的字母順序排列標志。在每組中按重要性或邏輯順序排列參數。在每組中按使用頻率排列子命令。

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>]

結合上面的準則,大家可以細心體會一下。

六. 測試和發布你的CLI程序

編寫優秀CLI程序的最后一個環節是測試和發布你的程序。測試確保你的程序可以按預期工作,并符合質量標準。發布可以使你的程序可供用戶使用和訪問。

在本節中,我將說明如何使用testing、testify/assert、mock包對你的代碼進行單元測試,如何使用go test、coverage、benchmark工具來運行測試和測量程序性能以及如何使用goreleaser包來構建跨平臺的二進制文件。

1. 使用testing、testify的assert及mock包對你的代碼進行單元測試

單元測試是一種驗證單個代碼單元(如函數、方法或類型)的正確性和功能的技術。單元測試可以幫助你盡早發現錯誤,提高代碼質量和可維護性,并促進重構和調試。

要為你的Go CLI程序編寫單元測試,你應該遵循一些最佳實踐:

  • 使用內置的測試包來創建測試函數,以Test開頭,后面是被測試的函數或方法的名稱。例如:func TestSum(t *testing.T) { ... };
  • 使用*testing.T類型的t參數,使用t.Error、t.Errorf、t.Fatal或t.Fatalf這樣的方法報告測試失敗。你也可以使用t.Log、t.Logf、t.Skip或t.Skipf這樣的方法來提供額外的信息或有條件地跳過測試。
  • 使用Go子測試(sub test)[26],通過t.Run方法將相關的測試分組。例如:
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
})
}
  • 使用表格驅動(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來簡化你的斷言或對外部的依賴性。比如說:
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,并優化你的代碼以提高速度和效率。

要使用go test、coverage、benchmark工具來運行測試和測量你的Go CLI程序的性能,你應該遵循一些步驟,比如說。

  • 將以_test.go結尾的測試文件寫在與被測試代碼相同的包中。例如:sum_test.go用于測試sum.go。
  • 使用go測試命令來運行一個包中的所有測試或某個特定的測試文件。你也可以使用一些標志,如-v,用于顯示verbose的輸出,-run用于按名字過濾測試用例,-cover用于顯示代碼覆蓋率,等等。例如:go test -v -cover ./...
  • 使用go工具cover命令來生成代碼覆蓋率的HTML報告,并高亮顯示代碼行。你也可以使用-func這樣的標志來顯示函數的代碼覆蓋率,用-html還可以在瀏覽器中打開覆蓋率結果報告等等。例如:go tool cover -html=coverage.out
  • 編寫性能基準函數,以Benchmark開頭,后面是被測試的函數或方法的名稱。使用類型為*testing.B的參數b來控制迭代次數,并使用b.N、b.ReportAllocs等方法控制報告結果的輸出。比如說
func BenchmarkSum(b *testing.B) {
for i := 0; i < b.N; i++ {
Sum(1 , 2)
}
}
  • 使用go test -bench命令來運行一個包中的所有性能基準測試或某個特定的基準文件。你也可以使用-benchmem這樣的標志來顯示內存分配的統計數據,-cpuprofile或-memprofile來生成CPU或內存profile文件等等。例如:go test -bench . -benchmem ./...
  • 使用pprof或benchstat等工具來分析和比較CPU或內存profile文件或基準測試結果。比如說。
# 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包構建跨平臺的二進制文件

構建跨平臺二進制文件意味著將你的代碼編譯成可執行文件,可以在不同的操作系統和架構上運行,如Windows、Linux、Mac OS、ARM等。這可以幫助你向更多的人分發你的程序,使用戶更容易安裝和運行你的程序而不需要任何依賴或配置。

為了給你的Go CLI程序建立跨平臺的二進制文件,你可以使用外部軟件包,比如goreleaser等 ,它們可以自動完成程序的構建、打包和發布過程。下面是使用goreleaser包構建程序的一些步驟。

  • 使用go get或go install命令安裝goreleaser。例如: go install github.com/goreleaser/goreleaser@latest
  • 創建一個配置文件(通常是.goreleaser.yml),指定如何構建和打包你的程序。你可以定制各種選項,如二進制名稱、版本、主文件、輸出格式、目標平臺、壓縮、校驗和、簽名等。例如。
# .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

運行goreleaser命令,根據配置文件構建和打包你的程序。你也可以使用-snapshot用于測試,-release-notes用于從提交信息中生成發布說明,-rm-dist用于刪除之前的構建,等等。例如:goreleaser --snapshot --rm-dist。

檢查輸出文件夾(通常是dist)中生成的二進制文件和其他文件。你也可以使用goreleaser的發布功能將它們上傳到源代碼庫或軟件包管理器中。

七. clig.dev指南要點

通過上述的系統說明,你現在應該可以設計并使用Go實現出一個CLI程序了。不過本文并非覆蓋了clig.dev指南的所有要點,因此,在結束本文之前,我們再來回顧一下clig.dev指南中的要點,大家再體會一下。

前面說過,clig.dev上的cli指南是一個開源指南,可以幫助你寫出更好的命令行程序,它采用了傳統的UNIX原則,并針對現代的情況進行了更新。

遵循cli準則的一些好處是:

  • 你可以創建易于使用、理解和記憶的CLI程序。
  • 你可以設計出能與其他程序進行很好配合的CLI程序,并遵循共同的慣例。
  • 你可以避免讓用戶和開發者感到沮喪的常見陷阱和錯誤。
  • 你可以從其他CLI設計者和用戶的經驗和智慧中學習。

下面是該指南的一些要點:

  • 理念

這一部分解釋了好的CLI設計背后的核心原則,如人本設計、可組合性、可發現性、對話性等。例如,以人為本的設計意味著CLI程序對人類來說應該易于使用和理解,而不僅僅是機器。可組合性意味著CLI程序應該通過遵循共同的慣例和標準與其他程序很好地協作。

  • 參數和標志

這一部分講述了如何在你的CLI程序中使用位置參數(positional arguments )和標志。它還解釋了如何處理默認值、必傳參數、布爾標志、多值等。例如,你應該對命令的主要對象或動作使用位置參數,對修改或可選參數使用標志。你還應該使用長短兩種形式的標志(如-v或-verbose),并遵循常見的命名模式(如--help或--version)。

  • 配置

這部分介紹了如何使用配置文件和環境變量來為你的CLI程序存儲持久的設置。它還解釋了如何處理配置選項的優先級、驗證、文檔等。例如,你應該使用配置文件來處理用戶很少改變的設置,或者是針對某個項目或環境的設置。對于特定于環境或會話的設置(如憑證或路徑),你也應該使用環境變量。

  • 輸出

這部分介紹了如何格式化和展示你的CLI程序的輸出。它還解釋了如何處理輸出verbose級別、進度指示器、顏色、表格等。例如,你應該使用標準輸出(stdout)進行正常的輸出,這樣輸出的信息可以通過管道輸送到其他程序或文件。你還應該使用標準錯誤(stderr)來處理不屬于正常輸出流的錯誤或警告。

  • 錯誤

這部分介紹了如何在你的CLI程序中優雅地處理錯誤。它還解釋了如何使用退出狀態碼、錯誤信息、堆棧跟蹤等。例如,你應該使用表明錯誤類型的退出代碼(如0代表成功,1代表一般錯誤)。你還應該使用簡潔明了的錯誤信息,解釋出錯的原因以及如何解決。

  • 子命令

這部分介紹了當CLI程序有多種操作或操作模式時,如何在CLI程序中使用子命令。它還解釋了如何分層構建子命令,組織幫助文本,以及處理常見的子命令(如help或version)。例如,當你的程序有不同的功能,需要不同的參數或標志時(如git clone或git commit),你應該使用子命令。你還應該提供一個默認的子命令,或者在沒有給出子命令時提供一個可用的子命令列表。

業界有許多精心設計的CLI工具的例子,它們都遵循cli準則,大家可以通過使用來深刻體會一下這些準則。下面是一些這樣的CLI工具的例子:

  • httpie:一個命令行HTTP客戶端,具有直觀的UI,支持JSON,語法高亮,類似wget的下載,插件等功能。例如,Httpie使用清晰簡潔的語法進行HTTP請求,支持多種輸出格式和顏色,優雅地處理錯誤并提供有用的文檔。
  • git:一個分布式的版本控制系統,讓你管理你的源代碼并與其他開發者合作。例如,Git使用子命令進行不同的操作(如git clone或git commit),遵循通用的標志(如-v或-verbose),提供有用的反饋和建議(如git status或git help),并支持配置文件和環境變量。
  • npm:一個JavaScript的包管理器,讓你為你的項目安裝和管理依賴性。例如,NPM使用一個簡單的命令結構(npm [args]),提供一個簡潔的初始幫助信息,有更詳細的選項(npm help npm),支持標簽完成和合理的默認值,并允許你通過配置文件(.npmrc)自定義設置。

八. 小結

在這篇文章中,我們系統說明了如何編寫出遵循命令行接口指南的Go CLI程序。

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

最后我們回顧了clig.dev的指南要點,希望你能更深刻理解這些要點的含義。

我希望你喜歡這篇文章并認為它很有用。如果你有任何問題或反饋,請隨時聯系我。編碼愉快!

注:本文系與New Bing Chat聯合完成,旨在驗證如何基于AIGC能力構思和編寫長篇文章。文章內容的正確性經過筆者全面審校,可放心閱讀。

 本文轉載自微信公眾號「 白明的贊賞賬戶」,可以通過以下二維碼關注。轉載本文請聯系 白明的贊賞賬戶公眾號。

責任編輯:武曉燕 來源: TonyBai
相關推薦

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

工具代碼開發

2020-12-11 06:44:16

命令行工具開發

2010-09-01 14:23:54

Linux命令行開發

2020-11-22 06:20:53

命令行Linux

2021-01-27 11:53:08

工具Go 開發

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數據設計

2009-05-30 09:26:38

AndroidGoogle移動OS

2010-07-26 09:14:22

Perl命令行
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 久久精品高清视频 | 伊人精品一区二区三区 | 国产精品成人在线 | 亚洲一区二区三区四区五区中文 | 欧美成人猛片aaaaaaa | 91免费观看| 在线观看国产h | 日韩欧美手机在线 | 91精品国产91久久久久久 | 国产精品久久久久久影视 | 国产在线网站 | 国产精品视频免费观看 | 在线色网| 国产精品资源在线 | 日韩1区2区| 国产精品久久久久国产a级 欧美日本韩国一区二区 | 欧美在线一区二区三区四区 | 波多野结衣一区二区 | 精品1区2区| 日韩欧美中文字幕在线观看 | 国产日韩精品一区二区三区 | 91久久夜色 | 美女黄网| 99久久精品国产一区二区三区 | 嫩草黄色影院 | 免费在线观看一区二区 | 激情91| 天天插天天干 | 亚洲成人一级 | 久久国产精品视频 | 亚洲第一网站 | 国产 日韩 欧美 在线 | 久久久www成人免费无遮挡大片 | 草比网站| 久久99精品久久久久久琪琪 | 中文字幕亚洲欧美日韩在线不卡 | 农村妇女毛片精品久久久 | 国产精品久久国产精品 | 中文字幕一区二区三区乱码在线 | 久草青青草 | 久久国产一区二区三区 |