An incomplete list of Go tools

Dominik Honnef · · 9347 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

An incomplete list of Go tools

Go is an amazing language, but not just because of its language features, but also because of its ecosystem and how easy it is to write tools that operate on Go code.

While languages and frameworks like Java and .NET have popular IDEs (e.g. Eclipse and Visual Studio) that can do formatting, refactoring, code generation etc, the Go ecosystem focuses on small standalone tools and reusable parsers. While Eclipse’s features are locked into that IDE, a huge and clunky piece of software, Go has tools like gofmt that can be used from the command line or any editor that allows calling external programs. Write a tool once, and everyone gets to use it. It doesn’t matter if you use acme, vim, Emacs or Sublime Text, you can benefit from a vast number of tools.

This article presents an incomplete list of official and 3rd party tools for working with Go – it’s inherently incomplete, as people keep writing new tools at a steady pace. Furthermore, not everyone may like every tool on this list. Some tools serve very specific needs, some are very opinionated and some are controversial.

The following index is sorted alphabetically for ease of use, but the article itself is sorted by theme for a nicer reading experience.

gofmt

Hopefully you know gofmt by now, but if you don’t: Run gofmt on your Go code, and it formats your code according to the only acceptable way of formatting it. That’s it.

go vet

Go comes with its own linter, go vet, for analysing Go code and finding common mistakes. So far, it can check your code for the following mistakes:

  • Useless assignments
  • Common mistakes when using sync/atomic
  • Invalid +build tags
  • Using composite literals without keyed fields
  • Passing locks by value
  • Comparing functions with nil
  • Using wrong printf format specifiers
  • Closing over loop variables the wrong way
  • Struct tags that do not follow the canonical format
  • Unreachable code
  • Misuse of unsafe.Pointer
  • Mistakes involving boolean operators

The following (constructed) piece of code tries to trigger as many errors as possible, to show go vet’s behaviour:

package main

import (
	"crypto/tls"
	"fmt"
	"sync"
	"sync/atomic"
	"unsafe"
)

// +build this,is,too,late

type T struct {
	A int `not a canonical tag`
}

func foo() []byte       { return []byte{} }
func bar(mu sync.Mutex) {} // Locks have to be passed as pointers

func main() {
	fmt.Sprintf("%s", 1) // Wrong verb
	fmt.Sprint("%s", 1)  // Wrong method

	var a int
	a = a // Useless assignment

	_ = tls.Certificate{nil, nil, nil, nil} // These fields should be keyed

	if foo != nil { // We meant to check foo() != nil
	}

	x := uintptr(0)
	_ = unsafe.Pointer(x) // Misuse of unsafe.Pointer

	for _, x := range []int{1, 2, 3} {
		go func() {
			fmt.Println(x) // Closing over x wrong, will probably produce "3 3 3"
		}()
	}

	var b int32
	b = atomic.AddInt32(&b, 1) // We shouldn't assign to b

	var c bool
	if c == false && c == true { // Always false
	}

	if c || c { // Redundant
	}

	return
	fmt.Println("unreachable") // Unreachable code
}

And go vet’s opinion of our code:

/tmp/vet.go:11: +build comment must appear before package clause and be followed by a blank line
/tmp/vet.go:14: struct field tag `not a canonical tag` not compatible with reflect.StructTag.Get
/tmp/vet.go:18: bar passes Lock by value: sync.Mutex
/tmp/vet.go:52: unreachable code
/tmp/vet.go:21: arg 1 for printf verb %s of wrong type: int
/tmp/vet.go:22: possible formatting directive in Sprint call
/tmp/vet.go:25: self-assignment of a to a
/tmp/vet.go:27: crypto/tls.Certificate composite literal uses unkeyed fields
/tmp/vet.go:29: comparison of function foo != nil is always true
/tmp/vet.go:33: possible misuse of unsafe.Pointer
/tmp/vet.go:37: range variable x captured by func literal
/tmp/vet.go:42: direct assignment to atomic value
/tmp/vet.go:45: suspect and: c == false && c == true
/tmp/vet.go:48: redundant or: c || c

