├── src ├── 28-performance.md ├── 32-line-length.md ├── 37-function-name.md ├── 41-else-unnecessary.md ├── 53-struct-pointer.md ├── 49-string-escape.md ├── 55-printf-const.md ├── 35-import-group.md ├── 02-interface-pointer.md ├── 29-strconv.md ├── 15-type-assert.md ├── 36-package-name.md ├── 52-struct-zero.md ├── 30-string-byte-slice.md ├── 26-goroutine-exit.md ├── 50-struct-field-key.md ├── 08-channel-size.md ├── 38-import-alias.md ├── 56-printf-name.md ├── 40-nest-less.md ├── 42-global-decl.md ├── 33-consistency.md ├── 07-defer-clean.md ├── 43-global-name.md ├── 09-enum-start.md ├── 51-struct-field-zero.md ├── 48-param-naked.md ├── 24-struct-tag.md ├── 45-var-decl.md ├── 47-var-scope.md ├── 39-function-order.md ├── 17-atomic.md ├── 18-global-mut.md ├── 13-error-name.md ├── 03-interface-compliance.md ├── 05-mutex-zero-value.md ├── 59-lint.md ├── 01-intro.md ├── 27-goroutine-init.md ├── 54-map-init.md ├── 46-slice-nil.md ├── 16-panic.md ├── 22-exit-main.md ├── 04-interface-receiver.md ├── 25-goroutine-forget.md ├── 06-container-copy.md ├── 20-builtin-name.md ├── 12-error-wrap.md ├── 34-decl-group.md ├── 14-error-once.md ├── 21-init.md ├── 31-container-capacity.md ├── 23-exit-once.md ├── 58-functional-option.md ├── 19-embed-public.md ├── 11-error-type.md ├── 44-struct-embed.md ├── 10-time.md └── 57-test-table.md ├── style.md └── README.md /src/28-performance.md: -------------------------------------------------------------------------------- 1 | # Desempenho 2 | 3 | As diretrizes específicas de desempenho se aplicam apenas ao caminho crítico. 4 | -------------------------------------------------------------------------------- /src/32-line-length.md: -------------------------------------------------------------------------------- 1 | # Evite linhas excessivamente longas 2 | 3 | Evite linhas de código que exijam que os leitores rolem horizontalmente 4 | ou virem muito a cabeça. 5 | 6 | Recomendamos um limite de comprimento de linha suave de **99 caracteres**. 7 | Os autores devem tentar quebrar as linhas antes de atingir esse limite, 8 | mas não é um limite rígido. 9 | É permitido que o código ultrapasse esse limite. 10 | -------------------------------------------------------------------------------- /src/37-function-name.md: -------------------------------------------------------------------------------- 1 | # Nomes de Funções 2 | 3 | Seguimos a convenção da comunidade Go de usar [MixedCaps para nomes de 4 | funções]. Uma exceção é feita para funções de teste, que podem conter 5 | underscores com o propósito de agrupar casos de teste relacionados, por exemplo, 6 | `TestMinhaFuncao_OQueEstaSendoTestado`. 7 | 8 | [MixedCaps para nomes de funções]: https://golang.org/doc/effective_go.html#mixed-caps 9 | -------------------------------------------------------------------------------- /src/41-else-unnecessary.md: -------------------------------------------------------------------------------- 1 | # Else Desnecessário 2 | 3 | Se uma variável é definida em ambos os ramos de um if, pode ser substituído por um 4 | único if. 5 | 6 |
| Ruim | Bom |
|---|---|
| 10 | 11 | ```go 12 | var a int 13 | if b { 14 | a = 100 15 | } else { 16 | a = 10 17 | } 18 | ``` 19 | 20 | | 21 | 22 | ```go 23 | a := 10 24 | if b { 25 | a = 100 26 | } 27 | ``` 28 | 29 | |
| Ruim | Bom |
|---|---|
| 10 | 11 | ```go 12 | sval := T{Name: "foo"} 13 | 14 | // inconsistent 15 | sptr := new(T) 16 | sptr.Name = "bar" 17 | ``` 18 | 19 | | 20 | 21 | ```go 22 | sval := T{Name: "foo"} 23 | 24 | sptr := &T{Name: "bar"} 25 | ``` 26 | 27 | |
| Ruim | Bom |
|---|---|
| 11 | 12 | ```go 13 | wantError := "unknown name:\"test\"" 14 | ``` 15 | 16 | | 17 | 18 | ```go 19 | wantError := `unknown error:"test"` 20 | ``` 21 | 22 | |
| Ruim | Bom |
|---|---|
| 11 | 12 | ```go 13 | msg := "valores inesperados %v, %v\n" 14 | fmt.Printf(msg, 1, 2) 15 | 16 | ``` 17 | 18 | | 19 | 20 | ```go 21 | const msg = "valores inesperados %v, %v\n" 22 | fmt.Printf(msg, 1, 2) 23 | 24 | ``` 25 | 26 | |
| Ruim | Bom |
|---|---|
| 14 | 15 | ```go 16 | import ( 17 | "fmt" 18 | "os" 19 | "go.uber.org/atomic" 20 | "golang.org/x/sync/errgroup" 21 | ) 22 | ``` 23 | 24 | | 25 | 26 | ```go 27 | import ( 28 | "fmt" 29 | "os" 30 | 31 | "go.uber.org/atomic" 32 | "golang.org/x/sync/errgroup" 33 | ) 34 | ``` 35 | 36 | |
| Ruim | Bom |
|---|---|
| 10 | 11 | ```go 12 | for i := 0; i < b.N; i++ { 13 | s := fmt.Sprint(rand.Int()) 14 | } 15 | ``` 16 | 17 | | 18 | 19 | ```go 20 | for i := 0; i < b.N; i++ { 21 | s := strconv.Itoa(rand.Int()) 22 | } 23 | ``` 24 | 25 | |
| 27 | 28 | ```plain 29 | BenchmarkFmtSprint-4 143 ns/op 2 allocs/op 30 | ``` 31 | 32 | | 33 | 34 | ```plain 35 | BenchmarkStrconv-4 64.2 ns/op 1 allocs/op 36 | ``` 37 | 38 | |
| Ruim | Bom |
|---|---|
| 12 | 13 | ```go 14 | t := i.(string) 15 | ``` 16 | 17 | | 18 | 19 | ```go 20 | t, ok := i.(string) 21 | if !ok { 22 | // lidar com o erro de forma adequada 23 | } 24 | ``` 25 | 26 | |
| Ruim | Bom |
|---|---|
| 10 | 11 | ```go 12 | user := User{} 13 | ``` 14 | 15 | | 16 | 17 | ```go 18 | var user User 19 | ``` 20 | 21 | |
| Ruim | Bom |
|---|---|
| 9 | 10 | ```go 11 | for i := 0; i < b.N; i++ { 12 | w.Write([]byte("Hello world")) 13 | } 14 | ``` 15 | 16 | | 17 | 18 | ```go 19 | data := []byte("Hello world") 20 | for i := 0; i < b.N; i++ { 21 | w.Write(data) 22 | } 23 | ``` 24 | 25 | |
| 27 | 28 | ```plain 29 | BenchmarkBad-4 50000000 22.2 ns/op 30 | ``` 31 | 32 | | 33 | 34 | ```plain 35 | BenchmarkGood-4 500000000 3.25 ns/op 36 | ``` 37 | 38 | |
| Ruim | Bom |
|---|---|
| 12 | 13 | ```go 14 | k := User{"John", "Doe", true} 15 | ``` 16 | 17 | | 18 | 19 | ```go 20 | k := User{ 21 | FirstName: "John", 22 | LastName: "Doe", 23 | Admin: true, 24 | } 25 | ``` 26 | 27 | |
| Ruim | Bom |
|---|---|
| 13 | 14 | ```go 15 | // Deve ser suficiente para qualquer pessoa! 16 | c := make(chan int, 64) 17 | ``` 18 | 19 | | 20 | 21 | ```go 22 | // Tamanho de um 23 | c := make(chan int, 1) // ou 24 | // Canal não armazenado em buffer, tamanho zero 25 | c := make(chan int) 26 | ``` 27 | 28 | |
| Ruim | Bom |
|---|---|
| 22 | 23 | ```go 24 | import ( 25 | "fmt" 26 | "os" 27 | runtimetrace "runtime/trace" 28 | 29 | nettrace "golang.net/x/trace" 30 | ) 31 | ``` 32 | 33 | | 34 | 35 | ```go 36 | import ( 37 | "fmt" 38 | "os" 39 | "runtime/trace" 40 | 41 | nettrace "golang.net/x/trace" 42 | ) 43 | ``` 44 | 45 | |
| Ruim | Bom |
|---|---|
| 9 | 10 | ```go 11 | for _, v := range data { 12 | if v.F1 == 1 { 13 | v = process(v) 14 | if err := v.Call(); err == nil { 15 | v.Send() 16 | } else { 17 | return err 18 | } 19 | } else { 20 | log.Printf("Inválido v: %v", v) 21 | } 22 | } 23 | ``` 24 | 25 | | 26 | 27 | ```go 28 | for _, v := range data { 29 | if v.F1 != 1 { 30 | log.Printf("Inválido v: %v", v) 31 | continue 32 | } 33 | 34 | v = process(v) 35 | if err := v.Call(); err != nil { 36 | return err 37 | } 38 | v.Send() 39 | } 40 | ``` 41 | 42 | |
| Bad | Good |
|---|---|
| 9 | 10 | ```go 11 | var _s string = F() 12 | 13 | func F() string { return "A" } 14 | ``` 15 | 16 | | 17 | 18 | ```go 19 | var _s = F() 20 | // Já que F já declara que retorna uma string, não precisamos especificar 21 | // o tipo novamente. 22 | 23 | func F() string { return "A" } 24 | ``` 25 | |
| Ruim | Bom |
|---|---|
| 9 | 10 | ```go 11 | p.Lock() 12 | if p.count < 10 { 13 | p.Unlock() 14 | return p.count 15 | } 16 | 17 | p.count++ 18 | newCount := p.count 19 | p.Unlock() 20 | 21 | return newCount 22 | 23 | // fácil perder desbloqueios devido a múltiplos retornos 24 | ``` 25 | 26 | | 27 | 28 | ```go 29 | p.Lock() 30 | defer p.Unlock() 31 | 32 | if p.count < 10 { 33 | return p.count 34 | } 35 | 36 | p.count++ 37 | return p.count 38 | 39 | // mais legível 40 | ``` 41 | 42 | |
| Ruim | Bom |
|---|---|
| 14 | 15 | ```go 16 | // foo.go 17 | 18 | const ( 19 | defaultPort = 8080 20 | defaultUser = "user" 21 | ) 22 | 23 | // bar.go 24 | 25 | func Bar() { 26 | defaultPort := 9090 27 | ... 28 | fmt.Println("Default port", defaultPort) 29 | 30 | // Não veremos um erro de compilação se a primeira linha de 31 | // Bar() for excluída. 32 | } 33 | ``` 34 | 35 | | 36 | 37 | ```go 38 | // foo.go 39 | 40 | const ( 41 | _defaultPort = 8080 42 | _defaultUser = "user" 43 | ) 44 | ``` 45 | 46 | |
| Ruim | Bom |
|---|---|
| 11 | 12 | ```go 13 | type Operation int 14 | 15 | const ( 16 | Add Operation = iota 17 | Subtract 18 | Multiply 19 | ) 20 | 21 | // Add=0, Subtract=1, Multiply=2 22 | ``` 23 | 24 | | 25 | 26 | ```go 27 | type Operation int 28 | 29 | const ( 30 | Add Operation = iota + 1 31 | Subtract 32 | Multiply 33 | ) 34 | 35 | // Add=1, Subtract=2, Multiply=3 36 | ``` 37 | 38 | |
| Ruim | Bom |
|---|---|
| 10 | 11 | ```go 12 | user := User{ 13 | FirstName: "John", 14 | LastName: "Doe", 15 | MiddleName: "", 16 | Admin: false, 17 | } 18 | ``` 19 | 20 | | 21 | 22 | ```go 23 | user := User{ 24 | FirstName: "John", 25 | LastName: "Doe", 26 | } 27 | ``` 28 | 29 | |
| Ruim | Bom |
|---|---|
| 9 | 10 | ```go 11 | // func printInfo(name string, isLocal, done bool) 12 | 13 | printInfo("foo", true, true) 14 | ``` 15 | 16 | | 17 | 18 | ```go 19 | // func printInfo(name string, isLocal, done bool) 20 | 21 | printInfo("foo", true /* isLocal */, true /* done */) 22 | ``` 23 | 24 | |
| Ruim | Bom |
|---|---|
| 11 | 12 | ```go 13 | type Stock struct { 14 | Price int 15 | Name string 16 | } 17 | 18 | bytes, err := json.Marshal(Stock{ 19 | Price: 137, 20 | Name: "UBER", 21 | }) 22 | ``` 23 | 24 | | 25 | 26 | ```go 27 | type Stock struct { 28 | Price int `json:"price"` 29 | Name string `json:"name"` 30 | // Seguro renomear Name para Symbol. 31 | } 32 | 33 | bytes, err := json.Marshal(Stock{ 34 | Price: 137, 35 | Name: "UBER", 36 | }) 37 | ``` 38 | 39 | |
| Ruim | Bom |
|---|---|
| 9 | 10 | ```go 11 | var s = "foo" 12 | ``` 13 | 14 | | 15 | 16 | ```go 17 | s := "foo" 18 | ``` 19 | 20 | |
| Ruim | Bom |
|---|---|
| 31 | 32 | ```go 33 | func f(list []int) { 34 | filtered := []int{} 35 | for _, v := range list { 36 | if v > 10 { 37 | filtered = append(filtered, v) 38 | } 39 | } 40 | } 41 | ``` 42 | 43 | | 44 | 45 | ```go 46 | func f(list []int) { 47 | var filtered []int 48 | for _, v := range list { 49 | if v > 10 { 50 | filtered = append(filtered, v) 51 | } 52 | } 53 | } 54 | ``` 55 | 56 | |
| Ruim | Bom |
|---|---|
| 9 | 10 | ```go 11 | err := os.WriteFile(name, data, 0644) 12 | if err != nil { 13 | return err 14 | } 15 | ``` 16 | 17 | | 18 | 19 | ```go 20 | if err := os.WriteFile(name, data, 0644); err != nil { 21 | return err 22 | } 23 | ``` 24 | 25 | |
| Ruim | Bom |
|---|---|
| 34 | 35 | ```go 36 | if data, err := os.ReadFile(name); err == nil { 37 | err = cfg.Decode(data) 38 | if err != nil { 39 | return err 40 | } 41 | 42 | fmt.Println(cfg) 43 | return nil 44 | } else { 45 | return err 46 | } 47 | ``` 48 | 49 | | 50 | 51 | ```go 52 | data, err := os.ReadFile(name) 53 | if err != nil { 54 | return err 55 | } 56 | 57 | if err := cfg.Decode(data); err != nil { 58 | return err 59 | } 60 | 61 | fmt.Println(cfg) 62 | return nil 63 | ``` 64 | 65 | |
| Ruim | Bom |
|---|---|
| 19 | 20 | ```go 21 | func (s *something) Cost() { 22 | return calcCost(s.weights) 23 | } 24 | 25 | type something struct{ ... } 26 | 27 | func calcCost(n []int) int {...} 28 | 29 | func (s *something) Stop() {...} 30 | 31 | func newSomething() *something { 32 | return &something{} 33 | } 34 | ``` 35 | 36 | | 37 | 38 | ```go 39 | type something struct{ ... } 40 | 41 | func newSomething() *something { 42 | return &something{} 43 | } 44 | 45 | func (s *something) Cost() { 46 | return calcCost(s.weights) 47 | } 48 | 49 | func (s *something) Stop() {...} 50 | 51 | func calcCost(n []int) int {...} 52 | ``` 53 | 54 | |
| Ruim | Bom |
|---|---|
| 17 | 18 | 19 | ```go 20 | type foo struct { 21 | running int32 // atômico 22 | } 23 | 24 | func (f *foo) start() { 25 | if atomic.SwapInt32(&f.running, 1) == 1 { 26 | // já está em execução... 27 | return 28 | } 29 | // iniciar o Foo 30 | } 31 | 32 | func (f *foo) isRunning() bool { 33 | return f.running == 1 // corrida! 34 | } 35 | ``` 36 | 37 | | 38 | 39 | ```go 40 | type foo struct { 41 | running atomic.Bool 42 | } 43 | 44 | func (f *foo) start() { 45 | if f.running.Swap(true) { 46 | // já está em execução... 47 | return 48 | } 49 | // iniciar o Foo 50 | } 51 | 52 | func (f *foo) isRunning() bool { 53 | return f.running.Load() 54 | } 55 | ``` 56 | 57 | |
| Ruim | Bom |
|---|---|
| 10 | 11 | ```go 12 | // sign.go 13 | 14 | var _timeNow = time.Now 15 | 16 | func sign(msg string) string { 17 | now := _timeNow() 18 | return signWithTime(msg, now) 19 | } 20 | ``` 21 | 22 | | 23 | 24 | ```go 25 | // sign.go 26 | 27 | type signer struct { 28 | now func() time.Time 29 | } 30 | 31 | func newSigner() *signer { 32 | return &signer{ 33 | now: time.Now, 34 | } 35 | } 36 | 37 | func (s *signer) Sign(msg string) string { 38 | now := s.now() 39 | return signWithTime(msg, now) 40 | } 41 | ``` 42 | 43 | |
| 45 | 46 | ```go 47 | // sign_test.go 48 | 49 | func TestSign(t *testing.T) { 50 | oldTimeNow := _timeNow 51 | _timeNow = func() time.Time { 52 | return someFixedTime 53 | } 54 | defer func() { _timeNow = oldTimeNow }() 55 | 56 | assert.Equal(t, want, sign(give)) 57 | } 58 | ``` 59 | 60 | | 61 | 62 | ```go 63 | // sign_test.go 64 | 65 | func TestSigner(t *testing.T) { 66 | s := newSigner() 67 | s.now = func() time.Time { 68 | return someFixedTime 69 | } 70 | 71 | assert.Equal(t, want, s.Sign(give)) 72 | } 73 | ``` 74 | 75 | |
| Ruim | Bom |
|---|---|
| 15 | 16 | ```go 17 | type Handler struct { 18 | // ... 19 | } 20 | 21 | 22 | 23 | func (h *Handler) ServeHTTP( 24 | w http.ResponseWriter, 25 | r *http.Request, 26 | ) { 27 | ... 28 | } 29 | ``` 30 | 31 | | 32 | 33 | ```go 34 | type Handler struct { 35 | // ... 36 | } 37 | 38 | var _ http.Handler = (*Handler)(nil) 39 | 40 | func (h *Handler) ServeHTTP( 41 | w http.ResponseWriter, 42 | r *http.Request, 43 | ) { 44 | // ... 45 | } 46 | ``` 47 | 48 | |
| Ruim | Bom |
|---|---|
| 10 | 11 | ```go 12 | mu := new(sync.Mutex) 13 | mu.Lock() 14 | ``` 15 | 16 | | 17 | 18 | ```go 19 | var mu sync.Mutex 20 | mu.Lock() 21 | ``` 22 | 23 | |
| Ruim | Bom |
|---|---|
| 33 | 34 | ```go 35 | type SMap struct { 36 | sync.Mutex 37 | 38 | data map[string]string 39 | } 40 | 41 | func NewSMap() *SMap { 42 | return &SMap{ 43 | data: make(map[string]string), 44 | } 45 | } 46 | 47 | func (m *SMap) Get(k string) string { 48 | m.Lock() 49 | defer m.Unlock() 50 | 51 | return m.data[k] 52 | } 53 | ``` 54 | 55 | | 56 | 57 | ```go 58 | type SMap struct { 59 | mu sync.Mutex 60 | 61 | data map[string]string 62 | } 63 | 64 | func NewSMap() *SMap { 65 | return &SMap{ 66 | data: make(map[string]string), 67 | } 68 | } 69 | 70 | func (m *SMap) Get(k string) string { 71 | m.mu.Lock() 72 | defer m.mu.Unlock() 73 | 74 | return m.data[k] 75 | } 76 | ``` 77 | 78 | |
| 81 | 82 | O campo `Mutex`, e os métodos `Lock` e `Unlock` fazem parte 83 | involuntariamente da API exportada de `SMap`. 84 | 85 | | 86 | 87 | O mutex e seus métodos são detalhes de implementação de `SMap` ocultos de seus 88 | chamadores. 89 | 90 | |
| Ruim | Bom |
|---|---|
| 15 | 16 | ```go 17 | func init() { 18 | go doWork() 19 | } 20 | 21 | func doWork() { 22 | for { 23 | // ... 24 | } 25 | } 26 | ``` 27 | 28 | | 29 | 30 | ```go 31 | type Worker struct{ /* ... */ } 32 | 33 | func NewWorker(...) *Worker { 34 | w := &Worker{ 35 | stop: make(chan struct{}), 36 | done: make(chan struct{}), 37 | // ... 38 | } 39 | go w.doWork() 40 | } 41 | 42 | func (w *Worker) doWork() { 43 | defer close(w.done) 44 | for { 45 | // ... 46 | case <-w.stop: 47 | return 48 | } 49 | } 50 | 51 | // Shutdown diz ao worker para parar 52 | // e espera até que tenha terminado. 53 | func (w *Worker) Shutdown() { 54 | close(w.stop) 55 | <-w.done 56 | } 57 | ``` 58 | 59 | |
| 61 | 62 | Cria uma goroutine em segundo plano incondicionalmente quando o usuário exporta este pacote. 63 | O usuário não tem controle sobre a goroutine nem um meio de interrompê-la. 64 | 65 | | 66 | 67 | Cria o worker apenas se o usuário solicitar. 68 | Fornece um meio de desligar o worker para que o usuário possa liberar 69 | recursos usados pelo worker. 70 | 71 | Observe que você deve usar `WaitGroup`s se o worker gerenciar várias 72 | goroutines. 73 | Veja [Espere as goroutines terminarem](26-goroutine-exit.md). 74 | 75 | |
| Ruim | Bom |
|---|---|
| 12 | 13 | ```go 14 | var ( 15 | // m1 é seguro para leitura e escrita; 16 | // m2 causará um pânico em operações de escrita. 17 | m1 = map[T1]T2{} 18 | m2 map[T1]T2 19 | ) 20 | ``` 21 | 22 | | 23 | 24 | ```go 25 | var ( 26 | // m1 é seguro para leitura e escrita; 27 | // m2 causará um pânico em operações de escrita. 28 | m1 = make(map[T1]T2) 29 | m2 map[T1]T2 30 | ) 31 | ``` 32 | 33 | |
| 35 | 36 | Declaração e inicialização são visualmente semelhantes. 37 | 38 | | 39 | 40 | Declaração e inicialização são visualmente distintas. 41 | 42 | |
| Ruim | Bom |
|---|---|
| 57 | 58 | ```go 59 | m := make(map[T1]T2, 3) 60 | m[k1] = v1 61 | m[k2] = v2 62 | m[k3] = v3 63 | ``` 64 | 65 | | 66 | 67 | ```go 68 | m := map[T1]T2{ 69 | k1: v1, 70 | k2: v2, 71 | k3: v3, 72 | } 73 | ``` 74 | 75 | |
| Ruim | Bom |
|---|---|
| 12 | 13 | ```go 14 | if x == "" { 15 | return []int{} 16 | } 17 | ``` 18 | 19 | | 20 | 21 | ```go 22 | if x == "" { 23 | return nil 24 | } 25 | ``` 26 | 27 | |
| Ruim | Bom |
|---|---|
| 37 | 38 | ```go 39 | func isEmpty(s []string) bool { 40 | return s == nil 41 | } 42 | ``` 43 | 44 | | 45 | 46 | ```go 47 | func isEmpty(s []string) bool { 48 | return len(s) == 0 49 | } 50 | ``` 51 | 52 | |
| Ruim | Bom |
|---|---|
| 62 | 63 | ```go 64 | nums := []int{} 65 | // or, nums := make([]int) 66 | 67 | if add1 { 68 | nums = append(nums, 1) 69 | } 70 | 71 | if add2 { 72 | nums = append(nums, 2) 73 | } 74 | ``` 75 | 76 | | 77 | 78 | ```go 79 | var nums []int 80 | 81 | if add1 { 82 | nums = append(nums, 1) 83 | } 84 | 85 | if add2 { 86 | nums = append(nums, 2) 87 | } 88 | ``` 89 | 90 | |
| Ruim | Bom |
|---|---|
| 12 | 13 | ```go 14 | func run(args []string) { 15 | if len(args) == 0 { 16 | panic("é necessário um argumento") 17 | } 18 | // ... 19 | } 20 | 21 | func main() { 22 | run(os.Args[1:]) 23 | } 24 | ``` 25 | 26 | | 27 | 28 | ```go 29 | func run(args []string) error { 30 | if len(args) == 0 { 31 | return errors.New("é necessário um argumento") 32 | } 33 | // ... 34 | return nil 35 | } 36 | 37 | func main() { 38 | if err := run(os.Args[1:]); err != nil { 39 | fmt.Fprintln(os.Stderr, err) 40 | os.Exit(1) 41 | } 42 | } 43 | ``` 44 | 45 | |
| Ruim | Bom |
|---|---|
| 60 | 61 | ```go 62 | // func TestFoo(t *testing.T) 63 | 64 | f, err := os.CreateTemp("", "test") 65 | if err != nil { 66 | panic("falha ao configurar o teste") 67 | } 68 | ``` 69 | 70 | | 71 | 72 | ```go 73 | // func TestFoo(t *testing.T) 74 | 75 | f, err := os.CreateTemp("", "test") 76 | if err != nil { 77 | t.Fatal("falha ao configurar o teste") 78 | } 79 | ``` 80 | 81 | |
| Ruim | Bom |
|---|---|
| 16 | 17 | ```go 18 | func main() { 19 | body := readFile(path) 20 | fmt.Println(body) 21 | } 22 | 23 | func readFile(path string) string { 24 | f, err := os.Open(path) 25 | if err != nil { 26 | log.Fatal(err) 27 | } 28 | 29 | b, err := io.ReadAll(f) 30 | if err != nil { 31 | log.Fatal(err) 32 | } 33 | 34 | return string(b) 35 | } 36 | ``` 37 | 38 | | 39 | 40 | ```go 41 | func main() { 42 | body, err := readFile(path) 43 | if err != nil { 44 | log.Fatal(err) 45 | } 46 | fmt.Println(body) 47 | } 48 | 49 | func readFile(path string) (string, error) { 50 | f, err := os.Open(path) 51 | if err != nil { 52 | return "", err 53 | } 54 | 55 | b, err := io.ReadAll(f) 56 | if err != nil { 57 | return "", err 58 | } 59 | 60 | return string(b), nil 61 | } 62 | ``` 63 | 64 | |
| Ruim | Bom |
|---|---|
| 29 | 30 | ```go 31 | go func() { 32 | for { 33 | flush() 34 | time.Sleep(delay) 35 | } 36 | }() 37 | ``` 38 | 39 | | 40 | 41 | ```go 42 | var ( 43 | stop = make(chan struct{}) // informa a goroutine para parar 44 | done = make(chan struct{}) // nos informa que a goroutine saiu 45 | ) 46 | go func() { 47 | defer close(done) 48 | 49 | ticker := time.NewTicker(delay) 50 | defer ticker.Stop() 51 | for { 52 | select { 53 | case <-ticker.C: 54 | flush() 55 | case <-stop: 56 | return 57 | } 58 | } 59 | }() 60 | 61 | // Em outro lugar... 62 | close(stop) // sinaliza para a goroutine parar 63 | <-done // e espera ela sair 64 | ``` 65 | 66 | |
| 68 | 69 | Não há maneira de parar essa goroutine. 70 | Ela continuará sendo executada até que a aplicação seja encerrada. 71 | 72 | | 73 | 74 | Esta goroutine pode ser interrompida com `close(stop)`, 75 | e podemos aguardar sua saída com `<-done`. 76 | 77 | |
| Ruim | Bom |
|---|---|
| 16 | 17 | ```go 18 | func (d *Driver) SetTrips(trips []Trip) { 19 | d.trips = trips 20 | } 21 | 22 | trips := ... 23 | d1.SetTrips(trips) 24 | 25 | // Você quis dizer modificar d1.trips? 26 | trips[0] = ... 27 | ``` 28 | 29 | | 30 |31 | 32 | ```go 33 | func (d *Driver) SetTrips(trips []Trip) { 34 | d.trips = make([]Trip, len(trips)) 35 | copy(d.trips, trips) 36 | } 37 | 38 | trips := ... 39 | d1.SetTrips(trips) 40 | 41 | // Agora podemos modificar trips[0] sem afetar d1.trips. 42 | trips[0] = ... 43 | ``` 44 | 45 | | 46 |
| Ruim | Bom |
|---|---|
| 59 | 60 | ```go 61 | type Stats struct { 62 | mu sync.Mutex 63 | counters map[string]int 64 | } 65 | 66 | // Snapshot retorna as estatísticas atuais. 67 | func (s *Stats) Snapshot() map[string]int { 68 | s.mu.Lock() 69 | defer s.mu.Unlock() 70 | 71 | return s.counters 72 | } 73 | 74 | // snapshot não está mais protegido pelo mutex, então qualquer 75 | // acesso ao snapshot está sujeito a corridas de dados. 76 | snapshot := stats.Snapshot() 77 | ``` 78 | 79 | | 80 | 81 | ```go 82 | type Stats struct { 83 | mu sync.Mutex 84 | counters map[string]int 85 | } 86 | 87 | func (s *Stats) Snapshot() map[string]int { 88 | s.mu.Lock() 89 | defer s.mu.Unlock() 90 | 91 | result := make(map[string]int, len(s.counters)) 92 | for k, v := range s.counters { 93 | result[k] = v 94 | } 95 | return result 96 | } 97 | 98 | // Snapshot agora é uma cópia. 99 | snapshot := stats.Snapshot() 100 | ``` 101 | 102 | |
| Ruim | Bom |
|---|---|
| 19 | 20 | ```go 21 | var erro string 22 | // `erro` sombreia o embutido 23 | 24 | // ou 25 | 26 | func lidarComMensagemDeErro(erro string) { 27 | // `erro` sombreia o embutido 28 | } 29 | ``` 30 | 31 | | 32 | 33 | ```go 34 | var mensagemDeErro string 35 | // `erro` se refere ao embutido 36 | 37 | // ou 38 | 39 | func lidarComMensagemDeErro(msg string) { 40 | // `erro` se refere ao embutido 41 | } 42 | ``` 43 | 44 | |
| 46 | 47 | ```go 48 | type Foo struct { 49 | // Embora esses campos tecnicamente não 50 | // constituam sombreamento, buscar por 51 | // strings `error` ou `string` agora é 52 | // ambíguo. 53 | erro error 54 | cadeia string 55 | } 56 | 57 | func (f Foo) Error() error { 58 | // `erro` e `f.erro` são 59 | // visualmente semelhantes 60 | return f.erro 61 | } 62 | 63 | func (f Foo) String() string { 64 | // `cadeia` e `f.cadeia` são 65 | // visualmente semelhantes 66 | return f.cadeia 67 | } 68 | 69 | ``` 70 | 71 | | 72 | 73 | ```go 74 | type Foo struct { 75 | // As strings `erro` e `cadeia` agora são 76 | // inequívocas. 77 | erro error 78 | cadeia string 79 | } 80 | 81 | func (f Foo) Error() error { 82 | return f.erro 83 | } 84 | 85 | func (f Foo) String() string { 86 | return f.cadeia 87 | } 88 | 89 | ``` 90 | 91 | |
| Ruim | Bom |
|---|---|
| 40 | 41 | ```go 42 | s, err := store.New() 43 | if err != nil { 44 | return fmt.Errorf( 45 | "ao criar novo armazenamento: %w", err) 46 | } 47 | ``` 48 | 49 | | 50 | 51 | ```go 52 | s, err := store.New() 53 | if err != nil { 54 | return fmt.Errorf( 55 | "novo armazenamento: %w", err) 56 | } 57 | ``` 58 | 59 | |
| 60 | 61 | ```plain 62 | falha ao x: falha ao y: ao criar novo armazenamento: o erro 63 | ``` 64 | 65 | | 66 | 67 | ```plain 68 | x: y: novo armazenamento: o erro 69 | ``` 70 | 71 | |
| Ruim | Bom |
|---|---|
| 9 | 10 | ```go 11 | import "a" 12 | import "b" 13 | ``` 14 | 15 | | 16 | 17 | ```go 18 | import ( 19 | "a" 20 | "b" 21 | ) 22 | ``` 23 | 24 | |
| Ruim | Bom |
|---|---|
| 33 | 34 | ```go 35 | 36 | const a = 1 37 | const b = 2 38 | 39 | 40 | 41 | var a = 1 42 | var b = 2 43 | 44 | 45 | 46 | type Area float64 47 | type Volume float64 48 | ``` 49 | 50 | | 51 | 52 | ```go 53 | const ( 54 | a = 1 55 | b = 2 56 | ) 57 | 58 | var ( 59 | a = 1 60 | b = 2 61 | ) 62 | 63 | type ( 64 | Area float64 65 | Volume float64 66 | ) 67 | ``` 68 | 69 | |
| Ruim | Bom |
|---|---|
| 78 | 79 | ```go 80 | type Operation int 81 | 82 | const ( 83 | Add Operation = iota + 1 84 | Subtract 85 | Multiply 86 | EnvVar = "MY_ENV" 87 | ) 88 | ``` 89 | 90 | | 91 | 92 | ```go 93 | type Operation int 94 | 95 | const ( 96 | Add Operation = iota + 1 97 | Subtract 98 | Multiply 99 | ) 100 | 101 | const EnvVar = "MY_ENV" 102 | ``` 103 | 104 | |
| Ruim | Bom |
|---|---|
| 114 | 115 | ```go 116 | func f() string { 117 | red := color.New(0xff0000) 118 | green := color.New(0x00ff00) 119 | blue := color.New(0x0000ff) 120 | 121 | // ... 122 | } 123 | ``` 124 | 125 | | 126 | 127 | ```go 128 | func f() string { 129 | var ( 130 | red = color.New(0xff0000) 131 | green = color.New(0x00ff00) 132 | blue = color.New(0x0000ff) 133 | ) 134 | 135 | // ... 136 | } 137 | ``` 138 | 139 | |
| Ruim | Bom |
|---|---|
| 150 | 151 | ```go 152 | func (c *client) request() { 153 | caller := c.name 154 | format := "json" 155 | timeout := 5*time.Second 156 | var err error 157 | 158 | // ... 159 | } 160 | ``` 161 | 162 | | 163 | 164 | ```go 165 | func (c *client) request() { 166 | var ( 167 | caller = c.name 168 | format = "json" 169 | timeout = 5*time.Second 170 | err error 171 | ) 172 | 173 | // ... 174 | } 175 | ``` 176 | 177 | |
| Descrição | Codigo |
|---|---|
| 29 | 30 | **Ruim**: Registrar o erro e retorná-lo 31 | 32 | Chamadores mais acima na pilha provavelmente tomarão uma ação semelhante com o erro. 33 | Fazer isso gera muito ruído nos logs da aplicação com pouco benefício. 34 | 35 | | 36 | 37 | ```go 38 | u, err := getUser(id) 39 | if err != nil { 40 | // Ruim: Veja a descrição 41 | log.Printf("Não foi possível obter o usuário %q: %v", id, err) 42 | return err 43 | } 44 | ``` 45 | 46 | |
| 48 | 49 | **Bom**: Encapsule o erro e o retorne 50 | 51 | Chamadores mais acima na pilha lidarão com o erro. 52 | O uso de `%w` garante que eles possam corresponder o erro com `errors.Is` ou `errors.As` 53 | se relevante. 54 | 55 | | 56 | 57 | ```go 58 | u, err := getUser(id) 59 | if err != nil { 60 | return fmt.Errorf("obter usuário %q: %w", id, err) 61 | } 62 | ``` 63 | 64 | |
| 66 | 67 | **Bom**: Registre o erro e degrade graciosamente 68 | 69 | Se a operação não for estritamente necessária, 70 | podemos fornecer uma experiência degradada, porém sem quebrar, 71 | ao nos recuperarmos dela. 72 | 73 | | 74 | 75 | ```go 76 | if err := emitMetrics(); err != nil { 77 | // Falha ao escrever métricas não deve 78 | // quebrar a aplicação. 79 | log.Printf("Não foi possível emitir métricas: %v", err) 80 | } 81 | ``` 82 | 83 | |
| 85 | 86 | **Bom**: Corresponda ao erro e degrade graciosamente 87 | 88 | Se o chamado define um erro específico em seu contrato, 89 | e a falha é recuperável, 90 | corresponda a esse caso de erro e degradação graciosamente. 91 | Para todos os outros casos, encapsule o erro e o retorne. 92 | 93 | Chamadores mais acima na pilha lidarão com outros erros. 94 | 95 | | 96 | 97 | ```go 98 | tz, err := getUserTimeZone(id) 99 | if err != nil { 100 | if errors.Is(err, ErrUsuarioNaoEncontrado) { 101 | // Usuário não existe. Use o UTC. 102 | tz = time.UTC 103 | } else { 104 | return fmt.Errorf("obter usuário %q: %w", id, err) 105 | } 106 | } 107 | ``` 108 | 109 | |
| Ruim | Bom |
|---|---|
| 25 | 26 | ```go 27 | type Foo struct { 28 | // ... 29 | } 30 | 31 | var _defaultFoo Foo 32 | 33 | func init() { 34 | _defaultFoo = Foo{ 35 | // ... 36 | } 37 | } 38 | ``` 39 | 40 | | 41 | 42 | ```go 43 | var _defaultFoo = Foo{ 44 | // ... 45 | } 46 | 47 | // ou, melhor, para testabilidade: 48 | 49 | var _defaultFoo = defaultFoo() 50 | 51 | func defaultFoo() Foo { 52 | return Foo{ 53 | // ... 54 | } 55 | } 56 | ``` 57 | 58 | |
| 60 | 61 | ```go 62 | type Config struct { 63 | // ... 64 | } 65 | 66 | var _config Config 67 | 68 | func init() { 69 | // Ruim: baseado no diretório atual 70 | cwd, _ := os.Getwd() 71 | 72 | // Ruim: I/O 73 | raw, _ := os.ReadFile( 74 | path.Join(cwd, "config", "config.yaml"), 75 | ) 76 | 77 | yaml.Unmarshal(raw, &_config) 78 | } 79 | ``` 80 | 81 | | 82 | 83 | ```go 84 | type Config struct { 85 | // ... 86 | } 87 | 88 | func loadConfig() Config { 89 | cwd, err := os.Getwd() 90 | // lidar com err 91 | 92 | raw, err := os.ReadFile( 93 | path.Join(cwd, "config", "config.yaml"), 94 | ) 95 | // lidar com err 96 | 97 | var config Config 98 | yaml.Unmarshal(raw, &config) 99 | 100 | return config 101 | } 102 | ``` 103 | 104 | |
| Ruim | Bom |
|---|---|
| 29 | 30 | ```go 31 | m := make(map[string]os.FileInfo) 32 | 33 | files, _ := os.ReadDir("./files") 34 | for _, f := range files { 35 | m[f.Name()] = f 36 | } 37 | ``` 38 | 39 | | 40 | 41 | ```go 42 | 43 | files, _ := os.ReadDir("./files") 44 | 45 | m := make(map[string]os.DirEntry, len(files)) 46 | for _, f := range files { 47 | m[f.Name()] = f 48 | } 49 | ``` 50 | 51 | |
| 53 | 54 | `m` é criado sem uma dica de tamanho; pode haver mais 55 | alocações no momento da atribuição. 56 | 57 | | 58 | 59 | `m` é criado com uma dica de tamanho; pode haver menos 60 | alocações no momento da atribuição. 61 | 62 | |
| Ruim | Bom |
|---|---|
| 80 | 81 | ```go 82 | for n := 0; n < b.N; n++ { 83 | data := make([]int, 0) 84 | for k := 0; k < size; k++{ 85 | data = append(data, k) 86 | } 87 | } 88 | ``` 89 | 90 | | 91 | 92 | ```go 93 | for n := 0; n < b.N; n++ { 94 | data := make([]int, 0, size) 95 | for k := 0; k < size; k++{ 96 | data = append(data, k) 97 | } 98 | } 99 | ``` 100 | 101 | |
| 103 | 104 | ```plain 105 | BenchmarkBad-4 100000000 2.48s 106 | ``` 107 | 108 | | 109 | 110 | ```plain 111 | BenchmarkGood-4 100000000 0.21s 112 | ``` 113 | 114 | |
| Ruim | Bom |
|---|---|
| 14 | 15 | ```go 16 | package main 17 | 18 | func main() { 19 | if err := executar(); err != nil { 20 | log.Fatal(err) 21 | } 22 | } 23 | 24 | func executar() error { 25 | args := os.Args[1:] 26 | if len(args) != 1 { 27 | return errors.New("arquivo ausente") 28 | } 29 | nome := args[0] 30 | 31 | f, err := os.Open(nome) 32 | if err != nil { 33 | return err 34 | } 35 | defer f.Close() 36 | 37 | // Se um erro ocorrer após esta linha, 38 | // f.Close ainda será chamado. 39 | 40 | b, err := io.ReadAll(f) 41 | if err != nil { 42 | return err 43 | } 44 | 45 | // ... 46 | 47 | return nil 48 | } 49 | ``` 50 | 51 | | 52 | 53 | ```go 54 | package main 55 | 56 | func main() { 57 | if err := run(); err != nil { 58 | log.Fatal(err) 59 | } 60 | } 61 | 62 | func run() error { 63 | args := os.Args[1:] 64 | if len(args) != 1 { 65 | return errors.New("arquivo ausente") 66 | } 67 | nome := args[0] 68 | 69 | f, err := os.Open(nome) 70 | if err != nil { 71 | return err 72 | } 73 | defer f.Close() 74 | 75 | b, err := io.ReadAll(f) 76 | if err != nil { 77 | return err 78 | } 79 | 80 | // ... 81 | } 82 | ``` 83 | 84 | |
| Ruim | Bom |
|---|---|
| 16 | 17 | ```go 18 | // package db 19 | 20 | func Open( 21 | addr string, 22 | cache bool, 23 | logger *zap.Logger 24 | ) (*Connection, error) { 25 | // ... 26 | } 27 | ``` 28 | 29 | | 30 | 31 | ```go 32 | // package db 33 | 34 | type Option interface { 35 | // ... 36 | } 37 | 38 | func WithCache(c bool) Option { 39 | // ... 40 | } 41 | 42 | func WithLogger(log *zap.Logger) Option { 43 | // ... 44 | } 45 | 46 | // Open creates a connection. 47 | func Open( 48 | addr string, 49 | opts ...Option, 50 | ) (*Connection, error) { 51 | // ... 52 | } 53 | ``` 54 | 55 | |
| 57 | 58 | O parâmetro de cache e o de logger devem sempre ser fornecidos, mesmo que o usuário queira usar os valores padrão. 59 | 60 | ```go 61 | db.Open(addr, db.DefaultCache, zap.NewNop()) 62 | db.Open(addr, db.DefaultCache, log) 63 | db.Open(addr, false /* cache */, zap.NewNop()) 64 | db.Open(addr, false /* cache */, log) 65 | ``` 66 | 67 | | 68 | 69 | Opções são fornecidas apenas se necessárias. 70 | 71 | ```go 72 | db.Open(addr) 73 | db.Open(addr, db.WithLogger(log)) 74 | db.Open(addr, db.WithCache(false)) 75 | db.Open( 76 | addr, 77 | db.WithCache(false), 78 | db.WithLogger(log), 79 | ) 80 | ``` 81 | 82 | |
| Ruim | Bom |
|---|---|
| 29 | 30 | ```go 31 | // ConcreteList é uma lista de entidades. 32 | type ConcreteList struct { 33 | *AbstractList 34 | } 35 | ``` 36 | 37 | | 38 | 39 | ```go 40 | // ConcreteList é uma lista de entidades. 41 | type ConcreteList struct { 42 | list *AbstractList 43 | } 44 | 45 | // Add adiciona uma entidade à lista. 46 | func (l *ConcreteList) Add(e Entity) { 47 | l.list.Add(e) 48 | } 49 | 50 | // Remove remove uma entidade da lista. 51 | func (l *ConcreteList) Remove(e Entity) { 52 | l.list.Remove(e) 53 | } 54 | ``` 55 | 56 | |
| Ruim | Bom |
|---|---|
| 81 | 82 | ```go 83 | // AbstractList é uma implementação generalizada 84 | // para vários tipos de listas de entidades. 85 | type AbstractList interface { 86 | Add(Entity) 87 | Remove(Entity) 88 | } 89 | 90 | // ConcreteList é uma lista de entidades. 91 | type ConcreteList struct { 92 | AbstractList 93 | } 94 | ``` 95 | 96 | | 97 | 98 | ```go 99 | // ConcreteList é uma lista de entidades. 100 | type ConcreteList struct { 101 | list AbstractList 102 | } 103 | 104 | // Add adiciona uma entidade à lista. 105 | func (l *ConcreteList) Add(e Entity) { 106 | l.list.Add(e) 107 | } 108 | 109 | // Remove removes uma entidade da lista. 110 | func (l *ConcreteList) Remove(e Entity) { 111 | l.list.Remove(e) 112 | } 113 | ``` 114 | 115 | |
| Sem correspondência de erro | Correspondência de erro |
|---|---|
| 39 | 40 | ```go 41 | // pacote foo 42 | 43 | func Abrir() error { 44 | return errors.New("não foi possível abrir") 45 | } 46 | 47 | // pacote bar 48 | 49 | if err := foo.Abrir(); err != nil { 50 | // Não é possível manipular o erro. 51 | panic("erro desconhecido") 52 | } 53 | ``` 54 | 55 | | 56 | 57 | ```go 58 | // pacote foo 59 | 60 | var ErrNaoFoiPossivelAbrir = errors.New("não foi possível abrir") 61 | 62 | func Abrir() error { 63 | return ErrNaoFoiPossivelAbrir 64 | } 65 | 66 | // pacote bar 67 | 68 | if err := foo.Abrir(); err != nil { 69 | if errors.Is(err, foo.ErrNaoFoiPossivelAbrir) { 70 | // manipular o erro 71 | } else { 72 | panic("erro desconhecido") 73 | } 74 | } 75 | ``` 76 | 77 | |
| Sem correspondência de erro | Correspondência de erro |
|---|---|
| 88 | 89 | ```go 90 | // pacote foo 91 | 92 | func Abrir(arquivo string) error { 93 | return fmt.Errorf("arquivo %q não encontrado", arquivo) 94 | } 95 | 96 | // pacote bar 97 | 98 | if err := foo.Abrir("arquivoteste.txt"); err != nil { 99 | // Não é possível manipular o erro. 100 | panic("erro desconhecido") 101 | } 102 | ``` 103 | 104 | | 105 | 106 | ```go 107 | // pacote foo 108 | 109 | type ErroNaoEncontrado struct { 110 | Arquivo string 111 | } 112 | 113 | func (e *ErroNaoEncontrado) Error() string { 114 | return fmt.Sprintf("arquivo %q não encontrado", e.Arquivo) 115 | } 116 | 117 | func Abrir(arquivo string) error { 118 | return &ErroNaoEncontrado{Arquivo: arquivo} 119 | } 120 | 121 | // pacote bar 122 | 123 | if err := foo.Abrir("arquivoteste.txt"); err != nil { 124 | var naoEncontrado *foo.ErroNaoEncontrado 125 | if errors.As(err, &naoEncontrado) { 126 | // Manipular o erro 127 | } else { 128 | panic("erro desconhecido") 129 | } 130 | } 131 | ``` 132 | 133 | |
| Ruim | Bom |
|---|---|
| 10 | 11 | ```go 12 | type Client struct { 13 | version int 14 | http.Client 15 | } 16 | ``` 17 | 18 | | 19 | 20 | ```go 21 | type Client struct { 22 | http.Client 23 | 24 | version int 25 | } 26 | ``` 27 | 28 | |
| Ruim | Bom |
|---|---|
| 65 | 66 | ```go 67 | type A struct { 68 | // Ruim: A.Lock() e A.Unlock() estão 69 | // agora disponíveis, não fornecem 70 | // benefício funcional e permitem 71 | // que os usuários controlem detalhes 72 | // sobre os internos de A. 73 | sync.Mutex 74 | } 75 | ``` 76 | 77 | | 78 | 79 | ```go 80 | type countingWriteCloser struct { 81 | // Bom: Write() é fornecido nesta camada 82 | // externa para um propósito específico 83 | // e delega o trabalho para Write() do tipo interno. 84 | io.WriteCloser 85 | 86 | count int 87 | } 88 | 89 | func (w *countingWriteCloser) Write(bs []byte) (int, error) { 90 | w.count += len(bs) 91 | return w.WriteCloser.Write(bs) 92 | } 93 | ``` 94 | 95 | |
| 97 | 98 | ```go 99 | type Book struct { 100 | // Ruim: a mudança do ponteiro afeta a 101 | // utilidade do valor zero 102 | io.ReadWriter 103 | 104 | // outros campos 105 | } 106 | 107 | // depois 108 | 109 | var b Book 110 | b.Read(...) // pânico: ponteiro nulo 111 | b.String() // pânico: ponteiro nulo 112 | b.Write(...) // pânico: ponteiro nulo 113 | ``` 114 | 115 | | 116 | 117 | ```go 118 | type Book struct { 119 | // Bom: tem um valor zero útil 120 | bytes.Buffer 121 | 122 | // outros campos 123 | } 124 | 125 | // depois 126 | 127 | var b Book 128 | b.Read(...) // ok 129 | b.String() // ok 130 | b.Write(...) // ok 131 | ``` 132 | 133 | |
| 135 | 136 | ```go 137 | type Client struct { 138 | sync.Mutex 139 | sync.WaitGroup 140 | bytes.Buffer 141 | url.URL 142 | } 143 | ``` 144 | 145 | | 146 | 147 | ```go 148 | type Client struct { 149 | mtx sync.Mutex 150 | wg sync.WaitGroup 151 | buf bytes.Buffer 152 | url url.URL 153 | } 154 | ``` 155 | 156 | |
| Ruim | Bom |
|---|---|
| 27 | 28 | ```go 29 | func isActive(now, start, stop int) bool { 30 | return start <= now && now < stop 31 | } 32 | ``` 33 | 34 | | 35 | 36 | ```go 37 | func isActive(now, start, stop time.Time) bool { 38 | return (start.Before(now) || start.Equal(now)) && now.Before(stop) 39 | } 40 | ``` 41 | 42 | |
| Ruim | Bom |
|---|---|
| 55 | 56 | ```go 57 | func poll(delay int) { 58 | for { 59 | // ... 60 | time.Sleep(time.Duration(delay) * time.Millisecond) 61 | } 62 | } 63 | 64 | poll(10) // eram segundos ou milissegundos? 65 | ``` 66 | 67 | | 68 | 69 | ```go 70 | func poll(delay time.Duration) { 71 | for { 72 | // ... 73 | time.Sleep(delay) 74 | } 75 | } 76 | 77 | poll(10*time.Second) 78 | ``` 79 | 80 | |
| Ruim | Bom |
|---|---|
| 129 | 130 | ```go 131 | // {"interval": 2} 132 | type Config struct { 133 | Interval int `json:"interval"` 134 | } 135 | ``` 136 | 137 | | 138 | 139 | ```go 140 | // {"intervalMillis": 2000} 141 | type Config struct { 142 | IntervalMillis int `json:"intervalMillis"` 143 | } 144 | ``` 145 | 146 | |
| Ruim | Bom |
|---|---|
| 16 | 17 | ```go 18 | // func TestSplitHostPort(t *testing.T) 19 | 20 | host, port, err := net.SplitHostPort("192.0.2.0:8000") 21 | require.NoError(t, err) 22 | assert.Equal(t, "192.0.2.0", host) 23 | assert.Equal(t, "8000", port) 24 | 25 | host, port, err = net.SplitHostPort("192.0.2.0:http") 26 | require.NoError(t, err) 27 | assert.Equal(t, "192.0.2.0", host) 28 | assert.Equal(t, "http", port) 29 | 30 | host, port, err = net.SplitHostPort(":8000") 31 | require.NoError(t, err) 32 | assert.Equal(t, "", host) 33 | assert.Equal(t, "8000", port) 34 | 35 | host, port, err = net.SplitHostPort("1:8") 36 | require.NoError(t, err) 37 | assert.Equal(t, "1", host) 38 | assert.Equal(t, "8", port) 39 | ``` 40 | 41 | | 42 | 43 | ```go 44 | // func TestSplitHostPort(t *testing.T) 45 | 46 | tests := []struct{ 47 | give string 48 | wantHost string 49 | wantPort string 50 | }{ 51 | { 52 | give: "192.0.2.0:8000", 53 | wantHost: "192.0.2.0", 54 | wantPort: "8000", 55 | }, 56 | { 57 | give: "192.0.2.0:http", 58 | wantHost: "192.0.2.0", 59 | wantPort: "http", 60 | }, 61 | { 62 | give: ":8000", 63 | wantHost: "", 64 | wantPort: "8000", 65 | }, 66 | { 67 | give: "1:8", 68 | wantHost: "1", 69 | wantPort: "8", 70 | }, 71 | } 72 | 73 | for _, tt := range tests { 74 | t.Run(tt.give, func(t *testing.T) { 75 | host, port, err := net.SplitHostPort(tt.give) 76 | require.NoError(t, err) 77 | assert.Equal(t, tt.wantHost, host) 78 | assert.Equal(t, tt.wantPort, port) 79 | }) 80 | } 81 | ``` 82 | 83 | |
| Ruim | Bom |
|---|---|
| 152 | 153 | ```go 154 | func TestComplicatedTable(t *testing.T) { 155 | tests := []struct { 156 | give string 157 | want string 158 | wantErr error 159 | shouldCallX bool 160 | shouldCallY bool 161 | giveXResponse string 162 | giveXErr error 163 | giveYResponse string 164 | giveYErr error 165 | }{ 166 | // ... 167 | } 168 | 169 | for _, tt := range tests { 170 | t.Run(tt.give, func(t *testing.T) { 171 | // setup mocks 172 | ctrl := gomock.NewController(t) 173 | xMock := xmock.NewMockX(ctrl) 174 | if tt.shouldCallX { 175 | xMock.EXPECT().Call().Return( 176 | tt.giveXResponse, tt.giveXErr, 177 | ) 178 | } 179 | yMock := ymock.NewMockY(ctrl) 180 | if tt.shouldCallY { 181 | yMock.EXPECT().Call().Return( 182 | tt.giveYResponse, tt.giveYErr, 183 | ) 184 | } 185 | 186 | got, err := DoComplexThing(tt.give, xMock, yMock) 187 | 188 | // verify results 189 | if tt.wantErr != nil { 190 | require.EqualError(t, err, tt.wantErr) 191 | return 192 | } 193 | require.NoError(t, err) 194 | assert.Equal(t, want, got) 195 | }) 196 | } 197 | } 198 | ``` 199 | 200 | | 201 | 202 | ```go 203 | func TestShouldCallX(t *testing.T) { 204 | // setup mocks 205 | ctrl := gomock.NewController(t) 206 | xMock := xmock.NewMockX(ctrl) 207 | xMock.EXPECT().Call().Return("XResponse", nil) 208 | 209 | yMock := ymock.NewMockY(ctrl) 210 | 211 | got, err := DoComplexThing("inputX", xMock, yMock) 212 | 213 | require.NoError(t, err) 214 | assert.Equal(t, "want", got) 215 | } 216 | 217 | func TestShouldCallYAndFail(t *testing.T) { 218 | // setup mocks 219 | ctrl := gomock.NewController(t) 220 | xMock := xmock.NewMockX(ctrl) 221 | 222 | yMock := ymock.NewMockY(ctrl) 223 | yMock.EXPECT().Call().Return("YResponse", nil) 224 | 225 | _, err := DoComplexThing("inputY", xMock, yMock) 226 | assert.EqualError(t, err, "Y failed") 227 | } 228 | ``` 229 | |