github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/talks/2014/hammers.slide (about) 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