Text file talks/2014/hammers.slide

     1  Gophers With Hammers
     2  
     3  Josh Bleecher Snyder
     4  PayPal
     5  josharian@gmail.com
     6  @offbymany
     7  
     8  
     9  * Go was designed with tools in mind. (Rob Pike)
    10  
    11  * Designed with tools in mind
    12  
    13  Simple, regular syntax
    14  Simple semantics
    15  Batteries included
    16  
    17  * Tools everywhere
    18  
    19  - go
    20  - gofmt, goimports
    21  - godoc
    22  - go test [-cover] [-race]
    23  - go vet
    24  - gofix, gofmt -r, eg
    25  - oracle
    26  - golint
    27  - godep
    28  
    29  and more...
    30  
    31  * go command
    32  
    33  	$ go list -f '{{.Deps}}' bytes
    34  	[errors io runtime sync sync/atomic unicode unicode/utf8 unsafe]
    35  
    36  * gofmt
    37  
    38  from
    39  
    40  	for{
    41  	fmt.Println(      "I feel pretty." );
    42  	       }
    43  
    44  to
    45  
    46  	for {
    47  		fmt.Println("I feel pretty.")
    48  	}
    49  
    50  * godoc
    51  
    52  	$ godoc strings Repeat
    53  	func Repeat(s string, count int) string
    54  	    Repeat returns a new string consisting of count copies of the string s.
    55  
    56  * go vet
    57  
    58  Oops
    59  
    60  	if suffix != ".md" || suffix != ".markdown" {
    61  
    62  Flagged
    63  
    64  	suspect or: suffix != ".md" || suffix != ".markdown"
    65  
    66  * go tool cover -mode=set
    67  
    68  	func Repeat(s string, count int) string {
    69  		b := make([]byte, len(s)*count)
    70  		bp := 0
    71  		for i := 0; i < count; i++ {
    72  			bp += copy(b[bp:], s)
    73  		}
    74  		return string(b)
    75  	}
    76  
    77  to
    78  
    79  	func Repeat(s string, count int) string {
    80  		GoCover.Count[0] = 1
    81  		b := make([]byte, len(s)*count)
    82  		bp := 0
    83  		for i := 0; i < count; i++ {
    84  			GoCover.Count[2] = 1
    85  			bp += copy(b[bp:], s)
    86  		}
    87  		GoCover.Count[1] = 1
    88  		return string(b)
    89  	}
    90  
    91  * go test -cover
    92  
    93  	$ go test -coverprofile=c.out strings
    94  	ok  	strings	0.455s	coverage: 96.9% of statements
    95  
    96  	$ go tool cover -func=c.out
    97  	strings/reader.go:	Len				66.7%
    98  	strings/reader.go:	Read				100.0%
    99  	strings/reader.go:	ReadAt				100.0%
   100  	strings/reader.go:	ReadByte			100.0%
   101  	strings/reader.go:	UnreadByte			100.0%
   102  	strings/reader.go:	ReadRune			100.0%
   103  	strings/reader.go:	UnreadRune			100.0%
   104  	strings/reader.go:	Seek				90.9%
   105  	strings/reader.go:	WriteTo				83.3%
   106  	...
   107  
   108  	$ go tool cover -html=c.out
   109  	# opens a browser window, shows line-by-line coverage
   110  
   111  
   112  * Tools to make tools
   113  
   114  - text/template
   115  - go/build
   116  - go/doc
   117  - go/format
   118  - go/{parser,token,ast,printer}
   119  - go.tools/go/types and friends
   120  
   121  and more...
   122  
   123  * Hammers are fun!
   124  
   125  # Why to write your own tools: Fun, learning, profit
   126  
   127  * impl
   128  
   129  Generate implementation stubs given an interface.
   130  
   131  	go get github.com/josharian/impl
   132  
   133  Generate
   134  
   135  	$ impl 'f *File' io.Reader
   136  	func (f *File) Read(p []byte) (n int, err error) {
   137  	}
   138  
   139  from
   140  
   141  	package io
   142  
   143  	type Reader interface {
   144  		Read(p []byte) (n int, err error)
   145  	}
   146  
   147  * impl
   148  
   149  Generate
   150  
   151  	$ impl 'f *File' io.ReadWriter
   152  	func (f *File) Read(p []byte) (n int, err error) {
   153  	}
   154  
   155  	func (f *File) Write(p []byte) (n int, err error) {
   156  	}
   157  
   158  from
   159  
   160  	package io
   161  
   162  	type ReadWriter interface {
   163  		Reader
   164  		Writer
   165  	}
   166  
   167  * impl
   168  
   169  Generate
   170  
   171  	$ impl 'c *Ctx' http.Handler
   172  	func (c *Ctx) ServeHTTP(http.ResponseWriter, *http.Request) {
   173  	}
   174  
   175  from
   176  
   177  	package http
   178  
   179  	type Handler interface {
   180  		ServeHTTP(ResponseWriter, *Request)
   181  	}
   182  
   183  * Plan
   184  
   185  *Find*import*path*and*interface*name*
   186  
   187  	http.Handler ⇒ net/http, Handler
   188  
   189  Parse interface
   190  
   191  	net/http, Handler ⇒ {{"ServeHTTP", {{"", "http.ResponseWriter"}, {"", "*http.Request"}}, {}}}}
   192  
   193  Generate output
   194  
   195  	{{"ServeHTTP", {{"", "http.ResponseWriter"}, {"", "*http.Request"}}, {}}}} ⇒ profit!
   196  
   197  * goimports ftw
   198  
   199  	import "golang.org/x/tools/imports"
   200  
   201  .play hammers/importpath.go /func main/,/^}/
   202  
   203  * Hello, AST
   204  
   205      *ast.File {
   206      .  Package: 1:1
   207      .  Name: *ast.Ident {
   208      .  .  NamePos: 1:9
   209      .  .  Name: "hack"
   210      .  }
   211      .  Decls: []ast.Decl (len = 2) {
   212      .  .  0: *ast.GenDecl {
   213      .  .  .  TokPos: 1:15
   214      .  .  .  Tok: import
   215      .  .  .  Lparen: -
   216      .  .  .  Specs: []ast.Spec (len = 1) {
   217      .  .  .  .  0: *ast.ImportSpec {
   218      .  .  .  .  .  Path: *ast.BasicLit {
   219      .  .  .  .  .  .  ValuePos: 1:22
   220      .  .  .  .  .  .  Kind: STRING
   221      .  .  .  .  .  .  Value: "\"net/http\""
   222      .  .  .  .  .  }
   223  
   224  [truncated]
   225  
   226  
   227  * Extract the import path
   228  
   229  	import (
   230  		"go/parser"
   231  		"go/token"
   232  	)
   233  
   234  .play hammers/extractpath.go /func main/,/^}/
   235  
   236  * Extract the interface name
   237  
   238  	import "go/ast"
   239  
   240  .play hammers/extractiface.go /func main/,/^}/
   241  
   242  A `GenDecl` can have many `Specs`
   243  
   244  	var (
   245  		r io.Reader
   246  		w io.Writer
   247  	)
   248  
   249  * Plan
   250  
   251  Find import path and interface name
   252  
   253  	http.Handler ⇒ net/http, Handler
   254  
   255  *Parse*interface*
   256  
   257  	net/http, Handler ⇒ {{"ServeHTTP", {{"", "http.ResponseWriter"}, {"", "*http.Request"}}, {}}}}
   258  
   259  Generate output
   260  
   261  	{{"ServeHTTP", {{"", "http.ResponseWriter"}, {"", "*http.Request"}}, {}}}} ⇒ profit!
   262  
   263  * Data structures
   264  
   265  Represent
   266  
   267  	Read(p []byte) (n int, err error)
   268  
   269  as
   270  
   271  	Func{
   272  		Name:   "Read",
   273  		Params: []Param{{Name: "p", Type: "[]byte"}},
   274  		Res: []Param{
   275  			{Name: "n", Type: "int"},
   276  			{Name: "err", Type: "error"},
   277  		},
   278  	},
   279  
   280  * Data structures
   281  
   282  .code hammers/types.go /type Func/,/^}/
   283  .code hammers/types.go /type Param/,/^}/
   284  
   285  * Find the code
   286  
   287  	import "go/build"
   288  
   289  .play hammers/findthecode.go /func main/,/^}/
   290  
   291  * Find the interface declaration
   292  
   293  	import "go/printer"
   294  
   295  .play hammers/findtheifacedecl.go /func main/,/^}/
   296  
   297  * Extract function names
   298  
   299  No name? It's an embedded interface. Recurse.
   300  
   301  	type ByteScanner interface {
   302  	    ByteReader
   303  	    UnreadByte() error
   304  	}
   305  
   306  * Extract params and results
   307  
   308  No name? Just use `""`.
   309  
   310  	type ByteWriter interface {
   311  	    WriteByte(c byte) error
   312  	}
   313  
   314  * Qualify types
   315  
   316  Types can be arbitrarily complicated.
   317  
   318  	type CrazyGopher interface {
   319  		CrazyGoph(int) func(chan<- [32]byte, map[string]int64) ([]rune, error)
   320  	}
   321  
   322  And we need to rewrite some of them.
   323  
   324  	int ⇒ int
   325  	*Request ⇒ *http.Request
   326  	io.Reader ⇒ io.Reader
   327  	func(io.Reader, chan map[S][]*T) ⇒ func(io.Reader, chan map[foo.S][]*foo.T))
   328  
   329  * Qualify types
   330  
   331  .play hammers/fulltype.go /func main/,/end main/
   332  
   333  * Plan
   334  
   335  Find import path and interface name
   336  
   337  	http.Handler ⇒ net/http, Handler
   338  
   339  Parse interface
   340  
   341  	net/http, Handler ⇒ {{"ServeHTTP", {{"", "http.ResponseWriter"}, {"", "*http.Request"}}, {}}}}
   342  
   343  *Generate*output*
   344  
   345  	{{"ServeHTTP", {{"", "http.ResponseWriter"}, {"", "*http.Request"}}, {}}}} ⇒ profit!
   346  
   347  * Method type
   348  
   349  .code hammers/types.go /type Method/,/^}/
   350  .code hammers/types.go /type Func/,/^}/
   351  .code hammers/types.go /type Param/,/^}/
   352  
   353  * Use text/template
   354  
   355  .play hammers/codegen.go /func main/,/^}/
   356  
   357  # Don't generate an AST. It's a lot of work, and Go is its own DSL.
   358  
   359  * Ugly is ok
   360  
   361  	import "go/format"
   362  
   363  .play hammers/format.go /func main/,/^}/
   364  
   365  * Great success
   366  
   367  Full code plus tests at `github.com/josharian/impl`
   368  
   369  * Tips
   370  
   371  Use `go`get`-d` to download lots of code from `godoc.org/-/index`. (Don't forget to set a temporary `GOPATH`!)
   372  
   373  Use (and improve) `github.com/yuroyoro/goast-viewer`.
   374  
   375  You don't have to generate all the code. And generating data is even better.
   376  
   377  The `go/ast` docs are your friend.
   378  
   379  `go.tools/go/types` is powerful.
   380  
   381  `go`generate` is coming.
   382  
   383  
   384  * Nails!
   385  
   386  - Break up long strings
   387  - Enums and flags to Stringers
   388  - Dynamic code analysis
   389  - Vet checks
   390  - Reflect ⇒ codegen
   391  - Convention-based http dispatch
   392  - Detect "last line" copy/paste bugs
   393  - AST-aware diff, merge, blame; automated fork analysis
   394  - Machine learning models of ASTs: anomaly detection, bug-prone code detection
   395  - Code fingerprinting
   396  - Examine usage patterns
   397  - Compiler stress tests
   398  
   399  

View as plain text