go語言標準包的testing提供了單元測試(功能性測試)和性能測試(壓力測試)經常使用方法的框架,能夠很是方便地利用其進行自動化測試。
go語言測試代碼只須要放到以 _test.go 結尾的文件中便可。golang的測試分爲單元測試和性能測試,單元測試的測試用例必須以Test開頭,其後的函數名不能以小寫字母開頭;性能測試必須以Benchmark開頭,其後的函數名不能以小寫字母開頭。爲了測試方法和被測試方法的可讀性,通常Test或Benchmark後爲被測試方法的函數名。測試代碼一般與測試對象文件在同一目錄下。html
Go語言單元測試的測試用例必須以Test開頭,其後的函數名不能以小寫字母開頭。
add.go文件:linux
package add func add(a,b int)int{ return a + b }
單元測試用例:git
package add import "testing" func TestAdd(t *testing.T){ sum := add(1,2) if sum == 3 { t.Logf("add(1,2) == %d",sum) } }
上述代碼測試數據與測試邏輯混合在一塊兒,根據Go語言的特色和工程實踐,產生了一種表格驅動測試方法。表格驅動測試將測試數據集中保存在切片中,測試數據與測試邏輯實現了分離。
表格驅動測試:github
package add import "testing" func TestAdd(t *testing.T) { //定義測試數據 tests := []struct{ a, b, c int }{ {3, 4, 7}, {5, 12, 17}, {8, 15, 23}, {12, 35, 47}, {30000, 40000, 70000}, } //測試邏輯 for _,tt := range tests{ if actual := add(tt.a,tt.b);actual != tt.c{ t.Errorf("Add(%d,%d) got %d;expected %d", tt.a,tt.b,actual,tt.c) } } }
表格驅動測試的優勢:
A、分離測試數據和測試邏輯
B、明確出錯信息
C、能夠部分失敗
D、Go語言更容易實現表格驅動測試
執行測試:go test
結果以下:golang
[user@localhost test]$ go test -v === RUN TestAdd --- PASS: TestAdd (0.00s) PASS ok _/home/user/GoLang/test 0.001s
性能測試即壓力測試(BMT: Benchmark Testing)。
性能測試用例:web
func BenchmarkAdd(t *testing.B){ //重置時間點 t.ResetTimer() for i := 0; i < t.N; i++{ add(1,2) } }
完整測試代碼以下:編程
package add import "testing" func TestAdd(t *testing.T) { //定義測試數據 tests := []struct{ a, b, c int }{ {3, 4, 7}, {5, 12, 17}, {8, 15, 23}, {12, 35, 47}, {30000, 40000, 70000}, } //測試邏輯 for _,tt := range tests{ if actual := add(tt.a,tt.b);actual != tt.c{ t.Errorf("Add(%d,%d) got %d;expected %d", tt.a,tt.b,actual,tt.c) } } } func BenchmarkAdd(t *testing.B){ //重置時間點 t.ResetTimer() for i := 0; i < t.N; i++{ add(1,2) } }
執行測試:go test -bench=.
結果以下:api
[user@localhost test]$ go test -bench=. goos: linux goarch: amd64 BenchmarkAdd-4 2000000000 0.38 ns/op PASS ok _/home/user/GoLang/test 0.803s
測試覆蓋率是用於經過執行某包的測試用例來確認到的描述其的代碼在測試用例中被執行的程度的術語。
在go語言的測試覆蓋率統計時,go test經過參數covermode的設定能夠對覆蓋率統計模式做以下三種設定:
A、set:缺省模式, 只記錄語句是否被執行過
B、count:記錄語句被執行的次數
C、atomic:記錄語句被執行的次數,並保證在併發執行時的正確性
執行覆蓋率測試:go test -cover
結果以下:數組
[user@localhost test]$ go test -cover PASS coverage: 100.0% of statements ok _/home/user/GoLang/test 0.001s
執行命令,生成代碼覆蓋率測試信息:go test -coverprofile=covprofile
查看covprofile文件信息:瀏覽器
[user@localhost test]$ cat covprofile mode: set _/home/user/GoLang/test/add.go:3.21,5.2 1 1 [user@localhost test]$
將生成代碼覆蓋率測試信息轉換爲HTML格式:go tool cover -html=covprofile -o coverage.html
使用瀏覽器查看coverage.html文件。
Golang內置cpu、mem、block三種profiler採樣工具,容許程序在運行時使用profiler進行數據採樣,生成採樣文件。經過go tool pprof工具能夠交互式分析採樣文件,獲得高可讀性的輸出信息。
任何以go tool開頭的Go命令內部指向的特殊工具都被保存在目錄$GOROOT/pkg/tool/$GOOS_$GOARCH/目錄,即Go工具目錄。pprof工具並非用Go語言編寫的,而是由Perl語言編寫。Perl語言能夠直接讀取源碼並運行。所以,pprof工具的源碼文件被直接保存在Go工具目錄下。
pprof工具是用Perl語言編寫的,執行go tool pprof命令的前提條件是須要在當前環境下安裝Perl語言
go tool pprof命令會分析指定的概要文件並使得可以以交互式的方式訪問其中的信息。但只有概要文件還不夠,還須要概要文件中信息的來源——命令源碼文件的可執行文件。而能夠運行的Go語言程序只能是編譯命令源碼文件後生成的可執行文件。
在Go語言中,能夠經過標準庫的代碼包runtime和runtime/pprof中的程序來生成三種包含實時性數據的概要文件,分別是CPU概要文件、內存概要文件和程序阻塞概要文件。
A、CPU概要文件
CPU的主頻,即CPU內核工做的時鐘頻率(CPU Clock Speed)。CPU的主頻的基本單位是赫茲(Hz)。時鐘頻率的倒數即爲時鐘週期。在一個時鐘週期內,CPU執行一條運算指令。在1000 Hz的CPU主頻下,每1毫秒能夠執行一條CPU運算指令;在1 MHz的CPU主頻下,每1微妙能夠執行一條CPU運算指令;在1 GHz的CPU主頻下,每1納秒能夠執行一條CPU運算指令。
在默認狀況下,Go語言的運行時系統會以100 Hz的的頻率對CPU使用狀況進行取樣,即每秒取樣100次(每10毫秒會取樣一次)。100 Hz既足夠產生有用的數據,又不至於讓系統產生停頓,而且100容易作換算。對CPU使用狀況的取樣就是對當前的Goroutine的堆棧上的程序計數器的取樣。由此,能夠從樣本記錄中分析出哪些代碼是計算時間最長或者說最耗CPU資源的部分。能夠經過如下代碼啓動對CPU使用狀況的記錄。
func startCPUProfile() { if *cpuProfile != "" { f, err := os.Create(*cpuProfile) if err != nil { fmt.Fprintf(os.Stderr, "Can not create cpu profile output file: %s", err) return } if err := pprof.StartCPUProfile(f); err != nil { fmt.Fprintf(os.Stderr, "Can not start cpu profile: %s", err) f.Close() return } } }
在函數startCPUProfile中,首先建立了一個用於存放CPU使用狀況記錄的文件,即CPU概要文件,其絕對路徑由*cpuProfile的值表示。而後,把profile文件的實例做爲參數傳入到函數pprof.StartCPUProfile中。若是pprof.StartCPUProfile函數沒有返回錯誤,說明記錄操做已經開始。只有CPU概要文件的絕對路徑有效時,pprof.StartCPUProfile函數纔會開啓記錄操做。
若是想要在某一時刻中止CPU使用狀況記錄操做,須要調用如下函數:
func stopCPUProfile() { if *cpuProfile != "" { pprof.StopCPUProfile() // 把記錄的概要信息寫到已指定的文件 } }
在以上函數中,並無代碼用於CPU概要文件寫入操做。在啓動CPU使用狀況記錄操做後,運行時系統就會以每秒100次的頻率將採樣數據寫入到CPU概要文件中。pprof.StopCPUProfile函數經過把CPU使用狀況取樣的頻率設置爲0來中止取樣操做。只有當全部CPU使用狀況記錄都被寫入到CPU概要文件後,pprof.StopCPUProfile函數纔會退出,保證CPU概要文件的完整性。
B、內存概要文件
內存概要文件用於保存在用戶程序執行期間的內存使用狀況,即程序運行過程當中堆內存的分配狀況。Go語言運行時系統會對用戶程序運行期間的全部的堆內存分配進行記錄。不論在取樣的哪一時刻、堆內存已用字節數是否有增加,只要有字節被分配且數量足夠,分析器就會對其進行取樣。開啓內存使用狀況記錄的可使用如下函數:
func startMemProfile() { if *memProfile != "" && *memProfileRate > 0 { runtime.MemProfileRate = *memProfileRate } }
開啓內存使用狀況記錄的方式很是簡單。在函數startMemProfile中,只有在memProfile和memProfileRate的值有效時纔會進行後續操做。memProfile的含義是內存概要文件的絕對路徑。memProfileRate的含義是分析器的取樣間隔,單位是字節。當將memProfileRate值賦給int類型的變量runtime.MemProfileRate時,意味着分析器將會在每分配指定的字節數量後對內存使用狀況進行取樣。實際上,即便不給runtime.MemProfileRate變量賦值,內存使用狀況的取樣操做也會照樣進行。此取樣操做會從用戶程序開始時啓動,且一直持續進行到用戶程序結束。runtime.MemProfileRate變量的默認值是512 1024,即512K個字節。只有當顯式的將0賦給runtime.MemProfileRate變量後,纔會取消取樣操做。
在默認狀況下,內存使用狀況的取樣數據只會被保存在運行時內存中,而保存到文件的操做只能由開發者本身來完成。取消採樣操做代碼以下:
func stopMemProfile() { if *memProfile != "" { f, err := os.Create(*memProfile) if err != nil { fmt.Fprintf(os.Stderr, "Can not create mem profile output file: %s", err) return } if err = pprof.WriteHeapProfile(f); err != nil { fmt.Fprintf(os.Stderr, "Can not write %s: %s", *memProfile, err) } f.Close() } }
stopMemProfile函數的功能是中止對內存使用狀況的取樣操做。stopMemProfile只作了將取樣數據保存到內存概要文件的操做。在stopMemProfile函數中,調用函數pprof.WriteHeapProfile,並把表明內存概要文件的文件實例做爲參數。若是pprof.WriteHeapProfile函數沒有返回錯誤,就說明數據已被寫入到了內存概要文件中。
對內存使用狀況進行取樣的程序會假定取樣間隔在用戶程序的運行期間內都是一成不變的,而且等於runtime.MemProfileRate變量的當前值。所以,應該在Go程序中只改變內存取樣間隔一次,且應儘早改變。好比,在命令源碼文件的main函數的開始處就改變內存採樣間隔。
C、程序阻塞概要文件
程序阻塞概要文件用於保存用戶程序中的Goroutine阻塞事件的記錄。開啓程序阻塞採樣的代碼以下:
func startBlockProfile() { if *blockProfile != "" && *blockProfileRate > 0 { runtime.SetBlockProfileRate(*blockProfileRate) } }
在函數startBlockProfile中,當blockProfile和blockProfileRate的值有效時,會設置對Goroutine阻塞事件的取樣間隔。blockProfile的含義爲程序阻塞概要文件的絕對路徑。blockProfileRate的含義是分析器的取樣間隔,單位是次。函數runtime.SetBlockProfileRate的惟一參數是int類型的,含義是分析器會在每發生幾回Goroutine阻塞事件時對阻塞事件進行取樣。若是不顯式的使用runtime.SetBlockProfileRate函數設置取樣間隔,那麼取樣間隔就爲1。即在默認狀況下,每發生一次Goroutine阻塞事件,分析器就會取樣一次。運行時系統對Goroutine阻塞事件的取樣操做也會貫穿於用戶程序的整個運行期。可是,若是經過runtime.SetBlockProfileRate函數將取樣間隔設置爲0或者負數,那麼取樣操做就會被取消。
在程序結束前能夠將被保存在運行時內存中的Goroutine阻塞事件記錄存放到指定的文件中。代碼以下:
func stopBlockProfile() { if *blockProfile != "" && *blockProfileRate >= 0 { f, err := os.Create(*blockProfile) if err != nil { fmt.Fprintf(os.Stderr, "Can not create block profile output file: %s", err) return } if err = pprof.Lookup("block").WriteTo(f, 0); err != nil { fmt.Fprintf(os.Stderr, "Can not write %s: %s", *blockProfile, err) } f.Close() } }
在建立程序阻塞概要文件後,stopBlockProfile函數會先經過函數pprof.Lookup將保存在運行時內存中的內存使用狀況記錄取出,並在記錄的實例上調用WriteTo方法將記錄寫入到文件中。
A、基準測試
使用go test -bench . -cpuprofile prof.cpu
生成基準測試的採樣文件,再經過命令go tool pprof [binary] prof.cpu
對採樣文件進行分析。
B、Web服務測試
若是應用是一個web服務,能夠在http服務啓動的代碼文件添加import _ net/http/pprof,Web服務會自動開啓profile功能,輔助開發者直接分析採樣結果。能夠在瀏覽器中使用http://localhost:port/debug/pprof/
直接看到當前web服務的狀態,包括CPU佔用狀況和內存使用狀況等。
C、應用程序
若是go程序是一個應用程序,不能使用net/http/pprof包,須要使用runtime/pprof包。使用pprof.StartCPUProfile、pprof.StopCPUProfile或是內存採樣、阻塞採樣接口等對運行時信息進行採樣。最終使用go tool pprof工具對採樣文件進行分析。
D、服務進程
若是go程序不是web服務器,而是一個服務進程,那麼也能夠選擇使用net/http/pprof包,一樣引入包net/http/pprof,而後再開啓另一個goroutine來開啓端口監聽。
go func() { log.Println(http.ListenAndServe("localhost:6666", nil)) }()
編寫一個簡單的應用程序,使用pprof.StartCPUProfile和pprof.StopCPUProfile對CPU信息進行採樣。
package main import ( "flag" "log" "os" "runtime/pprof" "fmt" ) // 斐波納契數列 func Fibonacci() func() int { back1, back2 := 1, 1 return func() int { //從新賦值 back1, back2 = back2, (back1 + back2) return back1 } } func count(){ a := 0; for i := 0; i < 10000000000; i++ { a = a + i } } var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file") func main() { flag.Parse() if *cpuprofile != "" { f, err := os.Create(*cpuprofile) if err != nil { log.Fatal(err) } pprof.StartCPUProfile(f) defer f.Close() } fibonacci := Fibonacci() for i := 0; i < 100; i++ { fmt.Println(fibonacci()) } count() defer pprof.StopCPUProfile() }
進行運行時信息採樣時,能夠指定不一樣的採樣參數:
--cpuprofile:指定CPU概要文件的保存路徑
--blockprofile:指定程序阻塞概要文件的保存路徑。
--blockprofilerate:定義其值爲n,指定每發生n次Goroutine阻塞事件時,進行一次取樣操做。
--memprofile:指定內存概要文件的保存路徑。
--memprofilerate:定義其值爲n,指定每分配n個字節的堆內存時,進行一次取樣操做。
運行go程序,對CPU信息進行採樣:go run fibonacci.go --cpuprofile=profile.cpu
分析CPU採樣文件profile.cpu:go tool pprof profile.cpu
若是Go程序很是簡單,好比只有fibonacci()函數調用(註釋count()函數),使用pprof.StartCPUProfile是打印不出任何信息的。
默認狀況下top命令會列出前10項內容。能夠top命令後面緊跟一個數字,限制列出的項數。
go-torch是Uber公司開源的一款針對Golang程序的火焰圖生成工具,能收集stack traces,整理成火焰圖,並直觀地顯示程序給開發人員。go-torch是基於使用BrendanGregg建立的火焰圖工具生成直觀的圖像,方便地分析Go的各個方法所佔用CPU的時間。
git clone https://github.com/brendangregg/FlameGraph.git
sudo cp FlameGraph/flamegraph.pl /usr/local/bin
在終端輸入flamegraph.pl -h測試FlameGraph是否安裝成功
go get -v github.com/uber/go-torch
go-torch默認安裝在GOPATH指定的第一個目錄中,位於bin目錄下。
安裝go-wrk壓力測試工具:go get -v github.com/adjust/go-wrk
執行35s 1W次高併發場景模擬:go-wrk -d 35 -n 10000 http://localhost:port/demo
在Web服務壓力測試過程當中,使用go-torch生成採樣文件。go-torch -u http://localhost:port -t 30
go-torch完成採樣時輸出以下信息:Writing svg to torch.svg
torch.svg是go-torch自動生成的profile文件,使用瀏覽器打開以下:
火焰圖的y軸表示cpu調用方法的前後,x軸表示在每一個採樣調用時間內,方法所佔的時間百分比,越寬表明佔據cpu時間越多。
根據火焰圖能夠清楚的查看哪一個方法調用耗時長,而後不斷的修正代碼,從新採樣,不斷優化。
A、將小對象合併成結構體一次分配,減小內存分配次數
Go runtime底層採用內存池機制,每一個span大小爲4k,同時維護一個cache。cache有一個0到n的list數組,list數組的每一個單元掛載的是一個鏈表,鏈表的每一個節點就是一塊可用的內存塊,同一鏈表中的全部節點內存塊都是大小相等的;可是不一樣鏈表的內存大小是不等的,即list數組的一個單元存儲的是一類固定大小的內存塊,不一樣單元裏存儲的內存塊大小是不等的。cache緩存的是不一樣類大小的內存對象,申請的內存大小最接近於哪類緩存內存塊時,就分配哪類內存塊。當cache不夠時再向spanalloc中分配。
B、緩存區內容一次分配足夠大小空間,並適當複用
在協議編解碼時,須要頻繁地操做[]byte,可使用bytes.Buffer或其它byte緩存區對象。
bytes.Buffer等經過預先分配足夠大的內存,避免當增加時動態申請內存,減小內存分配次數。對於byte緩存區對象須要考慮適當地複用。
C、slice和map採make建立時,預估大小指定容量
slice和map與數組不同,不存在固定空間大小,能夠根據增長元素來動態擴容。
slice初始會指定一個數組,當對slice進行append等操做時,當容量不夠時,會自動擴容:
若是新的大小是當前大小2倍以上,則容量增漲爲新的大小;
不然循環如下操做:若是當前容量小於1024,按2倍增長;不然每次按當前容量1/4增漲,直到增漲的容量超過或等新大小。
map的擴容比較複雜,每次擴容會增長到上次容量的2倍。map的結構體中有一個buckets和oldbuckets,用於實現增量擴容:
正常狀況下,直接使用buckets,oldbuckets爲空;
若是正在擴容,則oldbuckets不爲空,buckets是oldbuckets的2倍,
所以,建議初始化時預估大小指定容量
D、長調用棧避免申請較多的臨時對象
Goroutine的調用棧默認大小是4K(1.7修改成2K),採用連續棧機制,當棧空間不夠時,Go runtime會自動擴容:
當棧空間不夠時,按2倍增長,原有棧的變量會直接copy到新的棧空間,變量指針指向新的空間地址;
退棧會釋放棧空間的佔用,GC時發現棧空間佔用不到1/4時,則棧空間減小一半。
好比棧的最終大小2M,則極端狀況下,就會有10次的擴棧操做,會帶來性能降低。
所以,建議控制調用棧和函數的複雜度,不要在一個goroutine作完全部邏輯;如的確須要長調用棧,而考慮goroutine池化,避免頻繁建立goroutine帶來棧空間的變化。
E、避免頻繁建立臨時對象
Go在GC時會引起stop the world,即整個狀況暫停。Go1.8最壞狀況下GC爲100us。但暫停時間仍是取決於臨時對象的個數,臨時對象數量越多,暫停時間可能越長,並消耗CPU。
所以,建議GC優化方式是儘量地減小臨時對象的個數:儘可能使用局部變量;所多個局部變量合併一個大的結構體或數組,減小掃描對象的次數,一次回儘量多的內存。
A、高併發的任務處理使用goroutine池
Goroutine雖然輕量,但對於高併發的輕量任務處理,頻繁來建立goroutine來執行,執行效率並不會過高,由於:過多的goroutine建立,會影響go runtime對goroutine調度,以及GC消耗;高併發時若出現調用異常阻塞積壓,大量的goroutine短期積壓可能致使程序崩潰。
B、避免高併發調用同步系統接口
goroutine的實現,是經過同步來模擬異步操做。
網絡IO、鎖、channel、Time.sleep、基於底層系統異步調用的Syscall操做並不會阻塞go runtime的線程調度。
本地IO調用、基於底層系統同步調用的Syscall、CGo方式調用C語言動態庫中的調用IO或其它阻塞會建立新的調度線程。
網絡IO能夠基於epoll的異步機制(或kqueue等異步機制),但對於一些系統函數並無提供異步機制。例如常見的posix api中,對文件的操做就是同步操做。雖有開源的fileepoll來模擬異步文件操做。但Go的Syscall仍是依賴底層的操做系統的API。系統API沒有異步,Go也作不了異步化處理。
所以,建議:把涉及到同步調用的goroutine,隔離到可控的goroutine中,而不是直接高並的goroutine調用。
C、高併發時避免共享對象互斥
傳統多線程編程時,當併發衝突在4~8線程時,性能可能會出現拐點。Go推薦不經過共享內存來通訊,Go建立goroutine很是容易,當大量goroutine共享同一互斥對象時,也會在某一數量的goroutine出在拐點。
所以,建議:goroutine儘可能獨立,無衝突地執行;若goroutine間存在衝突,則能夠採分區來控制goroutine的併發個數,減小同一互斥對象衝突併發數。
A、避免使用CGO或者減小CGO調用次數
GO能夠調用C庫函數,但Go帶有垃圾收集器且Go的棧動態增漲,沒法與C無縫地對接。Go的環境轉入C代碼執行前,必須爲C建立一個新的調用棧,把棧變量賦值給C調用棧,調用結束現拷貝回來。調用開銷較大,須要維護Go與C的調用上下文,二者調用棧的映射。相比直接的GO調用棧,單純的調用棧可能有2個甚至3個數量級以上。
所以,建議:儘可能避免使用CGO,沒法避免時,要減小跨CGO的調用次數。
B、減小[]byte與string之間轉換,儘可能採用[]byte來字符串處理
GO裏面的string類型是一個不可變類型,GO中[]byte與string底層是兩個不一樣的結構,轉換存在實實在在的值對象拷貝,因此儘可能減小沒必要要的轉化。
所以,建議:存在字符串拼接等處理,儘可能採用[]byte。
C、字符串的拼接優先考慮bytes.Buffer
string類型是一個不可變類型,但拼接會建立新的string。GO中字符串拼接常見有以下幾種方式:
string + 操做 :致使屢次對象的分配與值拷貝
fmt.Sprintf :會動態解析參數,效率好不哪去
strings.Join :內部是[]byte的append
bytes.Buffer :能夠預先分配大小,減小對象分配與拷貝
所以,建議:對於高性能要求,優先考慮bytes.Buffer,預先分配大小。fmt.Sprintf能夠簡化不一樣類型轉換與拼接。
go doc 工具會從Go程序和包文件中提取頂級聲明的首行註釋以及每一個對象的相關注釋,並生成相關文檔。
go doc也能夠做爲一個提供在線文檔瀏覽的web服務器。
go doc package:獲取包的文檔註釋,例如:go doc fmt 會顯示使用 godoc 生成的 fmt 包的文檔註釋。
go doc package/subpackage: 獲取子包的文檔註釋,例如:go doc container/list。
go doc package function :獲取某個函數在某個包中的文檔註釋,例如:go doc fmt Printf 會顯示有關 fmt.Printf() 的使用說明。
godoc支持啓動一個Web在線API文檔服務,在命令行執行:godoc -http=:6666
啓動Web服務後,使用瀏覽器打開http://127.0.0.1:6666
,能夠看到本地文檔瀏覽服務器提供的頁面。
經測試,Google Chrome瀏覽器不能訪問godoc開啓的Web服務。
Go文檔工具支持開發人員本身寫的代碼,只要開發者按照必定的規則,就能夠自動生成文檔。
add.go文件以下:
/* add function will be add a and b. return a+b */ package add // add func add(a,b int)int{ return a + b }
生成文文檔以下:
[user@localhost add]$ go doc package add // import "add" add function will be add a and b. return a+b [user@localhost add]$
go doc工具會將包文件中頂級聲明的首行註釋提取出來。若是函數爲private(函數名稱首字母小寫),go doc工具會隱藏函數。
/* add function will be add a and b. return a+b */ package add // add func Add(a,b int)int{ return a + b }
生成文檔以下:
[user@localhost add]$ go doc package add // import "add" add function will be add a and b. return a+b func Add(a, b int) int [user@localhost add]$
Go語言的文檔中添加示例代碼的步驟以下:
A、示例代碼必須單獨存放在一個文件(文件名字爲example_test.go)中或是測試代碼文件中。
B、在示例代碼文件裏,定義一個名字爲Example的函數,參數爲空
C、示例的輸出採用註釋的方式,以// Output:開頭,另起一行,每行輸出佔一行。
package add import ( "fmt" ) func Example(){ sum := add(1,2) fmt.Println(sum) // Output: // 3 }
生成文檔結果以下: