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