Go 博客

Testable Examples in Go

2015/05/07

Introduction

Godoc examples are snippets of Go code that are displayed as package documentation and that are verified by running them as tests. They can also be run by a user visiting the godoc web page for the package and clicking the associated "Run" button.

Having executable documentation for a package guarantees that the information will not go out of date as the API changes.

The standard library includes many such examples (see the strings package, for instance).

This article explains how to write your own example functions.

Examples are tests

Examples are compiled (and optionally executed) as part of a package's test suite.

As with typical tests, examples are functions that reside in a package's _test.go files. Unlike normal test functions, though, example functions take no arguments and begin with the word Example instead of Test.

The stringutil package is part of the Go example repository. Here's an example that demonstrates its Reverse function:

package stringutil_test

import (
    "fmt"

    "github.com/golang/example/stringutil"
)

func ExampleReverse() {
    fmt.Println(stringutil.Reverse("hello"))
    // Output: olleh
}

This code might live in example_test.go in the stringutil directory.

Godoc will present this example alongside the Reverse function's documentation:

Running the package's test suite, we can see the example function is executed with no further arrangement from us:

$ go test -v
=== RUN TestReverse
--- PASS: TestReverse (0.00s)
=== RUN: ExampleReverse
--- PASS: ExampleReverse (0.00s)
PASS
ok      github.com/golang/example/stringutil    0.009s

Output comments

What does it mean that the ExampleReverse function "passes"?

As it executes the example, the testing framework captures data written to standard output and then compares the output against the example's "Output:" comment. The test passes if the test's output matches its output comment.

To see a failing example we can change the output comment text to something obviously incorrect

func ExampleReverse() {
    fmt.Println(stringutil.Reverse("hello"))
    // Output: golly
}

and run the tests again:

$ go test
--- FAIL: ExampleReverse (0.00s)
got:
olleh
want:
golly
FAIL

If we remove the output comment entirely

func ExampleReverse() {
    fmt.Println(stringutil.Reverse("hello"))
}

then the example function is compiled but not executed:

$ go test -v
=== RUN TestReverse
--- PASS: TestReverse (0.00s)
PASS
ok      github.com/golang/example/stringutil    0.009s

Examples without output comments are useful for demonstrating code that cannot run as unit tests, such as that which accesses the network, while guaranteeing the example at least compiles.

Example function names

Godoc uses a naming convention to associate an example function with a package-level identifier.

func ExampleFoo()     // documents the Foo function or type
func ExampleBar_Qux() // documents the Qux method of type Bar
func Example()        // documents the package as a whole

Following this convention, godoc displays the ExampleReverse example alongside the documentation for the Reverse function.

Multiple examples can be provided for a given identifier by using a suffix beginning with an underscore followed by a lowercase letter. Each of these examples documents the Reverse function:

func ExampleReverse()
func ExampleReverse_second()
func ExampleReverse_third()

Larger examples

Sometimes we need more than just a function to write a good example.

For instance, to demonstrate the sort package we should show an implementation of sort.Interface. Since methods cannot be declared inside a function body, the example must include some context in addition to the example function.

To achieve this we can use a "whole file example." A whole file example is a file that ends in _test.go and contains exactly one example function, no test or benchmark functions, and at least one other package-level declaration. When displaying such examples godoc will show the entire file.

Here is a whole file example from the sort package:

package sort_test

import (
    "fmt"
    "sort"
)

type Person struct {
    Name string
    Age  int
}

func (p Person) String() string {
    return fmt.Sprintf("%s: %d", p.Name, p.Age)
}

// ByAge implements sort.Interface for []Person based on
// the Age field.
type ByAge []Person

func (a ByAge) Len() int           { return len(a) }
func (a ByAge) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a ByAge) Less(i, j int) bool { return a[i].Age < a[j].Age }

func Example() {
    people := []Person{
        {"Bob", 31},
        {"John", 42},
        {"Michael", 17},
        {"Jenny", 26},
    }

    fmt.Println(people)
    sort.Sort(ByAge(people))
    fmt.Println(people)

    // Output:
    // [Bob: 31 John: 42 Michael: 17 Jenny: 26]
    // [Michael: 17 Jenny: 26 Bob: 31 John: 42]
}

