­

Go的程式碼規範指南-新人必看

  • 2019 年 10 月 11 日
  • 筆記
想要壞壞的藍貓,可以微信表情包搜索:墩墩貓

項目約定:

環境設置:*nix環境或者Mac環境,安裝go語言目錄,默認是usr/local/go,如果想要讓程式碼運行,需要將項目放到usr/local/go/src目錄下,如果忘記了go的配置目錄,可以通過go env查看go的環境變數設置。如果不希望每次使用sudo許可權執行,可以通過設置用戶目錄下創建~/.bashrc文件,增加 用戶目錄的環境變數設置比如

export GOPATH=~/go的安裝目錄  source ~/.bashrc 激活配置文件,然後可以通過go env或者echo $GOPATH命令看到變數輸出改變了。    如果不想上面那麼搞,則可以直接設置多個GOPATH目錄。  假設現在本地硬碟上有3個Go程式碼工程,分別為~/work/go-proj1、~/work2/goproj2和 ~/work3/work4/go-proj3,那麼GOPATH可以設置為如下內容:  export GOPATH=~/work/go-proj1:~/work2/goproj2:~/work3/work4/go-proj3  經過這樣的設置後,你可以在任意位置對以上的3個工程進行構建。

程式碼命名:以後綴.go作為開發文件,以xx_test.go作為測試文件。

列印日誌:通過fmt包下的Pringtf函數或者log模組的日誌功能

1. 命名  命名規則涉及變數、常量、全局函數、結構、介面、方法等的命名。Go語言從語法層面進 行了以下限定:  任何需要對外暴露的名字必須以大寫字母開頭,不需要對外暴露的則應該以小寫 字母開頭。  Go推行駝峰命名法排斥下劃線命名法  2、排列  Go的程式碼左花括弧嚴格要求不能換行  3、程式碼擺放亂  go fmt xx.go格式化為標準go文件  4、go的遠程包導入  import可以導入本地包,也可以導入GitHub上的遠程包,比如如下示例  package main  import ( "fmt"      "github.com/myteam/exp/crc32"  )  然後,在執行go build或者go install之前,只需要加這麼一句: go get github.com/myteam/exp/crc32

目錄結構:

舉個樣例:

程式碼doc文件輸出:

版權說明注釋、包說明注釋、函數說明注釋和最後添 加的遺留問題說明,舉個例子    $ go doc foo本地輸出文檔化    godoc -http=:76 -path="." 瀏覽器訪問http://localhost:76/pkg/foo/輸出文檔化

Go的單元測試:

Go的單元測試函數分為兩類:功能測試函數和性能測試函數

執行功能單元測試非常簡單,直接執行go test命令  go test simplematch(上面目錄結構圖)  性能測試的,增加-test.bench參數  go test–test.bench add.go

程式碼編程:

變數申明:

關鍵字var,而類型資訊放在變數名之後,示例如下:  var v1 int  var v2 string  var v3 [10]int // 數組  var v4 []int  // 數組切片  var v5 struct {     f int  }  var v6 *int // 指針  var v7 map[string]int // map,key為string類型,value為int類型  var v8 func(a int) int  變數聲明語句不需要使用分號作為結束符。  省略多個重複var的申明:  var (    v1 int    v2 string  )  

變數初始化:

var v1 int = 10 // 正確的使用方式1  var v2 = 10 // 正確的使用方式2,編譯器可以自動推導出v2的類型  v3 := 10 // 正確的使用方式3,編譯器可以自動推導出v3的類型

變數賦值:

在Go語法中,變數初始化和變數賦值是兩個不同的概念  var v10 int  v10 = 123

返回值的處理:

func GetName() (firstName, lastName, nickName string) {  return "May", "Chan", "Chibi Maruko"  }  若只想獲得nickName,則函數調用語句可以用如下方式編寫:  _, _, nickName := GetName()

常量:

