在go語言中,「go fmt」指令主要是用來幫開發者格式化所寫好的程式碼檔案。 「go fmt」指令會依照Go語言程式碼規格格式化指定程式碼包中的所有Go語言原始碼檔案的程式碼,所有Go語言原始碼檔案即包含指令原始碼檔案、函式庫原始碼檔案和測試原始碼檔案。 「go fmt」指令只會格式化直接儲存在指定程式碼包對應目錄下的Go語言原始碼檔案。
本教學操作環境:windows7系統、GO 1.18版本、Dell G3電腦。
go fmt指令介紹
對於程式語言來說,程式碼格式化是最容易引起爭議的問題,不同的開發者可能會有不同的編碼風格和習慣,但是如果所有開發者都能使用同一種格式來編寫程式碼,那麼開發者就可以將主要精力放在語言要解決的問題上,從而節省開發時間。
Go語言的開發團隊制定了統一的官方程式碼風格,並且推出了 gofmt 工具(gofmt 或 go fmt)來幫助開發者格式化他們的程式碼到統一的風格。
gofmt 是一個cli 程序,會優先讀取標準輸入,如果傳入了文件路徑的話,會格式化這個文件,如果傳入一個目錄,會格式化目錄中所有.go 文件,如果不傳參數,會格式化目前目錄下的所有.go 檔。
而Go語言中還有一個go fmt
指令,go fmt
指令是 gofmt 的簡單封裝。
go fmt指令主要是用來幫你格式化所寫好的程式碼檔案【很多第三方整合軟體都是使用了go fmt指令】
使用:
go fmt <文件名>.go
使用go fmt指令,更多時候是用gofmt,而且需要參數-w,否則格式化結果不會寫入文件。 gofmt -w src,可以格式化整個專案。
參數介紹
-l 顯示那些需要格式化的檔案
-w 把改寫後的內容直接寫入文件中,而不是作為結果印到標準輸出。
-r 加入形如「a[b:len(a)] -> a[b:]」的重寫規則,方便我們做批次替換
-s 簡化檔案中的程式碼
-d 顯示格式化前後的diff而不是寫入文件,預設是false
-e 列印所有的語法錯誤到標準輸出。如果不使用此標記,則只會列印不同行的前10個錯誤。
-cpuprofile 支援偵錯模式,寫入對應的cpufile到指定的檔案作用區域
go fmt與gofmt
go fmt指令會依照Go語言程式碼規格格式化指定程式碼包中的所有Go語言原始碼檔案的程式碼,所有Go語言原始碼檔案即包含指令源碼檔案、函式庫源碼文件和測試原始碼檔案。注意,當程式碼包還有子代碼包時,子代碼包中的Go語言源碼檔案是不包含在內的。也就是說,go fmt指令只會格式化直接儲存在指定程式碼包對應目錄下的Go語言原始碼檔。
與go doc指令和godoc指令的關係類似,go fmt指令是gofmt指令的簡單封裝。 go fmt指令本身可以接受兩個標記。標記-n可以讓命令程式僅列印出內部使用的gofmt命令及其標記和參數而不真正執行它。標記-x則會使命令程式既列印又執行這個命令。在go fmt指令程式內部,會在其呼叫的gofmt指令後面加上標記-l和-w,並以指定程式碼包中的所有Go語言原始碼檔案的路徑作為參數,就像這樣:
hc@ubt:~$ go fmt -n pkgtool gofmt -l -w golang/goc2p/src/pkgtool/envir.go golang/goc2p/src pkgtoolenvir_test.go golang/goc2p/src/pkgtool/fpath.go golang/goc2p/src/pkgtool ipath.go golang/goc2p/src/pkgtool/pnode.go golang/goc2p/src/pkgtool/util.go golang/goc2p/src/pkgtool/util_test.go
注意,作為gofmt指令參數的Go語言原始碼檔案的路徑是相對的,而不是絕對的。不過這只是為了讓參數看起來更短。所以,當我們直接執行gofmt指令的時候,使用原始碼檔案的絕對路徑作為參數也是沒有問題的。實際上,任何Go原始碼檔案或包含有Go語言原始碼檔案的目錄的相對路徑或絕對路徑都可以作為gofmt命令的參數。當使用包含有Go語言原始碼檔案的目錄的絕對路徑或相對路徑作為參數時,gofmt指令會把在這個目錄下的Go語言原始碼檔案當作目標原始碼檔案。
go fmt指令程式內部在執行gofmt指令時加入的標記是固定的。如果我們想要使用與之不同的標記集合就必須直接使用gofmt指令了。現在我們來看看gofmt指令可接受的所有標記。如下表。
表0-13 gofmt指令的標記說明
標記名稱 | 標記描述 |
---|---|
-cpuprofile | 把CPU概要寫入指定檔。文件的路徑應該作為此標記的值。 |
-d | 顯示格式化前後的不同(如果有的話),而不是直接格式化那些程式碼。 |
-e | 報告目標原始碼檔案中的所有錯誤。預設情況下,僅會顯示前10個錯誤。 |
-l | 只把那些不符合格式化規範的、需要被指令程式改寫的原始碼檔案的絕對路徑印到標準輸出。而不是把改寫後的全部內容都印到標準輸出。 |
-r | 新增形如「a[b:len(a)] -> a[b:]」的重寫規則。如果我們需要自訂某些額外的格式化規則,就需要使用它。規則字串應該作為此標記的值。 |
-s | 簡化檔案中的程式碼。 |
-w | 將改寫後的內容直接寫入檔案中,而不是作為結果印到標準輸出。 |
看過上表中的信息,我們就很容易理解go fmt指令的行為了。因為它在內部執行了gofmt指令,並加入了標記-l和-w。這會使命令程式列印需要改寫的檔案的絕對路徑到標準輸出,並且直接把格式化後的內容寫入到原始檔案中。在預設情況下,gofmt指令會把格式化後的內容直接印到標準輸出上。
實際上,指令程式會把目標原始碼檔案中的內容解析成抽象語法樹。當在解析過程中發現語法錯誤時,命令程式就會顯示錯誤提示訊息並退出。在預設情況下,目標原始碼檔案中的語法錯誤不會全部被顯示出來。我們可以加入標記-e以使命令程式列印出全部錯誤到標準輸出。
自訂改寫動作
在預設情況下,gofmt指令對Go語言原始碼檔案的改寫運算包含下列幾個方面:
以字典序排序依賴套件導入語句區塊中程式碼包導入路徑的先後順序。
標準化各個語言或語句區塊之間的縮排、空格和換行。比如,把所有的\r\n轉換成\n。
對程式碼語法的小修正。例如,消除用於判斷變數類型的switch語句區塊中多餘的圓括號。
如果想自訂額外的改寫操作,需要使用-r標記。 -r標記的值中必須包含“->”,例如a[b:len(a)] -> a[b:]。 「->」的左邊應該是需要被取代的表達式的範例,而右邊則應該是用來取代「->」左邊表達式的表達式的範例。
如果我們使用標記-r,那麼命令程式在解析原始碼檔案之前會將此標記值中的被替換表達式和替換表達式分別解析為抽象語法樹的表達式節點。如果解析不成功,也意味著無法進行後續的替換操作,命令程式會在列印錯誤提示訊息後退出。如果解析成功,那麼命令程式會在解析原始碼檔案成功之後進行表達式替換操作。命令程式會尋找該原始碼檔案的抽象語法樹中與被替換表達式相符的節點,並用替換表達式替換之。 gofmt指令已支援但不限於以下自訂替換操作:
程式實體名稱的替換。程式實體包括變數、常數、函數、結構體和介面。例如:-r=array1->array2和-r=FuncA->FuncB。
程式實體類型的替換,其中也包含函數的參數和結果的類型的替換。例如:-r=string->bool和-r=interface{}->int。
多餘圓括號的清除。例如:我們這樣設定標記-r=(x)->x會使目標碼中的a = (-x.s)被改寫為a = -x.s,也會使程式碼中的((b = -x.f() ))被改寫為b = -x.f(),也會使c = -(x).f改寫為c = -x.f,但不會去掉d = (&x).s和e = (-x).f ()中的圓括號。也就是說,命令程式會在不改變語意和不產生語法歧義的前提下清除程式碼中多餘的圓括號。
數值運算的替換。例如:我們這樣設定標記-r=x x->x*2會使代目標碼中的所有的x x被替換為x * 2。並且,如果需要被替換的表達式中包含註解的話,則在替換操作的過程中會去掉這些註解。例如,在同樣的標記設定的情況下x /* It's comment */ x仍然會被替換為x * 2。
基於參數清單的函數呼叫替換。例如:如果我們這樣設定標記-r='funcA(a)->FuncA(a, c)',則目標程式碼中呼叫函數funcA並以一個變數作為參數的語句都會被替換為呼叫函數FuncA並以變數a和變數c作為參數的語句。請注意,被替換表達式中作為參數的a只是表示函數funcA有一個參數,而並不關心這個參數的名稱是什麼。也就是說,在相同的標記設定的情況下,目標碼中的funcA(b)或funcA(x)都會被替換為FuncA(a, c)。再或者,如果我們這樣設定標記-r='funB(x...)->FunC(x)',則目標程式碼中的funB(x...)或funB(y...)或其它類似的呼叫函數都會被替換為FunC(x)。其中,當類型為陣列/切片的參數後面跟著三個英文半角句號「…」時,則表示需要把這個參數中的每一個元素都作為單獨的參數傳入到函數中。因此,這種替換方式可以用來在函數名稱和/或參數清單改變之後,批次的後續修正呼叫函數的程式碼。
程式碼簡化操作
當我們在執行gofmt指令時加入了標記-s,指令程式會在目標原始碼檔案中尋找可以簡化的程式碼並簡化它。簡化操作包括:
消除在陣列/切片初始化中的不必要的類型宣告。
消除在字典初始化中不必要的類型宣告。
消除在陣列/切片切片操作時不必要的索引指定。
消除迭代时的非必要临时变量赋值操作。
这些操作基本上都是出于尽量使用Go语言的语法糖已达到减少代码量的目的。我们在编写Go语言代码的时候应该直接使用这些语法糖而不应该依赖使用gofmt命令来简化。这里所说的Go语言的语法糖,我们在第3章中已经有所介绍。
我们在本小节中详细介绍了go fmt命令和gofmt命令。下面我们再汇总一下这两个命令可以为我们做的事情。如下表。
表0-14 go fmt命令和gofmt命令的功能
功能 | go fmt 命令 | gofmt 命令 |
---|---|---|
格式化代码 | √ | √ |
列出不规范的源码文件 | √ | √ |
自动改写源码文件 | √ | √ |
显示对比信息 | × | √ |
提示全部错误 | × | √ |
简化代码 | × | √ |
自定义替换/重构辅助 | × | √ |
CPU概要记录 | × | √ |
最后,值得一提的是,当我们执行gofmt命令且没有加任何参数的时候,该命令将会进入到交互模式。在这种模式下,我们可以直接在命令行界面中输入源码,并以Ctrl-d结束。在Linux操作系统下,Ctrl-d代表EOF(End Of File,中文译为文件结束符)。需要注意的是,如果在一行的中间按下Ctrl-d,则表示输出“标准输入”的缓存区,所以这时必须连续按两次Ctrl-d。另外,在Windows操作系统下,Ctrl-z代表EOF,所以需要以Ctrl-z结束。在这之后,gofmt命令会像从源码文件中读取源码那样从命令行界面(也称为标准输入)读取源码,并在格式化后将结果打印到命令行界面(也称为标准输出)中。示例如下:
hc@ubt:~$ gofmt -r='fmt.Println(a)->fmt.Printf("%s\n", a)' if a=="print" {fmt.Println(a)} <----- 在此行的末尾键入回车和Ctrl-d。 warning: rewrite ignored for incomplete programs <----- 此行及以下就是命令输出的内容。 if a == "print" { fmt.Println(a) }
由上述示例可知,我们可以使用gofmt命令的交互模式格式化任意的代码片段。虽然会显示一行警告信息,但是格式化后的结果仍然会被打印出来。并且,在交互模式下,当我们输入的代码片段不符合Go语言的语法规则时,命令程序也会打印出错误提示信息。在其它方面,命令程序在交互模式与普通模式下的行为也是基本一致的。
以上是go fmt指令的作用是什麼的詳細內容。更多資訊請關注PHP中文網其他相關文章!