A package can contain multiple whole file examples; one example per file. Take a look at the sort package's source code to see this in practice.

Conclusion

Godoc examples are a great way to write and maintain code as documentation. They also present editable, working, runnable examples your users can build on. Use them!

Andrew Gerrand 编写

命名的学问

2015/02/04

引言

Go 代码通过包来组织。在包内部,代码可以引用包中定义的任何标识符(名字), 而该包的用户则只能引用其中已导出的类型、函数、常量和变量。 这种引用总是将包名作为前缀:例如 foo.Bar 就引用了已导入包 foo 中的已导出名 Bar

好的包名能让代码组织得更好。包名提供了其内容的上下文,让用户更容易理解该包的用途和用法。 包名也有助于维护者在该包的演化过程中决定哪些东西属于它,哪些不属于它。 恰当的包名更易帮你找所需的代码。

实效 Go 编程中提供了一份关于包、类型、函数和变量的 命名指南。 本文拓展了该指南,考察了标准库中的命名,也讨论了不良包名及其改善方法。

包名

好的包名应当简短清晰。它们应该全部小写,没有下划线 under_scores 或混合大小写 mixedCaps 。它们通常是简单的名词,例如:

  • time (提供测量和显示时间的功能)
  • list (实现了双向链表)
  • http (提供 HTTP 客户端与服务端的实现)

另一种语言中典型的命名风格在 Go 程序中可能并不惯用。 下面两个例子在其它语言中可能是好的风格,但在 Go 中不合适:

  • computeServiceClient
  • priority_queue

一个Go包可导出多个类型和函数。例如,一个 compute 包可以导出一个 Client 类型, 它包含使用该服务的方法,以及将一个计算任务划分给多个客户端( Client )的函数。

慎用缩写。 当程序员对包名缩写比较熟悉时,可直接采用。常用包一般都有简短的名字:

  • strconv (字符串转换)
  • syscall (系统调用)
  • fmt (格式化 I/O)

反之,若包名缩写会产生歧义,那么请勿使用。

别抢走用户的好名字。 别把用户代码中常用的名字作为包名。例如,带缓冲的 I/O 叫做 bufio 而非 buf , 因为 buf 用做缓冲区是个不错的变量名。

包内容的命名

包名与其内容的名字是有联系的,因为用户的代码总是一起使用它们。当设计一个包时, 请站在用户的角度考虑。

别啰嗦。 由于用户代码在引用包的内容时会将包名作为前缀,因此这些内容的名字无需重复包名。 http 包提供的 HTTP 服务名为 http.Server ,而非 HTTPServer 。用户代码通过 http.Server 引用该类型,因此没有歧义。

简化函数名。pkg 包中某个函数的返回值类型为 pkg.Pkg (或 *pkg.Pkg )时, 函数名就算省略类型名也不会引起混淆。

start := time.Now()                                  // start is a time.Time
t, err := time.Parse(time.Kitchen, "6:06PM")         // t is a time.Time
ctx = context.WithTimeout(ctx, 10*time.Millisecond)  // ctx is a context.Context
ip, ok := userip.FromContext(ctx)                    // ip is a net.IP

pkg 包中名为 New 的函数会返回一个 pkg.Pkg 类型的值。 这是用户代码使用该类型的标准入口点:

q := list.New()  // q is a *list.List

当函数返回的值类型为 pkg.TT 不为 Pkg 时,函数名应包含 T 以便让用户代码更易理解。常见的情况是一个包带有多个类似 New 的函数:

d, err := time.ParseDuration("10s")  // d is a time.Duration
elapsed := time.Since(start)         // elapsed is a time.Duration
ticker := time.NewTicker(d)          // ticker is a *time.Ticker
timer := time.NewTimer(d)            // timer is a *time.Timer

不同包中的类型名可以相同,因为客户端可通过包名区分它们。 例如,标准库中含有多个名为 Reader 的类型,包括 jpeg.Readerbufio.Readercsv.Reader 。每个包名搭配 Reader 都是个不错的类型名。