golint

Install with go get github.com/golang/lint/golint

While go vet checks your code for actual programming errors, golint checks your code for style violations (so it’s not really a linter – naming things is hard.)

Gofmt already takes care of whitespace-related style questions, but it cannot cover things such as variable names. Golint follows the style that is used internally by Google, which is generally accepted by the open source Go community as well – in essence it checks the various style questions discussed in the wiki.

Golint does not emit errors or warnings, but “suggestions”: These suggestions can be wrong at times, and code that golint complains about isn’t necessarily wrong – it might just be hitting a false positive. Nevertheless, it’s more often right than wrong, and you should definitely run golint on your code from time to time and fix those suggestions that it is right about.

The amount of style violations golint checks for is a bit longer than the list of modules go vet has, so what follows is only a demonstration of golint’s checks, it’s not exhaustive. For the following piece of code

package lint

import (
	"errors"
	. "fmt"
)

var SomeError = errors.New("Capitalised error message")

type unexported int
type Exported int

func (this unexported) Foo() {}

func (oneName Exported) Foo() {
}

func (anotherName Exported) Bar() {
	Println("Hi")
}

golint has the following suggestions:

/tmp/lint.go:5:2: should not use dot imports
/tmp/lint.go:8:5: exported var SomeError should have comment or be unexported
/tmp/lint.go:11:6: exported type Exported should have comment or be unexported
/tmp/lint.go:15:1: exported method Exported.Foo should have comment or be unexported
/tmp/lint.go:18:1: exported method Exported.Bar should have comment or be unexported
/tmp/lint.go:8:5: error var SomeError should have name of the form ErrFoo
/tmp/lint.go:13:1: receiver name should be a reflection of its identity; don't use generic names such as "me", "this", or "self"
/tmp/lint.go:18:1: receiver name anotherName should be consistent with previous receiver name oneName for Exported

errcheck

Install with go get github.com/kisielk/errcheck

Ignoring errors in Go is more difficult than ignoring exceptions in some other languages, but it’s not impossible. Some people even say that it’s way too easy. Call a function like (*os.File).Close without looking at its return value, and that’s an error gone by unnoticed.

The errcheck tool has been written to help you find exactly those function calls. Running errcheck on a package will report all function calls that have unchecked errors.

The following code has three unchecked errors, one for each method call:

package main

import "os"

func main() {
	f, _ := os.Open("foo")
	f.Write([]byte("Hello, world."))
	f.Close()
}

Running errcheck with the default options on it will produce the following output:

foo/foo.go:7:9	f.Write([]byte("Hello, world."))
foo/foo.go:8:9	f.Close()

Do note that it is not printing the call to os.Open – that’s because assignment to _ is not interpreted as “unchecked” by default. That’s because assigning errors to _ can be a valid pattern, for when one truly doesn’t care about an error, or because it’s known that an error cannot occur (that’s true for some implementations of the io.Writer interface, for example). If you do worry about someone assigning to _ out of laziness, you can run errcheck with the -blank flag:

foo/foo.go:6:5	f, _ := os.Open("foo")
foo/foo.go:7:9	f.Write([]byte("Hello, world."))
foo/foo.go:8:9	f.Close()

Furthermore, errcheck comes with options for ignoring certain packages or functions, to avoid false positives. Together with go vet, it should be part of your quality assurance scripts.

go tool cover

Go 1.2 added support for generating code coverage reports, and while there’s an excellent blog post titled “The cover story”, it might not be receiving enough attention.

Code coverage reports provide a quick and easy way of finding untested code. Go’s support for these reports consists of two components: Support in go test for generating coverage profiles and go tool cover to generate reports from it.

Set the -coverprofile option while running go test and it will generate a coverage profile. You can then pass the profile to go tool cover and generate different kinds of reports. Currently, there are two forms of reports: A per-function listing of code coverage, and an HTML report that renders your code in a color-coded fashion, showing covered lines of code in green, uncovered lines in red, and irrelevant lines (such as comments, type definitions, etc) in grey.

