├── .github └── FUNDING.yml ├── LICENSE └── README.md /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: bahlo 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Go Styleguide 2 | 3 | This serves as a supplement to 4 | [Effective Go](https://golang.org/doc/effective_go.html), based on years of 5 | experience and inspiration/ideas from conference talks. 6 | 7 | ## Table of contents 8 | - [Add context to errors](#add-context-to-errors) 9 | - [Consistent error and log messages](#consistent-error-and-log-messages) 10 | - [Dependency management](#dependency-management) 11 | - [Use modules](#use-modules) 12 | - [Use Semantic Versioning](#use-semantic-versioning) 13 | - [Structured logging](#structured-logging) 14 | - [Avoid global variables](#avoid-global-variables) 15 | - [Keep the happy path left](#keep-the-happy-path-left) 16 | - [Testing](#testing) 17 | - [Use an assert library](#use-an-assert-library) 18 | - [Use sub-tests to structure functional tests](#use-sub-tests-to-structure-functional-tests) 19 | - [Use table-driven tests](#use-table-driven-tests) 20 | - [Avoid mocks](#avoid-mocks) 21 | - [Avoid DeepEqual](#avoid-deepequal) 22 | - [Avoid testing unexported funcs](#avoid-testing-unexported-funcs) 23 | - [Add examples to your test files to demonstrate usage](#add-examples-to-your-test-files-to-demonstrate-usage) 24 | - [Use linters](#use-linters) 25 | - [Use goimports](#use-goimports) 26 | - [Use meaningful variable names](#use-meaningful-variable-names) 27 | - [Avoid side effects](#avoid-side-effects) 28 | - [Favour pure functions](#favour-pure-functions) 29 | - [Don't over-interface](#dont-over-interface) 30 | - [Don't under-package](#dont-under-package) 31 | - [Handle signals](#handle-signals) 32 | - [Divide imports](#divide-imports) 33 | - [Avoid unadorned return](#avoid-unadorned-return) 34 | - [Use canonical import path](#use-canonical-import-path) 35 | - [Avoid empty interface](#avoid-empty-interface) 36 | - [Main first](#main-first) 37 | - [Use internal packages](#use-internal-packages) 38 | - [Avoid helper/util](#avoid-helperutil) 39 | - [Embed binary data](#embed-binary-data) 40 | - [Use `io.WriteString`](#use-iowritestring) 41 | - [Use functional options](#use-functional-options) 42 | - [Structs](#structs) 43 | - [Use named structs](#use-named-structs) 44 | - [Avoid new keyword](#avoid-new-keyword) 45 | - [Consistent header naming](#consistent-header-naming) 46 | - [Avoid magic numbers](#avoid-magic-numbers) 47 | - [Use context for cancellation](#use-context-for-cancellation) 48 | - [Avoid panic in production](#avoid-panic-in-production) 49 | - [Error handling and error types](#error-handling-and-error-types) 50 | - [Code formatting](#code-formatting) 51 | - [Concurrency patterns](#concurrency-patterns) 52 | - [Package documentation](#package-documentation) 53 | - [Avoid unnecessary abstraction](#avoid-unnecessary-abstraction) 54 | 55 | ## Add context to errors 56 | 57 | **Don't:** 58 | ```go 59 | file, err := os.Open("foo.txt") 60 | if err != nil { 61 | return err 62 | } 63 | ``` 64 | 65 | Using the approach above can lead to unclear error messages because of missing 66 | context. 67 | 68 | **Do:** 69 | ```go 70 | file, err := os.Open("foo.txt") 71 | if err != nil { 72 | return fmt.Errorf("open foo.txt failed: %w", err) 73 | } 74 | ``` 75 | 76 | Wrapping errors with a custom message provides context as it gets propagated up 77 | the stack. 78 | This does not always make sense. 79 | If you're unsure if the context of a returned error is at all times sufficient, 80 | wrap it. 81 | 82 | ## Dependency management 83 | 84 | ### Use modules 85 | Use [modules](https://github.com/golang/go/wiki/Modules), since it is the built-in go dependency 86 | management tooling and will be widely supported (available with Go 1.11+). 87 | 88 | ### Use Semantic Versioning 89 | Tag your packages using [Semantic Versioning](http://semver.org), check the [modules wiki](https://github.com/golang/go/wiki/Modules#how-to-prepare-for-a-release) for more information about 90 | best practices regarding releases. 91 | The git tag for your go package should have the format `v..`, e.g., `v1.0.1`. 92 | 93 | ## Structured logging 94 | 95 | **Don't:** 96 | ```go 97 | log.Printf("Listening on :%d", port) 98 | http.ListenAndServe(fmt.Sprintf(":%d", port), nil) 99 | // 2017/07/29 13:05:50 Listening on :80 100 | ``` 101 | 102 | **Do:** 103 | ```go 104 | import "github.com/sirupsen/logrus" 105 | // ... 106 | 107 | logger.WithField("port", port).Info("Server is listening") 108 | http.ListenAndServe(fmt.Sprintf(":%d", port), nil) 109 | // {"level":"info","msg":"Server is listening","port":"7000","time":"2017-12-24T13:25:31+01:00"} 110 | ``` 111 | 112 | This is a harmless example, but using structured logging makes debugging and log 113 | parsing easier. 114 | 115 | ## Avoid global variables 116 | 117 | **Don't:** 118 | ```go 119 | var db *sql.DB 120 | 121 | func main() { 122 | db = // ... 123 | http.HandleFunc("/drop", DropHandler) 124 | // ... 125 | } 126 | 127 | func DropHandler(w http.ResponseWriter, r *http.Request) { 128 | db.Exec("DROP DATABASE prod") 129 | } 130 | ``` 131 | 132 | Global variables make testing and readability hard and every method has access 133 | to them (even those, that don't need it). 134 | 135 | **Do:** 136 | ```go 137 | func main() { 138 | db := // ... 139 | handlers := Handlers{DB: db} 140 | http.HandleFunc("/drop", handlers.DropHandler) 141 | // ... 142 | } 143 | 144 | type Handlers struct { 145 | DB *sql.DB 146 | } 147 | 148 | func (h *Handlers) DropHandler(w http.ResponseWriter, r *http.Request) { 149 | h.DB.Exec("DROP DATABASE prod") 150 | } 151 | ``` 152 | Use structs to encapsulate the variables and make them available only to those functions that actually need them by making them methods implemented for that struct. 153 | 154 | Alternatively, higher-order functions can be used to inject dependencies via closures. 155 | ```go 156 | func main() { 157 | db := // ... 158 | http.HandleFunc("/drop", DropHandler(db)) 159 | // ... 160 | } 161 | 162 | func DropHandler(db *sql.DB) http.HandleFunc { 163 | return func (w http.ResponseWriter, r *http.Request) { 164 | db.Exec("DROP DATABASE prod") 165 | } 166 | } 167 | ``` 168 | 169 | If you really need global variables or constants, e.g., for defining errors or string constants, put them at the top of your file. 170 | 171 | **Don't:** 172 | ```go 173 | import "xyz" 174 | 175 | func someFunc() { 176 | //... 177 | } 178 | 179 | const route = "/some-route" 180 | 181 | func someOtherFunc() { 182 | // usage of route 183 | } 184 | 185 | var NotFoundErr = errors.New("not found") 186 | 187 | func yetAnotherFunc() { 188 | // usage of NotFoundErr 189 | } 190 | ``` 191 | 192 | **Do:** 193 | ```go 194 | import "xyz" 195 | 196 | const route = "/some-route" 197 | 198 | var NotFoundErr = errors.New("not found") 199 | 200 | func someFunc() { 201 | //... 202 | } 203 | 204 | func someOtherFunc() { 205 | // usage of route 206 | } 207 | 208 | func yetAnotherFunc() { 209 | // usage of NotFoundErr 210 | } 211 | ``` 212 | 213 | ## Keep the happy path left 214 | 215 | **Don't:** 216 | ```go 217 | if item, ok := someMap[someKey]; ok { 218 | return item 219 | } 220 | return ErrKeyNotFound 221 | ``` 222 | 223 | **Do:** 224 | ```go 225 | item, ok := someMap[someKey] 226 | if !ok { 227 | return ErrKeyNotFound 228 | } 229 | return item 230 | ``` 231 | 232 | This helps to keep your code clear and readable. Not doing it accumulates in 233 | larger functions and leads to the happy path being buried in a lot of if/for/... 234 | statements. 235 | 236 | ## Testing 237 | 238 | ### Use an assert library 239 | 240 | **Don't:** 241 | ```go 242 | func TestAdd(t *testing.T) { 243 | actual := 2 + 2 244 | expected := 4 245 | if (actual != expected) { 246 | t.Errorf("Expected %d, but got %d", expected, actual) 247 | } 248 | } 249 | ``` 250 | 251 | **Do:** 252 | ```go 253 | import "github.com/stretchr/testify/assert" 254 | 255 | func TestAdd(t *testing.T) { 256 | actual := 2 + 2 257 | expected := 4 258 | assert.Equal(t, expected, actual) 259 | } 260 | ``` 261 | 262 | Using assert libraries makes your tests more readable, requires less code and 263 | provides consistent error output. 264 | 265 | ### Use sub-tests to structure functional tests 266 | **Don't:** 267 | ```go 268 | func TestSomeFunctionSuccess(t *testing.T) { 269 | // ... 270 | } 271 | 272 | func TestSomeFunctionWrongInput(t *testing.T) { 273 | // ... 274 | } 275 | ``` 276 | 277 | **Do:** 278 | ```go 279 | func TestSomeFunction(t *testing.T) { 280 | t.Run("success", func(t *testing.T){ 281 | //... 282 | }) 283 | 284 | t.Run("wrong input", func(t *testing.T){ 285 | //... 286 | }) 287 | } 288 | ``` 289 | 290 | ### Use table driven tests 291 | 292 | **Don't:** 293 | ```go 294 | func TestAdd(t *testing.T) { 295 | assert.Equal(t, 1+1, 2) 296 | assert.Equal(t, 1+-1, 0) 297 | assert.Equal(t, 1, 0, 1) 298 | assert.Equal(t, 0, 0, 0) 299 | } 300 | ``` 301 | 302 | The above approach looks simpler, but it's much harder to find a failing case, 303 | especially when having hundreds of cases. 304 | 305 | **Do:** 306 | ```go 307 | func TestAdd(t *testing.T) { 308 | cases := []struct { 309 | A, B, Expected int 310 | }{ 311 | {1, 1, 2}, 312 | {1, -1, 0}, 313 | {1, 0, 1}, 314 | {0, 0, 0}, 315 | } 316 | 317 | for _, tc := range cases { 318 | tc := tc 319 | t.Run(fmt.Sprintf("%d + %d", tc.A, tc.B), func(t *testing.T) { 320 | t.Parallel() 321 | assert.Equal(t, tc.Expected, tc.A+tc.B) 322 | }) 323 | } 324 | } 325 | ``` 326 | 327 | Using table-driven tests in combination with subtests gives you direct insight 328 | about which case is failing and which cases are tested. 329 | – [Mitchell Hashimoto at GopherCon 2017](https://youtu.be/8hQG7QlcLBk?t=7m34s) 330 | 331 | Running subtests in parallel allow you to have a lot more test cases and still get those awesomely fast go build times. 332 | – [The Go Blog](https://blog.golang.org/subtests) 333 | 334 | A `tc := tc` is needed. Because without it, only one of the cases would be checked. 335 | – [Be Careful with Table Driven Tests and t.Parallel()](https://gist.github.com/posener/92a55c4cd441fc5e5e85f27bca008721) 336 | 337 | ### Avoid mocks 338 | 339 | **Don't:** 340 | ```go 341 | func TestRun(t *testing.T) { 342 | mockConn := new(MockConn) 343 | run(mockConn) 344 | } 345 | ``` 346 | 347 | **Do:** 348 | ```go 349 | import "github.com/stretchr/testify/assert" 350 | 351 | func TestRun(t *testing.T) { 352 | ln, err := net.Listen("tcp", "127.0.0.1:0") 353 | assert.Nil(t, err) 354 | 355 | go func() { 356 | defer ln.Close() 357 | _, err := ln.Accept() 358 | assert.Nil(t, err) 359 | }() 360 | 361 | client, err := net.Dial("tcp", ln.Addr().String()) 362 | assert.Nil(t, err) 363 | 364 | run(client) 365 | } 366 | ``` 367 | 368 | Only use mocks if not otherwise possible, favor real implementations. 369 | – [Mitchell Hashimoto at GopherCon 2017](https://youtu.be/8hQG7QlcLBk?t=26m51s) 370 | 371 | ### Avoid DeepEqual 372 | 373 | **Don't:** 374 | ```go 375 | type myType struct { 376 | id int 377 | name string 378 | irrelevant []byte 379 | } 380 | 381 | func TestSomething(t *testing.T) { 382 | actual := &myType{/* ... */} 383 | expected := &myType{/* ... */} 384 | assert.True(t, reflect.DeepEqual(expected, actual)) 385 | } 386 | ``` 387 | 388 | **Do:** 389 | ```go 390 | type myType struct { 391 | id int 392 | name string 393 | irrelevant []byte 394 | } 395 | 396 | func (m *myType) testString() string { 397 | return fmt.Sprintf("%d.%s", m.id, m.name) 398 | } 399 | 400 | func TestSomething(t *testing.T) { 401 | actual := &myType{/* ... */} 402 | expected := &myType{/* ... */} 403 | if actual.testString() != expected.testString() { 404 | t.Errorf("Expected '%s', got '%s'", expected.testString(), actual.testString()) 405 | } 406 | // or assert.Equal(t, actual.testString(), expected.testString()) 407 | } 408 | ``` 409 | 410 | Using `testString()` for comparing structs helps on complex structs with many 411 | fields that are not relevant for the equality check. 412 | This approach only makes sense for very big or tree-like structs. 413 | – [Mitchell Hashimoto at GopherCon 2017](https://youtu.be/8hQG7QlcLBk?t=30m45s) 414 | 415 | Google open sourced their [go-cmp](http://github.com/google/go-cmp) package as a more powerful and safer alternative to `reflect.DeepEqual`. 416 | – [Joe Tsai](https://twitter.com/francesc/status/885630175668346880). 417 | 418 | ### Avoid testing unexported funcs 419 | 420 | Only test unexported funcs if you can't access a path via exported funcs. 421 | Since they are unexported, they are prone to change. 422 | 423 | ### Add examples to your test files to demonstrate usage 424 | ```go 425 | func ExampleSomeInterface_SomeMethod(){ 426 | instance := New() 427 | result, err := instance.SomeMethod() 428 | fmt.Println(result, err) 429 | // Output: someResult, 430 | } 431 | ``` 432 | 433 | ## Use linters 434 | 435 | Use all the linters included in [golangci-lint](https://github.com/golangci/golangci-lint) to lint your projects before committing. 436 | ```bash 437 | # Installation - replace vX.X.X with the version you want to use 438 | GO111MODULE=on go get github.com/golangci/golangci-lint/cmd/golangci-lint@vX.X.X 439 | # traditional way without go module 440 | go get -u github.com/golangci/golangci-lint/cmd/golangci-lint 441 | 442 | 443 | # Usage in the project workspace 444 | golangci-lint run 445 | ``` 446 | For detailed usage and the ci-pipeline installation guide visit [golangci-lint](https://github.com/golangci/golangci-lint). 447 | 448 | ## Use goimports 449 | 450 | Only commit gofmt'd files. Use `goimports` for this to format/update the import statements as well. 451 | 452 | ## Use meaningful variable names 453 | Avoid single-letter variable names. They may seem more readable to you at the moment of writing but they make the code hard to understand for your colleagues and your future self. 454 | 455 | **Don't:** 456 | ```go 457 | func findMax(l []int) int { 458 | m := l[0] 459 | for _, n := range l { 460 | if n > m { 461 | m = n 462 | } 463 | } 464 | return m 465 | } 466 | ``` 467 | 468 | **Do:** 469 | ```go 470 | func findMax(inputs []int) int { 471 | max := inputs[0] 472 | for _, value := range inputs { 473 | if value > max { 474 | max = value 475 | } 476 | } 477 | return max 478 | } 479 | ``` 480 | Single-letter variable names are fine in the following cases. 481 | * They are absolute standard like ... 482 | * `t` in tests 483 | * `r` and `w` in http request handlers 484 | * `i` for the index in a loop 485 | * They name the receiver of a method, e.g., `func (s *someStruct) myFunction(){}` 486 | 487 | Of course also too long variables names like `createInstanceOfMyStructFromString` should be avoided. 488 | 489 | ## Avoid side-effects 490 | 491 | **Don't:** 492 | ```go 493 | func init() { 494 | someStruct.Load() 495 | } 496 | ``` 497 | 498 | Side effects are only okay in special cases (e.g. parsing flags in a cmd). 499 | If you find no other way, rethink and refactor. 500 | 501 | ## Favour pure functions 502 | 503 | > In computer programming, a function may be considered a pure function if both of the following statements about the function hold: 504 | > 1. The function always evaluates the same result value given the same argument value(s). The function result value cannot depend on any hidden information or state that may change while program execution proceeds or between different executions of the program, nor can it depend on any external input from I/O devices. 505 | > 2. Evaluation of the result does not cause any semantically observable side effect or output, such as mutation of mutable objects or output to I/O devices. 506 | 507 | – [Wikipedia](https://en.wikipedia.org/wiki/Pure_function) 508 | 509 | **Don't:** 510 | ```go 511 | func MarshalAndWrite(some *Thing) error { 512 | b, err := json.Marshal(some) 513 | if err != nil { 514 | return err 515 | } 516 | 517 | return ioutil.WriteFile("some.thing", b, 0644) 518 | } 519 | ``` 520 | 521 | **Do:** 522 | ```go 523 | // Marshal is a pure func (even though useless) 524 | func Marshal(some *Thing) ([]bytes, error) { 525 | return json.Marshal(some) 526 | } 527 | 528 | // ... 529 | ``` 530 | 531 | This is obviously not possible at all times, but trying to make every possible 532 | func pure makes code more understandable and improves debugging. 533 | 534 | ## Don't over-interface 535 | 536 | **Don't:** 537 | ```go 538 | type Server interface { 539 | Serve() error 540 | Some() int 541 | Fields() float64 542 | That() string 543 | Are([]byte) error 544 | Not() []string 545 | Necessary() error 546 | } 547 | 548 | func debug(srv Server) { 549 | fmt.Println(srv.String()) 550 | } 551 | 552 | func run(srv Server) { 553 | srv.Serve() 554 | } 555 | ``` 556 | 557 | **Do:** 558 | ```go 559 | type Server interface { 560 | Serve() error 561 | } 562 | 563 | func debug(v fmt.Stringer) { 564 | fmt.Println(v.String()) 565 | } 566 | 567 | func run(srv Server) { 568 | srv.Serve() 569 | } 570 | ``` 571 | 572 | Favour small interfaces and only expect the interfaces you need in your funcs. 573 | 574 | ## Don't under-package 575 | 576 | Deleting or merging packages is far easier than splitting big ones up. 577 | When unsure if a package can be split, do it. 578 | 579 | ## Handle signals 580 | 581 | **Don't:** 582 | ```go 583 | func main() { 584 | for { 585 | time.Sleep(1 * time.Second) 586 | ioutil.WriteFile("foo", []byte("bar"), 0644) 587 | } 588 | } 589 | ``` 590 | 591 | **Do:** 592 | ```go 593 | func main() { 594 | logger := // ... 595 | sc := make(chan os.Signal, 1) 596 | done := make(chan bool) 597 | 598 | go func() { 599 | for { 600 | select { 601 | case s := <-sc: 602 | logger.Info("Received signal, stopping application", 603 | zap.String("signal", s.String())) 604 | done <- true 605 | return 606 | default: 607 | time.Sleep(1 * time.Second) 608 | ioutil.WriteFile("foo", []byte("bar"), 0644) 609 | } 610 | } 611 | }() 612 | 613 | signal.Notify(sc, os.Interrupt, os.Kill) 614 | <-done // Wait for go-routine 615 | } 616 | ``` 617 | 618 | Handling signals allows us to gracefully stop our server, close open files and 619 | connections and therefore prevent file corruption among other things. 620 | 621 | ## Divide imports 622 | 623 | **Don't:** 624 | ```go 625 | import ( 626 | "encoding/json" 627 | "github.com/some/external/pkg" 628 | "fmt" 629 | "github.com/this-project/pkg/some-lib" 630 | "os" 631 | ) 632 | ``` 633 | 634 | **Do:** 635 | ```go 636 | import ( 637 | "encoding/json" 638 | "fmt" 639 | "os" 640 | 641 | "github.com/bahlo/this-project/pkg/some-lib" 642 | 643 | "github.com/bahlo/another-project/pkg/some-lib" 644 | "github.com/bahlo/yet-another-project/pkg/some-lib" 645 | 646 | "github.com/some/external/pkg" 647 | "github.com/some-other/external/pkg" 648 | ) 649 | ``` 650 | 651 | Divide imports into four groups sorted from internal to external for readability: 652 | 1. Standard library 653 | 2. Project internal packages 654 | 3. Company internal packages 655 | 4. External packages 656 | 657 | ## Avoid unadorned return 658 | 659 | **Don't:** 660 | ```go 661 | func run() (n int, err error) { 662 | // ... 663 | return 664 | } 665 | ``` 666 | 667 | **Do:** 668 | ```go 669 | func run() (n int, err error) { 670 | // ... 671 | return n, err 672 | } 673 | ``` 674 | 675 | Named returns are good for documentation, unadorned returns are bad for 676 | readability and error-prone. 677 | 678 | ## Use canonical import path 679 | 680 | **Don't:** 681 | ```go 682 | package sub 683 | ``` 684 | 685 | **Do:** 686 | ```go 687 | package sub // import "github.com/my-package/pkg/sth/else/sub" 688 | ``` 689 | 690 | Adding the canonical import path adds context to the package and makes 691 | importing easy. 692 | 693 | ## Avoid empty interface 694 | 695 | **Don't:** 696 | ```go 697 | func run(foo interface{}) { 698 | // ... 699 | } 700 | ``` 701 | 702 | Empty interfaces make code more complex and unclear, avoid them where you can. 703 | 704 | ## Main first 705 | 706 | **Don't:** 707 | ```go 708 | package main // import "github.com/me/my-project" 709 | 710 | func someHelper() int { 711 | // ... 712 | } 713 | 714 | func someOtherHelper() string { 715 | // ... 716 | } 717 | 718 | func Handler(w http.ResponseWriter, r *http.Reqeust) { 719 | // ... 720 | } 721 | 722 | func main() { 723 | // ... 724 | } 725 | ``` 726 | 727 | **Do:** 728 | ```go 729 | package main // import "github.com/me/my-project" 730 | 731 | func main() { 732 | // ... 733 | } 734 | 735 | func Handler(w http.ResponseWriter, r *http.Reqeust) { 736 | // ... 737 | } 738 | 739 | func someHelper() int { 740 | // ... 741 | } 742 | 743 | func someOtherHelper() string { 744 | // ... 745 | } 746 | ``` 747 | 748 | Putting `main()` first makes reading the file a lot easier. Only the 749 | `init()` function should be above it. 750 | 751 | ## Use internal packages 752 | 753 | If you're creating a cmd, consider moving libraries to `internal/` to prevent 754 | import of unstable, changing packages. 755 | 756 | ## Avoid helper/util 757 | 758 | Use clear names and try to avoid creating a `helper.go`, `utils.go` or even 759 | package. 760 | 761 | ## Embed binary data 762 | 763 | To enable single-binary deployments, use the `//go:embed` directive and the [embed](https://pkg.go.dev/embed) package to add templates and other static 764 | assets to your binary. 765 | For Go versions prior [v1.16](https://go.dev/doc/go1.16#library-embed), use external tools 766 | (e.g. [github.com/gobuffalo/packr](https://github.com/gobuffalo/packr)). 767 | 768 | ## Use `io.WriteString` 769 | A number of important types that satisfy `io.Writer` also have a `WriteString` 770 | method, including `*bytes.Buffer`, `*os.File` and `*bufio.Writer`. `WriteString` 771 | is behavioral contract with implicit assent that passed string will be written 772 | in efficient way, without a temporary allocation. Therefore using 773 | `io.WriteString` may improve performance at most, and at least string will be 774 | written in any way. 775 | 776 | **Don't:** 777 | ```go 778 | var w io.Writer = new(bytes.Buffer) 779 | str := "some string" 780 | w.Write([]byte(str)) 781 | ``` 782 | 783 | **Do:** 784 | ```go 785 | var w io.Writer = new(bytes.Buffer) 786 | str := "some string" 787 | io.WriteString(w, str) 788 | ``` 789 | 790 | ## Use functional options 791 | 792 | ```go 793 | 794 | func main() { 795 | // ... 796 | startServer( 797 | WithPort(8080), 798 | WithTimeout(1 * time.Second), 799 | ) 800 | } 801 | 802 | type Config struct { 803 | port int 804 | timeout time.Duration 805 | } 806 | 807 | type ServerOpt func(*Config) 808 | 809 | func WithPort(port int) ServerOpt { 810 | return func(cfg *Config) { 811 | cfg.port = port 812 | } 813 | } 814 | 815 | func WithTimeout(timeout time.Duration) ServerOpt { 816 | return func(cfg *Config) { 817 | cfg.timeout = timeout 818 | } 819 | } 820 | 821 | func startServer(opts ...ServerOpt) { 822 | cfg := new(Config) 823 | for _, fn := range opts { 824 | fn(cfg) 825 | } 826 | 827 | // ... 828 | } 829 | 830 | 831 | ``` 832 | 833 | ## Structs 834 | ### Use named structs 835 | If a struct has more than one field, include field names when instantiating it. 836 | 837 | **Don't:** 838 | ```go 839 | params := myStruct{ 840 | 1, 841 | true, 842 | } 843 | ``` 844 | 845 | **Do:** 846 | ```go 847 | params := myStruct{ 848 | Foo: 1, 849 | Bar: true, 850 | } 851 | ``` 852 | 853 | ### Avoid new keyword 854 | Using the normal syntax instead of the `new` keyword makes it more clear what is happening: a new instance of the struct is created `MyStruct{}` and we get the pointer for it with `&`. 855 | 856 | **Don't:** 857 | ```go 858 | s := new(MyStruct) 859 | ``` 860 | 861 | **Do:** 862 | ```go 863 | s := &MyStruct{} 864 | ``` 865 | 866 | ## Consistent header naming 867 | **Don't:** 868 | ```go 869 | r.Header.Get("authorization") 870 | w.Header.Set("Content-type") 871 | w.Header.Set("content-type") 872 | w.Header.Set("content-Type") 873 | ``` 874 | 875 | **Do:** 876 | ```go 877 | r.Header.Get("Authorization") 878 | w.Header.Set("Content-Type") 879 | ``` 880 | 881 | ## Avoid magic numbers 882 | A number without a name and any context is just a random value. It tells us nothing, so avoid them in your code (the exception might be the number 0, for example when creating loops). 883 | 884 | **Don't:** 885 | ```go 886 | func IsStrongPassword(password string) bool { 887 | return len(password) >= 8 888 | } 889 | ``` 890 | 891 | **Do:** 892 | ```go 893 | const minPasswordLength = 8 894 | 895 | func IsStrongPassword(password string) bool { 896 | return len(password) >= minPasswordLength 897 | } 898 | ``` 899 | 900 | ## Error handling and error types 901 | **Don't:** 902 | ```go 903 | func readFile(filename string) ([]byte, error) { 904 | data, err := ioutil.ReadFile(filename) 905 | if err != nil { 906 | return nil, err 907 | } 908 | return data, nil 909 | } 910 | ``` 911 | 912 | **Do:** 913 | ```go 914 | func readFile(filename string) ([]byte, error) { 915 | data, err := ioutil.ReadFile(filename) 916 | if err != nil { 917 | return nil, fmt.Errorf("error reading file %s: %v", filename, err) 918 | } 919 | return data, nil 920 | } 921 | ``` 922 | Using `fmt.Errorf` provides a simple and readable way to add context to errors. 923 | 924 | ## Package documentation 925 | **Don't:** 926 | ```go 927 | package main 928 | 929 | func main() { 930 | // No package or function documentation 931 | } 932 | ``` 933 | 934 | **Do:** 935 | ```go 936 | // Package main provides the entry point for the application. 937 | package main 938 | 939 | // main is the entry point for the application. 940 | func main() { 941 | // Start the application 942 | } 943 | ``` 944 | Documenting packages and functions enhances code understanding and usability. 945 | --------------------------------------------------------------------------------