A Bite of GoLang(中)

5. 函数式编程

5.0、函数式编程

Go语言作为一个通用型语言,它对函数式编程主要体现在闭包上面。

1、函数式编程 VS 函数指针

  • 函数是一等公民:参数、变量、返回值都可以是函数,在别的语言中大多不是这样的,比如在C++里面只有函数指针,在Java里面我们只能调用,不能把函数传给别人。
  • 高阶函数:参数可以是函数,1.6.3里面的apply函数就是一个高阶函数。
  • 函数 --> 闭包:首先用个例子来了解一下闭包的用法
package main

import "fmt"

func adder() func(int) int {
	sum := 0
	return func(v int) int {
		sum += v
		return sum
	}
}

func main() {

	a := adder()
	for i := 0; i < 10; i ++  {
		fmt.Printf("0 + 1 + 2 + ... + %d = %d\n", i, a(i))
	}
}

结果输出为

0 + 1 + 2 + ... + 0 = 0
0 + 1 + 2 + ... + 1 = 1
0 + 1 + 2 + ... + 2 = 3
0 + 1 + 2 + ... + 3 = 6
0 + 1 + 2 + ... + 4 = 10
0 + 1 + 2 + ... + 5 = 15
0 + 1 + 2 + ... + 6 = 21
0 + 1 + 2 + ... + 7 = 28
0 + 1 + 2 + ... + 8 = 36
0 + 1 + 2 + ... + 9 = 45

上述的 v 就称为局部变量, sum 称为自由变量,`func(v int) int {

	sum += v
	return sum
}` 称为函数体,整个就叫做一个闭包。用一张图来概括就是:

2、“正统”函数式编程

  • 不可变性:不能有状态,只有常量和函数;当然这和平常的函数不一样,连变量都没有,甚至连选择语句、循环语句都没有。
  • 函数只能有一个参数

要是上面的累加想做一个稍微正统函数怎么做呢?

type iAdder func(int) (int, iAdder)

func adderV2(base int) iAdder {
	return func(v int) (int, iAdder) {
		return base + v, adderV2(base + v)
	}
}

func main() {

	a := adderV2(0)
	for i := 0; i < 10; i ++ {
		var s int
		s, a = a(i)
		fmt.Printf("0 + 1 + 2 + ... + %d = %d\n", i, s)
	}
}

当然正统的不一定是最好的,正统式的写法经常导致代码的可读性变得不是很好。

5.1、函数式编程实例演示

1、斐波那契数列

Go语言的官方案列中,对闭包的讲解通过一个常见的例子:斐波那契数列,为了更好的理解闭包的感念,那这里我们就将这个例子再来演示一遍

func Fibonacci() func() int {

	a, b := 0, 1
	return func() int {
		a, b = b, a+b
		return a
	}
}

比如我们要打印这一串斐波那契数列,我们就需要不停的调用上面的斐波那契数列的生成器。

f := Fibonacci()

f() // 1
f() // 1
f() // 2
f() // 3
f() // 5
f() // 8

2、为函数实现接口

这个斐波那契数列的调用的生成器跟文件有点像,我们可以把它包装成一个 io.Reader 这样就跟打印一个文件一样生成这个斐波那契数列。

首先我们先定义我们的类型 func() int ,就取名Generate好了

type Generate func() int

同时需要将 Fibonacci() 函数的类型改掉

func Fibonacci() Generate {

	a, b := 0, 1
	return func() int {
		a, b = b, a+b
		return a
	}
}

是一个类型就可以实现接口,这就是Go语言灵活的地方,下一步我们实现这个Reader接口

func (gen Generate) Read(p []byte) (n int, err error) {

	nextNum := gen()
	numString := fmt.Sprintf("%d \n", nextNum)

	return strings.NewReader(numString).Read(p)
}

这里我们会发现函数也可以实现接口,这就是Go语言的不一样的地方,因为函数是一等公民,它既可以作为参数,也可以作为接收者。首先我们要先取到下一个元素 nextNum ,然后将下一个元素写进 p 。然后我们直接用一个写好的文件打印的函数

func printFileContents(reader io.Reader) {
	scanner := bufio.NewScanner(reader)

	for scanner.Scan() {
		fmt.Println(scanner.Text())
	}
}

最后我们就可以直接调用了

func main() {

	f := Fibonacci()
	printFileContents(f)
}