若你为某个包的内容想不出有意义的前缀包名,那这个包的抽象边界大概就错了。 请站在用户的角度写你的包的代码,如果你觉得不太对劲那么请重新组织你的包。 这样得到的包将不仅容易使用,也容易维护。

导入路径

一个 Go 包同时有名称和路径。包名由其源码文件中的包语句指定, 用户代码将其用作包的已导出名的前缀。用户代码通过包路径来导入包。 按照约定,包路径的最后一个元素即为包名:

import (
    "fmt"                       // package fmt
    "os/exec"                   // package exec
    "golang.org/x/net/context"  // package context
)

构建工具将包路径映射到目录。go 工具通过 GOPATH 环境变量在 $GOPATH/src/github.com/user/hello 目录中查找路径 "github.com/user/hello" 内的源文件。 (这种情况你应该熟悉,但弄清这些术语和包结构也很重要。)

目录。 标准库使用像 cryptocontainerencodingimage 之类的目录来为相关的协议和算法包分组。其中每个目录内的包之间并没有实际的联系, 目录只是为了便于分类文件。只要不会导致循环引用,任何包都能导入其它的包。

就像不同包中的类型可以同名而不会引起混淆,不同目录中的包也可以同名。 例如,runtime/pprof 提供了 pprof 剖析工具所需格式的剖析数据, 而 net/http/pprof 则提供了按这种格式展示剖析数据的HTTP端点。用户代码通过包路径导入该包,因此不会混淆。 若源文件需要导入两个 pprof 包,那么可以局部地将其中之一或二者都 重命名。在重命名已导入包时, 局部名也应遵循包名的命名准则(小写,不使用下划线 under_scores 或混合大小写 mixedCaps )。

不良包名

不良包名会让代码难以使用与维护。下面是一些识别及修复不良包名的准则。

避免无意义的包名。 名为 utilcommonmisc 的包不仅无法向用户传达其中的内容,还会让它们更难以使用, 维护者也不易保持它们的专用性。如此一来,依赖关系会日渐复杂,徒增编译时间,这在大型程序中尤甚。 由于这类包名过于通用,因此更易与客户代码中导入的其它包名相冲突,用户则必须重新取名来加以区分。

拆分过于通用的包。 修复这样的包需要包含通用名元素的类型和函数,并将它们放到自己的包中。例如,若你有以下代码:

package util
func NewStringSet(...string) map[string]bool {...}
func SortStringSet(map[string]bool) []string {...}

那么用户代码看起来会是这样:

set := util.NewStringSet("c", "a", "b")
fmt.Println(util.SortStringSet(set))

将这些函数从 util 移至新的包中,选一个与其内容相称的包名:

package stringset
func New(...string) map[string]bool {...}
func Sort(map[string]bool) []string {...}

那么用户代码会变成这样:

set := stringset.New("c", "a", "b")
fmt.Println(stringset.Sort(set))

一旦你做出这些改变,便更容易看出如何改进新包了:

package stringset
type Set map[string]bool
func New(...string) Set {...}
func (s Set) Sort() []string {...}

这样以来用户代码将更加简洁:

set := stringset.New("c", "a", "b")
fmt.Println(set.Sort())

名字是设计包的关键。请从你的项目中努力消除无意义的包名。

别把所有API都塞进一个包里。 有些好心的程序员会将他们的程序暴露出的所有接口都放到一个包里,取名为 apitypes 、或 interfaces ,他们觉着这样会更易于找到代码库的入口点。这是不对的。这种包遭遇到的问题和名为 utilcommon 的包相同,即无节制地增长,不为用户提供指导,积累依赖,以及和其它导入冲突。 请拆分它们,或许可以用目录将公共包从实现中分离出来。

避免不必要的包名冲突。 当不同目录中的包名相同时,经常一起使用的包应该有不同的名字。这能避免混淆, 减少在用户代码中局部重命名的必要。同理,也应当避免将 iohttp 之类广泛使用的标准包的名字用作包名。

结论

Go 程序的包名是良好命名的核心,请花点时间选好包名并组织好你的代码, 这能帮助用户理解并使用你的包,也有助于维护者优雅地发展它们。

扩展阅读

Sameer Ajmani 编写

Errors are values

2015/01/12