在Go語言中,常量是指編譯期間就已知且不可改變的值。常量可以是數值類型(包括整型、

浮點型和複數類型)、布爾類型、字元串類型等

常量定義:

  const a, b, c = 3, 4, "foo" //a=3,b=4,c="foo", 無類型整型和字元串常量  const zero = 0.0  const (  size int64 = 1024  eof = -1  // 無類型浮點常量 // 無類型整型常量  )  const Pi float64 = 3.14159265358979323846  const u, v float32 = 0, 3

預定義常量:

Go語言預定義了這些常量:true、false和iota。

枚舉:

const (          Sunday = iota          Monday          Tuesday          Wednesday          Thursday          Friday          Saturday          numberOfDays// 這個常量沒有導出,因為大寫字母外包可見,小寫字母屬於私有          )

數組切片:

創建一個初始元素個數為5的數組切片,元素初始值為0:  mySlice1 := make([]int, 5)  創建一個初始元素個數為5的數組切片,元素初始值為0,並預留10個元素的存儲空間:  mySlice2 := make([]int, 5, 10)  直接創建並初始化包含5個元素的數組切片:  mySlice3 := []int{1, 2, 3, 4, 5}

元素遍歷:

傳統的元素遍歷方法如下:  for i := 0; i <len(mySlice); i++ { fmt.Println("mySlice[", i, "] =", mySlice[i])  }  使用range關鍵字可以讓遍歷程式碼顯得更整潔。range表達式有兩個返回值,第一個是索引,  第二個是元素的值:  for i, v := range mySlice { fmt.Println("mySlice[", i, "] =", v)  }

數組切片的內容複製:

slice1 := []int{1, 2, 3, 4, 5}  slice2 := []int{5, 4, 3}  copy(slice2, slice1) // 只會複製slice1的前3個元素到slice2中 copy(slice1, slice2)  // 只會複製slice2的3個元素到slice1的前3個位置

map的使用:

申明:var myMap map[string] PersonInfo  創建:myMap = make(map[string] PersonInfo)  性能優化-指定分配存儲空間:myMap = make(map[string] PersonInfo, 100)  元素的賦值:  myMap["1234"] = PersonInfo{"1", "Jack", "Room 101,..."}  元素的刪除:  delete(myMap, "1234")  元素的查找:  value, ok := myMap["1234"]  if ok {// 找到了  // 處理找到的value  }

流程式控制制的使用:

if a < 5 {    return 0  } else {    return 1  }   條件語句不需要使用括弧將條件包含起來();   無論語句體內有幾條語句,花括弧{}都是必須存在的;   左花括弧{必須與if或者else處於同一行;   在if之後,條件語句之前,可以添加變數初始化語句,使用;間隔;   在有返回值的函數中,不允許將「最終的」return語句包含在if...else...結構中,  否則會編譯失敗:  switch i {    case 0:      fmt.Printf("0")    case 1:      fmt.Printf("1")    case 2:      fallthrough    case 3:      fmt.Printf("3")    case 4, 5, 6:      fmt.Printf("4, 5, 6")    default:      fmt.Printf("Default")  }  在使用switch結構時,我們需要注意以下幾點:   左花括弧{必須與switch處於同一行;   條件表達式不限制為常量或者整數;   單個case中,可以出現多個結果選項;   與C語言等規則相反,Go語言不需要用break來明確退出一個case;   只有在case中明確添加fallthrough關鍵字,才會繼續執行緊跟的下一個case;  可以不設定switch之後的條件表達式,在此種情況下,整個switch結構與多個  if...else...的邏輯作用等同  sum := 0  for i := 0; i < 10; i++ {   sum += i  }  在條件表達式中也支援多重賦值,如下所示:  a := []int{1, 2, 3, 4, 5, 6}  for i, j := 0, len(a) – 1; i < j; i, j = i + 1, j – 1 {      a[i], a[j] = a[j], a[i]  }  使用循環語句時,需要注意的有以下幾點。   左花括弧{必須與for處於同一行。   Go語言中的for循環與C語言一樣,都允許在循環條件中定義和初始化變數,唯一的區別  是,Go語言不支援以逗號為間隔的多個賦值語句,必須使用平行賦值的方式來初始化多  個變數。   Go語言的for循環同樣支援continue和break來控制循環,但是它提供了一個更高級的   break,可以選擇中斷哪一個循環  goto語句的語義非常簡單,就是跳轉到本函數內的某個標籤,如:  func myfunc() {      i := 0      HERE:      fmt.Println(i)      i++      if i < 10 {        goto HERE       }  }

函數的使用:

1. 不定參數類型  不定參數是指函數傳入的參數個數為不定數量。為了做到這點,首先需要將函數定義為接受 不定參數類型:    func myfunc(args ...int) {  for _, arg := range args {          fmt.Println(arg)      }  }  如下方式調用:      myfunc(2, 3, 4)      myfunc(1, 3, 7, 13)  2. 不定參數的傳遞  假設有另一個變參函數叫做myfunc3(args ...int),下面的例子演示了如何向其傳遞變參:  func myfunc(args ...int) { // 按原樣傳遞          myfunc3(args...)  // 傳遞片段,實際上任意的int slice都可以傳進去          myfunc3(args[1:]...)      }  3. 任意類型的不定參數  之前的例子中將不定參數類型約束為int,如果你希望傳任意類型,可以指定類型為 interface{}。下面是Go語言標準庫中fmt.Printf()的函數原型:  func Printf(format string, args ...interface{}) { // ...  }

並發的使用:

在一個函數調用前加上go關鍵字,這次調用就會在一個新的goroutine中並發執行。當被調用 的函數返回時,這個goroutine也自動結束了。需要注意的是,如果這個函數有返回值,那麼這個 返回值會被丟棄

「不要通過共享記憶體來通訊,而應該通過通訊來共享記憶體。」

一般channel的聲明形式為:  var chanName chan ElementType  與一般的變數聲明不同的地方僅僅是在類型之前加了chan關鍵字。ElementType指定這個 channel所能傳遞的元素類型。舉個例子,我們聲明一個傳遞類型為int的channel:   var ch chan int   或者,我們聲明一個map,元素是bool型的channel:  var m map[string] chan bool  定義一個channel也很簡單,直接使用內置的函數make()即可: ch := make(chan int)  在channel的用法中,最常見的包括寫入和讀出。將一個數據寫入(發送)至channel的語法  很直觀,如下:  ch <- value   向channel寫入數據通常會導致程式阻塞,直到有其他goroutine從這個channel中讀取數據。   從 channel中讀取數據的語法是  value := <-ch   如果channel之前沒有寫入數據,那麼從channel中讀取數據也會導致程式阻塞,直到channel 中被寫入數據為止。   我們之後還會提到如何控制channel只接受寫或者只允許讀取,即單向 channel。
Go語言直接在語言級別支援select關鍵字,用於處理非同步IO 問題。  select的用法與switch語言非常類似,由select開始一個新的選擇塊,每個選擇條件由 case語句來描述。  與switch語句可以選擇任何可使用相等比較的條件相比,select有比較多的 限制,  其中最大的一條限制就是每個case語句里必須是一個IO操作,大致的結構如下  select {  case <-chan1:  // 如果chan1成功讀到數據,則進行該case處理語句  case chan2 <- 1:  // 如果成功向chan2寫入數據,則進行該case處理語句 d  efault:  // 如果上面都沒有成功,則進入default處理流程  }  比如如下的死循環例子:  ch := make(chan int, 1) for {  select {  case ch <- 0:  case ch <- 1: }  i := <-ch          fmt.Println("Value received:", i)      }  

增加buffer的channel

 c := make(chan int, 1024)  在調用make()時將緩衝區大小作為第二個參數傳入即可  for i := range c { fmt.Println("Received:", i)  }  利用超時機制,防止死鎖問題  // 首先,我們實現並執行一個匿名的超時等待函數  timeout := make(chan bool, 1)  go func() {  time.Sleep(1e9) // 等待1秒鐘  timeout <- true }()  // 然後我們把timeout這個channel利用起來 select {  case <-ch:  // 從ch中讀取到數據  case <-timeout:  // 一直沒有從ch中讀取到數據,但從timeout中讀取到了數據     }
單向channel變數的聲明非常簡單,如下:  var ch1 chan int // ch1是一個正常的channel,不是單向的  var ch2 chan<- float64// ch2是單向channel,只用於寫float64數據  var ch3 <-chan int // ch3是單向channel,只用於讀取int數據  ch4 := make(chan int)  ch5 := <-chan int(ch4) // ch5就是一個單向的讀取  ch6 := chan<- int(ch4) // ch6 是一個單向的寫入channel
關閉channel非常簡單,直接使用Go語言內置的close()函數即可:   close(ch)   判斷是否關閉   x, ok := <-ch

多核並行化計算任務

type Vector []float64  // 分配給每個CPU的計算任務  func (v Vector) DoSome(i, n int, u Vector, c chan int) {   for ; i < n; i++ {  v[i] += u.Op(v[i])  }  c <- 1  // 發訊號告訴任務管理者我已經計算完成了  }  const NCPU = 16  func (v Vector) DoAll(u Vector) {  c := make(chan int, NCPU) // 用於接收每個CPU的任務完成訊號  for i := 0; i < NCPU; i++ {  go v.DoSome(i*len(v)/NCPU, (i+1)*len(v)/NCPU, u, c)  }  // 等待所有CPU的任務完成  for i := 0; i < NCPU; i++ {  <-c // 獲取到一個數據,表示一個CPU計算完成了 }  // 到這裡表示所有計算已經結束 }    Go語言如果不支援多核默認的話,可以手動設置  程式碼中啟動goroutine之前先調用以下這個語句以設置使用16個CPU 核心:  runtime.GOMAXPROCS(16)
對於從全局的角度只需要運行一次的程式碼,比如全局初始化操作,Go語言提供了一個Once  類型來保證全局的唯一性操作,具體程式碼如下:  var a string  var once sync.Once  func setup() {  a = "hello, world"  }  func doprint() { once.Do(setup)  print(a) }  func twoprint() { go doprint() go doprint()  }

網路編程

socket編程:  (1) 建立Socket:使用socket()函數。  (2) 綁定Socket:使用bind()函數。  (3) 監聽:使用listen()函數。或者連接:使用connect()函數。  (4) 接受連接:使用accept()函數。  (5) 接收:使用receive()函數。或者發送:使用send()函數  Go的網路編程:  Go語言標準庫對此過程進行了抽象和封裝。無論我們期望使用什麼協議建立什麼形式的連  接,都只需要調用net.Dial()即可  TCP鏈接:      conn, err := net.Dial("tcp", "192.168.0.10:2100")  UDP鏈接:  conn, err := net.Dial("udp", "192.168.0.12:975")  ICMP鏈接(使用協議名稱):  conn, err := net.Dial("ip4:icmp", "www.baidu.com")  ICMP鏈接(使用協議編號):  conn, err := net.Dial("ip4:1", "10.0.0.3")  目前,Dial()函數支援如下幾種網路協議:"tcp"、"tcp4"(僅限IPv4)、"tcp6"(僅限 IPv6)、"udp"、"udp4"(僅限IPv4)、"udp6"(僅限IPv6)、"ip"、"ip4"(僅限IPv4)和"ip6"  (僅限IPv6)      HTTP編程  HTTP(HyperText Transfer Protocol,超文本傳輸協議)是互聯網上應用最為廣泛的一種網路 協議,  定義了客戶端和服務端之間請求與響應的傳輸標準  基本方法  net/http包的Client類型提供了如下幾個方法,讓我們可以用最簡潔的方式實現 HTTP 請求:  func (c *Client) Get(url string) (r *Response, err error)  func (c *Client) Post(url string, bodyType string, body io.Reader) (r *Response, err  error)  func (c *Client) PostForm(url string, data url.Values) (r *Response, err error) func (c *Client) Head(url string) (r *Response, err error)  func (c *Client) Do(req *Request) (resp *Response, err error)      RPC編程  RPC 採用客戶端—伺服器(Client/Server)的工作模式。請求程式就是一個客戶端(Client),  而服務提供程式就是一個伺服器(Server)。當執行一個遠程過程調用時,客戶端程式首先發送一  個帶有參數的調用資訊到服務端,然後等待服務端響應。在服務端,服務進程保持睡眠狀態直到  客戶端的調用資訊到達為止。當一個調用資訊到達時,服務端獲得進程參數,計算出結果,  並向 客戶端發送應答資訊,然後等待下一個調用。最後,客戶端接收來自服務端的應答資訊,  獲得進 程結果,然後調用執行並繼續進行  一個對象中只有滿足如下這些條件的方法,才能被 RPC 服務端設置為可供遠程訪問:   必須是在對象外部可公開調用的方法(首字母大寫);   必須有兩個參數,且參數的類型都必須是包外部可以訪問的類型或者是Go內建支援的類  型;   第二個參數必須是一個指針;   方法必須返回一個error類型的值。  以上4個條件,可以簡單地用如下一行程式碼表示:  func (t *T) MethodName(argType T1, replyType *T2) error    JSON處理  使用json.Marshal()函數可以對一組數據進行JSON格式的編碼。json.Marshal()函數 的聲明如下:  func Marshal(v interface{}) ([]byte, error)  假如有如下一個Book類型的結構體:  type Book struct {  Title string  Authors []string  Publisher string  IsPublished bool  Price float  }  並且有如下一個 Book 類型的實例對象:  gobook := Book{ "Go語言編程",  ["XuShiwei", "HughLv", "Pandaman", "GuaguaSong", "HanTuo", "BertYuan", "XuDaoli"],  "ituring.com.cn", true,  9.99  }  然後,我們可以使用 json.Marshal() 函數將gobook實例生成一段JSON格式的文本:      b, err := json.Marshal(gobook)  如果編碼成功,err 將賦於零值 nil,變數b 將會是一個進行JSON格式化之後的[]byte 類型:  b == []byte(`{  "Title": "Go語言編程",  "Authors": ["XuShiwei", "HughLv", "Pandaman", "GuaguaSong", "HanTuo", "BertYuan",               "XuDaoli"],           "Publisher": "ituring.com.cn",           "IsPublished": true,           "Price": 9.99  }`)    在Go中,JSON轉化前後的數據類型映射如下。   布爾值轉化為JSON後還是布爾類型。   浮點數和整型會被轉化為JSON裡邊的常規數字。   字元串將以UTF-8編碼轉化輸出為Unicode字符集的字元串,特殊字元比如<將會被轉義為   u003c。   數組和切片會轉化為JSON裡邊的數組,但[]byte類型的值將會被轉化為 Base64 編碼後  的字元串,slice類型的零值會被轉化為 null。   結構體會轉化為JSON對象,並且只有結構體裡邊以大寫字母開頭的可被導出的欄位才會  被轉化輸出,而這些可導出的欄位會作為JSON對象的字元串索引。 3 轉化一個map類型的數據結構時,該數據的類型必須是 map[string]T(T可以是  encoding/json 包支援的任意數據類型)。    可以使用json.Unmarshal()函數將JSON格式的文本解碼為Go裡邊預期的數據結構。 json.Unmarshal()函數的原型如下:     func Unmarshal(data []byte, v interface{}) error    在解碼JSON 數據的過程中,JSON數據裡邊的元素類型將做如下轉換:   JSON中的布爾值將會轉換為Go中的bool類型;  數值會被轉換為Go中的float64類型;   字元串轉換後還是string類型;   JSON數組會轉換為[]interface{}類型;   JSON對象會轉換為map[string]interface{}類型;  null值會轉換為nil。  在Go的標準庫encoding/json包中,允許使用map[string]interface{}和[]interface{} 類型的值來分別存放未知結構的JSON對象或數組

安全編程:

常見的單密鑰加密演算法有DES、AES、 RC4等。  私鑰和公鑰都可以被用作加密或者解密,但是用私 鑰加密的明文,必須要用對應的公鑰解密,用公鑰加密的明文,必須用對應的私鑰解密。  常見的 雙密鑰加密演算法有RSA等。  常見的哈希演算法包括MD5、SHA-1等。    證書加密HTTPS  (1) 在瀏覽器中輸入HTTPS協議的網址。  (2) 伺服器向瀏覽器返回證書,瀏覽器檢查該證書的合法性  (3) 驗證合法性  (4) 瀏覽器使用證書中的公鑰加密一個隨機對稱密鑰,並將加密後的密鑰和使用密鑰加密後 的請求URL一起發送到伺服器  (5) 伺服器用私鑰解密隨機對稱密鑰,並用獲取的密鑰解密加密的請求URL  (6) 伺服器把用戶請求的網頁用密鑰加密,並返回給用戶  (7) 用戶瀏覽器用密鑰解密伺服器發來的網頁數據,並將其顯示出來  SSL協議由兩層組成,上層協議包括SSL握手協議、更改密碼規格協議、警報協議,下層協 議包括SSL記錄協議。  SSL握手協議建立在SSL記錄協議之上,在實際的數據傳輸開始前,用於在客戶與伺服器之 間進行「握手」。「握手」是一個協商過程。這個協議使得客戶和伺服器能夠互相鑒別身份,協商 加密演算法。在任何數據傳輸之前,必須先進行「握手」。  在「握手」完成之後,才能進行SSL記錄協議,它的主要功能是為高層協議提供數據封裝、 壓縮、添加MAC、加密等支援

錯誤處理策略:

//1、將錯誤返回給調用者  resp, err := http.Get(url)  if err != nil{  return nill, err  }  //2、將構造新的資訊返回給調用者  doc, err := html.Parse(resp.Body)  resp.Body.Close()  if err != nil {  return nil, fmt.Errorf("parsing %s as HTML: %v", url,err)   }   //3、如果錯誤發生後,程式無法繼續運行,我們就可以採用第三種策略:輸出錯誤資訊並結束程式。   //需 要注意的是,這種策略只應在main中執行。對庫函數而言,應僅向上傳播錯誤,除非該錯誤意味著   //程式內部包含不一致性,即遇到了bug,才能在庫函數中結束程式。  if err := WaitForServer(url); err != nil {   fmt.Fprintf(os.Stderr, "Site is down: %vn", err)   os.Exit(1)  }  //4、第四種策略:有時,我們只需要輸出錯誤資訊就足夠了,不需要中斷程式的運行。我們可以通過 log包提供函數  if err := Ping(); err != nil {  log.Printf("ping failed: %v; networking disabled",err)  }  //或者標準錯誤流輸出錯誤資訊。  if err := Ping(); err != nil {  fmt.Fprintf(os.Stderr, "ping failed: %v; networking disabledn", err)  }  //5、第五種,也是最後一種策略:我們可以直接忽略掉錯誤。  dir, err := ioutil.TempDir("", "scratch") if err != nil {  return fmt.Errorf("failed to create temp dir: %v",err)  }  // ...use temp dir...  os.RemoveAll(dir)  //儘管os.RemoveAll會失敗,但上面的例子並沒有做錯誤處理。  //這是因為作業系統會定期的清理臨時 目錄。正因如此,雖然程式沒有處理錯誤,  //但程式的邏輯不會因此受到影響。我們應該在每次函數 調用後,都養成考慮錯誤處理的習慣,  //當你決定忽略某個錯誤時,你應該在清晰的記錄下你的意 圖。 

資料庫編程:

import "database/sql"  func listTracks(db sql.DB, artist string, minYear, maxYear int) {  result, err := db.Exec(  "SELECT * FROM tracks WHERE artist = ? AND ? <= year AND year <= ?",  artist, minYear, maxYear)  // ...  }  Exec方法使用SQL字面量替換在查詢字元串中的每個'?';SQL字面量表示相應參數的值,  它有可能 是一個布爾值,一個數字,一個字元串,或者nil空值。用這種方式構造查詢可以幫助避免SQL注入 攻擊;    如果希望通過類型開關來控制入參可以使用如下方式:  func sqlQuote(x interface{}) string {  switch x := x.(type) {  case nil:    return "NULL"  case int, uint:    return fmt.Sprintf("%d", x) // x has type interface{} here. case bool:    if x {    return "TRUE"    }    return "FALSE"  case string:    return sqlQuoteString(x) // (not shown) default:  panic(fmt.Sprintf("unexpected type %T: %v", x, x)) }  }

測試編程:

  在 *_test.go 文件中,有三種類型的函數:測試函數、基準測試(benchmark)函數、示例函數。  一 個測試函數是以Test為函數名前綴的函數,用於測試程式的一些邏輯行為是否正確;  go test命令會 調用這些測試函數並報告測試結果是PASS或FAIL。  基準測試函數是以Benchmark為函數名前綴的 函數,它們用于衡量一些函數的性能;  go test命令會多次運行基準函數以計算一個平均的執行時 間。  示例函數是以Example為函數名前綴的函數,提供一個由編譯器保證正確性的示例文檔。    go test命令會遍歷所有的*_test.go文件中符合上述命名規則的函數,生成一個臨時的main包用於  調用相應的測試函數,接著構建並運行、報告測試結果,最後清理測試中生成的臨時文件。    

測試函數:

每個測試函數必須導入testing包。測試函數有如下的簽名:

func TestName(t *testing.T) { // ...  }  測試函數的名字必須以Test開頭,可選的後綴名必須以大寫字母開頭:  func TestLog(t *testing.T) { /* ... */ }  其中t參數用於報告測試失敗和附加的日誌資訊。  比如如下例子:  // Package word provides utilities for word games.  package word  // IsPalindrome reports whether s reads the same forward and backward. // (Our first attempt.)  func IsPalindrome(s string) bool {  for i := range s {  if s[i] != s[len(s)‐1‐i] {  return false }  }  return true }    在相同的目錄下,創建word_test.go測試文件  package word  import "testing"  func TestPalindrome(t *testing.T) {  if !IsPalindrome("detartrated") {  t.Error(`IsPalindrome("detartrated") = false`) }  if !IsPalindrome("kayak") {  t.Error(`IsPalindrome("kayak") = false`)  } }  func TestNonPalindrome(t *testing.T) {  if IsPalindrome("palindrome") {  t.Error(`IsPalindrome("palindrome") = true`) }  }  參數 ‐v 可用於列印每個測試函數的名字和運行時間:  go test ‐v  === RUN TestPalindrome  ‐‐‐ PASS: TestPalindrome (0.00s)  === RUN TestNonPalindrome  ‐‐‐ PASS: TestNonPalindrome (0.00s) === RUN TestFrenchPalindrome  ‐‐‐ FAIL: TestFrenchPalindrome (0.00s)  word_test.go:28: IsPalindrome("été") = false === RUN TestCanalPalindrome  ‐‐‐ FAIL: TestCanalPalindrome (0.00s)  word_test.go:35: IsPalindrome("A man, a plan, a canal: Panama") = false FAIL  exit status 1  FAIL gopl.io/ch11/word1 0.017s    參數‐run對應一個正則表達式,只有測試函數名被它正確匹配的測試函數才會被go test測試命令 運行:   go test ‐v ‐run="French|Canal"  === RUN TestFrenchPalindrome  ‐‐‐ FAIL: TestFrenchPalindrome (0.00s)  word_test.go:28: IsPalindrome("été") = false === RUN TestCanalPalindrome  ‐‐‐ FAIL: TestCanalPalindrome (0.00s)  word_test.go:35: IsPalindrome("A man, a plan, a canal: Panama") = false FAIL  exit status 1  FAIL gopl.io/ch11/word1 0.014s

報告的輸出:

$ go tool cover  Usage of 'go tool cover':  Given a coverage profile produced by 'go test':  go test ‐coverprofile=c.out  Open a web browser displaying annotated source code: go tool cover ‐html=c.out  ...

基準測試:

基準測試是測量一個程式在固定工作負載下的性能。在Go語言中,基準測試函數和普通測試函數 寫法類似,  但是以Benchmark為前綴名,並且帶有一個 *testing.B 類型的參數;  *testing.B 參數除 了提供和 *testing.T 類似的方法,還有額外一些和性能測量相關的方法。  它還提供了一個整數N, 用於指定操作執行的循環次數。  下面是IsPalindrome函數的基準測試,其中循環將執行N次。   import "testing"  func BenchmarkIsPalindrome(b *testing.B) {  for i := 0; i < b.N; i++ {  IsPalindrome("A man, a plan, a canal: Panama") }  }  $ go test ‐cpuprofile=cpu.out  $ go test ‐blockprofile=block.out  $ go test ‐memprofile=mem.out      go test ‐run=NONE ‐bench=ClientServerParallelTLS64  ‐cpuprofile=cpu.log net/http  PASS  BenchmarkClientServerParallelTLS64‐8 1000  3141325 ns/op 143010 B/op 1747 allocs/op ok net/http 3.395s  $ go tool pprof ‐text ‐nodecount=10 ./http.test cpu.log 2570ms of 3590ms total (71.59%)  Dropped 129 nodes (cum <= 17.95ms)  Showing top 10 nodes out of 166 (cum >= 60ms)

示例函數:

第三種被go test特別對待的函數是示例函數,以Example為函數名開頭。示例函數沒有函數參數  和返回值。  func ExampleIsPalindrome() {  fmt.Println(IsPalindrome("A man, a plan, a canal: Panama")) fmt.Println(IsPalindrome("palindrome"))  // Output:  // true  // false  }  示例函數有三個用處。  最主要的一個是作為文檔:  一個包的例子可以更簡潔直觀的方式來演示函數 的用法,比文字描述更直接易懂,特別是作為一個提醒或快速參考時。  一個示例函數也可以方便展 示屬於同一個介面的幾種類型或函數之間的關係,所有的文檔都必須關聯到一個地方,就像一個類  型或函數聲明都統一到包一樣。同時,示例函數和注釋並不一樣,示例函數是真實的Go程式碼,需 要接受編譯器的編譯時檢查,  這樣可以保證源程式碼更新時,示例程式碼不會脫節。  示例文檔的第二個用處是,在 執行測試的時候也會運行示例函數測試。如果示例函數內含 有類似上面例子中的 格式的注釋,  那麼測試工具會執行這個示例函數,然後檢查示例函 數的標準輸出與注釋是否匹配。  示例函數的第三個目的提供一個真實的演練場。 http://golang.org 就是由godoc提供的文檔服務

本文的內容來源於Go語言編程和Go程式設計語言,如果有需要可以購買書籍查看。

後續會持續更新Go語言的生態內容,如果有需要可以關注。或者百度搜索wolf_love666。