当然,上述的代码是存在瑕疵的,比如这个 printFileContents 函数会一直读下去,就变成一个死循环了,我们需要设置其终止条件。比如上面的 p 太小的话,只读了一半,当然这边就留给读者后期拓展了。

6. 错误处理和资源管理

我们实际的代码不止 Hello World ,我们的代码是要运行在服务器上的,要和各种各样的用户进行交互,所以我们这里就要了解一下Go语言的资源管理和出错处理。

6.0、defer调用

1、确保在函数结束时调用

比如一个常见的代码

package main

import "fmt"

func main() {

	fmt.Println(1)
	fmt.Println(2)
}

我要是想要让1在2后面输出该如何做呢?你说调换一下顺序呗,道理我都懂,但是我们今天要介绍的不是这个,我们只需要在打印1之前加一个 defer 就可以了

package main

import "fmt"

func main() {

	defer fmt.Println(1)
	fmt.Println(2)
}

要是有多个defer呢?它的输出顺序又是什么样的呢?

package main

import "fmt"

func main() {

	defer fmt.Println(1)
	defer fmt.Println(2)
	fmt.Println(3)
}

上面这段代码,输出的结果又是什么?

3
2
1

这里我们可以发现 defer 的调用实际是一个栈,先进后出。当然 defer 的最大的好处是什么呢?就是当程序中间有return返回甚至panic的时候,依然不影响 defer 后面的代码的执行。

package main

import "fmt"

func main() {

	defer fmt.Println(1)
	defer fmt.Println(2)
	fmt.Println(3)
	panic("whoops, something went wrong....")
	fmt.Println(4)
}

上述的代码在panic之后,1 2 依然能够正常输出。

2、场景演示

当然说了这么多,我们在代码中常见的使用defer的场景有哪些呢?比如我们创建文件,写文件这些,过去我们用别的语言经常会在处理文件的最后释放句柄,因为中间隔了很多的文件操作,经常可能会忘记释放句柄。那Go语言就针对这样的场景做了非常好的优化,通过defer关键字实现,下面我们就通过一个简单的写文件事例来演示一下:

package main

import (
	"os"
	"bufio"
	"fmt"
)

func main() {

	filename := "demo.txt"
	file, err := os.Create(filename)
	if err != nil {
		panic(err)
	}
	defer file.Close()

	writer := bufio.NewWriter(file)
	defer writer.Flush()

	fmt.Fprintln(writer, "你好,杭州")
}

一个完整的事例就演示到这边,比如常见的 Open/Close、Lock/Unlock这些成对出现的都可以使用 defer

6.1、错误处理概念

因为在我们实际的程序中,有错直接panic中断程序执行,这时非常不友好的,通常我们会对其出错处理。比如上面的事例中 os.Create 函数返回的 err 不为 nil 的时候,我们需要做一个出错处理,

filename := "demo.txt"
file, err := os.Create(filename)
if err != nil {
	fmt.Println(err.Error())
	return 
}

我们可以直接打印出相关的错误信息,然后直接返回。这就是常见的错误处理方式之一,当然在函数内部也可以将错误信息直接作为结果返回。

6.2、panic和recover

1、panic

  • 停止当前函数执行

panic和我们其他语言的throw exception很像

  • 一直向上返回,执行每一层的defer

当然相对还是友好的,每层的defer还是会用到,一层一层的返回,返回到最后程序就会自动退出了

  • 如果没有遇见recover,程序退出

2、recover

  • 仅在defer调用中使用
  • 获取panic的值
  • 如果无法处理,可充新panic

主要的特性就可以用上述几句话概括,为了更好的理解上述的概念,下面用一个简短的代码来学以致用

func tryDefer() {
	for i := 0; i < 100; i++ {
		defer fmt.Println(i)
		if i == 10 {
			panic("就打印到这吧")
		}
	}
}

上面就是一个 panic 和 defer 的结合使用,他的输出结果会是什么样的呢?

10
9
8
7
6
5
4
3
2
1
0
panic: 就打印到这吧

goroutine 1 [running]:
main.tryDefer()
	/Users/verton/GoLangProject/src/shengguocun.com/web/web.go:11 +0x11d
main.main()
	/Users/verton/GoLangProject/src/shengguocun.com/web/web.go:18 +0x20

从上述输出结果我们可以看到panic的前两个特性,那结合recover又会是什么样的呢?

