學習go語言編程之工程管理

Go命令行工具

安裝了Go語言的安裝包後,就直接自帶Go命令行工具。

# 查看當前安裝的Golang版本
go version

# 查看go命令行工具的幫助信息
go help

Go命令行工具可以完成如下工作:

  • 代碼格式化
  • 代碼質量分析和修復
  • 單元測試與性能測試
  • 工程構建
  • 代碼文檔的提取和展示
  • 依賴包管理
  • 執行其他的包含指令

代碼風格

代碼風格,是一個與人相關、與機器無關的問題。
代碼風格的好壞,不影響編譯器的工作,但是影響團隊協同,影響代碼的複用、演進以及缺陷修復。

強制性編碼規範

命名

命名規則涉及變量常量全局函數結構接口方法等的命名。
Go語言從語法層面進行了以下限定:任何需要對外暴露的名字必須以大寫字母開頭,不需要對外暴露的則應該以小寫字母開頭。
軟件開發行業最流行的兩種命名法分別爲駝峯命名法和下劃線法,Go語言明確宣告了擁護駝峯命名法而排斥下劃線法。

排列

Go語言甚至對代碼的排列方式也進行了語法級別的檢查,約定了代碼塊中花括號的明確擺放位置。

非強制性編碼風格建議

使用go fmt命令可以對代碼進行格式化,例如:

# 格式化單個Golang源文件
go fmt hello.go

更詳盡的用法可以查看:go help fmt

當然,這個工具並非只能一次格式化一個文件,比如不帶任何參數直接運行go fmt的話,可以直接格式化當前目錄下的所有*.go文件,或者也可以指定一個GOPATH中可以找到的包名。

遠程import支持

Go語言不僅允許我們導入本地包,還支持在語言級別調用遠程的包。
假如,有一個用於計算CRC32的包託管於Github,那麼可以這樣寫:

package main 
import ( 
    "fmt" 
    "github.com/myteam/exp/crc32" 
)

在執行go build或者go install之前,只需要加這麼一句:go get github.com/myteam/exp/crc32
當執行完go get之後,會在src目錄中看到github.com目錄,其中包含myteam/exp/crc32目錄。在crc32中,就是該包的所有源代碼。
也就是說,go工具會自動獲取位於遠程的包源碼,在隨後的編譯中,也會在pkg目錄中生成對應的.a文件。

工程組織

GOPATH

Go命令行工具大部分功能其實不再針對當前目錄,而是針對包名,於是如何才能定位到對應的源代碼就落到了GOPATH環境變量身上。
假設現在本地硬盤上有3個Go代碼工程,分別爲~/work/go-proj1~/work2/goproj2~/work3/go-proj3,那麼GOPATH可以設置爲如下內容:

export GOPATH=~/work/go-proj1:~/work2/goproj2:~/work3/go-proj3

經過這樣的設置後,就可以在任意位置對以上的3個工程進行構建。

目錄結構

一個完整的Go項目工程結構通常如下:

<calcproj>
  - README
  - LICENSE
  - bin
    - calc
  - pkg
    - <linux_amd64>
      - simplemath.a
  - src
    - calc
      - calc.go
    - simplemath
      - add.go
      - add_test.go
      - sqrt.go
      - sqrt_test.go

Go語言工程不需要任何工程文件,一個比較完整的工程會在根目錄處放置這樣幾個文本文件。

  • README:簡單介紹本項目目標和關鍵的注意事項,通常第一次使用時應該先閱讀本文檔。
  • LICENSE:本工程採用的分發協議,所有開源項目通常都有這個文件。

一個標準的Go語言工程包含以下幾個目錄:srcpkgbin
src用於包含所有的源代碼,這是Go命令行工具的一個強制的規則,而pkgbin則無需手動創建,如果必要Go命令行工具在構建過程中會自動創建它們。
構建過程中Go命令行工具對包結構的理解完全依賴於src下面的目錄結構。

文檔管理

所謂的文檔,更多的是指代碼中的註釋、函數、接口的輸入、輸出、功能和參數說明,這些對於後續的維護和複用有着至關重要的作用。
Go語言讓開發者完全甩掉註釋語法的包袱,專注於內容。