fmt/format.go:	init			100.0%
fmt/format.go:	clearflags		100.0%
fmt/format.go:	init			100.0%
fmt/format.go:	computePadding		84.6%
fmt/format.go:	writePadding		100.0%
fmt/format.go:	pad			100.0%
fmt/format.go:	padString		100.0%
fmt/format.go:	fmt_boolean		100.0%
fmt/format.go:	integer			100.0%
fmt/format.go:	truncate		100.0%
fmt/format.go:	fmt_s			100.0%
fmt/format.go:	fmt_sbx			100.0%
go tool cover in the browser
go tool cover in the browser

Because the generation of a profile and actually using it are separated, one can also use other tools than go tool cover to interpret profiles. Vim and Emacs have support for Go’s coverage profiles, and so does GoConvey.

go tool cover support in Emacs
go tool cover support in Emacs

benchcmp

Install with go get golang.org/x/tools/cmd/benchcmp

Benchmarks are a crucial tool when optimizing code. Anyone who has asked “Is A or B faster?” will have (or should have!) heard an answer along the lines of “Write a benchmark!”. Go’s testing package has built-in benchmarking facilities that produce useful statistics about speed and allocations.

Comparing numbers by hand, however, can be tedious, and usually we’re interested in relative changes: Is my new version faster, and by how much? The benchcmp tool can answer this question. It takes two text files as input, the output of go test -bench for the old and new version of your code, and outputs a delta, measured in per cent, for each benchmark.

The following output is the result of running the benchmarks for the image/png package in Go 1.3 and Go 1.4 and running the output through benchcmp:

benchmark                        old ns/op     new ns/op     delta
BenchmarkPaeth                   5.22          5.30          +1.53%
BenchmarkDecodeGray              1037540       1018369       -1.85%
BenchmarkDecodeNRGBAGradient     4391776       3838947       -12.59%
BenchmarkDecodeNRGBAOpaque       3148985       3145987       -0.10%
BenchmarkDecodePaletted          747152        803097        +7.49%
BenchmarkDecodeRGB               2861484       2751671       -3.84%
BenchmarkEncodeGray              3912599       3940407       +0.71%
BenchmarkEncodeNRGBOpaque        12066176      12072256      +0.05%
BenchmarkEncodeNRGBA             13863837      13898732      +0.25%
BenchmarkEncodePaletted          3908491       3939133       +0.78%
BenchmarkEncodeRGBOpaque         12021250      12040434      +0.16%
BenchmarkEncodeRGBA              33623263      48841059      +45.26%

benchmark                        old MB/s     new MB/s     speedup
BenchmarkDecodeGray              63.16        64.35        1.02x
BenchmarkDecodeNRGBAGradient     59.69        68.29        1.14x
BenchmarkDecodeNRGBAOpaque       83.25        83.33        1.00x
BenchmarkDecodePaletted          87.71        81.60        0.93x
BenchmarkDecodeRGB               91.61        95.27        1.04x
BenchmarkEncodeGray              78.52        77.96        0.99x
BenchmarkEncodeNRGBOpaque        101.84       101.79       1.00x
BenchmarkEncodeNRGBA             88.63        88.41        1.00x
BenchmarkEncodePaletted          78.60        77.99        0.99x
BenchmarkEncodeRGBOpaque         102.22       102.06       1.00x
BenchmarkEncodeRGBA              36.55        25.16        0.69x

We can easily see that most functions didn’t have significant changes in speed, but BenchmarkEncodeRGBA sticks out, as its performance has nearly halved in Go 1.4.

If your benchmarks don’t produce stable output, maybe because your system isn’t entirely idle, or because your CPU is applying thermal throttling of some sort, you can repeat a single benchmark multiple times and tell benchcmp to pick the best result, by passing the -best option. You can also omit any results that are the same between the two versions, and sort results by magnitude of change.

benchcmp used to be written in Perl and was part of the Go distribution, but it has since been rewritten in Go and must be installed separately.