// The recover built-in function allows a program to manage behavior of a
// panicking goroutine. Executing a call to recover inside a deferred
// function (but not any function called by it) stops the panicking sequence
// by restoring normal execution and retrieves the error value passed to the
// call of panic. If recover is called outside the deferred function it will
// not stop a panicking sequence. In this case, or when the goroutine is not
// panicking, or if the argument supplied to panic was nil, recover returns
// nil. Thus the return value from recover reports whether the goroutine is
// panicking.
func recover() interface{}
func tryRecover() {
	defer func() {
		r := recover()
		if err, ok := r.(error); ok {
			fmt.Println("错误信息: ", err)
		} else {
			panic(r)
		}
	}()

	panic(errors.New("这是一个 error"))
}

从上面我们可以看到 recover 是一个interface, 所以在判断的时候需要判断 r 是否是一个 error,结果自然会是输出

错误信息:  这是一个 error

那我们再用一个实际一点的例子来测试一下,比如除数为0的例子

func tryRecover() {
	defer func() {
		r := recover()
		if err, ok := r.(error); ok {
			fmt.Println("错误信息: ", err)
		} else {
			panic(r)
		}
	}()

	b := 0
	a := 5 / b
	fmt.Println(a)
}

结果输出

错误信息:  runtime error: integer divide by zero

上面的两个例子简单介绍了panic、recover的基本使用,下面通过一个稍微实际一点的例子来综合讲述一下一般的项目中是如何统一处理错误的。

6.3、服务器统一出错处理

现在呢我们就通过一个Http服务来展开如何统一处理服务器出错这件事,结合一个实际读取目录内文件的例子来简单介绍一下

func main() {
	
	http.HandleFunc("/list/",
		func(writer http.ResponseWriter, request *http.Request) {

		path := request.URL.Path[len("/list/"):]
		file, err := os.Open(path)
		if err != nil {
			panic(err)
		}
		defer  file.Close()

		all, err := ioutil.ReadAll(file)
		if err != nil {
			panic(err)
		}

		writer.Write(all)
	})

	err := http.ListenAndServe(":2872", nil)
	if err != nil {
		panic(err)
	}
}

因为在GOPATH下有一个 demo.txt 文件,浏览器输入一下地址 http://localhost:2872/list/demo.txt ,浏览器正确输出结果

万一我访问一个不存在的文件呢?会得到什么样的结果,比如我现在访问 http://localhost:2872/list/demo.txts GOPATH目录下没有demo.txts文件,自然你会想到会panic一个错误

2018/05/04 17:08:54 http: panic serving [::1]:51946: open demo.txts: no such file or directory
goroutine 5 [running]:
net/http.(*conn).serve.func1(0xc4200968c0)
	/usr/local/Cellar/go/1.10.2/libexec/src/net/http/server.go:1726 +0xd0
panic(0x124fde0, 0xc420086fc0)
	/usr/local/Cellar/go/1.10.2/libexec/src/runtime/panic.go:502 +0x229
main.main.func1(0x12d1420, 0xc42010e000, 0xc42010a000)
	/Users/verton/GoLangProject/src/shengguocun.com/web/web.go:52 +0x144
net/http.HandlerFunc.ServeHTTP(0x12aff28, 0x12d1420, 0xc42010e000, 0xc42010a000)
	/usr/local/Cellar/go/1.10.2/libexec/src/net/http/server.go:1947 +0x44
net/http.(*ServeMux).ServeHTTP(0x140b3e0, 0x12d1420, 0xc42010e000, 0xc42010a000)
	/usr/local/Cellar/go/1.10.2/libexec/src/net/http/server.go:2337 +0x130
net/http.serverHandler.ServeHTTP(0xc420089110, 0x12d1420, 0xc42010e000, 0xc42010a000)

从上面的部分的报错信息来看,

相关的错误信息都是 /usr/local/Cellar/go/1.10.2/libexec/src/net/http/server.goserve 函数报出的,具体是哪一步报出的我就不细说了,有兴趣的可以自己按照例子自己查阅相关的源码,说到这那错误统一处理又是如何处理呢?

我们先把第一个panic替换成

path := request.URL.Path[len("/list/"):]
file, err := os.Open(path)
if err != nil {

	http.Error(writer, err.Error(), http.StatusInternalServerError)
	return 
}

我们再来访问上述地址

