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。