prettybench

Install with go get github.com/cespare/prettybench

While we’re on the topic of benchmarks, let’s mention prettybench, a small utility that makes the output of go test -bench a bit nicer, by correctly aligning all columns and printing column headers. Simply pipe go test’s output through prettybench. The downside of using prettybench, however, is that it has to buffer output, so you won’t see any results until all benchmarks have finished running.

Before:

PASS
BenchmarkCSSEscaper	 1000000	      2843 ns/op
BenchmarkCSSEscaperNoSpecials	 5000000	       671 ns/op
BenchmarkDecodeCSS	 1000000	      1183 ns/op
BenchmarkDecodeCSSNoSpecials	50000000	        32 ns/op
BenchmarkCSSValueFilter	 5000000	       501 ns/op
BenchmarkCSSValueFilterOk	 5000000	       707 ns/op
BenchmarkEscapedExecute	  500000	      6191 ns/op
BenchmarkHTMLNospaceEscaper	 1000000	      2523 ns/op
BenchmarkHTMLNospaceEscaperNoSpecials	 5000000	       596 ns/op
BenchmarkStripTags	 1000000	      2351 ns/op
BenchmarkStripTagsNoSpecials	10000000	       260 ns/op
BenchmarkJSValEscaperWithNum	 1000000	      1123 ns/op
BenchmarkJSValEscaperWithStr	  500000	      4882 ns/op
BenchmarkJSValEscaperWithStrNoSpecials	 1000000	      1461 ns/op
BenchmarkJSValEscaperWithObj	  500000	      5052 ns/op
BenchmarkJSValEscaperWithObjNoSpecials	 1000000	      1897 ns/op
BenchmarkJSStrEscaperNoSpecials	 5000000	       608 ns/op
BenchmarkJSStrEscaper	 1000000	      2633 ns/op
BenchmarkJSRegexpEscaperNoSpecials	 5000000	       661 ns/op
BenchmarkJSRegexpEscaper	 1000000	      2510 ns/op
BenchmarkURLEscaper	  500000	      4424 ns/op
BenchmarkURLEscaperNoSpecials	 5000000	       422 ns/op
BenchmarkURLNormalizer	  500000	      3068 ns/op
BenchmarkURLNormalizerNoSpecials	 5000000	       431 ns/op
ok  	html/template	62.874s

After:

PASS
benchmark                                    iter    time/iter
---------                                    ----    ---------
BenchmarkCSSEscaper                       1000000   2843 ns/op
BenchmarkCSSEscaperNoSpecials             5000000    671 ns/op
BenchmarkDecodeCSS                        1000000   1183 ns/op
BenchmarkDecodeCSSNoSpecials             50000000     32 ns/op
BenchmarkCSSValueFilter                   5000000    501 ns/op
BenchmarkCSSValueFilterOk                 5000000    707 ns/op
BenchmarkEscapedExecute                    500000   6191 ns/op
BenchmarkHTMLNospaceEscaper               1000000   2523 ns/op
BenchmarkHTMLNospaceEscaperNoSpecials     5000000    596 ns/op
BenchmarkStripTags                        1000000   2351 ns/op
BenchmarkStripTagsNoSpecials             10000000    260 ns/op
BenchmarkJSValEscaperWithNum              1000000   1123 ns/op
BenchmarkJSValEscaperWithStr               500000   4882 ns/op
BenchmarkJSValEscaperWithStrNoSpecials    1000000   1461 ns/op
BenchmarkJSValEscaperWithObj               500000   5052 ns/op
BenchmarkJSValEscaperWithObjNoSpecials    1000000   1897 ns/op
BenchmarkJSStrEscaperNoSpecials           5000000    608 ns/op
BenchmarkJSStrEscaper                     1000000   2633 ns/op
BenchmarkJSRegexpEscaperNoSpecials        5000000    661 ns/op
BenchmarkJSRegexpEscaper                  1000000   2510 ns/op
BenchmarkURLEscaper                        500000   4424 ns/op
BenchmarkURLEscaperNoSpecials             5000000    422 ns/op
BenchmarkURLNormalizer                     500000   3068 ns/op
BenchmarkURLNormalizerNoSpecials          5000000    431 ns/op
ok  	html/template	62.874s