相比之前,提示稍微友好一点了,但是这对用户来讲还是不合适的,直接将程序内部错误信息输出给用户有些欠妥。我们可以包装成一个外部的Error,首先我们先定义一个函数appHandler, 返回一个error

type appHandler func(writer http.ResponseWriter, request *http.Request) error

然后定义一个 errWrapper 函数, 返回一个handler 里面需要的函数

type appHandler func(writer http.ResponseWriter, request *http.Request) error

func errWrapper(handler appHandler) func(http.ResponseWriter, *http.Request) {

	return func(writer http.ResponseWriter, request *http.Request) {

		err := handler(writer, request)
		if err != nil {
			switch {

    			case os.IsNotExist(err):
    				http.Error(writer, http.StatusText(http.StatusNotFound), http.StatusNotFound)
			}
		}
	}
}

然后将writer和request传进handler,通过switch判断err的类型,做一个统一的返回处理;这时我们需要将原来的业务逻辑的代码稍微做一下调整,

http.HandleFunc("/list/",
		errWrapper(func(writer http.ResponseWriter, request *http.Request) error {
			path := request.URL.Path[len("/list/"):]
			file, err := os.Open(path)
			if err != nil {
				return err
			}
			defer  file.Close()

			all, err := ioutil.ReadAll(file)
			if err != nil {
				return err
			}

			writer.Write(all)
			return nil
		}))

err := http.ListenAndServe(":2872", nil)
if err != nil {
	panic(err)
}

http.HandleFunc 的第二个参数我们需要改为 errWrapper 同时将原来的函数作为参数传进去,当然这个函数为了代码的可读性应该单独抽离出来,相应的返回直接返回error就可以了,这时候我们再去访问之前的一个不存在的URL

这时候的错误就明显友好了很多,讲到这就是一个简单的统一错误处理的思路。

7. 测试和性能调优

7.0、测试

1、传统测试 VS 表格驱动测试

测试的作用对于一个软件行业从业者而言都是毋庸置疑的,Go语言在测试这块它有自己独特的见解,下面我们先介绍一下这两种模式下的测试

传统测试
  • 测试数据和测试逻辑混在一起
  • 出错信息不明确
  • 一旦一个数据出错测试全部结束

下面我们简单的举个例子:

public function testCase(){

    assertEquals(2, add(1, 1));
    assertEquals(1, add(1, 3));
    assertEquals(0, add(1, -1));
}

很明显上面的几个特征它都占了,那下面我们来看一段Go语言的测试case

tests := []struct{
    a, b, c int32
}{
    {2, 1, 1},
    {1, 1, 3},
    {0, 1, -1},
}
for _, test := range tests {
    if act := add(test.a, test.b); act != test.c {
        // 相应的错误处理...
    }
}

上述就是一个典型的表格驱动测试

表格驱动测试
  • 分离测试数据和测试逻辑
  • 明确的出错信息
  • 可以部分失败

Go语言的语法使得我们更容易使用表格驱动测试的测试模式

2、实例演示

说了这么多,我们通常又是如何写测试用例呢?首先下面是一段加法的代码

package calculator

func Add(a, b int32) int32 {

	return a + b
}

现在就写上面的函数的测试用例

package calculator

import (
	"testing"
)

func TestAdd(t *testing.T) {

	tests := []struct{
		a, b, c int32
	}{
		{1, 1, 2},
		{-1, 1, 0},
	}

	for _, test := range tests {
		if act := Add(test.a, test.b); act != test.c {

			t.Errorf("%d + %d != %d 实际为 %d", test.a, test.b, test.c, act)
		}
	}
}

用IDE的同学直接点击 Run Test 就可以了,当然也同样支持命令行运行,进入到指定的文件目录下面

sheng$ go test ./
ok  	shengguocun.com/functional/calculator	0.006s

运行相关的执行命令就可以了,要是有错误的case依然不影响相关的测试的执行,比如:

package calculator

import (
	"testing"
	"math"
)

func TestAdd(t *testing.T) {

	tests := []struct{
		a, b, c int32
	}{
		{1, 1, 2},
		{-1, 1, 0},
		{math.MaxInt32, 1, math.MaxInt32},
	}

	for _, test := range tests {
		if act := Add(test.a, test.b); act != test.c {

			t.Errorf("%d + %d != %d 实际为 %d", test.a, test.b, test.c, act)
		}
	}
}

测试用例的执行结果为