A common point of discussion among Go programmers, especially those new to the language, is how to handle errors. The conversation often turns into a lament at the number of times the sequence

if err != nil {
    return err
}

shows up. We recently scanned all the open source projects we could find and discovered that this snippet occurs only once per page or two, less often than some would have you believe. Still, if the perception persists that one must type

if err != nil

all the time, something must be wrong, and the obvious target is Go itself.

This is unfortunate, misleading, and easily corrected. Perhaps what is happening is that programmers new to Go ask, "How does one handle errors?", learn this pattern, and stop there. In other languages, one might use a try-catch block or other such mechanism to handle errors. Therefore, the programmer thinks, when I would have used a try-catch in my old language, I will just type if err != nil in Go. Over time the Go code collects many such snippets, and the result feels clumsy.

Regardless of whether this explanation fits, it is clear that these Go programmers miss a fundamental point about errors: Errors are values.

Values can be programmed, and since errors are values, errors can be programmed.

Of course a common statement involving an error value is to test whether it is nil, but there are countless other things one can do with an error value, and application of some of those other things can make your program better, eliminating much of the boilerplate that arises if every error is checked with a rote if statement.

Here's a simple example from the bufio package's Scanner type. Its Scan method performs the underlying I/O, which can of course lead to an error. Yet the Scan method does not expose an error at all. Instead, it returns a boolean, and a separate method, to be run at the end of the scan, reports whether an error occurred. Client code looks like this:

scanner := bufio.NewScanner(input)
for scanner.Scan() {
    token := scanner.Text()
    // process token
}
if err := scanner.Err(); err != nil {
    // process the error
}

Sure, there is a nil check for an error, but it appears and executes only once. The Scan method could instead have been defined as

func (s *Scanner) Scan() (token []byte, error)

and then the example user code might be (depending on how the token is retrieved),

scanner := bufio.NewScanner(input)
for {
    token, err := scanner.Scan()
    if err != nil {
        return err // or maybe break
    }
    // process token
}

This isn't very different, but there is one important distinction. In this code, the client must check for an error on every iteration, but in the real Scanner API, the error handling is abstracted away from the key API element, which is iterating over tokens. With the real API, the client's code therefore feels more natural: loop until done, then worry about errors. Error handling does not obscure the flow of control.

Under the covers what's happening, of course, is that as soon as Scan encounters an I/O error, it records it and returns false. A separate method, Err, reports the error value when the client asks. Trivial though this is, it's not the same as putting

if err != nil

everywhere or asking the client to check for an error after every token. It's programming with error values. Simple programming, yes, but programming nonetheless.

It's worth stressing that whatever the design, it's critical that the program check the errors however they are exposed. The discussion here is not about how to avoid checking errors, it's about using the language to handle errors with grace.

The topic of repetitive error-checking code arose when I attended the autumn 2014 GoCon in Tokyo. An enthusiastic gopher, who goes by @jxck_ on Twitter, echoed the familiar lament about error checking. He had some code that looked schematically like this:

_, err = fd.Write(p0[a:b])
if err != nil {
    return err
}
_, err = fd.Write(p1[c:d])
if err != nil {
    return err
}
_, err = fd.Write(p2[e:f])
if err != nil {
    return err
}
// and so on

It is very repetitive. In the real code, which was longer, there is more going on so it's not easy to just refactor this using a helper function, but in this idealized form, a function literal closing over the error variable would help:

var err error
write := func(buf []byte) {
    if err != nil {
        return
    }
    _, err = w.Write(buf)
}
write(p0[a:b])
write(p1[c:d])
write(p2[e:f])
// and so on
if err != nil {
    return err
}

This pattern works well, but requires a closure in each function doing the writes; a separate helper function is clumsier to use because the err variable needs to be maintained across calls (try it).

We can make this cleaner, more general, and reusable by borrowing the idea from the Scan method above. I mentioned this technique in our discussion but @jxck_ didn't see how to apply it. After a long exchange, hampered somewhat by a language barrier, I asked if I could just borrow his laptop and show him by typing some code.

I defined an object called an errWriter, something like this:

type errWriter struct {
    w   io.Writer
    err error
}