benchviz

Install with go get github.com/ajstarks/svgo/benchviz

Another way of visualising benchmark results is via benchviz, a tool that generates graphical output. It depends on benchcmp and uses bar charts to show positive and negative change. Simply pipe the output of benchcmp to benchviz and it will produce SVG output on stdout. Running benchviz -h will reveal several settings that can control the way the image will look like.

By default, it will look similar to the following image.

benchviz output for the benchmarks from earlier
benchviz output for the benchmarks from earlier

go generate

A new subcommand that was added in Go 1.4 is go generate, which is supposed to replace various uses of makefiles and shell scripts in Go packages.

All go generate does is look for directives (in the form of special comments) in your Go code that direct it to execute programs. These programs usually generate new Go files, hence the name.

Example uses of go generate include running yacc to turn grammar files into Go code, generating Go code from protobuff definitions or embedding binary files in Go programs.

A core principle of go generate is that it has to be run manually and is supposed to be run by the developer of the package. It will not run on go build, and by extension not on go get, either. A developer is supposed to run go generate and then check the generated files into version control. That way, when a user downloads the package, he already has all the Go files he needs to compile it and doesn’t need to run arbitrary tools (which would also be a major security risk.)

What go generate explicitly is not is a general build system à la make – it does not offer any form of dependency analysis and instead simply runs commands it encounters in the order it finds them.

For a detailed description on how to use go generate, run go generate -h.

stringer

Install with go get golang.org/x/tools/cmd/stringer

Constants in Go are a compile-time concept. Once compilation finishes and you run your program, code isn’t aware of constants or their names anymore. But what if you have a set of typed constants and want to implement the Stringer interface for them? In the past, the easiest solutions were using a switch, or having a slice or map of names. Writing it felt a bit silly (why do I need to write each word as a constant name and as a string?) and you had to remember to update it after adding a new constant.

With the new go generate command in Go 1.4, and the stringer tool, however, we can easily automate this.

Given something like

    package date

    type Day int

    const (
            Monday Day = iota
            Tuesday
            Wednesday
            Thursday
            Friday
            Saturday
            Sunday
    )

running stringer -type Day on it will create a new file (day_string.go) that looks like

// generated by stringer -type Day; DO NOT EDIT

package date

import "fmt"

const _Day_name = "MondayTuesdayWednesdayThursdayFridaySaturdaySunday"

var _Day_index = [...]uint8{6, 13, 22, 30, 36, 44, 50}

func (i Day) String() string {
        if i < 0 || i >= Day(len(_Day_index)) {
                return fmt.Sprintf("Day(%d)", i)
        }
        hi := _Day_index[i]
        lo := uint8(0)
        if i > 0 {
                lo = _Day_index[i-1]
        }
        return _Day_name[lo:hi]
}

Not only is this process fully automatic, it also generates a more efficient implementation than a human would write. The specific implementation that is used depends on the number and density of your constants.

Add

//go:generate stringer -type Day

to your .go file (the one containing the constants), and every time you run go generate, the Stringer implementation will be regenerated for you.

impl

Install with go get github.com/josharian/impl

Another tool that’s inspired by an IDE feature is impl, which, given an interface, will create method stubs for implementing that interface.

Now, interfaces in Go are usually small, often only consisting of one or two methods, so impl isn’t as badly needed as stub generators in e.g. Java, but it can still provide a small speedup if integrated with your editor. It’s probably mostly useful when writing tests and wanting to stub out multiple interfaces. Here, impl allows you to generate stub implementations for all interfaces with only a few keystrokes.

Generics