sheng$ go test ./
--- FAIL: TestAdd (0.00s)
	add_test.go:21: 2147483647 + 1 != 2147483647 实际为 -2147483648
FAIL
FAIL	shengguocun.com/functional/calculator	0.006s

我们需要将不符合预期的case做出检查,看是否是代码逻辑有问题,还是case的问题,这就是一个完整的测试用例的编写的过程。

7.1、代码覆盖率和性能测试

1、代码覆盖率

用IDE的同学我们会发现点击 Run Test 按钮的时候还有一个 with coverage 的选项

=== RUN   TestAdd
--- PASS: TestAdd (0.00s)
PASS
coverage: 100.0% of statements

Process finished with exit code 0

这就是一个测试用例的代码覆盖率的结果。

IDE这块有详细的覆盖率报告,可以看到左侧的绿色就是代码的覆盖的范围,右侧有详细的每个文件的覆盖率。当然除了IDE之外命令行也是同样支持的

sheng$ go test -coverprofile=a.out
PASS
coverage: 100.0% of statements
ok  	shengguocun.com/functional/calculator	0.006s

直接查看这个 a.out 文件,似乎看得不是很明白,当然我们有一个工具叫 go tool cover

sheng$ go tool cover -html=a.out

运行上面的命令,就会展现一个下面的静态页面

这就是一个详细的覆盖率报告

2、性能测试

对于程序员而言,代码的性能是每个人都会去关注的,Go语言在性能测试这块依然有它的独特见解 Benchmark

func BenchmarkAdd(b *testing.B) {

	aa := int32(math.MaxInt32 / 16)
	bb := int32(math.MaxInt32 / 16)
	cc := int32(math.MaxInt32 / 8) - 1

	for i := 0; i < b.N; i ++ {

		act := Add(aa, bb)
		if act != cc {

			b.Errorf("%d + %d != %d 实际为 %d",
				aa, bb, cc, act)
		}
	}
}

上面就是一段性能测试代码,我们不需要关注这段代码具体要跑多少次,Go语言自身会帮你决定,IDE点击 Run Test 完,输出相关的结果

goos: darwin
goarch: amd64
pkg: shengguocun.com/functional/calculator
2000000000	         0.35 ns/op
PASS

Process finished with exit code 0

总共跑了多少次以及每次的平均耗时,都会给出结果。当然同样支持命令行的交互方式

sheng$ go test -bench .
goos: darwin
goarch: amd64
pkg: shengguocun.com/functional/calculator
BenchmarkAdd-4   	2000000000	         0.34 ns/op
PASS
ok  	shengguocun.com/functional/calculator	0.721s

7.2、使用pprof进行性能调优

上面我们刚提到了性能测试,下一步自然就是我们该如何优化代码的性能,这里我们需要介绍一下Go语言的性能分析工具 pprof ,就依然用上面的这个例子进行阐述它的基本用法,我们要是想了解一段代码具体它慢在哪里,首先呢我们先让它生成一个cpuprofile

sheng$ go test -bench . -cpuprofile=cpu.out
goos: darwin
goarch: amd64
pkg: shengguocun.com/functional/calculator
BenchmarkAdd-4   	2000000000	         0.34 ns/op
PASS
ok  	shengguocun.com/functional/calculator	0.916s

这时候我们发现现在多了一个 cpu.out 文件

sheng$ ls
a.out		add.go		add_test.go	calculator.test	cpu.out

查看之后你会发现是一个二进制文件,那我们该如何处理呢?Go语言的 pprof 就要登场了

sheng$ less cpu.out
"cpu.out" may be a binary file.  See it anyway?
sheng$ go tool pprof cpu.out
Main binary filename not available.
Type: cpu
Time: May 9, 2018 at 5:40pm (CST)
Duration: 907.82ms, Total samples = 600ms (66.09%)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof)

这时候出现了一个交互式的命令行,我们可以通过输入 help 得到相关的使用说明