// Copyright 2011 The Go Authors. All rights reserved. 
// Use of this source code is governed by a BSD-style 
// license that can be found in the LICENSE file.

/* 
Package foo implements a set of simple mathematical functions. These comments are for 
demonstration purpose only. Nothing more. 
If you have any questions, please don’t hesitate to add yourself to 
[email protected]. 
You can also visit golang.org for full Go documentation. 
*/
package foo

import "fmt"

// Foo compares the two input values and returns the larger 
// value. If the two values are equal, returns 0. 
func Foo(a, b int) (ret int, err error) { 
    if a > b { 
        return a, nil
    } else { 
        return b, nil
    } 
    return 0, nil
}

// BUG(jack): #1: I'm sorry but this code has an issue to be solved. 
// BUG(tom): #2: An issue assigned to another person.

如上代碼文檔示例中,添加了4條註釋:版權說明註釋、包說明註釋、函數說明註釋和最後添加的遺留問題說明。

關於註釋的編寫,要遵守如下的基本規則:

  • 註釋需要緊貼在對應的包聲明和函數之前,不能有空行。
  • 註釋如果要新起一個段落,應該用一個空白註釋行隔開,因爲直接換行書寫會被認爲是正常的段內折行。
  • 開發者可以直接在代碼內用// BUG(author): 的方式記錄該代碼片段中的遺留問題,這些遺留問題也會被抽取到文檔中。

工程構建

使用go build命令來執行構建,它會在你運行該命令的目錄中生成工程的目標二進制文件,而不產生其他結果。
如果項目工程路徑已經被加入到了全局變量GOPATH中,可以在任意位置執行go build命令,而不必關心是否能找到源代碼。
注意: 在構建可執行程序工程時,會在當前所在的目錄中生成可執行程序,所以通常選擇在項目目錄下的bin目錄中執行構建。

構建示例如下:

# calc是項目src目錄下的包名
go build calc

單元測試

Golang本身提供了一套輕量級的測試框架,符合規則的測試代碼會在運行測試時被自動識別並執行。
單元測試源文件的命名規則如下:在需要測試的包下面創建以“_test”結尾的go文件,形如[^.]*_test.go

Golang的單元測試函數分爲兩類:功能測試函數和性能測試函數,分別爲以TestBenchmark爲函數名前綴並以*testing.T*testing.B爲單一參數的函數。
示例如下:

// 功能測試函數以Test爲函數名前綴
func TestAdd1(t *testing.T) 
// 性能測試函數以Benchmark爲函數名前綴
func BenchmarkAdd1(t *testing.B)

測試工具會根據函數中的實際執行動作得到不同的測試結果:功能測試函數會根據測試代碼執行過程中是否發生錯誤來返回不同的結果,而性能測試函數僅僅打印整個測試過程的花費時間。

功能單元測試

功能單元測試示例代碼:

func TestAdd(t *testing.T) {
	r := Add(1, 2)
	if r != 3 {
		t.Errorf("Add(1,2) failed. Got %d, expected 3.", r)
	}
}

執行功能單元測試非常簡單,直接執行go test命令即可。

# 執行當前所在包的所有單元測試
go test

當然,也可以在IDE中對單個方法執行單元測試。

性能單元測試

性能單元測試示例代碼:

func BenchmarkAdd(b *testing.B) {
	for i := 0; i < b.N; i++ {
		Add(1, 2)
	}
}

性能測試與功能測試代碼相比,最大的區別在於代碼裏的這個for循環,寫這個for循環的原因是爲了能夠讓測試運行足夠長的時間便於進行平均運行時間的計算。

如果測試代碼中一些準備工作的時間太長,也可以這樣處理以明確排除這些準備工作所花費時間對於性能測試的時間影響:

func BenchmarkAdd(b *testing.B) {
    b.StopTimer()   // 暫停計時器
    DoPreparation() // 一個耗時較長的準備工作,比如讀文件
    b.StartTimer()  // 開啓計時器,之前的準備時間未計入總花費時間內

	for i := 0; i < b.N; i++ {
		Add(1, 2)
	}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章