OLD | NEW |
(Empty) | |
| 1 Go for gophers |
| 2 GopherCon closing keynote |
| 3 25 Apr 2014 |
| 4 |
| 5 Andrew Gerrand |
| 6 adg@golang.org |
| 7 |
| 8 |
| 9 * About me |
| 10 |
| 11 .image go4gophers/gopherswim.jpg |
| 12 |
| 13 I joined Google and the Go team in February 2010. |
| 14 |
| 15 Had to re-think some of my preconceptions about programming. |
| 16 |
| 17 Let me share what I have learned since. |
| 18 |
| 19 |
| 20 * Interfaces |
| 21 |
| 22 |
| 23 * Interfaces: first impressions |
| 24 |
| 25 I used to think about classes and types. |
| 26 |
| 27 Go resists this: |
| 28 |
| 29 - No inheritance. |
| 30 - No subtype polymorphism. |
| 31 - No generics. |
| 32 |
| 33 It instead emphasizes _interfaces_. |
| 34 |
| 35 |
| 36 * Interfaces: the Go way |
| 37 |
| 38 Go interfaces are small. |
| 39 |
| 40 type Stringer interface { |
| 41 String() string |
| 42 } |
| 43 |
| 44 A `Stringer` can pretty print itself. |
| 45 Anything that implements `String` is a `Stringer`. |
| 46 |
| 47 |
| 48 * An interface example |
| 49 |
| 50 An `io.Reader` value emits a stream of binary data. |
| 51 |
| 52 type Reader interface { |
| 53 Read([]byte) (int, error) |
| 54 } |
| 55 |
| 56 Like a UNIX pipe. |
| 57 |
| 58 |
| 59 * Implementing interfaces |
| 60 |
| 61 .code go4gophers/reader.go /ByteReader/,/^}/ |
| 62 |
| 63 |
| 64 * Wrapping interfaces |
| 65 |
| 66 .code go4gophers/reader.go /LogReader/,/STOP/ |
| 67 |
| 68 Wrapping a `ByteReader` with a `LogReader`: |
| 69 |
| 70 .play go4gophers/reader.go /START/,/STOP/ |
| 71 |
| 72 By wrapping we compose interface _values_. |
| 73 |
| 74 |
| 75 * Chaining interfaces |
| 76 |
| 77 Wrapping wrappers to build chains: |
| 78 |
| 79 .code go4gophers/chain.go /START/,/STOP/ |
| 80 |
| 81 More succinctly: |
| 82 |
| 83 .play go4gophers/chain.go /LogReader{io/ |
| 84 |
| 85 Implement complex behavior by composing small pieces. |
| 86 |
| 87 |
| 88 * Programming with interfaces |
| 89 |
| 90 Interfaces separate data from behavior. |
| 91 |
| 92 With interfaces, functions can operate on _behavior:_ |
| 93 |
| 94 // Copy copies from src to dst until either EOF is reached |
| 95 // on src or an error occurs. It returns the number of bytes |
| 96 // copied and the first error encountered while copying, if any. |
| 97 func Copy(dst Writer, src Reader) (written int64, err error) { |
| 98 |
| 99 .play go4gophers/chain.go /LogReader{io/ |
| 100 |
| 101 `Copy` can't know about the underlying data structures. |
| 102 |
| 103 |
| 104 * A larger interface |
| 105 |
| 106 `sort.Interface` describes the operations required to sort a collection: |
| 107 |
| 108 type Interface interface { |
| 109 Len() int |
| 110 Less(i, j int) bool |
| 111 Swap(i, j int) |
| 112 } |
| 113 |
| 114 `IntSlice` can sort a slice of ints: |
| 115 |
| 116 type IntSlice []int |
| 117 |
| 118 func (p IntSlice) Len() int { return len(p) } |
| 119 func (p IntSlice) Less(i, j int) bool { return p[i] < p[j] } |
| 120 func (p IntSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] } |
| 121 |
| 122 `sort.Sort` uses can sort a `[]int` with `IntSlice`: |
| 123 |
| 124 .play go4gophers/sort.go /START/,/STOP/ |
| 125 |
| 126 |
| 127 * Another interface example |
| 128 |
| 129 The `Organ` type describes a body part and can print itself: |
| 130 |
| 131 .play go4gophers/organs.go /type Organ/,$ |
| 132 |
| 133 |
| 134 * Sorting organs |
| 135 |
| 136 The `Organs` type knows how to describe and mutate a slice of organs: |
| 137 |
| 138 .code go4gophers/organs2.go /PART1/,/PART2/ |
| 139 |
| 140 The `ByName` and `ByWeight` types embed `Organs` to sort by different fields: |
| 141 |
| 142 .code go4gophers/organs2.go /PART2/,/PART3/ |
| 143 |
| 144 With embedding we compose _types_. |
| 145 |
| 146 |
| 147 * Sorting organs (continued) |
| 148 |
| 149 To sort a `[]*Organ`, wrap it with `ByName` or `ByWeight` and pass it to `sort.S
ort`: |
| 150 |
| 151 .play go4gophers/organs2.go /START/,/STOP/ |
| 152 |
| 153 |
| 154 * Another wrapper |
| 155 |
| 156 The `Reverse` function takes a `sort.Interface` and |
| 157 returns a `sort.Interface` with an inverted `Less` method: |
| 158 |
| 159 .code go4gophers/organs3.go /func Reverse/,$ |
| 160 |
| 161 To sort the organs in descending order, compose our sort types with `Reverse`: |
| 162 |
| 163 .play go4gophers/organs3.go /START/,/STOP/ |
| 164 |
| 165 |
| 166 * Interfaces: why they work |
| 167 |
| 168 These are not just cool tricks. |
| 169 |
| 170 This is how we structure programs in Go. |
| 171 |
| 172 |
| 173 * Interfaces: Sigourney |
| 174 |
| 175 Sigourney is a modular audio synthesizer I wrote in Go. |
| 176 |
| 177 .image go4gophers/sigourney.png |
| 178 |
| 179 Audio is generated by a chain of `Processors`: |
| 180 |
| 181 type Processor interface { |
| 182 Process(buffer []Sample) |
| 183 } |
| 184 |
| 185 ([[https://github.com/nf/sigourney][github.com/nf/sigourney]]) |
| 186 |
| 187 |
| 188 * Interfaces: Roshi |
| 189 |
| 190 Roshi is a time-series event store written by Peter Bourgon. It provides this AP
I: |
| 191 ········ |
| 192 Insert(key, timestamp, value) |
| 193 Delete(key, timestamp, value) |
| 194 Select(key, offset, limit) []TimestampValue |
| 195 |
| 196 The same API is implemented by the `farm` and `cluster` parts of the system. |
| 197 |
| 198 .image go4gophers/roshi.png |
| 199 |
| 200 An elegant design that exhibits composition. |
| 201 ([[https://github.com/soundcloud/roshi][github.com/soundcloud/roshi]]) |
| 202 |
| 203 |
| 204 * Interfaces: why they work (continued) |
| 205 |
| 206 Interfaces are _the_ generic programming mechanism. |
| 207 |
| 208 This gives all Go code a familiar shape. |
| 209 |
| 210 Less is more. |
| 211 |
| 212 |
| 213 * Interfaces: why they work (continued) |
| 214 |
| 215 It's all about composition. |
| 216 |
| 217 Interfaces—by design and convention—encourage us to write composable code. |
| 218 |
| 219 |
| 220 * Interfaces: why they work (continued) |
| 221 |
| 222 Interfaces types are just types |
| 223 and interface values are just values. |
| 224 |
| 225 They are orthogonal to the rest of the language. |
| 226 |
| 227 |
| 228 * Interfaces: why they work (continued) |
| 229 |
| 230 Interfaces separate data from behavior. (Classes conflate them.) |
| 231 |
| 232 type HandlerFunc func(ResponseWriter, *Request) |
| 233 |
| 234 func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { |
| 235 f(w, r) |
| 236 } |
| 237 |
| 238 |
| 239 * Interfaces: what I learned |
| 240 |
| 241 Think about composition. |
| 242 |
| 243 Better to have many small simple things than one big complex thing. |
| 244 |
| 245 Also: what I thought of as small is pretty big. |
| 246 |
| 247 Some repetition in the small is okay when it benefits "the large". |
| 248 |
| 249 |
| 250 * Concurrency |
| 251 |
| 252 |
| 253 * Concurrency: first impressions |
| 254 |
| 255 My first exposure to concurrency was in C, Java, and Python. |
| 256 Later: event-driven models in Python and JavaScript. |
| 257 |
| 258 When I saw Go I saw: |
| 259 |
| 260 "The performance of an event-driven model without callback hell." |
| 261 |
| 262 But I had questions: "Why can't I wait on or kill a goroutine?" |
| 263 |
| 264 |
| 265 * Concurrency: the Go way |
| 266 |
| 267 Goroutines provide concurrent execution. |
| 268 |
| 269 Channels express the communication and synchronization of independent processes. |
| 270 |
| 271 Select enables computation on channel operations. |
| 272 |
| 273 .image go4gophers/gopherflag.png |
| 274 |
| 275 |
| 276 * A concurrency example |
| 277 |
| 278 The binary tree comparison exercise from the Go Tour. |
| 279 |
| 280 "Implement a function |
| 281 |
| 282 func Same(t1, t2 *tree.Tree) bool |
| 283 |
| 284 that compares the contents of two binary trees." |
| 285 |
| 286 .image go4gophers/tree.png |
| 287 |
| 288 |
| 289 * Walking a tree |
| 290 |
| 291 type Tree struct { |
| 292 Left, Right *Tree |
| 293 Value int |
| 294 } |
| 295 |
| 296 A simple depth-first tree traversal: |
| 297 |
| 298 .play go4gophers/tree-walk.go /func Walk/,$ |
| 299 |
| 300 |
| 301 * Comparing trees (1/2) |
| 302 |
| 303 A concurrent walker: |
| 304 |
| 305 .code go4gophers/tree-thread.go /func Walk/,/STOP/ |
| 306 |
| 307 |
| 308 * Comparing trees (2/2) |
| 309 |
| 310 Walking two trees concurrently: |
| 311 |
| 312 .play go4gophers/tree-thread.go /func Same/,$ |
| 313 |
| 314 |
| 315 * Comparing trees without channels (1/3) |
| 316 |
| 317 .code go4gophers/tree-nothread.go /func Same/,/^}/ |
| 318 |
| 319 The `Walk` function has nearly the same signature: |
| 320 |
| 321 .code go4gophers/tree-nothread.go /func Walk/ |
| 322 .code go4gophers/tree-nothread.go /func.+Next/ |
| 323 |
| 324 (We call `Next` instead of the channel receive.) |
| 325 |
| 326 |
| 327 * Comparing trees without channels (2/3) |
| 328 |
| 329 But the implementation is much more complex: |
| 330 |
| 331 .code go4gophers/tree-nothread.go /func Walk/,/CUT/ |
| 332 |
| 333 |
| 334 * Comparing trees without channels (3/3) |
| 335 |
| 336 .code go4gophers/tree-nothread.go /CUT/,/STOP/ |
| 337 |
| 338 |
| 339 * Another look at the channel version |
| 340 |
| 341 .code go4gophers/tree-thread.go /func Walk/,/STOP/ |
| 342 |
| 343 But there's a problem: when an inequality is found, |
| 344 a goroutine might be left blocked sending to `ch`. |
| 345 |
| 346 |
| 347 * Stopping early |
| 348 |
| 349 Add a `quit` channel to the walker so we can stop it mid-stride. |
| 350 |
| 351 .code go4gophers/tree-select.go /func Walk/,/STOP/ |
| 352 |
| 353 |
| 354 * Stopping early (continued) |
| 355 |
| 356 Create a `quit` channel and pass it to each walker. |
| 357 By closing `quit` when the `Same` exits, any running walkers are terminated. |
| 358 |
| 359 .code go4gophers/tree-select.go /func Same/,/^}/ |
| 360 |
| 361 |
| 362 * Why not just kill the goroutines? |
| 363 |
| 364 Goroutines are invisible to Go code. They can't be killed or waited on. |
| 365 |
| 366 You have to build that yourself. |
| 367 |
| 368 There's a reason: |
| 369 |
| 370 As soon as Go code knows in which thread it runs you get thread-locality. |
| 371 |
| 372 Thread-locality defeats the concurrency model. |
| 373 |
| 374 |
| 375 * Concurrency: why it works |
| 376 |
| 377 The model makes concurrent code easy to read and write. |
| 378 (Makes concurrency is *accessible*.) |
| 379 |
| 380 This encourages the decomposition of independent computations. |
| 381 |
| 382 |
| 383 * Concurrency: why it works (continued) |
| 384 |
| 385 The simplicity of the concurrency model makes it flexible. |
| 386 |
| 387 Channels are just values; they fit right into the type system. |
| 388 |
| 389 Goroutines are invisible to Go code; this gives you concurrency anywhere. |
| 390 |
| 391 Less is more. |
| 392 |
| 393 |
| 394 * Concurrency: what I learned |
| 395 |
| 396 Concurrency is not just for doing more things faster. |
| 397 |
| 398 It's for writing better code. |
| 399 |
| 400 |
| 401 * Syntax |
| 402 |
| 403 |
| 404 * Syntax: first impressions |
| 405 |
| 406 At first, Go syntax felt a bit inflexible and verbose. |
| 407 |
| 408 It affords few of the conveniences to which I was accustomed. |
| 409 |
| 410 For instance: |
| 411 |
| 412 - No getters/setters on fields. |
| 413 - No map/filter/reduce/zip. |
| 414 - No optional arguments. |
| 415 |
| 416 |
| 417 * Syntax: the Go way |
| 418 |
| 419 Favor readability above all. |
| 420 |
| 421 Offer enough sugar to be productive, but not too much. |
| 422 |
| 423 |
| 424 * Getters and setters (or "properties") |
| 425 |
| 426 Getters and setters turn assignments and reads into function calls. |
| 427 This leads to surprising hidden behavior. |
| 428 |
| 429 In Go, just write (and call) the methods. |
| 430 |
| 431 The control flow cannot be obscured. |
| 432 |
| 433 |
| 434 * Map/filter/reduce/zip |
| 435 |
| 436 Map/filter/reduce/zip are useful in Python. |
| 437 |
| 438 a = [1, 2, 3, 4] |
| 439 b = map(lambda x: x+1, a) |
| 440 |
| 441 In Go, you just write the loops. |
| 442 |
| 443 a := []int{1, 2, 3, 4} |
| 444 b := make([]int, len(a)) |
| 445 for i, x := range a { |
| 446 b[i] = x+1 |
| 447 } |
| 448 |
| 449 This is a little more verbose, |
| 450 but makes the performance characteristics obvious. |
| 451 |
| 452 It's easy code to write, and you get more control. |
| 453 |
| 454 |
| 455 * Optional arguments |
| 456 |
| 457 Go functions can't have optional arguments. |
| 458 |
| 459 Instead, use variations of the function: |
| 460 |
| 461 func NewWriter(w io.Writer) *Writer |
| 462 func NewWriterLevel(w io.Writer, level int) (*Writer, error) |
| 463 |
| 464 Or an options struct: |
| 465 |
| 466 func New(o *Options) (*Jar, error) |
| 467 |
| 468 type Options struct { |
| 469 PublicSuffixList PublicSuffixList |
| 470 } |
| 471 |
| 472 Or a variadic list of options. |
| 473 |
| 474 Create many small simple things, not one big complex thing. |
| 475 |
| 476 |
| 477 * Syntax: why it works |
| 478 |
| 479 The language resists convoluted code. |
| 480 |
| 481 With obvious control flow, it's easy to navigate unfamiliar code. |
| 482 |
| 483 Instead we create more small things that are easy to document and understand. |
| 484 |
| 485 So Go code is easy to read. |
| 486 |
| 487 (And with gofmt, it's easy to write readable code.) |
| 488 |
| 489 |
| 490 * Syntax: what I learned |
| 491 |
| 492 I was often too clever for my own good. |
| 493 |
| 494 I appreciate the consistency, clarity, and _transparency_ of Go code. |
| 495 |
| 496 I sometimes miss the conveniences, but rarely. |
| 497 |
| 498 |
| 499 * Error handling |
| 500 |
| 501 |
| 502 * Error handling: first impressions |
| 503 |
| 504 I had previously used exceptions to handle errors. |
| 505 |
| 506 Go's error handling model felt verbose by comparison. |
| 507 |
| 508 I was immediately tired of typing this: |
| 509 |
| 510 if err != nil { |
| 511 return err |
| 512 } |
| 513 |
| 514 |
| 515 * Error handling: the Go way |
| 516 |
| 517 Go codifies errors with the built-in `error` interface: |
| 518 |
| 519 type error interface { |
| 520 Error() string |
| 521 } |
| 522 |
| 523 Error values are used just like any other value. |
| 524 |
| 525 func doSomething() error |
| 526 |
| 527 err := doSomething() |
| 528 if err != nil { |
| 529 log.Println("An error occurred:", err) |
| 530 } |
| 531 |
| 532 Error handling code is just code. |
| 533 |
| 534 (Started as a convention (`os.Error`). We made it built in for Go 1.) |
| 535 |
| 536 |
| 537 * Error handling: why it works |
| 538 |
| 539 Error handling is important. |
| 540 |
| 541 Go makes error handling as important as any other code. |
| 542 |
| 543 |
| 544 * Error handling: why it works (continued) |
| 545 |
| 546 Errors are just values; they fit easily into the rest of the language |
| 547 (interfaces, channels, and so on). |
| 548 |
| 549 Result: Go code handles errors correctly and elegantly. |
| 550 |
| 551 |
| 552 * Error handling: why it works (continued) |
| 553 |
| 554 We use the same language for errors as everything else. |
| 555 |
| 556 Lack of hidden control flow (throw/try/catch/finally) improves readability. |
| 557 |
| 558 Less is more. |
| 559 |
| 560 |
| 561 |
| 562 * Error handling: what I learned |
| 563 |
| 564 To write good code we must think about errors. |
| 565 |
| 566 Exceptions make it easy to avoid thinking about errors. |
| 567 (Errors shouldn't be "exceptional!") |
| 568 |
| 569 Go encourages us to consider every error condition. |
| 570 |
| 571 My Go programs are far more robust than my programs in other languages. |
| 572 |
| 573 I don't miss exceptions at all. |
| 574 |
| 575 |
| 576 * Packages |
| 577 |
| 578 |
| 579 * Packages: first impressions |
| 580 |
| 581 I found the capital-letter-visibility rule weird; |
| 582 "Let me use my own naming scheme!" |
| 583 |
| 584 I didn't like "package per directory"; |
| 585 "Let me use my own structure!" |
| 586 |
| 587 I was disappointed by lack of monkey patching. |
| 588 |
| 589 |
| 590 * Packages: the Go way |
| 591 |
| 592 Go packages are a name space for types, functions, variables, and constants. |
| 593 |
| 594 |
| 595 * Visibility |
| 596 |
| 597 Visibility is at the package level. |
| 598 Names are "exported" when they begin with a capital letter. |
| 599 |
| 600 package zip |
| 601 |
| 602 func NewReader(r io.ReaderAt, size int64) (*Reader, error) // exported |
| 603 |
| 604 type Reader struct { // exported |
| 605 File []*File // exported |
| 606 Comment string // exported |
| 607 r io.ReaderAt // unexported |
| 608 } |
| 609 |
| 610 func (f *File) Open() (rc io.ReadCloser, err error) // exported |
| 611 |
| 612 func (f *File) findBodyOffset() (int64, error) // unexported |
| 613 |
| 614 func readDirectoryHeader(f *File, r io.Reader) error // unexported |
| 615 |
| 616 Good for readability: easy to see whether a name is part of the public interface
. |
| 617 Good for design: couples naming decisions with interface decisions. |
| 618 |
| 619 |
| 620 * Package structure |
| 621 |
| 622 Packages can be spread across multiple files. |
| 623 |
| 624 Permits shared private implementation and informal code organization. |
| 625 |
| 626 Packages files must live in a directory unique to the package. |
| 627 |
| 628 The path to that directory determines the package's import path. |
| 629 |
| 630 The build system locates dependencies from the source alone. |
| 631 |
| 632 |
| 633 * "Monkey patching" |
| 634 |
| 635 Go forbids modifying package declarations from outside the package. |
| 636 |
| 637 But we can get similar behavior using global variables: |
| 638 |
| 639 package flag |
| 640 |
| 641 var Usage = func() { |
| 642 fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0]) |
| 643 PrintDefaults() |
| 644 } |
| 645 |
| 646 Or registration functions: |
| 647 |
| 648 package http |
| 649 |
| 650 func Handle(pattern string, handler Handler) |
| 651 |
| 652 This gives the flexibility of monkey patching but on the package author's terms. |
| 653 |
| 654 (This depends on Go's initialization semantics.) |
| 655 |
| 656 |
| 657 * Packages: why they work |
| 658 |
| 659 The loose organization of packages lets us write and refactor code quickly. |
| 660 |
| 661 But packages encourage the programmer to consider the public interface. |
| 662 |
| 663 This leads to good names and simpler interfaces. |
| 664 |
| 665 With the source as the single source of truth, |
| 666 there are no makefiles to get out of sync. |
| 667 |
| 668 (This design enables great tools like [[http://godoc.org][godoc.org]] and goimpo
rts.) |
| 669 |
| 670 Predictable semantics make packages easy to read, understand, and use. |
| 671 |
| 672 |
| 673 * Packages: what I learned |
| 674 |
| 675 Go's package system taught me to prioritize the consumer of my code. |
| 676 (Even if that consumer is me.) |
| 677 |
| 678 It also stopped me from doing gross stuff. |
| 679 |
| 680 Packages are rigid where it matters, and loose where it doesn't. |
| 681 It just feels right. |
| 682 |
| 683 Probably my favorite part of the language. |
| 684 |
| 685 |
| 686 * Documentation |
| 687 |
| 688 |
| 689 * Documentation: first impressions |
| 690 |
| 691 Godoc reads documentation from Go source code, like `pydoc` or `javadoc`. |
| 692 |
| 693 But unlike those two, it doesn't support complex formatting or other meta data. |
| 694 Why? |
| 695 |
| 696 |
| 697 * Documentation: the Go way |
| 698 |
| 699 Godoc comments precede the declaration of an exported identifier: |
| 700 |
| 701 // Join concatenates the elements of a to create a single string. |
| 702 // The separator string sep is placed between elements in the resulting
string. |
| 703 func Join(a []string, sep string) string { |
| 704 |
| 705 It extracts the comments and presents them: |
| 706 |
| 707 $ godoc strings Join |
| 708 func Join(a []string, sep string) string |
| 709 Join concatenates the elements of a to create a single string. The |
| 710 separator string sep is placed between elements in the resulting str
ing. |
| 711 |
| 712 Also integrated with the testing framework to provide testable example functions
. |
| 713 |
| 714 func ExampleJoin() { |
| 715 s := []string{"foo", "bar", "baz"} |
| 716 fmt.Println(strings.Join(s, ", ")) |
| 717 // Output: foo, bar, baz |
| 718 } |
| 719 |
| 720 |
| 721 * Documentation: the Go way (continued) |
| 722 |
| 723 .image go4gophers/godoc.png |
| 724 |
| 725 |
| 726 * Documentation: why it works |
| 727 |
| 728 Godoc wants you to write good comments, so the source looks great: |
| 729 |
| 730 // ValidMove reports whether the specified move is valid. |
| 731 func ValidMove(from, to Position) bool |
| 732 |
| 733 Javadoc just wants to produce pretty documentation, so the source is hideous: |
| 734 |
| 735 /** |
| 736 * Validates a chess move. |
| 737 * |
| 738 * @param fromPos position from which a piece is being moved |
| 739 * @param toPos position to which a piece is being moved |
| 740 * @return true if the move is valid, otherwise false |
| 741 */ |
| 742 boolean isValidMove(Position fromPos, Position toPos) |
| 743 |
| 744 (Also a grep for `"ValidMove"` will return the first line of documentation.) |
| 745 |
| 746 |
| 747 * Documentation: what I learned |
| 748 |
| 749 Godoc taught me to write documentation _as_I_code._ |
| 750 |
| 751 Writing documentation _improves_the_code_ I write. |
| 752 |
| 753 |
| 754 * More |
| 755 |
| 756 There are many more examples. |
| 757 |
| 758 The overriding theme: |
| 759 |
| 760 - At first, something seemed weird or lacking. |
| 761 - I realized it was a design decision. |
| 762 |
| 763 Those decisions make the language—and Go code—better. |
| 764 |
| 765 Sometimes you have to live with the language a while to see it. |
| 766 |
| 767 |
| 768 * Lessons |
| 769 |
| 770 |
| 771 * Code is communication |
| 772 |
| 773 Be articulate: |
| 774 |
| 775 - Choose good names. |
| 776 - Design simple interfaces. |
| 777 - Write precise documentation. |
| 778 - Don't be too clever. |
| 779 |
| 780 |
| 781 * Less is exponentially more |
| 782 |
| 783 New features can weaken existing features. |
| 784 |
| 785 Features multiply complexity. |
| 786 |
| 787 Complexity defeats orthogonality. |
| 788 |
| 789 Orthogonality is vital: it enables composition. |
| 790 |
| 791 |
| 792 * Composition is key |
| 793 |
| 794 Don't solve problems by building _a_ thing. |
| 795 |
| 796 Instead, combine simple tools and compose them. |
| 797 |
| 798 |
| 799 * Design good interfaces |
| 800 |
| 801 .image go4gophers/gophertraining.png |
| 802 |
| 803 .html go4gophers/gophertraining.html |
| 804 |
| 805 |
| 806 * Simplicity is hard |
| 807 |
| 808 Invest the time to find the simple solution. |
| 809 |
| 810 |
| 811 * Go's effect on me |
| 812 |
| 813 These lessons were all things I already "knew". |
| 814 |
| 815 Go helped me internalize them. |
| 816 |
| 817 .image go4gophers/gopherhat.jpg |
| 818 |
| 819 Go made me a better programmer. |
| 820 |
| 821 |
| 822 * A message for gophers everywhere |
| 823 |
| 824 Let's build small, simple, and beautiful things together. |
| 825 |
| 826 .image go4gophers/gopherswrench.jpg |
| 827 |
OLD | NEW |