(pprof) help
  Commands:
    callgrind        Outputs a graph in callgrind format
    comments         Output all profile comments
    disasm           Output assembly listings annotated with samples
    dot              Outputs a graph in DOT format
    eog              Visualize graph through eog
    evince           Visualize graph through evince
    gif              Outputs a graph image in GIF format
    gv               Visualize graph through gv
    kcachegrind      Visualize report in KCachegrind
    list             Output annotated source for functions matching regexp
    pdf              Outputs a graph in PDF format
    peek             Output callers/callees of functions matching regexp
    png              Outputs a graph image in PNG format
    proto            Outputs the profile in compressed protobuf format
    ps               Outputs a graph in PS format
    raw              Outputs a text representation of the raw profile
    svg              Outputs a graph in SVG format
    tags             Outputs all tags in the profile
    text             Outputs top entries in text form
    top              Outputs top entries in text form
    topproto         Outputs top entries in compressed protobuf format
    traces           Outputs all profile samples in text form
    tree             Outputs a text rendering of call graph
    web              Visualize graph through web browser
    weblist          Display annotated source in a web browser
    o/options        List options and their current values
    quit/exit/^D     Exit pprof

  Options:
    call_tree        Create a context-sensitive call tree
    compact_labels   Show minimal headers
    divide_by        Ratio to divide all samples before visualization
    drop_negative    Ignore negative differences
    edgefraction     Hide edges below <f>*total
    focus            Restricts to samples going through a node matching regexp
    hide             Skips nodes matching regexp
    ignore           Skips paths going through any nodes matching regexp
    mean             Average sample value over first value (count)
    nodecount        Max number of nodes to show
    nodefraction     Hide nodes below <f>*total
    normalize        Scales profile based on the base profile.
    output           Output filename for file-based outputs
    positive_percentages Ignore negative samples when computing percentages
    prune_from       Drops any functions below the matched frame.
    relative_percentages Show percentages relative to focused subgraph
    sample_index     Sample value to report (0-based index or name)
    show             Only show nodes matching regexp
    source_path      Search path for source files
    tagfocus         Restricts to samples with tags in range or matched by regexp
    taghide          Skip tags matching this regexp
    tagignore        Discard samples with tags in range or matched by regexp
    tagshow          Only consider tags matching this regexp
    trim             Honor nodefraction/edgefraction/nodecount defaults
    unit             Measurement units to display

  Option groups (only set one per group):
    cumulative
      cum              Sort entries based on cumulative weight
      flat             Sort entries based on own weight
    granularity
      addresses        Aggregate at the function level.
      addressnoinlines Aggregate at the function level, including functions' addresses in the output.
      files            Aggregate at the file level.
      functions        Aggregate at the function level.
      lines            Aggregate at the source code line level.
      noinlines        Aggregate at the function level.
  :   Clear focus/ignore/hide/tagfocus/tagignore

  type "help <cmd|option>" for more information
(pprof)

我们这里就介绍一个最简单的方式,敲入web回车,z这里做一个温馨提示

(pprof) web
Failed to execute dot. Is Graphviz installed? Error: exec: "dot": executable file not found in $PATH

出现上述报错的,是因为Graphviz没有安装,安装好了之后再敲入web会生成一个SVG文件,用浏览器打开它

一张图可以很明显的表现出哪边花的时间多哪边花的时间少,当然也可以从框框的大小来做判断,我们需要优化比较大的框框的部分。上述的代码因为太过于简单,大家可以试着用自己写的代码进行性能分析。

7.3、生成文档和事例代码

在我们实际的开发过程中,文档的重要性不必多说,服务调用方、协同开发的小伙伴、QA都需要文档;其他的语言我们经常需要依赖其他的文档工具,比如:ApiDoc、doxmate、daux等等。

首先我们先介绍一下 go doc 的常规的用法

sheng$ go doc
package calculator // import "shengguocun.com/functional/calculator"

func Add(a, b int32) int32
sheng$ go doc Add
func Add(a, b int32) int32

除此之外呢,我们可以通过help来查看

sheng$ go help doc
usage: go doc [-u] [-c] [package|[package.]symbol[.methodOrField]]

Doc prints the documentation comments associated with the item identified by its
arguments (a package, const, func, type, var, method, or struct field)
followed by a one-line summary of each of the first-level items "under"
that item (package-level declarations for a package, methods for a type,
etc.).

Doc accepts zero, one, or two arguments.

Given no arguments, that is, when run as

	go doc

it prints the package documentation for the package in the current directory.
If the package is a command (package main), the exported symbols of the package
are elided from the presentation unless the -cmd flag is provided.

When run with one argument, the argument is treated as a Go-syntax-like
representation of the item to be documented. What the argument selects depends
on what is installed in GOROOT and GOPATH, as well as the form of the argument,
which is schematically one of these:

	go doc <pkg>
	go doc <sym>[.<methodOrField>]
	go doc [<pkg>.]<sym>[.<methodOrField>]
	go doc [<pkg>.][<sym>.]<methodOrField>

