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。