It is impossible to have a tool like go generate and not have people write tools to imitate generics. It is equally impossible to write a list of Go tools without people mentioning their favourite tool for imitating generics. As there are as many tools for this problem as there are different interpretations of what generics really are, there’s little point in listing all of them individually. If you’re interested in imitating generics/code generation in Go, you might want to look at one of the following tools:

All of these tools put their own spin on the code generation problem, so it’s up to you to find out which one you like the most.

goimports

Install with go get golang.org/x/tools/cmd/goimports

Does ./foo.go:3: imported and not used: "log" make you twitch? Do you wish Go would just allow you to import unused packages? Or are you even tired of having to manually import all those packages you use? Then goimports might be for you. It’s a drop-in replacement for gofmt that, additionally to formatting your code, also manages your imports for you.

It removes unused imports and adds missing imports, as long as doing so is not ambiguous. If multiple packages with the same name, such as text/template and html/template can be used to make the code compile, then goimports errs on the side of safety and imports neither. But that’s a rare occurrence in everyday code. More often than not, package names are sufficiently unique, especially because goimports takes into account which identifiers you use.

Before:

package main

func main() {
     f, _ := os.Open("foo")
     log.Println("opened file")
     io.Copy(os.Stdout, f)
}

After:

package main

import (
	"io"
	"log"
	"os"
)

func main() {
	f, _ := os.Open("foo")
	log.Println("opened file")
	io.Copy(os.Stdout, f)
}

Since it’s a drop-in replacement for gofmt, all editors that permit changing the gofmt executable can use goimports.

goreturns

Install with go get sourcegraph.com/sqs/goreturns

Inspired by the way goimports magically fixes incomplete code by adding import statements, goreturns fills in incomplete return statements with zero values.

Given the following snippet

func F() (*MyType, int, error) { return errors.New("foo") }

running goreturns on it will transform it into

func F() (*MyType, int, error) { return nil, 0, errors.New("foo") }

The primary idea here is that returning errors usually also requires returning one or more zero values, and if there are several error conditions on which to return, it can become annoying to manually write those zero values over and over again.

Goreturns acts as a drop-in replacement for goimports, by calling goimports after it’s done doing its own thing.

The following 30 seconds long screencast shows goreturns in action.

godef

Install with go get code.google.com/p/rog-go/exp/cmd/godef

One of those features that a lot of IDE users love, and why people coming from languages like Java often ask for a “Go IDE”, is “go to definition” – being able to jump to the definition of constants, variables and functions.

Luckily, godef has us covered. Its most interesting mode of operation allows printing the file/line number in which a given identifier is defined. Given that information, it’s trivial to implement a “go to” feature in any modern editor. Support for acme is included with godef itself, vim and Emacs have support in vim-godef and go-mode respectively.

Another mode that godef supports is printing information about an identifier. Given a variable of some struct type, godef can list all fields of that struct. Given a constant, it can show you the constant’s value.

Godef works across all packages in your GOPATH as well as your GOROOT (i.e. the standard library), so any identifier you have, you can check out its code. This is a crucial feature while reading or even debugging someone else’s code. Even traversing deep into the standard library is no problem. And to make things even better, godef is so fast that the moment you decide to jump somewhere, you’re already there.

The following short clip shows godef in action:

gocode

Install with go get github.com/nsf/gocode

Another popular feature of IDEs is auto completion. And as with most popular IDE features, the Go community came up with a reusable tool for it: gocode. Gocode provides context-sensitive auto completion and uses a client/server architecture, where the server is a caching daemon, and the clients are a command line utility as well as all popular editors.

It uses Go object files (that is, go install‘d packages) to complete identifiers of imported packages, and parses the source of the current package to provide completion for said package. Due to the efficient layout of Go object files, the fast parser and gocode’s caching, auto completion completes almost instantly (compared to auto completion in a popular Java IDE, which may remind you that Java is also a brand of coffee and you’d really like one now.)

There’s not much else to say about auto completion, but you may appreciate a video demonstrating it:

Do note that gocode itself just provides information about identifiers – how your tools or editors make use of it is up to you – Emacs, for example, has several traditional completion backends, but also eldoc integration, to show the signature of the function call at point in a status bar.