The first item in this list matched by the argument is the one whose documentation
is printed. (See the examples below.) However, if the argument starts with a capital
letter it is assumed to identify a symbol or method in the current directory.

For packages, the order of scanning is determined lexically in breadth-first order.
That is, the package presented is the one that matches the search and is nearest
the root and lexically first at its level of the hierarchy. The GOROOT tree is
always scanned in its entirety before GOPATH.

If there is no package specified or matched, the package in the current
directory is selected, so "go doc Foo" shows the documentation for symbol Foo in
the current package.

The package path must be either a qualified path or a proper suffix of a
path. The go tool's usual package mechanism does not apply: package path
elements like . and ... are not implemented by go doc.

When run with two arguments, the first must be a full package path (not just a
suffix), and the second is a symbol, or symbol with method or struct field.
This is similar to the syntax accepted by godoc:

	go doc <pkg> <sym>[.<methodOrField>]

In all forms, when matching symbols, lower-case letters in the argument match
either case but upper-case letters match exactly. This means that there may be
multiple matches of a lower-case argument in a package if different symbols have
different cases. If this occurs, documentation for all matches is printed.

Examples:
	go doc
		Show documentation for current package.
	go doc Foo
		Show documentation for Foo in the current package.
		(Foo starts with a capital letter so it cannot match
		a package path.)
	go doc encoding/json
		Show documentation for the encoding/json package.
	go doc json
		Shorthand for encoding/json.
	go doc json.Number (or go doc json.number)
		Show documentation and method summary for json.Number.
	go doc json.Number.Int64 (or go doc json.number.int64)
		Show documentation for json.Number's Int64 method.
	go doc cmd/doc
		Show package docs for the doc command.
	go doc -cmd cmd/doc
		Show package docs and exported symbols within the doc command.
	go doc template.new
		Show documentation for html/template's New function.
		(html/template is lexically before text/template)
	go doc text/template.new # One argument
		Show documentation for text/template's New function.
	go doc text/template new # Two arguments
		Show documentation for text/template's New function.

	At least in the current tree, these invocations all print the
	documentation for json.Decoder's Decode method:

	go doc json.Decoder.Decode
	go doc json.decoder.decode
	go doc json.decode
	cd go/src/encoding/json; go doc decode

Flags:
	-c
		Respect case when matching symbols.
	-cmd
		Treat a command (package main) like a regular package.
		Otherwise package main's exported symbols are hidden
		when showing the package's top-level documentation.
	-u
		Show documentation for unexported as well as exported
		symbols, methods, and fields.

再比如我们可以查看系统的文档

sheng$ go doc json.Decoder.Decode
func (dec *Decoder) Decode(v interface{}) error
    Decode reads the next JSON-encoded value from its input and stores it in the
    value pointed to by v.

    See the documentation for Unmarshal for details about the conversion of JSON
    into a Go value.
    
sheng$ go doc fmt.Printf
func Printf(format string, a ...interface{}) (n int, err error)
    Printf formats according to a format specifier and writes to standard
    output. It returns the number of bytes written and any write error
    encountered.

当然我们最常用的命令是 godoc ,我们help看一下它的基本用法

sheng$ godoc -help
usage: godoc package [name ...]
	godoc -http=:6060
  -analysis string
    	comma-separated list of analyses to perform (supported: type, pointer). See http://golang.org/lib/godoc/analysis/help.html
  -ex
    	show examples in command line mode
  -goroot string
    	Go root directory (default "/usr/local/Cellar/go/1.10.2/libexec")
  -html
    	print HTML in command-line mode
  -http string
    	HTTP service address (e.g., ':6060')
  -httptest.serve string
    	if non-empty, httptest.NewServer serves on this address and blocks
  -index
    	enable search index
  -index_files string
    	glob pattern specifying index files; if not empty, the index is read from these files in sorted order
  -index_interval duration
    	interval of indexing; 0 for default (5m), negative to only index once at startup
  -index_throttle float
    	index throttle value; 0.0 = no time allocated, 1.0 = full throttle (default 0.75)
  -links
    	link identifiers to their declarations (default true)
  -maxresults int
    	maximum number of full text search results shown (default 10000)
  -notes string
    	regular expression matching note markers to show (default "BUG")
  -play
    	enable playground in web interface
  -q	arguments are considered search queries
  -server string
    	webserver address for command line searches
  -src
    	print (exported) source in command-line mode
  -tabwidth int
    	tab width (default 4)
  -templates string
    	load templates/JS/CSS from disk in this directory
  -timestamps
    	show timestamps with directory listings
  -url string
    	print HTML for named URL
  -v	verbose mode
  -write_index
    	write index to a file; the file name must be specified with -index_files
  -zip string
    	zip file providing the file system to serve; disabled if empty

