├── LICENSE ├── README.md └── README_zhcn.md /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 | 9 | - [Add context to errors](#add-context-to-errors) 10 | - [Dependency managemenet](#dependency-management) 11 | - [Use dep](#use-dep) 12 | - [Use Semantic Versioning](#use-semantic-versioning) 13 | - [Avoid gopkg.in](#avoid-gopkgin) 14 | - [Structured logging](#structured-logging) 15 | - [Avoid global variables](#avoid-global-variables) 16 | - [Testing](#testing) 17 | - [Use assert-libraries](#use-assert-libraries) 18 | - [Use table-driven tests](#use-table-driven-tests) 19 | - [Avoid mocks](#avoid-mocks) 20 | - [Avoid DeepEqual](#avoid-deepequal) 21 | - [Avoid testing unexported funcs](#avoid-testing-unexported-funcs) 22 | - [Use linters](#use-linters) 23 | - [Use gofmt](#use-gofmt) 24 | - [Avoid side effects](#avoid-side-effects) 25 | - [Favour pure funcs](#favour-pure-funcs) 26 | - [Don't over-interface](#dont-over-interface) 27 | - [Don't under-package](#dont-under-package) 28 | - [Handle signals](#handle-signals) 29 | - [Divide imports](#divide-imports) 30 | - [Avoid unadorned return](#avoid-unadorned-return) 31 | - [Use canonical import path](#use-canonical-import-path) 32 | - [Avoid empty interface](#avoid-empty-interface) 33 | - [Main first](#main-first) 34 | - [Use internal packages](#use-internal-packages) 35 | - [Avoid helper/util](#avoid-helperutil) 36 | - [Embed binary data](#embed-binary-data) 37 | - [Use functional options](#use-functional-options) 38 | 39 | ## Add context to errors 40 | 41 | **Don't:** 42 | ```go 43 | file, err := os.Open("foo.txt") 44 | if err != nil { 45 | return err 46 | } 47 | ``` 48 | 49 | Using the approach above can lead to unclear error messages because of missing 50 | context. 51 | 52 | **Do:** 53 | ```go 54 | import "github.com/pkg/errors" // for example 55 | 56 | // ... 57 | 58 | file, err := os.Open("foo.txt") 59 | if err != nil { 60 | return errors.Wrap(err, "open foo.txt failed") 61 | } 62 | ``` 63 | 64 | Wrapping errors with a custom message provides context as it gets propagated up 65 | the stack. 66 | This does not always make sense. 67 | If you're unsure if the context of a returned error is at all times sufficient, 68 | wrap it. 69 | Make sure the root error is still accessible somehow for type checking. 70 | 71 | ## Dependency management 72 | 73 | ### Use dep 74 | Use [dep](https://github.com/golang/dep), since it's production ready and will 75 | soon become part of the toolchain. 76 | – [Sam Boyer at GopherCon 2017](https://youtu.be/5LtMb090AZI?t=27m57s) 77 | 78 | ### Use Semantic Versioning 79 | Since `dep` can handle versions, tag your packages using 80 | [Semantic Versioning](http://semver.org). 81 | 82 | ### Avoid gopkg.in 83 | While [gopkg.in](http://labix.org/gopkg.in) is a great tool and was really 84 | useful, it tags one version and is not meant to work with `dep`. 85 | Prefer direct import and specify version in `Gopkg.toml`. 86 | 87 | ## Structured logging 88 | 89 | **Don't:** 90 | ```go 91 | log.Printf("Listening on :%d", port) 92 | http.ListenAndServe(fmt.Sprintf(":%d", port), nil) 93 | // 2017/07/29 13:05:50 Listening on :80 94 | ``` 95 | 96 | **Do:** 97 | ```go 98 | import "github.com/uber-go/zap" // for example 99 | 100 | // ... 101 | 102 | logger, _ := zap.NewProduction() 103 | defer logger.Sync() 104 | logger.Info("Server started", 105 | zap.Int("port", port), 106 | zap.String("env", env), 107 | ) 108 | http.ListenAndServe(fmt.Sprintf(":%d", port), nil) 109 | // {"level":"info","ts":1501326297.511464,"caller":"Desktop/structured.go:17","msg":"Server started","port":80,"env":"production"} 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 | http.HandleFunc("/drop", DropHandler(db)) 140 | // ... 141 | } 142 | 143 | func DropHandler(db *sql.DB) http.HandleFunc { 144 | return func (w http.ResponseWriter, r *http.Request) { 145 | db.Exec("DROP DATABASE prod") 146 | } 147 | } 148 | ``` 149 | 150 | Use higher-order functions instead of global variables to inject dependencies 151 | accordingly. 152 | 153 | ## Testing 154 | 155 | ### Use assert-libraries 156 | 157 | **Don't:** 158 | ```go 159 | func TestAdd(t *testing.T) { 160 | actual := 2 + 2 161 | expected := 4 162 | if (actual != expected) { 163 | t.Errorf("Expected %d, but got %d", expected, actual) 164 | } 165 | } 166 | ``` 167 | 168 | **Do:** 169 | ```go 170 | import "github.com/stretchr/testify/assert" // for example 171 | 172 | func TestAdd(t *testing.T) { 173 | actual := 2 + 2 174 | expected := 4 175 | assert.Equal(t, expected, actual) 176 | } 177 | ``` 178 | 179 | Using assert libraries makes your tests more readable, requires less code and 180 | provides consistent error output. 181 | 182 | ### Use table driven tests 183 | 184 | **Don't:** 185 | ```go 186 | func TestAdd(t *testing.T) { 187 | assert.Equal(t, 1+1, 2) 188 | assert.Equal(t, 1+-1, 0) 189 | assert.Equal(t, 1, 0, 1) 190 | assert.Equal(t, 0, 0, 0) 191 | } 192 | ``` 193 | 194 | The above approach looks simpler, but it's much harder to find a failing case, 195 | especially when having hundreds of cases. 196 | 197 | **Do:** 198 | ```go 199 | func TestAdd(t *testing.T) { 200 | cases := []struct { 201 | A, B, Expected int 202 | }{ 203 | {1, 1, 2}, 204 | {1, -1, 0}, 205 | {1, 0, 1}, 206 | {0, 0, 0}, 207 | } 208 | 209 | for _, tc := range cases { 210 | t.Run(fmt.Sprintf("%d + %d", tc.A, tc.B), func(t *testing.T) { 211 | assert.Equal(t, t.Expected, tc.A+tc.B) 212 | }) 213 | } 214 | } 215 | ``` 216 | 217 | Using table driven tests in combination with subtests gives you direct insight 218 | about which case is failing and which cases are tested. 219 | – [Mitchell Hashimoto at GopherCon 2017](https://youtu.be/8hQG7QlcLBk?t=7m34s) 220 | 221 | ### Avoid mocks 222 | 223 | **Don't:** 224 | ```go 225 | func TestRun(t *testing.T) { 226 | mockConn := new(MockConn) 227 | run(mockConn) 228 | } 229 | ``` 230 | 231 | **Do:** 232 | ```go 233 | func TestRun(t *testing.T) { 234 | ln, err := net.Listen("tcp", "127.0.0.1:0") 235 | t.AssertNil(t, err) 236 | 237 | var server net.Conn 238 | go func() { 239 | defer ln.Close() 240 | server, err := ln.Accept() 241 | t.AssertNil(t, err) 242 | }() 243 | 244 | client, err := net.Dial("tcp", ln.Addr().String()) 245 | t.AssertNil(err) 246 | 247 | run(client) 248 | } 249 | ``` 250 | 251 | Only use mocks if not otherwise possible, favor real implementations. 252 | – [Mitchell Hashimoto at GopherCon 2017](https://youtu.be/8hQG7QlcLBk?t=26m51s) 253 | 254 | ### Avoid DeepEqual 255 | 256 | **Don't:** 257 | ```go 258 | type myType struct { 259 | id int 260 | name string 261 | irrelevant []byte 262 | } 263 | 264 | func TestSomething(t *testing.T) { 265 | actual := &myType{/* ... */} 266 | expected := &myType{/* ... */} 267 | assert.True(t, reflect.DeepEqual(expected, actual)) 268 | } 269 | ``` 270 | 271 | **Do:** 272 | ```go 273 | type myType struct { 274 | id int 275 | name string 276 | irrelevant []byte 277 | } 278 | 279 | func (m *myType) testString() string { 280 | return fmt.Sprintf("%d.%s", m.id, m.name) 281 | } 282 | 283 | func TestSomething(t *testing.T) { 284 | actual := &myType{/* ... */} 285 | expected := &myType{/* ... */} 286 | if actual.testString() != expected.testString() { 287 | t.Errorf("Expected '%s', got '%s'", expected.testString(), actual.testString()) 288 | } 289 | // or assert.Equal(t, actual.testString(), expected.testString()) 290 | } 291 | ``` 292 | 293 | Using `testString()` for comparing structs helps on complex structs with many 294 | fields that are not relevant for the equality check. 295 | This approach only makes sense for very big or tree-like structs. 296 | – [Mitchell Hashimoto at GopherCon 2017](https://youtu.be/8hQG7QlcLBk?t=30m45s) 297 | 298 | ### Avoid testing unexported funcs 299 | 300 | Only test unexported funcs if you can't access a path via exported funcs. 301 | Since they are unexported, they are prone to change. 302 | 303 | ## Use linters 304 | 305 | Use linters (e.g. [gometalinter](https://github.com/alecthomas/gometalinter)) to 306 | lint your projects before committing. 307 | 308 | ## Use gofmt 309 | 310 | Only commit gofmt'd files, use `-s` to simplify code. 311 | 312 | ## Avoid side-effects 313 | 314 | **Don't:** 315 | ```go 316 | func init() { 317 | someStruct.Load() 318 | } 319 | ``` 320 | 321 | Side effects are only okay in special cases (e.g. parsing flags in a cmd). 322 | If you find no other way, rethink and refactor. 323 | 324 | ## Favour pure funcs 325 | 326 | > In computer programming, a function may be considered a pure function if both of the following statements about the function hold: 327 | > 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. 328 | > 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. 329 | 330 | – [Wikipedia](https://en.wikipedia.org/wiki/Pure_function) 331 | 332 | **Don't:** 333 | ```go 334 | func MarshalAndWrite(some *Thing) error { 335 | b, err := json.Marshal(some) 336 | if err != nil { 337 | return err 338 | } 339 | 340 | return ioutil.WriteFile("some.thing", b, 0644) 341 | } 342 | ``` 343 | 344 | **Do:** 345 | ```go 346 | // Marshal is a pure func (even though useless) 347 | func Marshal(some *Thing) ([]bytes, error) { 348 | return json.Marshal(some) 349 | } 350 | 351 | // ... 352 | ``` 353 | 354 | This is obviously not possible at all times, but trying to make every possible 355 | func pure makes code more understandable and improves debugging. 356 | 357 | ## Don't over-interface 358 | 359 | **Don't:** 360 | ```go 361 | type Server interface { 362 | Serve() error 363 | Some() int 364 | Fields() float64 365 | That() string 366 | Are([]byte) error 367 | Not() []string 368 | Necessary() error 369 | } 370 | 371 | func debug(srv Server) { 372 | fmt.Println(srv.String()) 373 | } 374 | 375 | func run(srv Server) { 376 | srv.Serve() 377 | } 378 | ``` 379 | 380 | **Do:** 381 | ```go 382 | type Server interface { 383 | Serve() error 384 | } 385 | 386 | func debug(v fmt.Stringer) { 387 | fmt.Println(v.String()) 388 | } 389 | 390 | func run(srv Server) { 391 | srv.Serve() 392 | } 393 | ``` 394 | 395 | Favour small interfaces and only expect the interfaces you need in your funcs. 396 | 397 | ## Don't under-package 398 | 399 | Deleting or merging packages is far more easier than splitting big ones up. 400 | When unsure if a package can be split, do it. 401 | 402 | ## Handle signals 403 | 404 | **Don't:** 405 | ```go 406 | func main() { 407 | for { 408 | time.Sleep(1 * time.Second) 409 | ioutil.WriteFile("foo", []byte("bar"), 0644) 410 | } 411 | } 412 | ``` 413 | 414 | **Do:** 415 | ```go 416 | func main() { 417 | logger := // ... 418 | sc := make(chan os.Signal, 1) 419 | done := make(chan bool) 420 | 421 | go func() { 422 | for { 423 | select { 424 | case s := <-sc: 425 | logger.Info("Received signal, stopping application", 426 | zap.String("signal", s.String())) 427 | done <- true 428 | return 429 | default: 430 | time.Sleep(1 * time.Second) 431 | ioutil.WriteFile("foo", []byte("bar"), 0644) 432 | } 433 | } 434 | }() 435 | 436 | signal.Notify(sc, os.Interrupt, os.Kill) 437 | <-done // Wait for go-routine 438 | } 439 | ``` 440 | 441 | Handling signals allows us to gracefully stop our server, close open files and 442 | connections and therefore prevent file corruption among other things. 443 | 444 | ## Divide imports 445 | 446 | **Don't:** 447 | ```go 448 | import ( 449 | "encoding/json" 450 | "github.com/some/external/pkg" 451 | "fmt" 452 | "github.com/this-project/pkg/some-lib" 453 | "os" 454 | ) 455 | ``` 456 | 457 | **Do:** 458 | ```go 459 | import ( 460 | "encoding/json" 461 | "fmt" 462 | "os" 463 | 464 | "github.com/some/external/pkg" 465 | 466 | "github.com/this-project/pkg/some-lib" 467 | ) 468 | ``` 469 | 470 | Dividing std, external and internal imports improves readability. 471 | 472 | ## Avoid unadorned return 473 | 474 | **Don't:** 475 | ```go 476 | func run() (n int, err error) { 477 | // ... 478 | return 479 | } 480 | ``` 481 | 482 | **Do:** 483 | ```go 484 | func run() (n int, err error) { 485 | // ... 486 | return n, err 487 | } 488 | ``` 489 | 490 | Named returns are good for documentation, unadorned returns are bad for 491 | readability and error-prone. 492 | 493 | ## Use canonical import path 494 | 495 | **Don't:** 496 | ```go 497 | package sub 498 | ``` 499 | 500 | **Do:** 501 | ```go 502 | package sub // import "github.com/my-package/pkg/sth/else/sub" 503 | ``` 504 | 505 | Adding the canonical import path adds context to the package and makes 506 | importing easy. 507 | 508 | ## Avoid empty interface 509 | 510 | **Don't:** 511 | ```go 512 | func run(foo interface{}) { 513 | // ... 514 | } 515 | ``` 516 | 517 | Empty interfaces make code more complex and unclear, avoid them where you can. 518 | 519 | ## Main first 520 | 521 | **Don't:** 522 | ```go 523 | package main // import "github.com/me/my-project" 524 | 525 | func someHelper() int { 526 | // ... 527 | } 528 | 529 | func someOtherHelper() string { 530 | // ... 531 | } 532 | 533 | func Handler(w http.ResponseWriter, r *http.Reqeust) { 534 | // ... 535 | } 536 | 537 | func main() { 538 | // ... 539 | } 540 | ``` 541 | 542 | **Do:** 543 | ```go 544 | package main // import "github.com/me/my-project" 545 | 546 | func main() { 547 | // ... 548 | } 549 | 550 | func Handler(w http.ResponseWriter, r *http.Reqeust) { 551 | // ... 552 | } 553 | 554 | func someHelper() int { 555 | // ... 556 | } 557 | 558 | func someOtherHelper() string { 559 | // ... 560 | } 561 | ``` 562 | 563 | Putting `main()` first makes reading the file a lot more easier. Only the 564 | `init()` function should be above it. 565 | 566 | ## Use internal packages 567 | 568 | If you're creating a cmd, consider moving libraries to `internal/` to prevent 569 | import of unstable, changing packages. 570 | 571 | ## Avoid helper/util 572 | 573 | Use clear names and try to avoid creating a `helper.go`, `utils.go` or even 574 | package. 575 | 576 | ## Embed binary data 577 | 578 | To enable single-binary deployments, use tools to add templates and other static 579 | assets to your binary 580 | (e.g. [github.com/jteeuwen/go-bindata](https://github.com/jteeuwen/go-bindata)). 581 | 582 | ## Use functional options 583 | 584 | ```go 585 | 586 | func main() { 587 | // ... 588 | startServer( 589 | WithPort(8080), 590 | WithTimeout(1 * time.Second), 591 | ) 592 | } 593 | 594 | type Config struct { 595 | port int 596 | timeout time.Duration 597 | } 598 | 599 | type ServerOpt func(*Config) 600 | 601 | func WithPort(port int) ServerOpt { 602 | return func(cfg *Config) { 603 | cfg.port = port 604 | } 605 | } 606 | 607 | func WithTimeout(timeout time.Duration) ServerOpt { 608 | return func(cfg *Config) { 609 | cfg.timeout = timeout 610 | } 611 | } 612 | 613 | func startServer(opts ...ServerOpt) { 614 | cfg := new(Config) 615 | for _, fn := range opts { 616 | fn(cfg) 617 | } 618 | 619 | // ... 620 | } 621 | 622 | 623 | ``` 624 | -------------------------------------------------------------------------------- /README_zhcn.md: -------------------------------------------------------------------------------- 1 | # Go Styleguide 2 | 3 | 本文是对 4 | [Effective Go](https://golang.org/doc/effective_go.html) 的补充, 其条目来自于经年累月的会议上得到的经验和灵感。 5 | 6 | ## Table of contents 7 | 8 | - [给错误添加上下文](#给错误添加上下文) 9 | - [依赖管理](#依赖管理) 10 | - [使用 dep](#使用dep) 11 | - [使用 semantic 版本号](#使用semantic版本号) 12 | - [避免使用 gopkg.in](#避免使用gopkgin) 13 | - [结构化的日志](#结构化的日志) 14 | - [避免全局变量](#避免全局变量) 15 | - [测试](#测试) 16 | - [使用assert库](#使用assert库) 17 | - [使用表驱动的测试](#使用表驱动的测试) 18 | - [避免 mock](#避免mock) 19 | - [避免使用 deepequal](#避免deepequal) 20 | - [不要测试非导出的函数](#不要测试非导出函数) 21 | - [使用 linter](#使用linter) 22 | - [使用 gofmt](#使用gofmt) 23 | - [避免 side-effects](#避免side-effects) 24 | - [尽量使用纯函数](#尽量使用纯函数) 25 | - [避免接口臃肿](#避免接口臃肿) 26 | - [Don't under-package](#dont-under-package) 27 | - [处理信号](#处理信号) 28 | - [分块组织import](#分块组织import) 29 | - [避免不加修饰的 return](#避免不加修饰的return) 30 | - [添加包的权威导入路径](#添加包的权威导入路径) 31 | - [避免空接口](#避免空接口) 32 | - [main 函数先行](#main函数先行) 33 | - [使用 internal 包](#使用internal包) 34 | - [避免使用 helper/util 的文件名、包名](#避免使用helper/util的文件名、包名) 35 | - [将二进制内容嵌入到程序中](#内嵌二进制数据) 36 | - [函数式的配置设置](#使用函数式的配置选项) 37 | 38 | ## 给错误添加上下文 39 | 40 | **Don't:** 41 | ```go 42 | file, err := os.Open("foo.txt") 43 | if err != nil { 44 | return err 45 | } 46 | ``` 47 | 48 | 这种处理方式会导致错误信息不清晰,因为丢失了错误本来的上下文。 49 | 50 | **Do:** 51 | ```go 52 | import "github.com/pkg/errors" // for example 53 | 54 | // ... 55 | 56 | file, err := os.Open("foo.txt") 57 | if err != nil { 58 | return errors.Wrap(err, "open foo.txt failed") 59 | } 60 | ``` 61 | 62 | 用自定义的 message 包装错误可以在错误从栈中向上“冒泡”的时候提供错误的上下文。 63 | 这么做并不一定总有意义。 64 | 如果你不确定一个返回的错误信息是否充分(译注:能够帮助判断问题在哪里), 65 | 那么就对 error 进行 wrap。 66 | 确保根 error 在 wrap 之后仍然可以访问到,用于 type checking。 67 | 68 | ## 依赖管理 69 | 70 | ### 使用dep 71 | 由于 dep 已经 production ready,并且将来会成为官方的工具链之一 72 | – [Sam Boyer at GopherCon 2017](https://youtu.be/5LtMb090AZI?t=27m57s) 73 | 因此开始使用 dep 吧。 [dep](https://github.com/golang/dep) 74 | 75 | ### 使用Semantic版本号 76 | 由于 dep 可以管理依赖版本,尽量使用 semver 对你的项目打 tag。 77 | [Semantic Versioning](http://semver.org). 78 | 79 | ### 避免使用gopkgin 80 | [gopkg.in](http://labix.org/gopkg.in) 是很棒很有用的工具,这个工具会将你的依赖打 tag,但其本来的设计并不是要与 dep 协作。 81 | 请直接 import,使用 dep 并在 `Gopkg.toml` 中指定版本。 82 | 83 | ## 结构化的日志 84 | 85 | **Don't:** 86 | ```go 87 | log.Printf("Listening on :%d", port) 88 | http.ListenAndServe(fmt.Sprintf(":%d", port), nil) 89 | // 2017/07/29 13:05:50 Listening on :80 90 | ``` 91 | 92 | **Do:** 93 | ```go 94 | import "github.com/uber-go/zap" // for example 95 | 96 | // ... 97 | 98 | logger, _ := zap.NewProduction() 99 | defer logger.Sync() 100 | logger.Info("Server started", 101 | zap.Int("port", port), 102 | zap.String("env", env), 103 | ) 104 | http.ListenAndServe(fmt.Sprintf(":%d", port), nil) 105 | // {"level":"info","ts":1501326297.511464,"caller":"Desktop/structured.go:17","msg":"Server started","port":80,"env":"production"} 106 | ``` 107 | 108 | 这个例子并不是很有说服力,不过使用结构化的日志可以让你的日志无论是 debug 还是被日志收集 parse 都变得更容易。 109 | 110 | ## 避免全局变量 111 | 112 | **Don't:** 113 | ```go 114 | var db *sql.DB 115 | 116 | func main() { 117 | db = // ... 118 | http.HandleFunc("/drop", DropHandler) 119 | // ... 120 | } 121 | 122 | func DropHandler(w http.ResponseWriter, r *http.Request) { 123 | db.Exec("DROP DATABASE prod") 124 | } 125 | ``` 126 | 127 | 全局变量会使测试难度增加,会使代码的可读性降低,每一个函数都能够访问这些全局变量(即使是那些根本就不需要操作全局变量的函数)。 128 | 129 | **Do:** 130 | ```go 131 | func main() { 132 | db := // ... 133 | http.HandleFunc("/drop", DropHandler(db)) 134 | // ... 135 | } 136 | 137 | func DropHandler(db *sql.DB) http.HandleFunc { 138 | return func (w http.ResponseWriter, r *http.Request) { 139 | db.Exec("DROP DATABASE prod") 140 | } 141 | } 142 | ``` 143 | 144 | 使用高阶函数(high-order function)来按需注入依赖,而不是全局变量。 145 | 146 | ## 测试 147 | 148 | ### 使用assert库 149 | 150 | **Don't:** 151 | ```go 152 | func TestAdd(t *testing.T) { 153 | actual := 2 + 2 154 | expected := 4 155 | if (actual != expected) { 156 | t.Errorf("Expected %d, but got %d", expected, actual) 157 | } 158 | } 159 | ``` 160 | 161 | **Do:** 162 | ```go 163 | import "github.com/stretchr/testify/assert" // for example 164 | 165 | func TestAdd(t *testing.T) { 166 | actual := 2 + 2 167 | expected := 4 168 | assert.Equal(t, expected, actual) 169 | } 170 | ``` 171 | 172 | 使用 assert 库使测试代码更可读,节省冗余的代码并提供稳定的错误输出。 173 | 174 | ### 使用表驱动的测试 175 | 176 | **Don't:** 177 | ```go 178 | func TestAdd(t *testing.T) { 179 | assert.Equal(t, 1+1, 2) 180 | assert.Equal(t, 1+-1, 0) 181 | assert.Equal(t, 1, 0, 1) 182 | assert.Equal(t, 0, 0, 0) 183 | } 184 | ``` 185 | 186 | 上面的程序看着还算简单,但是想找一个 fail 掉的 case 却非常麻烦,特别是有几百个 test case 的时候尤其如此。 187 | 188 | **Do:** 189 | ```go 190 | func TestAdd(t *testing.T) { 191 | cases := []struct { 192 | A, B, Expected int 193 | }{ 194 | {1, 1, 2}, 195 | {1, -1, 0}, 196 | {1, 0, 1}, 197 | {0, 0, 0}, 198 | } 199 | 200 | for _, tc := range cases { 201 | t.Run(fmt.Sprintf("%d + %d", tc.A, tc.B), func(t *testing.T) { 202 | assert.Equal(t, t.Expected, tc.A+tc.B) 203 | }) 204 | } 205 | } 206 | ``` 207 | 208 | 使用表驱动的 tests 结合子测试能够让你直接看到哪些 case 被测试,哪一个 case 失败了。 209 | – [Mitchell Hashimoto at GopherCon 2017](https://youtu.be/8hQG7QlcLBk?t=7m34s) 210 | 211 | ### 避免mock 212 | 213 | **Don't:** 214 | ```go 215 | func TestRun(t *testing.T) { 216 | mockConn := new(MockConn) 217 | run(mockConn) 218 | } 219 | ``` 220 | 221 | **Do:** 222 | ```go 223 | func TestRun(t *testing.T) { 224 | ln, err := net.Listen("tcp", "127.0.0.1:0") 225 | t.AssertNil(t, err) 226 | 227 | var server net.Conn 228 | go func() { 229 | defer ln.Close() 230 | server, err := ln.Accept() 231 | t.AssertNil(t, err) 232 | }() 233 | 234 | client, err := net.Dial("tcp", ln.Addr().String()) 235 | t.AssertNil(err) 236 | 237 | run(client) 238 | } 239 | ``` 240 | 241 | 只在没有其它办法的时候才使用 mock,尽量使用真正的实现。 242 | – [Mitchell Hashimoto at GopherCon 2017](https://youtu.be/8hQG7QlcLBk?t=26m51s) 243 | 244 | ### 避免DeepEqual 245 | 246 | **Don't:** 247 | ```go 248 | type myType struct { 249 | id int 250 | name string 251 | irrelevant []byte 252 | } 253 | 254 | func TestSomething(t *testing.T) { 255 | actual := &myType{/* ... */} 256 | expected := &myType{/* ... */} 257 | assert.True(t, reflect.DeepEqual(expected, actual)) 258 | } 259 | ``` 260 | 261 | **Do:** 262 | ```go 263 | type myType struct { 264 | id int 265 | name string 266 | irrelevant []byte 267 | } 268 | 269 | func (m *myType) testString() string { 270 | return fmt.Sprintf("%d.%s", m.id, m.name) 271 | } 272 | 273 | func TestSomething(t *testing.T) { 274 | actual := &myType{/* ... */} 275 | expected := &myType{/* ... */} 276 | if actual.testString() != expected.testString() { 277 | t.Errorf("Expected '%s', got '%s'", expected.testString(), actual.testString()) 278 | } 279 | // or assert.Equal(t, actual.testString(), expected.testString()) 280 | } 281 | ``` 282 | 283 | 使用 `testString()` 这种方式来比较 struct,在结构体比较复杂,并且内含有逻辑上不影响相等判断的字段,那么就应该使用这种方式来进行相等判断。 284 | 这种方式只在结构体比较大,或者是“类树”的结构体比较中比较有用: 285 | – [Mitchell Hashimoto at GopherCon 2017](https://youtu.be/8hQG7QlcLBk?t=30m45s) 286 | 287 | ### 不要测试非导出函数 288 | 289 | 只对导出的函数进行测试,如果一个函数是 unexported 并且没有办法通过 exported 函数走到其逻辑,说明这个函数很可能会经常变动,没有必要进行测试。 290 | 291 | ## 使用linter 292 | 293 | 使用 linter, (e.g. [gometalinter](https://github.com/alecthomas/gometalinter)) 在提交你的项目之前先进行 lint 来帮助查找潜在的规范问题和代码错误。 294 | 295 | ## 使用gofmt 296 | 297 | 在提交之前一定要对文件进行 gofmt,使用 `-s` 参数来简化代码。 298 | 299 | ## 避免side-effects 300 | 301 | **Don't:** 302 | ```go 303 | func init() { 304 | someStruct.Load() 305 | } 306 | ``` 307 | 308 | [side-effects](https://en.wikipedia.org/wiki/Side_effect_(computer_science)) 309 | 指函数或者代码会改变其作用域外的内容或值的行为。只有在一些特定的情况下 side-effects 是允许的(比如:在命令行中解析 flags) 310 | 311 | 如果你想不出其它的办法来避免,那么就重新思考并尝试重构吧。 312 | 313 | ## 尽量使用纯函数 314 | 315 | > 在计算机程序中,如果一个函数满足下面的几个条件,那么这个函数就是一个纯函数: 316 | > 1. 这个函数在相同的参数下一定会产生相同的结果。即函数的返回值不依赖于任何隐藏在函数内的信息或者状态,而这些隐藏的内容在程序的运行期还可能会变化。且函数不应依赖于任何从 I/O 设备中输入的信息。 317 | > 2. 对函数的返回结果进行操作不会引起任何语义上的副作用或者输出,比如导致可变对象的变化或者输出数据到 I/O 设备去。 318 | 319 | – [Wikipedia](https://en.wikipedia.org/wiki/Pure_function) 320 | 321 | **Don't:** 322 | ```go 323 | func MarshalAndWrite(some *Thing) error { 324 | b, err := json.Marshal(some) 325 | if err != nil { 326 | return err 327 | } 328 | 329 | return ioutil.WriteFile("some.thing", b, 0644) 330 | } 331 | ``` 332 | 333 | **Do:** 334 | ```go 335 | // Marshal is a pure func (even though useless) 336 | func Marshal(some *Thing) ([]bytes, error) { 337 | return json.Marshal(some) 338 | } 339 | 340 | // ... 341 | ``` 342 | 343 | 纯函数并不一定在所有场景下都适用,但保证你用到的函数尽量都是纯函数能够让你的代码更易理解,且更容易 debug。 344 | 345 | ## 避免接口臃肿 346 | 347 | **Don't:** 348 | ```go 349 | type Server interface { 350 | Serve() error 351 | Some() int 352 | Fields() float64 353 | That() string 354 | Are([]byte) error 355 | Not() []string 356 | Necessary() error 357 | } 358 | 359 | func debug(srv Server) { 360 | fmt.Println(srv.String()) 361 | } 362 | 363 | func run(srv Server) { 364 | srv.Serve() 365 | } 366 | ``` 367 | 368 | **Do:** 369 | ```go 370 | type Server interface { 371 | Serve() error 372 | } 373 | 374 | func debug(v fmt.Stringer) { 375 | fmt.Println(v.String()) 376 | } 377 | 378 | func run(srv Server) { 379 | srv.Serve() 380 | } 381 | ``` 382 | 383 | 尽量使用小的 interface,并且在你的函数中只要求传入需要的 interface。 384 | 385 | ## Don't under-package 386 | 387 | 删除或者合并 package 要比将大的 package 分开容易得多。如果不确定一个包是否可以分开,那么最好去试一试。 388 | 389 | ## 处理信号 390 | 391 | **Don't:** 392 | ```go 393 | func main() { 394 | for { 395 | time.Sleep(1 * time.Second) 396 | ioutil.WriteFile("foo", []byte("bar"), 0644) 397 | } 398 | } 399 | ``` 400 | 401 | **Do:** 402 | ```go 403 | func main() { 404 | logger := // ... 405 | sc := make(chan os.Signal, 1) 406 | done := make(chan bool) 407 | 408 | go func() { 409 | for { 410 | select { 411 | case s := <-sc: 412 | logger.Info("Received signal, stopping application", 413 | zap.String("signal", s.String())) 414 | done <- true 415 | return 416 | default: 417 | time.Sleep(1 * time.Second) 418 | ioutil.WriteFile("foo", []byte("bar"), 0644) 419 | } 420 | } 421 | }() 422 | 423 | signal.Notify(sc, os.Interrupt, os.Kill) 424 | <-done // Wait for go-routine 425 | } 426 | ``` 427 | 428 | 对 os 的信号进行处理能够让我们 gracefully 地停止服务,关闭打开的文件和连接,并且能够防止因为服务的意外关闭而导致文件损坏或其它问题。 429 | 430 | 431 | ## 分块组织import 432 | 433 | **Don't:** 434 | ```go 435 | import ( 436 | "encoding/json" 437 | "github.com/some/external/pkg" 438 | "fmt" 439 | "github.com/this-project/pkg/some-lib" 440 | "os" 441 | ) 442 | ``` 443 | 444 | **Do:** 445 | ```go 446 | import ( 447 | "encoding/json" 448 | "fmt" 449 | "os" 450 | 451 | "github.com/some/external/pkg" 452 | 453 | "github.com/this-project/pkg/some-lib" 454 | ) 455 | ``` 456 | 457 | 将 std,外部包和 internal 导入分开写,可以提高可读性。 458 | 459 | ## 避免不加修饰的return 460 | 461 | **Don't:** 462 | ```go 463 | func run() (n int, err error) { 464 | // ... 465 | return 466 | } 467 | ``` 468 | 469 | **Do:** 470 | ```go 471 | func run() (n int, err error) { 472 | // ... 473 | return n, err 474 | } 475 | ``` 476 | 477 | 命名返回的值对文档编写或者生成是有益的,不加任何修饰的 return 会让代码变得难读而易错。 478 | 479 | ## 添加包的权威导入路径 480 | 481 | **Don't:** 482 | ```go 483 | package sub 484 | ``` 485 | 486 | **Do:** 487 | ```go 488 | package sub // import "github.com/my-package/pkg/sth/else/sub" 489 | ``` 490 | 491 | 在注释中添加权威导入路径,能够给包添加上下文,也能够帮助用户更容易地导入你的包。 492 | 493 | ## 避免空接口 494 | 495 | **Don't:** 496 | ```go 497 | func run(foo interface{}) { 498 | // ... 499 | } 500 | ``` 501 | 502 | 空接口会让代码变得复杂而不清晰,只要能够不使用,就应该在任何时候避免。 503 | 504 | ## main函数先行 505 | 506 | **Don't:** 507 | ```go 508 | package main // import "github.com/me/my-project" 509 | 510 | func someHelper() int { 511 | // ... 512 | } 513 | 514 | func someOtherHelper() string { 515 | // ... 516 | } 517 | 518 | func Handler(w http.ResponseWriter, r *http.Reqeust) { 519 | // ... 520 | } 521 | 522 | func main() { 523 | // ... 524 | } 525 | ``` 526 | 527 | **Do:** 528 | ```go 529 | package main // import "github.com/me/my-project" 530 | 531 | func main() { 532 | // ... 533 | } 534 | 535 | func Handler(w http.ResponseWriter, r *http.Reqeust) { 536 | // ... 537 | } 538 | 539 | func someHelper() int { 540 | // ... 541 | } 542 | 543 | func someOtherHelper() string { 544 | // ... 545 | } 546 | ``` 547 | 548 | 将 `main()` 函数放在你文件的最开始,能够让阅读这个文件变得更加轻松。如果有 `init()` 函数的话,应该再放在 `main()` 之前。 549 | 550 | ## 使用internal包 551 | 552 | 如果你想创建一个 cmd,考虑将 libraries 移动到 `internal/` 包中,而避免这些不稳定可能经常会变化的库被其它项目引用。 553 | 554 | ## 避免使用helper/util的文件名、包名 555 | 556 | 使用清晰的命名,避免创建形如:`helper.go`,`util.go` 这样的文件名或者 package。 557 | 558 | ## 内嵌二进制数据 559 | 560 | 为了在部署阶段只有一个二进制文件,使用工具来将 templates 和其它静态内容嵌入到你的二进制文件中 561 | (e.g. [github.com/jteeuwen/go-bindata](https://github.com/jteeuwen/go-bindata)). 562 | 563 | ## 使用函数式的配置选项 564 | 565 | ```go 566 | 567 | func main() { 568 | // ... 569 | startServer( 570 | WithPort(8080), 571 | WithTimeout(1 * time.Second), 572 | ) 573 | } 574 | 575 | type Config struct { 576 | port int 577 | timeout time.Duration 578 | } 579 | 580 | type ServerOpt func(*Config) 581 | 582 | func WithPort(port int) ServerOpt { 583 | return func(cfg *Config) { 584 | cfg.port = port 585 | } 586 | } 587 | 588 | func WithTimeout(timeout time.Duration) ServerOpt { 589 | return func(cfg *Config) { 590 | cfg.timeout = timeout 591 | } 592 | } 593 | 594 | func startServer(opts ...ServerOpt) { 595 | cfg := new(Config) 596 | for _, fn := range opts { 597 | fn(cfg) 598 | } 599 | 600 | // ... 601 | } 602 | 603 | 604 | ``` 605 | --------------------------------------------------------------------------------