and gave it one method, write. It doesn't need to have the standard Write signature, and it's lower-cased in part to highlight the distinction. The write method calls the Write method of the underlying Writer and records the first error for future reference:

func (ew *errWriter) write(buf []byte) {
    if ew.err != nil {
        return
    }
    _, ew.err = ew.w.Write(buf)
}

As soon as an error occurs, the write method becomes a no-op but the error value is saved.

Given the errWriter type and its write method, the code above can be refactored:

ew := &errWriter{w: fd}
ew.write(p0[a:b])
ew.write(p1[c:d])
ew.write(p2[e:f])
// and so on
if ew.err != nil {
    return ew.err
}

This is cleaner, even compared to the use of a closure, and also makes the actual sequence of writes being done easier to see on the page. There is no clutter any more. Programming with error values (and interfaces) has made the code nicer.

It's likely that some other piece of code in the same package can build on this idea, or even use errWriter directly.

Also, once errWriter exists, there's more it could do to help, especially in less artificial examples. It could accumulate the byte count. It could coalesce writes into a single buffer that can then be transmitted atomically. And much more.

In fact, this pattern appears often in the standard library. The archive/zip and net/http packages use it. More salient to this discussion, the bufio package's Writer is actually an implementation of the errWriter idea. Although bufio.Writer.Write returns an error, that is mostly about honoring the io.Writer interface. The Write method of bufio.Writer behaves just like our errWriter.write method above, with Flush reporting the error, so our example could be written like this:

b := bufio.NewWriter(fd)
b.Write(p0[a:b])
b.Write(p1[c:d])
b.Write(p2[e:f])
// and so on
if b.Flush() != nil {
    return b.Flush()
}

There is one significant drawback to this approach, at least for some applications: there is no way to know how much of the processing completed before the error occurred. If that information is important, a more fine-grained approach is necessary. Often, though, an all-or-nothing check at the end is sufficient.

We've looked at just one technique for avoiding repetitive error handling code. Keep in mind that the use of errWriter or bufio.Writer isn't the only way to simplify error handling, and this approach is not suitable for all situations. The key lesson, however, is that errors are values and the full power of the Go programming language is available for processing them.

Use the language to simplify your error handling.

But remember: Whatever you do, always check your errors!

Finally, for the full story of my interaction with @jxck_, including a little video he recorded, visit his blog.

Rob Pike 编写

GothamGo: gophers in the big apple

2015/01/09

Last November more than two hundred gophers from all across the United States got together for the first full-day Go conference in New York City.

The diverse speaker lineup included university students, industry experts, and Go team members.

And good news, everybody! All the talks were recorded and are available:

Two more talks come from the Go meetup in New York City, which met the day before GothamGo:

  • Benchmarking Go by Brian Bulkowski - the founder of Aerospike talks about profiling tools for Go and Linux, and micro benchmarks for goroutines, channels, buffers, and and other Go features.
  • Go Static Analysis Tools by Alan Donovan - a member of the Go team at Google NY gives a guided tour of several static analysis tools designed to help Go programmers understand, navigate , and refactor their code.

Make sure to have a look at all of those in preparation for the FOSDEM Go devroom FOSDEM in Brussels (Belgium) and gophercon.in in Bengaluru (India).

Francesc Campoy 编写

The Gopher Gala is the first worldwide Go hackathon

2015/01/07

The Gopher Gala is the first Go hackathon at a global scale and will take place from January 23rd through the 25th. The event is organized by the community, supported by the Go team, and sponsored by Google among others.

Fancy Gopher, by Renée French

You can read about the rules of the hackathon here, but if you know about Rails Rumble or Node Knockout you already have a pretty good idea of what to expect.

During this event gophers from all around the globe will form teams to build great applications using Go as the main tool. Afterwards, all the participants will vote for their favorite applications and the 20 highest voted applications will be ranked by a jury of renowned names from the community, including some members of the Go core team. More information on the judging phase can be found here.

And in case you needed one more reason to get involved there will be prizes!

"What prizes?" you ask. Well, that’s a secret until January 10th but we’re pretty sure you won’t be disappointed.

Francesc Campoy 编写

更多文章请查看索引