我们看到有个http的用法,现在我们试一下

sheng$ godoc -http :6060

打开浏览器,输入 http://localhost:6060

完整的Web版的Go语言的文档就可以使用了。当然不单单包含系统函数,同时还包含我们自己写的函数的文档,现在我们就演示一下

// 加法函数
func Add(a, b int32) int32 {

	return a + b
}

我们在函数前面加上了注释,这是我们重新启动 godoc -http :6060 我们会发现

相关的注释已经加上了。Go语言除此之外还提供了写示例代码的方法

func ExampleAdd() {

	c := Add(1, 3)
	fmt.Println(c)

	// Output:
	// 1
}

直接添加一个 ExampleAdd 函数,还是像之前一样写代码,最后我们要写一个 Output 的注释,那你现在是否有疑问,下面的 1 是什么意思?这里说下,这是我随便写的,这时候 Run Test 这段代码

=== RUN   ExampleAdd
--- FAIL: ExampleAdd (0.00s)
got:
4
want:
1
FAIL

Process finished with exit code 1

我们再把正确的输出贴到上面的输出中,重启godoc

这时候完整的示例代码就已经生成到文档中了。

欢迎关注 个人微信公众号

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

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

编辑于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏大内老A

ASP.NET MVC下基于异常处理的完整解决方案

EntLib的异常处理应用块(Exception Handling Application Block)是一个不错的异常处理框架,它使我们可以采用配置的方式来定...

1946
来自专栏恰同学骚年

设计模式的征途—17.模板方法(Template Method)模式

在现实生活中,很多事情都需要经过几个步骤才能完成,例如请客吃饭,无论吃什么,一般都包含:点单、吃东西、买单等几个步骤,通常情况下这几个步骤的次序是:点单=>吃东...

833
来自专栏Theo Tsao

Vim的基本使用(一)

本文包含Vim的基本使用有: 移动光标、屏幕滚动、模式查找、位置标记、删除文本、撤销与重做、插入文本、复制与移动、修改文本、写入与退出。

993
来自专栏后台全栈之路

XML 语法速查笔记

相比起 Json,XML 是一种相对古老和复杂、但功能更加强大的数据存储/传输格式。也因为其复杂,有一些语法需要记录一下,在使用多种语言进行 XML 操作的时候...

3286
来自专栏大内老A

ASP.NET MVC的Model元数据与Model模板:预定义模板

通过ModelMetadata表示的Model元数据的一个主要的作用在于为定义在HtmlHelper和HtmlHelper<TModel>中的模板方法(这些模板...

24910
来自专栏烂笔头

Python标准库笔记(6) — struct模块

目录[-] 该模块作用是完成Python数值和C语言结构体的Python字符串形式间的转换。这可以用于处理存储在文件中或从网络连接中存储的二进制数据,以及其...

4615
来自专栏jojo的技术小屋

原 荐 自己写JSON编辑器

作者:汪娇娇 时间:2018年1月15日 下一篇:自己写代码对比工具 时间过得好快,一下子就2018年了,想起好久没写博客,不觉有些浪费了时光,今天便来补一篇。...

8457
来自专栏逆向技术

异常处理第二讲,结构化异常(微软未公开)

            异常处理第二讲,结构化异常(微软未公开) 讲解之前,请熟悉WinDbg的使用,工具使用的博客链接 一丶认识段寄存器FS的内容,以及作用 ...

2157
来自专栏Python

仿照wtform自定义Form组件

仿照wtforms自定义Form组件 1.wtforms 点击查看源码分析及使用方法 2.自定义Form组件 #!usr/bin/env python # -*...

2257
来自专栏Hongten

Javascript 笔记

JavaScript表单验证电话号码,判断一个输入量是否为电话号码,通过正则表达式实现。 //检查电话号码 function isTel(str){      ...

841

扫码关注云+社区

领取腾讯云代金券