专栏首页小诚信驿站Go的代码规范指南-新人必看
原创

Go的代码规范指南-新人必看

想要坏坏的蓝猫,可以微信表情包搜索:墩墩猫

项目约定:

环境设置:*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: %v\n", 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 disabled\n", 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。

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 通过 Git 推算程序员大佬作息,这波操作有点秀!

    俄罗斯程序员 Ivan Bessarabov 根据大佬们在 git 上的工作时间推算出他们的作息时间。我们查看了他们的工作时间以及照片,只能羡慕地说:同样是熬夜...

    GitHubDaily
  • 除了FastJson,你还有选择: Gson简易指南

    前几天被几个技术博主的同一篇公众号文章 fastjson又被发现漏洞,这次危害可导致服务瘫痪! 刷屏,离之前漏洞事件没多久,fastjson 又出现严重 Bu...

    闻人的技术博客
  • ChromeDriver与Chrome浏览器版本对应表

    http://npm.taobao.org/mirrors/chromedriver/

    上帝De助手
  • 谷歌peering.google.com中的本地文件包含(LFI)漏洞

    众所周知,本地文件包含漏洞(LFI)可以造成信息泄露甚至入侵系统,即使其中的包含代码不具备执行权限,但攻击者也可以从中获取一些深入渗透目标系统的有价值信息。该篇...

    FB客服
  • Go 语言并发编程系列(十一)—— sync 包系列:条件变量

    简介 sync 包还提供了一个条件变量类型 sync.Cond,它可以和互斥锁或读写锁(以下统称互斥锁)组合使用,用来协调想要访问共享资源的线程。

    学院君
  • Go语言实践篇之MongoDB

    关于MongoDB数据的基本介绍与环境搭建相关知识,可参见我的另一篇文章 文档数据库 MongoDB

    arcticfox
  • 【转】go get命令使用socket代理

    由于某些不可描述的原因,国内使用go get命令安装某些包的时候会超时导致失败,比如net包、sys包、tools包等。第一种解决办法就是自己从git上下载后添...

    yiduwangkai
  • 【转】Go 语言函数

    Go 语言标准库提供了多种可动用的内置的函数。例如,len() 函数可以接受不同类型参数并返回该类型的长度。如果我们传入的是字符串则返回字符串的长度,如果传入的...

    yiduwangkai
  • 【k8s开发必备技能】使用client-go包访问Kubernetes CRD

    Kubernetes API服务器可通过自定义资源定义轻松扩展。但是,用client-go库访问这些资源有点麻烦,官方也没有完整的文档。如kubebuilder...

    sealyun
  • Golang error 的突围

    姗姗来迟的 Go 1.13 修改了 errors 包,增加了几个函数,用于增强 error 的功能,这篇文章介绍 error 相关的用法。

    梦醒人间

扫码关注云+社区

领取腾讯云代金券