oracle

Install with go get golang.org/x/tools/cmd/oracle

The Go oracle is a source analysis tool capable of answering a multitude of questions about Go programs, such as:

  • What is the type of this expression? What are its methods?
  • What’s the value of this constant expression?
  • Where is the definition of this identifier?
  • What are the exported members of this imported package?
  • What are the free variables of the selected block of code?
  • What interfaces does this type satisfy?
  • Which concrete types implement this interface?
  • What are the possible concrete types of this interface value?
  • What are the possible callees of this dynamic call?
  • What are the possible callers of this function?
  • What objects might this pointer point to?
  • Where are the corresponding sends/receives of this channel receive/send?
  • Which statements could update this field/local/global/map/array/etc?
  • Which functions might be called indirectly from this one?

If one were to address the technical aspects of the Oracle, one could mention that it uses pointer analysis on an SSA form of Go programs to approximate an answer to these questions, and if you want to know more about the design of the Oracle, there is an in-depth design document – but for the sake of keeping it simple, and because I can’t possibly represent the design in an accurate way, we’ll refer to what the Oracle is doing as “magic”.

We do, however, need to peek behind the curtain a bit and explain what is necessary for the magic to work. In order to answer most questions, the Oracle needs to trace all possible program flows. For this, it needs both a scope and entry points. The scope defines the set of files or packages it considers during its analysis. The entry points can either be main packages or tests – the Oracle will trace the program flow from these entry points, and the union of all flows is what it will use to answer questions.

This is why the Oracle is able to answer questions such as Who is calling this function? – because it saw who was calling it. If no entry point leads to the call of said function, it has no callers. If no entry point leads to a channel operation on a given channel, no operations will be listed, and so on.

To demonstrate some of the coolest features of the Oracle, we’ll be asking the following questions:

  • What channel operations are happening for a given channel? - What concrete methods will a dynamic method call invoke? - Who is calling our function?

Channel peers

For our first question, we’ll look at the following piece of code:

package main

import "fmt"

func odd(ch chan int) {
	i := 1
	for {
		ch <- i
		i += 2
	}
}

func even(ch chan int) {
	i := 0
	for {
		ch <- i
		i += 2
	}
}

func main() {
	ch := make(chan int)
	go odd(ch)
	go even(ch)

	for i := 0; i < 10; i++ {
		fmt.Println(<-ch)
	}
}

Specifically, we’ll look at the channel receive operation in fmt.Println(<-ch), and we will inquire who is sending us values on that channel. Executing the corresponding query will result in the following output:

$ oracle -pos="/tmp/demo2.go:#263" peers  /tmp/demo2.go
/tmp/demo2.go:27:15: This channel of type chan int may be:
/tmp/demo2.go:22:12: 	allocated here
/tmp/demo2.go:8:6: 	sent to, here
/tmp/demo2.go:16:6: 	sent to, here
/tmp/demo2.go:27:15: 	received from, here

It tells us everything there is to know about ch: Where it was created, where it is being sent to, and where it is being received from. The interesting thing here is that the Oracle is able to tell that we’re passing ch to two functions, odd and even, and that these functions are sending to ch. If we remove one of the goroutines, the corresponding send operation will disappear from the output, too.

In big, highly concurrent programs, it might not always be obvious where we’re receiving a value from or where it is being sent to, so being able to ask this kind of question can be very valuable. A common example would be a quit channel that can be closed in many different places, and received from in multiple places as well.

Callees

For our second question (What method are we calling?) we’ll be using the following small program:

package main

import (
	"fmt"
	"math/rand"
	"time"
)

type A struct{}
type B struct{}

func (A) String() string { return "A" }
func (B) String() string { return "B" }

func Print(s fmt.Stringer) {
	fmt.Println("Hello " + s.String())
}

func main() {
	rand.Seed(time.Now().UnixNano())
	if rand.Intn(2) == 1 {
		Print(A{})
	} else {
		Print(B{})
	}
}

