Go语言作为一个通用型语言,它对函数式编程主要体现在闭包上面。
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
}` 称为函数体,整个就叫做一个闭包。用一张图来概括就是:
要是上面的累加想做一个稍微正统函数怎么做呢?
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)
}
}
当然正统的不一定是最好的,正统式的写法经常导致代码的可读性变得不是很好。
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
这个斐波那契数列的调用的生成器跟文件有点像,我们可以把它包装成一个 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 太小的话,只读了一半,当然这边就留给读者后期拓展了。
我们实际的代码不止 Hello World
,我们的代码是要运行在服务器上的,要和各种各样的用户进行交互,所以我们这里就要了解一下Go语言的资源管理和出错处理。
比如一个常见的代码
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 依然能够正常输出。
当然说了这么多,我们在代码中常见的使用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
因为在我们实际的程序中,有错直接panic中断程序执行,这时非常不友好的,通常我们会对其出错处理。比如上面的事例中 os.Create
函数返回的 err
不为 nil
的时候,我们需要做一个出错处理,
filename := "demo.txt"
file, err := os.Create(filename)
if err != nil {
fmt.Println(err.Error())
return
}
我们可以直接打印出相关的错误信息,然后直接返回。这就是常见的错误处理方式之一,当然在函数内部也可以将错误信息直接作为结果返回。
panic和我们其他语言的throw exception很像
当然相对还是友好的,每层的defer还是会用到,一层一层的返回,返回到最后程序就会自动退出了
主要的特性就可以用上述几句话概括,为了更好的理解上述的概念,下面用一个简短的代码来学以致用
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的基本使用,下面通过一个稍微实际一点的例子来综合讲述一下一般的项目中是如何统一处理错误的。
现在呢我们就通过一个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.go
的 serve
函数报出的,具体是哪一步报出的我就不细说了,有兴趣的可以自己按照例子自己查阅相关的源码,说到这那错误统一处理又是如何处理呢?
我们先把第一个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
这时候的错误就明显友好了很多,讲到这就是一个简单的统一错误处理的思路。
测试的作用对于一个软件行业从业者而言都是毋庸置疑的,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语言的语法使得我们更容易使用表格驱动测试的测试模式
说了这么多,我们通常又是如何写测试用例呢?首先下面是一段加法的代码
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的问题,这就是一个完整的测试用例的编写的过程。
用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
运行上面的命令,就会展现一个下面的静态页面
这就是一个详细的覆盖率报告
对于程序员而言,代码的性能是每个人都会去关注的,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
上面我们刚提到了性能测试,下一步自然就是我们该如何优化代码的性能,这里我们需要介绍一下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文件,用浏览器打开它
一张图可以很明显的表现出哪边花的时间多哪边花的时间少,当然也可以从框框的大小来做判断,我们需要优化比较大的框框的部分。上述的代码因为太过于简单,大家可以试着用自己写的代码进行性能分析。
在我们实际的开发过程中,文档的重要性不必多说,服务调用方、协同开发的小伙伴、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
这时候完整的示例代码就已经生成到文档中了。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。