We’ll look at the Print function and ask what concrete methods we might be calling when calling s.String():

$ oracle -pos="/tmp/demo.go:#227" callees  /tmp/demo.go
/tmp/demo.go:16:33: this dynamic method call dispatches to:
/tmp/demo.go:12:10: 	(main.A).String
/tmp/demo.go:13:10: 	(main.B).String

The Oracle knows which types we might end up passing to Print. If we had more calls to Print with even more types, these would be listed as well.

Callers

For our final question (Who is calling this method?), we’ll be reusing the program from the previous question, looking at A.String instead and asking who might be calling this method:

$ oracle -pos="/tmp/demo.go:#116" callers  /tmp/demo.go
/tmp/demo.go:12:10: (main.A).String is called from these 1 sites:
/tmp/demo.go:16:33: 	dynamic method call from main.Print

The same information that allows the Oracle to tell which methods we’re calling also allows it to tell where a method ends up being called from, even if these calls are dynamic.

Editor integration

The Oracle isn’t really meant to be used from the command line. Instead, it’s supposed to be used as the universal solution to Go source analysis in editors. The Oracle can generate output in plain text, JSON or XML, which should cater to most editor’s needs. It comes with Emacs integration, Vim has support for it via vim-go. And if your editor of choice isn’t offering integration with the Oracle yet, there’s also Pythia, a browser-based solution.

For more information about the Oracle’s features and how to use it, check out the user guide.

gorename

Install with go get golang.org/x/tools/cmd/gorename

Renaming identifiers can range in difficulty from very easy to oh god why… – unless you’re using gorename, a relatively new tool for type-aware renaming of identifiers – that is constants, variables, types, functions and methods.

It is type-aware and workspace-aware. Workspace-aware means that while you can absolutely use gorename to rename local variables, which will have no effect on the code outside of a function or even a package, you can also use it to rename exported identifiers. In that case, gorename will check all packages in your GOPATH for uses of that identifier, and rename those uses as well.

Being type-aware, and generally smart, means that gorename will refuse to make changes that stop the code from compiling correctly. If changing an identifier would cause a naming conflict or a shadowing error, gorename will abort. In the same way, if renaming a method would stop a type from implementing an interface that it has to implement, gorename will abort. Renaming a method in an interface, however, will cause gorename to update all known implementations and uses of the interface.

It has to be noted that gorename is “as strong as a compiler”, which means that if a compiler is able to complain about a type error, gorename is able to deal with it, too (either by renaming even more identifiers, or by aborting). However, there are more dynamic ways of using interfaces which can only be checked at runtime, and gorename isn’t able to detect these cases (as it’s an undecidable problem). While it is very rare that renaming an identifier will cause code to break due to this, it is still required to validate the changes that gorename makes.

Support for go-mode is included with gorename, support for vim can be found in vim-go.

godepgraph

Install with go get github.com/kisielk/godepgraph

Another handy utility is godepgraph, a tool for generating dependency graphs (in Graphviz format) for Go packages.

Dependency graph of golint, rendered with dot
Dependency graph of golint, rendered with dot

Packages in the standard library are colored green, while packages from your GOPATH are colored blue. If a package imports the pseudo package C, it will be colored orange, but C itself will not show up in the graph, as it isn’t a real package. Also note how godepgraph doesn’t delve into the dependencies of the standard library, it only shows the packages that are imported directly, and even those can be hidden with a command line flag.

Dependency graph of golint, rendered with circo
Dependency graph of golint, rendered with `circo`

Because godepgraph just outputs a directed graph in Graphviz format, you can tweak the final image in many ways: You can use different Graphviz filters, such as dot or circo, and you can adjust all kinds of options affecting the layout. But using dot and default settings usually works well enough – for small packages, anyway.


有疑问加站长微信联系(非本文作者)

本文来自:Dominik Honnef

感谢作者:Dominik Honnef

查看原文:An incomplete list of Go tools

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

9347 次点击  ∙  1 赞  
加入收藏 微博
0 回复
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传