├── 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 | 7 | 8 | 9 | 30 |
RuimBom
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 |
31 | -------------------------------------------------------------------------------- /src/53-struct-pointer.md: -------------------------------------------------------------------------------- 1 | # Inicializando Referências de Estruturas 2 | 3 | Use `&T{}` em vez de `new(T)` ao inicializar referências de estruturas para que 4 | seja consistente com a inicialização da estrutura. 5 | 6 | 7 | 8 | 9 | 28 |
RuimBom
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 |
29 | -------------------------------------------------------------------------------- /src/49-string-escape.md: -------------------------------------------------------------------------------- 1 | # Use Raw String Literals Para Evitar Sequências de Escape 2 | 3 | Go suporta [literais de string "raw"](https://golang.org/ref/spec#raw_string_lit), 4 | que podem abranger várias linhas e incluir aspas. Use-as para evitar 5 | strings com caracteres escapados manualmente, que são muito mais difíceis de ler. 6 | 7 | 8 | 9 | 10 | 23 |
RuimBom
11 | 12 | ```go 13 | wantError := "unknown name:\"test\"" 14 | ``` 15 | 16 | 17 | 18 | ```go 19 | wantError := `unknown error:"test"` 20 | ``` 21 | 22 |
24 | -------------------------------------------------------------------------------- /src/55-printf-const.md: -------------------------------------------------------------------------------- 1 | # Formate Strings fora do Printf 2 | 3 | Se você declarar strings de formato para funções estilo `Printf` fora de um literal de string, faça delas valores `const`. 4 | 5 | Isso ajuda o `go vet` a realizar uma análise estática da string de formato. 6 | 7 | 8 | 9 | 10 | 27 |
RuimBom
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 |
28 | -------------------------------------------------------------------------------- /src/35-import-group.md: -------------------------------------------------------------------------------- 1 | # Ordem de Agrupamento de Importações 2 | 3 | Deve haver dois grupos de importações: 4 | 5 | - Biblioteca padrão 6 | - Todo o resto 7 | 8 | Este é o agrupamento aplicado pelo goimports por padrão. 9 | 10 | 11 | 12 | 13 | 37 |
RuimBom
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 |
38 | -------------------------------------------------------------------------------- /src/02-interface-pointer.md: -------------------------------------------------------------------------------- 1 | # Ponteiros para Interfaces 2 | 3 | Você quase nunca precisa de um ponteiro para uma interface. Você deve estar passando 4 | interfaces como valores - os dados subjacentes ainda podem ser um ponteiro. 5 | 6 | Uma interface possui dois campos: 7 | 8 | 1. Um ponteiro para alguma informação específica do tipo. Você pode pensar nisso como 9 | "tipo." 10 | 2. Ponteiro de dados. Se os dados armazenados são um ponteiro, eles são armazenados diretamente. Se 11 | os dados armazenados são um valor, então um ponteiro para o valor é armazenado. 12 | 13 | Se você deseja que os métodos da interface modifiquem os dados subjacentes, você deve usar um 14 | ponteiro. 15 | -------------------------------------------------------------------------------- /src/29-strconv.md: -------------------------------------------------------------------------------- 1 | # Prefira strconv em vez de fmt 2 | 3 | Ao converter primitivos de / para strings, `strconv` é mais rápido do que 4 | `fmt`. 5 | 6 | 7 | 8 | 9 | 26 | 39 |
RuimBom
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 |
40 | -------------------------------------------------------------------------------- /src/15-type-assert.md: -------------------------------------------------------------------------------- 1 | # Lidar com Falhas na Asserção de Tipo 2 | 3 | A forma de retorno único de uma [asserção de tipo] gerará um pânico em um tipo incorreto. 4 | Portanto, sempre utilize o "idioma do 'comma ok'" (vírgula ok). 5 | 6 | [asserção de tipo]: https://golang.org/ref/spec#Type_assertions 7 | 8 | 9 | 10 | 11 | 27 |
RuimBom
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 |
28 | 29 | 31 | -------------------------------------------------------------------------------- /src/36-package-name.md: -------------------------------------------------------------------------------- 1 | # Nomes de Pacotes 2 | 3 | Ao nomear pacotes, escolha um nome que seja: 4 | 5 | - Todo em minúsculas. Sem maiúsculas ou sublinhados. 6 | - Não precisa ser renomeado usando imports nomeados na maioria dos locais de chamada. 7 | - Curto e sucinto. Lembre-se de que o nome é identificado na íntegra em cada local de chamada. 8 | - Não plural. Por exemplo, `net/url`, não `net/urls`. 9 | - Não "comum" (common), "util", "compartilhado" (shared) ou "lib". Esses são nomes ruins e pouco informativos. 10 | 11 | Veja também [Nomes de Pacotes] e [Diretrizes de estilo para pacotes Go]. 12 | 13 | [Nomes de Pacotes]: https://blog.golang.org/package-names 14 | [Diretrizes de estilo para pacotes Go]: https://rakyll.org/style-packages/ 15 | -------------------------------------------------------------------------------- /src/52-struct-zero.md: -------------------------------------------------------------------------------- 1 | # Use `var` para Estruturas com Valor Zero 2 | 3 | Quando todos os campos de uma estrutura são omitidos em uma declaração, use a forma `var` 4 | para declarar a estrutura. 5 | 6 | 7 | 8 | 9 | 22 |
RuimBom
10 | 11 | ```go 12 | user := User{} 13 | ``` 14 | 15 | 16 | 17 | ```go 18 | var user User 19 | ``` 20 | 21 |
23 | 24 | Isso diferencia estruturas com valores zero daquelas com campos não nulos 25 | de maneira semelhante à distinção criada para [inicialização de mapas](54-map-init.md), e coincide com a forma 26 | como preferimos [declarar fatias vazias]. 27 | 28 | [declarar fatias vazias]: https://go.dev/wiki/CodeReviewComments#declaring-empty-slices 29 | -------------------------------------------------------------------------------- /src/30-string-byte-slice.md: -------------------------------------------------------------------------------- 1 | # Evite conversões repetidas de string para byte 2 | 3 | Não crie slices de bytes a partir de uma string fixa repetidamente. Em vez disso, faça a conversão uma vez e capture o resultado. 4 | 5 | 6 | 7 | 8 | 26 | 39 |
RuimBom
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 |
40 | -------------------------------------------------------------------------------- /src/26-goroutine-exit.md: -------------------------------------------------------------------------------- 1 | # Aguarde as goroutines terminarem 2 | 3 | Dada uma goroutine iniciada pelo sistema, deve haver uma maneira de aguardar a saída da goroutine. Existem duas maneiras populares de fazer isso: 4 | 5 | - Use um `sync.WaitGroup`. 6 | Faça isso se houver várias goroutines das quais você deseja aguardar a conclusão: 7 | 8 | ```go 9 | var wg sync.WaitGroup 10 | for i := 0; i < N; i++ { 11 | wg.Go(...) 12 | } 13 | 14 | // To wait for all to finish: 15 | wg.Wait() 16 | ``` 17 | 18 | - Adicione outro `chan struct{}` que a goroutine fecha quando termina. 19 | Faça isso se houver apenas uma goroutine: 20 | 21 | ```go 22 | done := make(chan struct{}) 23 | go func() { 24 | defer close(done) 25 | // ... 26 | }() 27 | 28 | // Para aguardar a goroutine terminar: 29 | <-done 30 | ``` 31 | -------------------------------------------------------------------------------- /src/50-struct-field-key.md: -------------------------------------------------------------------------------- 1 | # Use Nomes de Campos para Inicializar Estruturas 2 | 3 | Você deve quase sempre especificar os nomes dos campos ao inicializar estruturas. Isso 4 | agora é aplicado por [`go vet`]. 5 | 6 | [`go vet`]: https://golang.org/cmd/vet/ 7 | 8 | 9 | 10 | 11 | 28 |
RuimBom
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 |
29 | 30 | Exceção: Os nomes dos campos *podem* ser omitidos em tabelas de teste quando houver 3 ou 31 | menos campos. 32 | 33 | ```go 34 | tests := []struct{ 35 | op Operation 36 | want string 37 | }{ 38 | {Add, "add"}, 39 | {Subtract, "subtract"}, 40 | } 41 | ``` 42 | -------------------------------------------------------------------------------- /src/08-channel-size.md: -------------------------------------------------------------------------------- 1 | # Tamanho do Canal é Um ou Nenhum 2 | 3 | Os canais geralmente devem ter um tamanho de um ou serem não armazenados em buffer. Por padrão, 4 | os canais não são armazenados em buffer e têm um tamanho de zero. Qualquer outro tamanho 5 | deve ser sujeito a um alto nível de escrutínio. Considere como o tamanho é 6 | determinado, o que impede que o canal seja preenchido sob carga e bloqueie 7 | escritores, e o que acontece quando isso ocorre. 8 | 9 | 10 | 11 | 12 | 29 |
RuimBom
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 |
30 | -------------------------------------------------------------------------------- /src/38-import-alias.md: -------------------------------------------------------------------------------- 1 | # Alias de Importação 2 | 3 | O alias de importação deve ser usado se o nome do pacote não corresponder ao último 4 | elemento do caminho de importação. 5 | 6 | ```go 7 | import ( 8 | "net/http" 9 | 10 | client "example.com/client-go" 11 | trace "example.com/trace/v2" 12 | ) 13 | ``` 14 | 15 | Em todos os outros cenários, o uso de alias de importação deve ser evitado, a menos que haja um 16 | conflito direto entre importações. 17 | 18 | 19 | 20 | 21 | 46 |
RuimBom
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 |
47 | -------------------------------------------------------------------------------- /src/56-printf-name.md: -------------------------------------------------------------------------------- 1 | # Nomeando Funções no Estilo Printf 2 | 3 | Ao declarar uma função no estilo `Printf`, certifique-se de que o `go vet` pode detectá-la e verificar a string de formato. 4 | 5 | Isso significa que você deve usar nomes de função pré-definidos no estilo `Printf`, se possível. O `go vet` verificará esses por padrão. Veja [Printf family] para mais informações. 6 | 7 | [Printf family]: https://golang.org/cmd/vet/#hdr-Printf_family 8 | 9 | Se usar os nomes pré-definidos não for uma opção, termine o nome que você escolher com f: `Wrapf`, não `Wrap`. O `go vet` pode ser configurado para verificar nomes específicos no estilo `Printf`, mas eles devem terminar com f. 10 | 11 | ```shell 12 | go vet -printfuncs=wrapf,statusf 13 | ``` 14 | 15 | Veja também [go vet: Printf family check]. 16 | 17 | [go vet: Printf family check]: https://kuzminva.wordpress.com/2017/11/07/go-vet-printf-family-check/ 18 | -------------------------------------------------------------------------------- /src/40-nest-less.md: -------------------------------------------------------------------------------- 1 | # Reduza o Aninhamento 2 | 3 | O código deve reduzir o aninhamento sempre que possível, lidando com casos de erro / condições especiais primeiro e retornando cedo ou continuando o loop. Reduza a quantidade de código aninhado em vários níveis. 4 | 5 | 6 | 7 | 8 | 43 |
RuimBom
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 |
44 | -------------------------------------------------------------------------------- /src/42-global-decl.md: -------------------------------------------------------------------------------- 1 | # Declarações de Variáveis no Nível Superior 2 | 3 | No nível superior, use a palavra-chave padrão `var`. Não especifique o tipo, a menos que não seja o mesmo tipo que o da expressão. 4 | 5 | 6 | 7 | 8 | 26 |
BadGood
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 |
27 | 28 | Especifique o tipo se o tipo da expressão não corresponder exatamente ao tipo desejado. 29 | 30 | ```go 31 | type myError struct{} 32 | 33 | func (myError) Error() string { return "error" } 34 | 35 | func F() myError { return myError{} } 36 | 37 | var _e error = F() 38 | // F retorna um objeto do tipo myError, mas queremos um error. 39 | ``` 40 | -------------------------------------------------------------------------------- /src/33-consistency.md: -------------------------------------------------------------------------------- 1 | # Seja Consistente 2 | 3 | Algumas das diretrizes delineadas neste documento podem ser avaliadas objetivamente; 4 | outras são situacionais, contextuais ou subjetivas. 5 | 6 | Acima de tudo, **seja consistente**. 7 | 8 | Código consistente é mais fácil de manter, é mais fácil de racionalizar, requer menos 9 | carga cognitiva e é mais fácil de migrar ou atualizar à medida que novas convenções surgem 10 | ou classes de bugs são corrigidas. 11 | 12 | Por outro lado, ter estilos múltiplos, díspares ou conflitantes dentro de um único 13 | código gera sobrecarga de manutenção, incerteza e dissonância cognitiva, 14 | todos os quais podem contribuir diretamente para uma menor velocidade, revisões de código dolorosas 15 | e bugs. 16 | 17 | Ao aplicar essas diretrizes a um código, é recomendado que as alterações 18 | sejam feitas em um nível de pacote (ou superior): a aplicação em um nível de subpacote 19 | viola a preocupação acima, introduzindo estilos múltiplos no mesmo código. 20 | -------------------------------------------------------------------------------- /src/07-defer-clean.md: -------------------------------------------------------------------------------- 1 | # Utilize Defer para Limpeza 2 | 3 | Use defer para limpar recursos como arquivos e travas. 4 | 5 | 6 | 7 | 8 | 43 |
RuimBom
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 |
44 | 45 | O defer tem uma sobrecarga extremamente pequena e deve ser evitado apenas se você puder 46 | provar que o tempo de execução da sua função está na ordem de nanossegundos. O 47 | ganho de legibilidade ao usar defers vale o custo minúsculo de usá-los. Isso 48 | é especialmente verdadeiro para métodos maiores que têm mais do que simples acessos à memória, onde os outros cálculos são mais significativos do que o `defer`. 49 | -------------------------------------------------------------------------------- /src/43-global-name.md: -------------------------------------------------------------------------------- 1 | # Prefixe Variáveis e Constantes Não Exportadas com _ 2 | 3 | Prefixe `var`s e `const`s não exportadas com `_` para deixar claro quando 4 | elas são símbolos globais. 5 | 6 | Justificativa: Variáveis e constantes de nível superior têm escopo de pacote. 7 | Usar um nome genérico facilita o uso acidental do valor errado em um arquivo 8 | diferente. 9 | 10 | 11 | 12 | 13 | 47 |
RuimBom
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 |
48 | 49 | **Exceção**: Valores de erro não exportados podem usar o prefixo `err` sem o sublinhado. 50 | Consulte [Nomeação de Erros](13-error-name.md). 51 | -------------------------------------------------------------------------------- /src/09-enum-start.md: -------------------------------------------------------------------------------- 1 | # Inicie Enums em Um 2 | 3 | A maneira padrão de introduzir enumerações em Go é declarar um tipo personalizado 4 | e um grupo `const` com `iota`. Como as variáveis têm um valor padrão de 0, você 5 | normalmente deve começar suas enums com um valor não nulo. 6 | 7 | 8 | 9 | 10 | 39 |
RuimBom
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 |
40 | 41 | Existem casos em que usar o valor zero faz sentido, por exemplo, quando o caso de valor zero é o comportamento padrão desejado. 42 | 43 | ```go 44 | type LogOutput int 45 | 46 | const ( 47 | LogToStdout LogOutput = iota 48 | LogToFile 49 | LogToRemote 50 | ) 51 | 52 | // LogToStdout=0, LogToFile=1, LogToRemote=2 53 | ``` 54 | 55 | 56 | -------------------------------------------------------------------------------- /src/51-struct-field-zero.md: -------------------------------------------------------------------------------- 1 | # Omitir Campos com Valor Zero em Estruturas 2 | 3 | Ao inicializar estruturas com nomes de campos, omita os campos que têm valores zero 4 | a menos que forneçam contexto significativo. Caso contrário, deixe que o Go defina esses valores automaticamente. 5 | 6 | 7 | 8 | 9 | 30 |
RuimBom
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 |
31 | 32 | Isso ajuda a reduzir o ruído para os leitores, omitindo valores que são padrão naquele contexto. Somente valores significativos são especificados. 33 | 34 | Inclua valores zero onde os nomes dos campos fornecem contexto significativo. Por exemplo, 35 | os casos de teste em [Tabelas de Teste](57-test-table.md) podem se beneficiar dos nomes dos campos 36 | mesmo quando têm valor zero. 37 | 38 | ```go 39 | tests := []struct{ 40 | give string 41 | want int 42 | }{ 43 | {give: "0", want: 0}, 44 | // ... 45 | } 46 | ``` 47 | -------------------------------------------------------------------------------- /src/48-param-naked.md: -------------------------------------------------------------------------------- 1 | # Evite Parâmetros Desnecessários 2 | 3 | Parâmetros nus em chamadas de função podem prejudicar a legibilidade. Adicione comentários no estilo C (`/* ... */`) para os nomes dos parâmetros quando o significado deles não for óbvio. 4 | 5 | 6 | 7 | 8 | 25 |
RuimBom
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 |
26 | 27 | Melhor ainda, substitua tipos `bool` nus por tipos personalizados para um código mais legível e seguro em relação ao tipo. Isso permite mais do que apenas dois estados (true/false) para esse parâmetro no futuro. 28 | 29 | ```go 30 | type Region int 31 | 32 | const ( 33 | UnknownRegion Region = iota 34 | Local 35 | ) 36 | 37 | type Status int 38 | 39 | const ( 40 | StatusReady Status = iota + 1 41 | StatusDone 42 | // Talvez tenhamos um StatusInProgress no futuro. 43 | ) 44 | 45 | func printInfo(name string, region Region, status Status) 46 | ``` 47 | -------------------------------------------------------------------------------- /src/24-struct-tag.md: -------------------------------------------------------------------------------- 1 | # Use tags de campo em structs serializadas 2 | 3 | Qualquer campo de estrutura que seja serializado em JSON, YAML, 4 | ou outros formatos que suportam a nomenclatura de campo baseada em tags 5 | deve ser anotado com a tag relevante. 6 | 7 | 8 | 9 | 10 | 40 |
RuimBom
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 |
41 | 42 | Justificativa: 43 | A forma serializada da estrutura é um contrato entre diferentes sistemas. 44 | Alterações na estrutura da forma serializada - incluindo nomes de campos - quebram 45 | esse contrato. Especificar nomes de campos dentro de tags torna o contrato explícito 46 | e protege contra quebras acidentais do contrato por meio de refatoração ou 47 | renomeação de campos. 48 | -------------------------------------------------------------------------------- /src/45-var-decl.md: -------------------------------------------------------------------------------- 1 | # Declarações Locais de Variáveis 2 | 3 | Declarações curtas de variáveis (`:=`) devem ser utilizadas se uma variável estiver sendo atribuída a algum valor explicitamente. 4 | 5 | 6 | 7 | 8 | 21 |
RuimBom
9 | 10 | ```go 11 | var s = "foo" 12 | ``` 13 | 14 | 15 | 16 | ```go 17 | s := "foo" 18 | ``` 19 | 20 |
22 | 23 | No entanto, há casos em que o valor padrão é mais claro quando a palavra-chave `var` é utilizada. Por exemplo, [Declarando Fatias Vazias](https://go.dev/wiki/CodeReviewComments#declaring-empty-slices). 24 | 25 | [Declarando Fatias Vazias]: https://github.com/golang/go/wiki/CodeReviewComments#declaring-empty-slices 26 | 27 | 28 | 29 | 30 | 57 |
RuimBom
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 |
58 | -------------------------------------------------------------------------------- /src/47-var-scope.md: -------------------------------------------------------------------------------- 1 | # Reduza o Escopo de Variáveis 2 | 3 | Sempre que possível, reduza o escopo das variáveis. Não reduza o escopo se isso entrar em conflito com [Reduzir o Aninhamento](40-nest-less.md). 4 | 5 | 6 | 7 | 8 | 26 |
RuimBom
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 |
27 | 28 | Se você precisa do resultado de uma chamada de função fora do bloco `if`, então você não deve tentar reduzir o escopo. 29 | 30 | 31 | 32 | 33 | 66 |
RuimBom
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 |
67 | -------------------------------------------------------------------------------- /src/39-function-order.md: -------------------------------------------------------------------------------- 1 | # Agrupamento e Ordenação de Funções 2 | 3 | - As funções devem ser ordenadas de acordo com a ordem de chamada aproximada. 4 | - As funções em um arquivo devem ser agrupadas pelo receptor. 5 | 6 | Portanto, as funções exportadas devem aparecer primeiro em um arquivo, após 7 | definições de `struct`, `const`, `var`. 8 | 9 | Um `newXYZ()`/`NewXYZ()` pode aparecer após o tipo ser definido, mas antes do 10 | resto dos métodos no receptor. 11 | 12 | Como as funções são agrupadas pelo receptor, funções utilitárias simples devem 13 | aparecer no final do arquivo. 14 | 15 | 16 | 17 | 18 | 55 |
RuimBom
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 |
56 | -------------------------------------------------------------------------------- /src/17-atomic.md: -------------------------------------------------------------------------------- 1 | # Use go.uber.org/atomic 2 | 3 | Operações atômicas com o pacote [sync/atomic] operam nos tipos brutos 4 | (`int32`, `int64`, etc.), então é fácil esquecer de usar a operação atômica para 5 | ler ou modificar as variáveis. 6 | 7 | [go.uber.org/atomic] adiciona segurança de tipo a essas operações, ocultando o 8 | tipo subjacente. Além disso, inclui um tipo conveniente `atomic.Bool`. 9 | 10 | [go.uber.org/atomic]: https://godoc.org/go.uber.org/atomic 11 | [sync/atomic]: https://golang.org/pkg/sync/atomic/ 12 | 13 | 14 | 15 | 16 | 58 |
RuimBom
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 |
59 | -------------------------------------------------------------------------------- /src/18-global-mut.md: -------------------------------------------------------------------------------- 1 | # Evite Variáveis Globais Mutáveis 2 | 3 | Evite mutar variáveis globais, optando em vez disso pela injeção de dependência. 4 | Isso se aplica a ponteiros de funções, assim como outros tipos de valores. 5 | 6 | 7 | 8 | 9 | 44 | 76 |
RuimBom
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 |
77 | -------------------------------------------------------------------------------- /src/13-error-name.md: -------------------------------------------------------------------------------- 1 | # Nomeação de Erros 2 | 3 | Para valores de erro armazenados como variáveis globais, 4 | use o prefixo `Err` ou `err` dependendo se eles são exportados. 5 | Esta orientação substitui a [Prefixo para Globais Não Exportadas com _](43-global-name.md). 6 | 7 | ```go 8 | var ( 9 | // Os dois erros a seguir são exportados 10 | // para que os usuários deste pacote possam correspondê-los 11 | // com errors.Is. 12 | 13 | ErrLinkQuebrado = errors.New("link quebrado") 14 | ErrNaoFoiPossivelAbrir = errors.New("não foi possível abrir") 15 | 16 | // Este erro não é exportado porque 17 | // não queremos torná-lo parte de nossa API pública. 18 | // Ainda podemos usá-lo dentro do pacote 19 | // com errors.Is. 20 | 21 | errNaoEncontrado = errors.New("não encontrado") 22 | ) 23 | ``` 24 | 25 | Para tipos de erro personalizados, use o sufixo `Error`. 26 | 27 | ```go 28 | // Da mesma forma, este erro é exportado 29 | // para que os usuários deste pacote possam correspondê-lo 30 | // com errors.As. 31 | 32 | type ErroNaoEncontrado struct { 33 | Arquivo string 34 | } 35 | 36 | func (e *ErroNaoEncontrado) Error() string { 37 | return fmt.Sprintf("arquivo %q não encontrado", e.Arquivo) 38 | } 39 | 40 | // E este erro não é exportado porque 41 | // não queremos torná-lo parte da API pública. 42 | // Ainda podemos usá-lo dentro do pacote 43 | // com errors.As. 44 | 45 | type erroResolucao struct { 46 | Caminho string 47 | } 48 | 49 | func (e *erroResolucao) Error() string { 50 | return fmt.Sprintf("resolver %q", e.Caminho) 51 | } 52 | ``` 53 | -------------------------------------------------------------------------------- /src/03-interface-compliance.md: -------------------------------------------------------------------------------- 1 | # Verificar Conformidade de Interface 2 | 3 | Verifique a conformidade de interface em tempo de compilação quando apropriado. Isso inclui: 4 | 5 | - Tipos exportados que são obrigados a implementar interfaces específicas como parte do 6 | seu contrato de API 7 | - Tipos exportados ou não exportados que fazem parte de uma coleção de tipos 8 | implementando a mesma interface 9 | - Outros casos em que violar uma interface quebraria os usuários 10 | 11 | 12 | 13 | 14 | 49 |
RuimBom
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 |
50 | 51 | A declaração `var _ http.Handler = (*Handler)(nil)` falhará na compilação se 52 | `*Handler` deixar de corresponder à interface `http.Handler`. 53 | 54 | O lado direito da atribuição deve ser o valor zero do tipo afirmado. Isso é `nil` para tipos de ponteiro (como `*Handler`), slices e maps, e 55 | uma struct vazia para tipos de struct. 56 | 57 | ```go 58 | type LogHandler struct { 59 | h http.Handler 60 | log *zap.Logger 61 | } 62 | 63 | var _ http.Handler = LogHandler{} 64 | 65 | func (h LogHandler) ServeHTTP( 66 | w http.ResponseWriter, 67 | r *http.Request, 68 | ) { 69 | // ... 70 | } 71 | ``` 72 | -------------------------------------------------------------------------------- /src/05-mutex-zero-value.md: -------------------------------------------------------------------------------- 1 | # Mutexes com Valor Zero são Válidos 2 | 3 | O valor zero de `sync.Mutex` e `sync.RWMutex` é válido, então você quase 4 | nunca precisa de um ponteiro para um mutex. 5 | 6 | 7 | 8 | 9 | 24 |
RuimBom
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 |
25 | 26 | Se você usar uma struct por ponteiro, então o mutex deve ser um campo não ponteiro 27 | nele. Não aninhe o mutex na struct, mesmo que a struct não seja exportada. 28 | 29 | 30 | 31 | 32 | 79 | 80 | 91 |
RuimBom
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 |
92 | -------------------------------------------------------------------------------- /src/59-lint.md: -------------------------------------------------------------------------------- 1 | # Linting 2 | 3 | Mais importante do que qualquer conjunto "bendito" de linters é aplicar lint de forma consistente em todo o 4 | código. 5 | 6 | Recomendamos o uso dos seguintes linters no mínimo, porque acreditamos que eles 7 | ajudam a detectar os problemas mais comuns e também estabelecem um padrão elevado para a qualidade do código sem serem desnecessariamente prescritivos: 8 | 9 | - [errcheck] para garantir que os erros sejam tratados 10 | - [goimports] para formatar o código e gerenciar os imports 11 | - [golint] para apontar erros de estilo comuns 12 | - [govet] para analisar o código em busca de erros comuns 13 | - [staticcheck] para realizar várias verificações de análise estática 14 | 15 | [errcheck]: https://github.com/kisielk/errcheck 16 | [goimports]: https://godoc.org/golang.org/x/tools/cmd/goimports 17 | [golint]: https://github.com/golang/lint 18 | [govet]: https://golang.org/cmd/vet/ 19 | [staticcheck]: https://staticcheck.io/ 20 | 21 | ## Lint Runners 22 | 23 | Recomendamos o [golangci-lint] como o principal executor de lint para código Go, principalmente devido 24 | à sua performance em código-bases maiores e à capacidade de configurar e usar muitos 25 | linters canônicos de uma vez. Este repositório possui um exemplo de arquivo de configuração [.golangci.yml] com linters e configurações recomendados. 26 | 27 | golangci-lint possui [vários linters] disponíveis para uso. Os linters mencionados acima são 28 | recomendados como um conjunto base, e encorajamos as equipes a adicionar qualquer linter adicional 29 | que faça sentido para seus projetos. 30 | 31 | [golangci-lint]: https://github.com/golangci/golangci-lint 32 | [.golangci.yml]: https://github.com/uber-go/guide/blob/master/.golangci.yml 33 | [vários linters]: https://golangci-lint.run/usage/linters/ 34 | -------------------------------------------------------------------------------- /src/01-intro.md: -------------------------------------------------------------------------------- 1 | # Introdução 2 | 3 | Estilos são as convenções que governam nosso código. O termo estilo é um pouco enganoso, pois essas convenções abrangem muito mais do que apenas a formatação de arquivos de origem — gofmt cuida disso para nós. 4 | 5 | O objetivo deste guia é gerenciar essa complexidade descrevendo detalhadamente os (O que fazer e o que não fazer) de escrever código Go na Uber. Essas regras existem para manter a base de código gerenciável, permitindo ainda que os engenheiros usem os recursos da linguagem Go de maneira produtiva. 6 | 7 | Este guia foi originalmente criado por [Prashant Varanasi] e [Simon Newton] como uma maneira de atualizar alguns colegas sobre o uso do Go. Ao longo dos anos, ele foi modificado com base no feedback de outros. 8 | 9 | [Prashant Varanasi]: https://github.com/prashantv 10 | [Simon Newton]: https://github.com/nomis52 11 | 12 | Este documento descreve convenções idiomáticas no código Go que seguimos na Uber. Muitas dessas são diretrizes gerais para Go, enquanto outras se baseiam em recursos externos: 13 | 14 | 1. [Guia Efetivo Go](https://golang.org/doc/effective_go.html) 15 | 2. [Erros Comuns em Go](https://go.dev/wiki/CommonMistakes) 16 | 3. [Comentários para Revisão de Código em Go](https://github.com/golang/go/wiki/CodeReviewComments) 17 | 18 | Temos como objetivo que os exemplos de código sejam precisos para as duas versões menores mais recentes das [versões](https://go.dev/doc/devel/release) do Go. 19 | 20 | Todo o código deve estar sem erros ao passar por `golint` e `go vet`. Recomendamos configurar seu editor para: 21 | 22 | - Executar `goimports` ao salvar 23 | - Executar `golint` e `go vet` para verificar erros 24 | 25 | Você pode encontrar informações sobre o suporte do editor para ferramentas Go aqui: 26 | 27 | -------------------------------------------------------------------------------- /src/27-goroutine-init.md: -------------------------------------------------------------------------------- 1 | # Sem goroutines em `init()` 2 | 3 | Funções `init()` não devem criar goroutines. 4 | Veja também [Evite init()](21-init.md). 5 | 6 | Se um pacote precisa de uma goroutine em segundo plano, 7 | ele deve expor um objeto responsável por gerenciar a vida útil da goroutine. 8 | O objeto deve fornecer um método (`Close`, `Stop`, `Shutdown`, etc) 9 | que sinalize para a goroutine de segundo plano parar e espere ela encerrar. 10 | 11 | 12 | 13 | 14 | 60 | 76 |
RuimBom
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 |
77 | -------------------------------------------------------------------------------- /src/54-map-init.md: -------------------------------------------------------------------------------- 1 | # Inicializando Mapas 2 | 3 | Prefira `make(..)` para mapas vazios e mapas populados 4 | programaticamente. Isso torna a inicialização do mapa visualmente 5 | diferente da declaração, e facilita a adição de dicas de tamanho 6 | mais tarde, se disponíveis. 7 | 8 | 9 | 10 | 11 | 34 | 43 |
RuimBom
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 |
44 | 45 | Sempre que possível, forneça dicas de capacidade ao inicializar 46 | mapas com `make()`. Veja 47 | [Especificando Dicas de Capacidade para Mapas](31-container-capacity.md#specifying-map-capacity-hints) 48 | para mais informações. 49 | 50 | Por outro lado, se o mapa contiver um conjunto fixo de elementos, 51 | use literais de mapas para inicializá-lo. 52 | 53 | 54 | 55 | 56 | 76 |
RuimBom
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 |
77 | 78 | A regra básica é usar literais de mapas ao adicionar um conjunto fixo de 79 | elementos durante a inicialização; caso contrário, use `make` (e especifique uma dica de tamanho 80 | se disponível). 81 | -------------------------------------------------------------------------------- /src/46-slice-nil.md: -------------------------------------------------------------------------------- 1 | # nil é uma fatia válida 2 | 3 | `nil` é uma fatia válida com comprimento 0. Isso significa que, 4 | 5 | - Você não deve retornar explicitamente uma fatia de comprimento zero. Retorne `nil` 6 | em vez disso. 7 | 8 | 9 | 10 | 11 | 28 |
RuimBom
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 |
29 | 30 | - Para verificar se uma fatia está vazia, sempre use `len(s) == 0`. Não verifique 31 | `nil`. 32 | 33 | 34 | 35 | 36 | 53 |
RuimBom
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 |
54 | 55 | - O valor zero (uma fatia declarada com `var`) pode ser usado imediatamente sem 56 | `make()`. 57 | 58 | 59 | 60 | 61 | 91 |
RuimBom
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 |
92 | 93 | Lembre-se de que, embora seja uma fatia válida, uma fatia nula não é equivalente a uma 94 | fatia alocada com comprimento 0 - uma é nula e a outra não é - e as duas podem 95 | ser tratadas de maneira diferente em situações diferentes (como serialização). 96 | -------------------------------------------------------------------------------- /src/16-panic.md: -------------------------------------------------------------------------------- 1 | # Não entre em Pânico 2 | 3 | Código em produção deve evitar panics. Panics são uma fonte principal de [falhas em cascata]. Se ocorrer um erro, a função deve retornar um erro e permitir que o chamador decida como lidar com ele. 4 | 5 | [falhas em cascata]: https://en.wikipedia.org/wiki/Cascading_failure 6 | 7 | 8 | 9 | 10 | 11 | 46 |
RuimBom
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 |
47 | 48 | Panic/recover não é uma estratégia de tratamento de erros. Um programa deve entrar em pânico apenas quando algo irrecuperável acontece, como uma referência nula. Uma exceção a isso é a inicialização do programa: coisas ruins no início do programa que devem abortá-lo podem causar um panic. 49 | 50 | ```go 51 | var _statusTemplate = template.Must(template.New("name").Parse("_statusHTML")) 52 | ``` 53 | 54 | Mesmo nos testes, prefira `t.Fatal` ou `t.FailNow` em vez de panics para garantir que o teste seja marcado como falhado. 55 | 56 | 57 | 58 | 59 | 82 |
RuimBom
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 |
83 | -------------------------------------------------------------------------------- /src/22-exit-main.md: -------------------------------------------------------------------------------- 1 | # Sair no Main 2 | 3 | Programas Go utilizam [`os.Exit`] ou [`log.Fatal*`] para sair imediatamente. (Entrar em pânico 4 | não é uma boa maneira de encerrar programas, por favor [não entre em pânico](16-panic.md).) 5 | 6 | [`os.Exit`]: https://golang.org/pkg/os/#Exit 7 | [`log.Fatal*`]: https://golang.org/pkg/log/#Fatal 8 | 9 | Chame um dos `os.Exit` ou `log.Fatal*` **apenas em `main()`**. Todas as outras 10 | funções devem retornar erros para sinalizar falha. 11 | 12 | 13 | 14 | 15 | 65 |
RuimBom
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 |
66 | 67 | Justificativa: Programas com várias funções que encerram apresentam alguns problemas: 68 | 69 | - Fluxo de controle não óbvio: Qualquer função pode encerrar o programa, tornando 70 | difícil compreender o fluxo de controle. 71 | - Difícil de testar: Uma função que encerra o programa também encerrará o teste 72 | que a chama. Isso torna a função difícil de testar e introduz o risco de 73 | pular outros testes que ainda não foram executados pelo `go test`. 74 | - Falta de limpeza: Quando uma função encerra o programa, ela pula as chamadas 75 | de função enfileiradas com instruções `defer`. Isso aumenta o risco de pular tarefas importantes de limpeza. 76 | -------------------------------------------------------------------------------- /src/04-interface-receiver.md: -------------------------------------------------------------------------------- 1 | # Receptores e Interfaces 2 | 3 | Métodos com receptores de valor podem ser chamados em ponteiros, assim como em valores. 4 | Métodos com receptores de ponteiro só podem ser chamados em ponteiros ou [valores endereçáveis]. 5 | 6 | [valores endereçáveis]: https://golang.org/ref/spec#Method_values 7 | 8 | Por exemplo, 9 | 10 | ```go 11 | type S struct { 12 | data string 13 | } 14 | 15 | func (s S) Read() string { 16 | return s.data 17 | } 18 | 19 | func (s *S) Write(str string) { 20 | s.data = str 21 | } 22 | 23 | // Não podemos obter ponteiros para valores armazenados em mapas, porque eles não são 24 | // valores endereçáveis. 25 | sVals := map[int]S{1: {"A"}} 26 | 27 | // Podemos chamar o método Read em valores armazenados no mapa porque o método Read 28 | // tem um receptor de valor, que não requer que o valor seja endereçável. 29 | sVals[1].Read() 30 | 31 | // Não podemos chamar o método Write em valores armazenados no mapa porque o método Write 32 | // tem um receptor de ponteiro, e não é possível obter um ponteiro 33 | // para um valor armazenado em um mapa. 34 | // 35 | // sVals[1].Write("test") 36 | 37 | sPtrs := map[int]*S{1: {"A"}} 38 | 39 | // Podemos chamar tanto Read quanto Write se o mapa armazenar ponteiros, 40 | // porque os ponteiros são intrinsecamente endereçáveis. 41 | sPtrs[1].Read() 42 | sPtrs[1].Write("test") 43 | ``` 44 | 45 | De forma semelhante, uma interface pode ser satisfeita por um ponteiro, mesmo que o método tenha um 46 | receptor de valor. 47 | 48 | ```go 49 | type F interface { 50 | f() 51 | } 52 | 53 | type S1 struct{} 54 | 55 | func (s S1) f() {} 56 | 57 | type S2 struct{} 58 | 59 | func (s *S2) f() {} 60 | 61 | s1Val := S1{} 62 | s1Ptr := &S1{} 63 | s2Val := S2{} 64 | s2Ptr := &S2{} 65 | 66 | var i F 67 | i = s1Val 68 | i = s1Ptr 69 | i = s2Ptr 70 | 71 | // O seguinte não compila, já que s2Val é um valor, e não há um receptor de valor para f. 72 | // i = s2Val 73 | ``` 74 | 75 | O Effective Go tem uma boa explicação sobre [Ponteiros vs. Valores]. 76 | 77 | [Ponteiros vs. Valores]: https://golang.org/doc/effective_go.html#pointers_vs_values 78 | -------------------------------------------------------------------------------- /src/25-goroutine-forget.md: -------------------------------------------------------------------------------- 1 | # Não dispare e esqueça goroutines 2 | 3 | Goroutines são leves, mas não são gratuitas: 4 | no mínimo, elas consomem memória para suas pilhas e CPU para serem agendadas. 5 | Enquanto esses custos são pequenos para usos típicos de goroutines, 6 | eles podem causar problemas de desempenho significativos 7 | quando são criados em grande quantidade sem controle de tempo de vida. 8 | Goroutines com tempos de vida não gerenciados também podem causar outros problemas 9 | como impedir que objetos não utilizados sejam coletados pelo coletor de lixo 10 | e reter recursos que não são mais utilizados. 11 | 12 | Portanto, não vaze goroutines em código de produção. 13 | Use [go.uber.org/goleak](https://pkg.go.dev/go.uber.org/goleak) 14 | para testar vazamentos de goroutine dentro de pacotes que podem iniciar goroutines. 15 | 16 | Em geral, cada goroutine: 17 | 18 | - deve ter um momento previsível em que ela vai parar de ser executada; ou 19 | - deve haver uma maneira de sinalizar para a goroutine que ela deve parar 20 | 21 | Em ambos os casos, deve haver uma maneira para o código bloquear e aguardar a goroutine terminar. 22 | 23 | Por exemplo: 24 | 25 | 26 | 27 | 28 | 67 | 78 |
RuimBom
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 |
79 | -------------------------------------------------------------------------------- /src/06-container-copy.md: -------------------------------------------------------------------------------- 1 | # Copie Slices e Maps nos Limites 2 | 3 | Slices e maps contêm ponteiros para os dados subjacentes, então tenha cuidado com cenários 4 | em que eles precisam ser copiados. 5 | 6 | ## Recebendo Slices e Maps 7 | 8 | Lembre-se de que os usuários podem modificar um mapa ou slice que você recebeu como argumento 9 | se você armazenar uma referência a ele. 10 | 11 | 12 | 13 | 14 | 15 | 30 | 46 | 47 | 48 | 49 |
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 | 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 |
50 | 51 | ## Retornando Slices e Maps 52 | 53 | Da mesma forma, tenha cuidado com as modificações dos usuários em maps ou slices que expõem o estado interno. 54 | 55 | 56 | 57 | 58 | 103 |
RuimBom
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 |
104 | 105 | 106 | -------------------------------------------------------------------------------- /src/20-builtin-name.md: -------------------------------------------------------------------------------- 1 | # Evite Usar Nomes Embutidos 2 | 3 | A [especificação da linguagem Go] delineia vários identificadores embutidos, 4 | [chamados predefinidos] que não devem ser usados como nomes em programas Go. 5 | 6 | Dependendo do contexto, reutilizar esses identificadores como nomes irá sombrear 7 | o original dentro do escopo léxico atual (e quaisquer escopos aninhados) ou tornar 8 | o código afetado confuso. No melhor dos casos, o compilador emitirá um aviso; no 9 | pior dos casos, tal código pode introduzir bugs latentes difíceis de serem identificados. 10 | 11 | [especificação da linguagem Go]: https://golang.org/ref/spec 12 | [chamados predefinidos]: https://golang.org/ref/spec#Predeclared_identifiers 13 | 14 | 15 | 16 | 17 | 18 | 45 | 92 |
RuimBom
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 |
93 | 94 | Observe que o compilador não gerará erros ao usar identificadores predefinidos, mas ferramentas como `go vet` devem apontar corretamente esses casos e outros de sombreamento. 95 | -------------------------------------------------------------------------------- /src/12-error-wrap.md: -------------------------------------------------------------------------------- 1 | # Envoltório de Erro 2 | 3 | Existem três opções principais para propagar erros se uma chamada falhar: 4 | 5 | - retornar o erro original como está 6 | - adicionar contexto com `fmt.Errorf` e o verbo `%w` 7 | - adicionar contexto com `fmt.Errorf` e o verbo `%v` 8 | 9 | Retorne o erro original como está se não houver contexto adicional a ser adicionado. 10 | Isso mantém o tipo e a mensagem de erro originais. 11 | Isso é adequado para casos em que a mensagem de erro subjacente 12 | tem informações suficientes para rastrear de onde veio. 13 | 14 | Caso contrário, adicione contexto à mensagem de erro sempre que possível, 15 | para que, em vez de um erro vago como "conexão recusada", 16 | você obtenha erros mais úteis como "chamar o serviço foo: conexão recusada". 17 | 18 | Use `fmt.Errorf` para adicionar contexto aos seus erros, 19 | escolhendo entre os verbos `%w` ou `%v` 20 | com base em se o chamador deve poder 21 | corresponder e extrair a causa subjacente. 22 | 23 | - Use `%w` se o chamador deve ter acesso ao erro subjacente. 24 | Este é um bom padrão para a maioria dos erros encapsulados, 25 | mas esteja ciente de que os chamadores podem começar a depender desse comportamento. 26 | Portanto, para casos em que o erro encapsulado é um `var` ou tipo conhecido, 27 | documente e teste-o como parte do contrato da sua função. 28 | - Use `%v` para obscurecer o erro subjacente. 29 | Os chamadores não poderão correspondê-lo, 30 | mas você pode mudar para `%w` no futuro, se necessário. 31 | 32 | Ao adicionar contexto a erros retornados, mantenha o contexto sucinto, evitando 33 | frases como "falha ao", que afirmam o óbvio e se acumulam à medida que o erro 34 | se propaga pela pilha: 35 | 36 | 37 | 38 | 39 | 72 |
RuimBom
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 |
73 | 74 | Entretanto, assim que o erro é enviado para outro sistema, deve ficar claro que 75 | a mensagem é um erro (por exemplo, uma etiqueta err ou um prefixo "Falha" nos logs). 76 | 77 | Veja também [Não apenas verifique erros, manipule-os com elegância]. 78 | 79 | [Não apenas verifique erros, manipule-os com elegância]: https://dave.cheney.net/2016/04/27/dont-just-check-errors-handle-them-gracefully 80 | -------------------------------------------------------------------------------- /src/34-decl-group.md: -------------------------------------------------------------------------------- 1 | # Agrupe Declarações Semelhantes 2 | 3 | Go suporta a agrupação de declarações semelhantes. 4 | 5 | 6 | 7 | 8 | 25 |
RuimBom
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 |
26 | 27 | Isso também se aplica a constantes, variáveis e declarações de tipo. 28 | 29 | 30 | 31 | 32 | 70 |
RuimBom
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 |
71 | 72 | Agrupe apenas declarações relacionadas. Não agrupe declarações que não têm relação entre si. 73 | 74 | 75 | 76 | 77 | 105 |
RuimBom
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 |
106 | 107 | Os grupos não têm limitações quanto ao seu uso. Por exemplo, você pode usá-los 108 | dentro de funções. 109 | 110 | 111 | 112 | 113 | 140 |
RuimBom
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 |
141 | 142 | Exceção: Declarações de variáveis, especialmente dentro de funções, devem ser 143 | agrupadas se declaradas adjacentes a outras variáveis. Faça isso para variáveis 144 | declaradas juntas, mesmo que não estejam relacionadas. 145 | 146 | 147 | 148 | 149 | 178 |
RuimBom
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 |
179 | -------------------------------------------------------------------------------- /src/14-error-once.md: -------------------------------------------------------------------------------- 1 | # Lide com os Erros Apenas uma Vez 2 | 3 | Quando um chamador recebe um erro de um chamado, 4 | ele pode tratá-lo de várias maneiras diferentes 5 | dependendo do que sabe sobre o erro. 6 | 7 | Isso inclui, mas não está limitado a: 8 | 9 | - se o contrato do chamado define erros específicos, 10 | correspondendo ao erro com `errors.Is` ou `errors.As` 11 | e tratando os ramos de forma diferente 12 | - se o erro é recuperável, 13 | registrando o erro e degradando graciosamente 14 | - se o erro representa uma condição de falha específica do domínio, 15 | retornando um erro bem definido 16 | - retornando o erro, seja [encapsulado](12-error-wrap.md) ou de forma direta 17 | 18 | Independentemente de como o chamador lida com o erro, 19 | ele deve geralmente lidar com cada erro apenas uma vez. 20 | O chamador não deve, por exemplo, registrar o erro e depois retorná-lo, 21 | porque *seus* chamadores também podem lidar com o erro. 22 | 23 | Por exemplo, considere os seguintes casos: 24 | 25 | 26 | 27 | 28 | 47 | 65 | 84 | 110 |
DescriçãoCodigo
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 |
111 | -------------------------------------------------------------------------------- /src/21-init.md: -------------------------------------------------------------------------------- 1 | # Evite `init()` 2 | 3 | Evite `init()` sempre que possível. Quando `init()` é inevitável ou desejável, o código 4 | deve tentar: 5 | 6 | 1. Ser completamente determinístico, independentemente do ambiente ou invocação do programa. 7 | 2. Evitar depender da ordem ou efeitos colaterais de outras funções `init()`. 8 | Embora a ordem das funções `init()` seja bem conhecida, o código pode mudar, e assim 9 | os relacionamentos entre as funções `init()` podem tornar o código frágil e 10 | propenso a erros. 11 | 3. Evitar acessar ou manipular o estado global ou de ambiente, como informações da máquina, 12 | variáveis de ambiente, diretório de trabalho, argumentos/entradas do programa, etc. 13 | 4. Evitar I/O, incluindo chamadas de sistema de arquivos, de rede e do sistema. 14 | 15 | Código que não pode atender a esses requisitos provavelmente deve ser um ajudante 16 | chamado como parte do `main()` (ou em outro lugar no ciclo de vida de um programa), ou 17 | ser escrito como parte do próprio `main()`. Em particular, bibliotecas destinadas 18 | a serem usadas por outros programas devem ter um cuidado especial para serem completamente 19 | determinísticas e não realizar "mágica de init". 20 | 21 | 22 | 23 | 24 | 59 | 105 |
RuimBom
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 |
106 | 107 | Considerando o acima, algumas situações em que `init()` pode ser preferível ou 108 | necessário incluem: 109 | 110 | - Expressões complexas que não podem ser representadas como atribuições únicas. 111 | - Hooks plugáveis, como dialetos `database/sql`, registros de tipos de codificação, etc. 112 | - Otimizações para [Google Cloud Functions] e outras formas de pré-computação determinística. 113 | 114 | [Google Cloud Functions]: https://cloud.google.com/functions/docs/bestpractices/tips#use_global_variables_to_reuse_objects_in_future_invocations 115 | -------------------------------------------------------------------------------- /src/31-container-capacity.md: -------------------------------------------------------------------------------- 1 | # Prefira Especificar a Capacidade do Contêiner 2 | 3 | Especifique a capacidade do contêiner sempre que possível para alocar memória 4 | para o contêiner antecipadamente. Isso minimiza alocações subsequentes (por cópia e 5 | redimensionamento do contêiner) à medida que elementos são adicionados. 6 | 7 | ## Especificando Dicas de Capacidade para Mapas 8 | 9 | Sempre que possível, forneça dicas de capacidade ao inicializar 10 | mapas com `make()`. 11 | 12 | ```go 13 | make(map[T1]T2, hint) 14 | ``` 15 | 16 | Fornecer uma dica de capacidade para `make()` tenta ajustar o tamanho certo 17 | do mapa no momento da inicialização, o que reduz a necessidade de aumentar 18 | o mapa e as alocações à medida que os elementos são adicionados ao mapa. 19 | 20 | Observe que, ao contrário de slices, dicas de capacidade para mapas não garantem uma alocação completa e preventiva, 21 | mas são usadas para aproximar o número de buckets do hashmap 22 | necessários. Consequentemente, alocações ainda podem ocorrer ao adicionar elementos ao 23 | mapa, mesmo até a capacidade especificada. 24 | 25 | 26 | 27 | 28 | 52 | 63 |
RuimBom
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 |
64 | 65 | ## Especificando a Capacidade de um Slice 66 | 67 | Sempre que possível, forneça dicas de capacidade ao inicializar slices com `make()`, 68 | especialmente ao usar a função `append()`. 69 | 70 | ```go 71 | make([]T, length, capacity) 72 | ``` 73 | 74 | Ao contrário dos mapas, a capacidade de um slice não é uma dica: o compilador alocará memória suficiente para a capacidade do slice conforme fornecida para `make()`, o que significa que as operações `append()` subsequentes não terão alocações (até que o comprimento do slice corresponda à capacidade, após o qual qualquer `append()` adicional exigirá um redimensionamento para acomodar elementos adicionais). 75 | 76 | 77 | 78 | 79 | 102 | 115 |
RuimBom
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 |
116 | -------------------------------------------------------------------------------- /src/23-exit-once.md: -------------------------------------------------------------------------------- 1 | # Encerrar Apenas uma Vez 2 | 3 | Se possível, prefira chamar `os.Exit` ou `log.Fatal` **no máximo uma vez** em seu 4 | `main()`. Se houver vários cenários de erro que interrompem a execução do programa, 5 | coloque essa lógica em uma função separada e retorne erros dela. 6 | 7 | Isso tem o efeito de encurtar a função `main()` e colocar toda a lógica principal 8 | de negócios em uma função separada e testável. 9 | 10 | 11 | 12 | 13 | 85 |
RuimBom
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 |
86 | 87 | O exemplo acima utiliza `log.Fatal`, mas a orientação também se aplica a 88 | `os.Exit` ou qualquer código de biblioteca que chame `os.Exit`. 89 | 90 | ```go 91 | func main() { 92 | if err := run(); err != nil { 93 | fmt.Fprintln(os.Stderr, err) 94 | os.Exit(1) 95 | } 96 | } 97 | ``` 98 | 99 | Você pode alterar a assinatura de `run()` de acordo com suas necessidades. 100 | Por exemplo, se seu programa precisar sair com códigos específicos para falhas, 101 | `run()` pode retornar o código de saída em vez de um erro. 102 | Isso permite que testes unitários verifiquem esse comportamento diretamente. 103 | 104 | ```go 105 | func main() { 106 | os.Exit(run(args)) 107 | } 108 | 109 | func run() (exitCode int) { 110 | // ... 111 | } 112 | ``` 113 | 114 | De forma mais geral, observe que a função `run()` usada nesses exemplos 115 | não é destinada a ser prescritiva. 116 | Há flexibilidade no nome, assinatura e configuração da função `run()`. 117 | Entre outras coisas, você pode: 118 | 119 | - aceitar argumentos de linha de comando não analisados (por exemplo, `run(os.Args[1:])`) 120 | - analisar os argumentos da linha de comando em `main()` e passá-los para `run` 121 | - usar um tipo de erro personalizado para transmitir o código de saída de volta para `main()` 122 | - colocar a lógica de negócios em uma camada de abstração diferente de `package main` 123 | 124 | Essa orientação apenas exige que haja um único local em seu `main()` 125 | responsável por encerrar efetivamente o processo. 126 | -------------------------------------------------------------------------------- /style.md: -------------------------------------------------------------------------------- 1 | # Guia de Estilo Uber Go 2 | 3 | - [Introdução](src/01-intro.md) 4 | - Diretrizes 5 | - [Ponteiros para Interfaces](src/02-interface-pointer.md) 6 | - [Verificar Conformidade de Interfaces](src/03-interface-compliance.md) 7 | - [Receptores e Interfaces](src/04-interface-receiver.md) 8 | - [Mutex com Valor Zero é Válido](src/05-mutex-zero-value.md) 9 | - [Copiar Slices e Maps em Limites](src/06-container-copy.md) 10 | - [Defer para Limpeza](src/07-defer-clean.md) 11 | - [Tamanho de Canal é Um ou Nenhum](src/08-channel-size.md) 12 | - [Iniciar Enums em Um](src/09-enum-start.md) 13 | - [Usar `"time"` para manipular o tempo](src/10-time.md) 14 | - Erros 15 | - [Tipos de Erro](src/11-error-type.md) 16 | - [Envolver Erros](src/12-error-wrap.md) 17 | - [Nomeação de Erros](src/13-error-name.md) 18 | - [Lidar com Erros uma Vez](src/14-error-once.md) 19 | - [Lidar com Falhas de Assertiva de Tipo](src/15-type-assert.md) 20 | - [Não Entre em Pânico](src/16-panic.md) 21 | - [Usar go.uber.org/atomic](src/17-atomic.md) 22 | - [Evitar Globais Mutáveis](src/18-global-mut.md) 23 | - [Evitar Incorporação de Tipos em Estruturas Públicas](src/19-embed-public.md) 24 | - [Evitar Usar Nomes Embutidos](src/20-builtin-name.md) 25 | - [Evitar `init()`](src/21-init.md) 26 | - [Sair em Main](src/22-exit-main.md) 27 | - [Sair Apenas uma Vez](src/23-exit-once.md) 28 | - [Usar tags de campos em structs serializadas](src/24-struct-tag.md) 29 | - [Não esquecer e abandonar goroutines](src/25-goroutine-forget.md) 30 | - [Aguardar goroutines finalizarem](src/26-goroutine-exit.md) 31 | - [Sem goroutines em `init()`](src/27-goroutine-init.md) 32 | - [Desempenho](src/28-performance.md) 33 | - [Preferir strconv a fmt](src/29-strconv.md) 34 | - [Evitar Conversões Repetidas de String para Bytes](src/30-string-byte-slice.md) 35 | - [Preferir Especificar a Capacidade de Contêineres](src/31-container-capacity.md) 36 | - Estilo 37 | - [Evitar Linhas Excessivamente Longas](src/32-line-length.md) 38 | - [Ser Consistente](src/33-consistency.md) 39 | - [Agrupar Declarações Semelhantes](src/34-decl-group.md) 40 | - [Ordem de Importação em Grupos](src/35-import-group.md) 41 | - [Nomes de Pacotes](src/36-package-name.md) 42 | - [Nomes de Funções](src/37-function-name.md) 43 | - [Alias de Importação](src/38-import-alias.md) 44 | - [Agrupamento e Ordem de Funções](src/39-function-order.md) 45 | - [Reduzir Nidificação](src/40-nest-less.md) 46 | - [Else Desnecessário](src/41-else-unnecessary.md) 47 | - [Declarações de Variáveis Globais no Topo](src/42-global-decl.md) 48 | - [Prefixar Variáveis Globais Não Exportadas com _](src/43-global-name.md) 49 | - [Incorporação em Estruturas](src/44-struct-embed.md) 50 | - [Declarações de Variáveis Locais](src/45-var-decl.md) 51 | - [nil é um slice válido](src/46-slice-nil.md) 52 | - [Reduzir o Escopo de Variáveis](src/47-var-scope.md) 53 | - [Evitar Parâmetros Isolados](src/48-param-naked.md) 54 | - [Usar Literais de String Raw para Evitar Escape](src/49-string-escape.md) 55 | - Inicializando Estruturas 56 | - [Usar Nomes de Campos para Inicializar Estruturas](src/50-struct-field-key.md) 57 | - [Omitir Campos de Valor Zero em Estruturas](src/51-struct-field-zero.md) 58 | - [Usar `var` para Estruturas de Valor Zero](src/52-struct-zero.md) 59 | - [Inicialização de Referências de Estruturas](src/53-struct-pointer.md) 60 | - [Inicializando Mapas](src/54-map-init.md) 61 | - [Formatar Strings fora do Printf](src/55-printf-const.md) 62 | - [Nomenclatura de Funções Estilo Printf](src/56-printf-name.md) 63 | - Padrões 64 | - [Tabelas de Teste](src/57-test-table.md) 65 | - [Opções Funcionais](src/58-functional-option.md) 66 | - [Linting](src/59-lint.md) 67 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Este repositório contém o [Guia de Estilo do Go da Uber](style.md), que documenta 2 | padrões e convenções usados no código Go da Uber em Português BR. 3 | 4 | # Guia de Estilo Uber Go 5 | 6 | - [Introdução](src/01-intro.md) 7 | - Diretrizes 8 | - [Ponteiros para Interfaces](src/02-interface-pointer.md) 9 | - [Verificar Conformidade de Interfaces](src/03-interface-compliance.md) 10 | - [Receptores e Interfaces](src/04-interface-receiver.md) 11 | - [Mutex com Valor Zero é Válido](src/05-mutex-zero-value.md) 12 | - [Copiar Slices e Maps em Limites](src/06-container-copy.md) 13 | - [Defer para Limpeza](src/07-defer-clean.md) 14 | - [Tamanho de Canal é Um ou Nenhum](src/08-channel-size.md) 15 | - [Iniciar Enums em Um](src/09-enum-start.md) 16 | - [Usar `"time"` para manipular o tempo](src/10-time.md) 17 | - Erros 18 | - [Tipos de Erro](src/11-error-type.md) 19 | - [Envolver Erros](src/12-error-wrap.md) 20 | - [Nomeação de Erros](src/13-error-name.md) 21 | - [Lidar com Erros uma Vez](src/14-error-once.md) 22 | - [Lidar com Falhas de Assertiva de Tipo](src/15-type-assert.md) 23 | - [Não Entre em Pânico](src/16-panic.md) 24 | - [Usar go.uber.org/atomic](src/17-atomic.md) 25 | - [Evitar Globais Mutáveis](src/18-global-mut.md) 26 | - [Evitar Incorporação de Tipos em Estruturas Públicas](src/19-embed-public.md) 27 | - [Evitar Usar Nomes Embutidos](src/20-builtin-name.md) 28 | - [Evitar `init()`](src/21-init.md) 29 | - [Sair em Main](src/22-exit-main.md) 30 | - [Sair Apenas uma Vez](src/23-exit-once.md) 31 | - [Usar tags de campos em structs serializadas](src/24-struct-tag.md) 32 | - [Não esquecer e abandonar goroutines](src/25-goroutine-forget.md) 33 | - [Aguardar goroutines finalizarem](src/26-goroutine-exit.md) 34 | - [Sem goroutines em `init()`](src/27-goroutine-init.md) 35 | - [Desempenho](src/28-performance.md) 36 | - [Preferir strconv a fmt](src/29-strconv.md) 37 | - [Evitar Conversões Repetidas de String para Bytes](src/30-string-byte-slice.md) 38 | - [Preferir Especificar a Capacidade de Contêineres](src/31-container-capacity.md) 39 | - Estilo 40 | - [Evitar Linhas Excessivamente Longas](src/32-line-length.md) 41 | - [Ser Consistente](src/33-consistency.md) 42 | - [Agrupar Declarações Semelhantes](src/34-decl-group.md) 43 | - [Ordem de Importação em Grupos](src/35-import-group.md) 44 | - [Nomes de Pacotes](src/36-package-name.md) 45 | - [Nomes de Funções](src/37-function-name.md) 46 | - [Alias de Importação](src/38-import-alias.md) 47 | - [Agrupamento e Ordem de Funções](src/39-function-order.md) 48 | - [Reduzir Nidificação](src/40-nest-less.md) 49 | - [Else Desnecessário](src/41-else-unnecessary.md) 50 | - [Declarações de Variáveis Globais no Topo](src/42-global-decl.md) 51 | - [Prefixar Variáveis Globais Não Exportadas com _](src/43-global-name.md) 52 | - [Incorporação em Estruturas](src/44-struct-embed.md) 53 | - [Declarações de Variáveis Locais](src/45-var-decl.md) 54 | - [nil é um slice válido](src/46-slice-nil.md) 55 | - [Reduzir o Escopo de Variáveis](src/47-var-scope.md) 56 | - [Evitar Parâmetros Isolados](src/48-param-naked.md) 57 | - [Usar Literais de String Raw para Evitar Escape](src/49-string-escape.md) 58 | - Inicializando Estruturas 59 | - [Usar Nomes de Campos para Inicializar Estruturas](src/50-struct-field-key.md) 60 | - [Omitir Campos de Valor Zero em Estruturas](src/51-struct-field-zero.md) 61 | - [Usar `var` para Estruturas de Valor Zero](src/52-struct-zero.md) 62 | - [Inicialização de Referências de Estruturas](src/53-struct-pointer.md) 63 | - [Inicializando Mapas](src/54-map-init.md) 64 | - [Formatar Strings fora do Printf](src/55-printf-const.md) 65 | - [Nomenclatura de Funções Estilo Printf](src/56-printf-name.md) 66 | - Padrões 67 | - [Tabelas de Teste](src/57-test-table.md) 68 | - [Opções Funcionais](src/58-functional-option.md) 69 | - [Linting](src/59-lint.md) 70 | -------------------------------------------------------------------------------- /src/58-functional-option.md: -------------------------------------------------------------------------------- 1 | # Opções Funcionais 2 | 3 | Opções funcionais são um padrão no qual você declara um tipo opaco `Option` 4 | que registra informações em alguma struct interna. Você aceita um número 5 | variável dessas opções e age sobre as informações completas registradas pelas 6 | opções na struct interna. 7 | 8 | Use esse padrão para argumentos opcionais em construtores e outras APIs 9 | públicas que você prevê precisar expandir, especialmente se você já tiver 10 | três ou mais argumentos nessas funções. 11 | 12 | 13 | 14 | 15 | 56 | 83 |
RuimBom
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 |
84 | 85 | A maneira sugerida de implementar esse padrão é com uma interface `Option` 86 | que possui um método não exportado, registrando opções em uma struct não 87 | exportada chamada `options`. 88 | 89 | ```go 90 | type options struct { 91 | cache bool 92 | logger *zap.Logger 93 | } 94 | 95 | type Option interface { 96 | apply(*options) 97 | } 98 | 99 | type cacheOption bool 100 | 101 | func (c cacheOption) apply(opts *options) { 102 | opts.cache = bool(c) 103 | } 104 | 105 | func WithCache(c bool) Option { 106 | return cacheOption(c) 107 | } 108 | 109 | type loggerOption struct { 110 | Log *zap.Logger 111 | } 112 | 113 | func (l loggerOption) apply(opts *options) { 114 | opts.logger = l.Log 115 | } 116 | 117 | func WithLogger(log *zap.Logger) Option { 118 | return loggerOption{Log: log} 119 | } 120 | 121 | // Open cria uma conexão. 122 | func Open( 123 | addr string, 124 | opts ...Option, 125 | ) (*Connection, error) { 126 | options := options{ 127 | cache: defaultCache, 128 | logger: zap.NewNop(), 129 | } 130 | 131 | for _, o := range opts { 132 | o.apply(&options) 133 | } 134 | 135 | // ... 136 | } 137 | ``` 138 | 139 | Observe que há uma maneira de implementar esse padrão com closures, mas acreditamos que o padrão acima oferece mais flexibilidade para os autores e é mais fácil de depurar e testar para os usuários. Em particular, ele permite que as opções sejam comparadas entre si em testes e mocks, ao contrário das closures, onde isso é impossível. Além disso, permite que as opções implementem outras interfaces, incluindo `fmt.Stringer`, o que permite representações de string legíveis para o usuário das opções. 140 | 141 | Veja também: 142 | 143 | - [Funções auto-referenciais e o design de opções] 144 | - [Opções funcionais para APIs amigáveis] 145 | 146 | [Funções auto-referenciais e o design de opções]: https://commandcenter.blogspot.com/2014/01/self-referential-functions-and-design.html 147 | [Opções funcionais para APIs amigáveis]: https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis 148 | 149 | 150 | -------------------------------------------------------------------------------- /src/19-embed-public.md: -------------------------------------------------------------------------------- 1 | # Evite Incorporar Tipos em Structs Públicas 2 | 3 | Esses tipos incorporados vazam detalhes de implementação, inibem a evolução do tipo e 4 | obscurecem a documentação. 5 | 6 | Supondo que você tenha implementado uma variedade de tipos de listas usando uma 7 | `AbstractList` compartilhada, evite incorporar a `AbstractList` em suas implementações concretas de lista. 8 | Em vez disso, escreva manualmente apenas os métodos para sua lista concreta que delegarão 9 | para a lista abstrata. 10 | 11 | ```go 12 | type AbstractList struct {} 13 | 14 | // Add adiciona uma entidade à lista. 15 | func (l *AbstractList) Add(e Entity) { 16 | // ... 17 | } 18 | 19 | // Remove remove uma entidade da lista. 20 | func (l *AbstractList) Remove(e Entity) { 21 | // ... 22 | } 23 | ``` 24 | 25 | 26 | 27 | 28 | 57 |
RuimBom
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 |
58 | 59 | Go permite [incorporação de tipos] como um compromisso entre herança e composição. 60 | O tipo externo obtém cópias implícitas dos métodos do tipo incorporado. 61 | Esses métodos, por padrão, delegam para o mesmo método da instância incorporada. 62 | 63 | [incorporação de tipos]: https://golang.org/doc/effective_go.html#embedding 64 | 65 | A struct também ganha um campo com o mesmo nome do tipo. 66 | Portanto, se o tipo incorporado for público, o campo também é público. 67 | Para manter a compatibilidade com versões anteriores, cada versão futura do tipo externo deve 68 | manter o tipo incorporado. 69 | 70 | Um tipo incorporado raramente é necessário. 71 | É uma conveniência que ajuda a evitar a escrita de métodos delegados tediosos. 72 | 73 | Mesmo incorporar uma interface *AbstractList* compatível, em vez da struct, 74 | ofereceria ao desenvolvedor mais flexibilidade para mudanças no futuro, mas ainda 75 | vazaria o detalhe de que as listas concretas usam uma implementação abstrata. 76 | 77 | 78 | 79 | 80 | 116 |
RuimBom
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 |
117 | 118 | Quer seja com uma struct incorporada ou uma interface incorporada, o tipo incorporado 119 | impõe limites à evolução do tipo. 120 | 121 | - Adicionar métodos a uma interface incorporada é uma mudança que quebra a compatibilidade. 122 | - Remover métodos de uma struct incorporada é uma mudança que quebra a compatibilidade. 123 | - Remover o tipo incorporado é uma mudança que quebra a compatibilidade. 124 | - Substituir o tipo incorporado, mesmo por um alternativo que satisfaça a mesma 125 | interface, é uma mudança que quebra a compatibilidade. 126 | 127 | Embora escrever esses métodos delegados seja tedioso, o esforço adicional esconde 128 | um detalhe de implementação, deixa mais oportunidades para mudanças e também 129 | elimina a indireção para descobrir a interface completa da Lista na 130 | documentação. 131 | -------------------------------------------------------------------------------- /src/11-error-type.md: -------------------------------------------------------------------------------- 1 | # Tipos de Erro 2 | 3 | Existem poucas opções para declarar erros. 4 | Considere o seguinte antes de escolher a opção mais adequada para o seu caso de uso. 5 | 6 | - O chamador precisa corresponder ao erro para que ele possa manipulá-lo? 7 | Se sim, devemos suportar as funções [`errors.Is`] ou [`errors.As`] 8 | ao declarar uma variável de erro no nível superior ou um tipo de erro personalizado. 9 | - A mensagem de erro é uma string estática 10 | ou é uma string dinâmica que requer informações contextuais? 11 | Para o primeiro caso, podemos usar [`errors.New`], mas para o segundo devemos 12 | usar [`fmt.Errorf`] ou um tipo de erro personalizado. 13 | - Estamos propagando um novo erro retornado por uma função downstream? 14 | Se sim, consulte a [seção sobre encapsulamento de erros](12-error-wrap.md). 15 | 16 | [`errors.Is`]: https://golang.org/pkg/errors/#Is 17 | [`errors.As`]: https://golang.org/pkg/errors/#As 18 | 19 | | Corresponder ao Erro? | Mensagem de Erro | Orientação | 20 | |-----------------------|------------------|---------------------------------------| 21 | | Não | estática | [`errors.New`] | 22 | | Não | dinâmica | [`fmt.Errorf`] | 23 | | Sim | estática | `var` no nível superior com [`errors.New`] | 24 | | Sim | dinâmica | tipo de erro personalizado | 25 | 26 | [`errors.New`]: https://golang.org/pkg/errors/#New 27 | [`fmt.Errorf`]: https://golang.org/pkg/fmt/#Errorf 28 | 29 | Por exemplo, 30 | use [`errors.New`] para um erro com uma string estática. 31 | Exporte este erro como uma variável para suportar a correspondência com `errors.Is` 32 | se o chamador precisar corresponder e manipular este erro. 33 | 34 | 35 | 36 | 37 | 38 | 78 |
Sem correspondência de erroCorrespondê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 |
79 | 80 | Para um erro com uma string dinâmica, 81 | use [`fmt.Errorf`] se o chamador não precisar corresponder a ele, 82 | e um tipo de `error` personalizado se o chamador precisar corresponder a ele. 83 | 84 | 85 | 86 | 87 | 134 |
Sem correspondência de erroCorrespondê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 |
135 | 136 | Observe que se você exportar variáveis de erro ou tipos de um pacote, 137 | eles farão parte da API pública do pacote. 138 | -------------------------------------------------------------------------------- /src/44-struct-embed.md: -------------------------------------------------------------------------------- 1 | # Incorporação em Estruturas 2 | 3 | Tipos incorporados devem estar no topo da lista de campos de uma 4 | estrutura, e deve haver uma linha em branco separando campos incorporados de campos regulares. 5 | 6 | 7 | 8 | 9 | 29 |
RuimBom
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 |
30 | 31 | A incorporação deve fornecer benefícios tangíveis, como adicionar ou aprimorar 32 | funcionalidade de maneira semanticamente apropriada. Deve fazer isso sem ter 33 | efeitos adversos visíveis para o usuário (veja também: [Evite Incorporar Tipos em Estruturas Públicas](19-embed-public.md). 34 | 35 | Exceção: Mutexes não devem ser incorporados, mesmo em tipos não exportados. Veja também: [Mutexes com Valor Zero são Válidos](05-mutex-zero-value.md). 36 | 37 | [Evite Incorporar Tipos em Estruturas Públicas]: #evite-incorporar-tipos-em-estruturas-públicas 38 | [Mutexes com Valor Zero são Válidos]: #mutexes-com-valor-zero-são-válidos 39 | 40 | A incorporação **não deve**: 41 | 42 | - Ser puramente estética ou orientada à conveniência. 43 | - Dificultar a construção ou utilização dos tipos externos. 44 | - Afetar os valores zero dos tipos externos. Se o tipo externo tiver um valor zero útil, ele 45 | ainda deve ter um valor zero útil após a incorporação do tipo interno. 46 | - Expor funções ou campos não relacionados do tipo externo como um efeito colateral 47 | da incorporação do tipo interno. 48 | - Expor tipos não exportados. 49 | - Afetar a semântica de cópia dos tipos externos. 50 | - Alterar a API ou semântica do tipo externo. 51 | - Incorporar uma forma não canônica do tipo interno. 52 | - Expor detalhes de implementação do tipo externo. 53 | - Permitir que os usuários observem ou controlem detalhes internos do tipo. 54 | - Alterar o comportamento geral das funções internas através do encapsulamento de uma maneira que 55 | razoavelmente surpreenderia os usuários. 56 | 57 | Simplesmente coloque, incorpore consciente e intencionalmente. Um bom teste é, "todos esses métodos/campos internos exportados seriam adicionados diretamente ao tipo externo?"; 58 | se a resposta for "alguns" ou "não", não incorpore o tipo interno - use um campo 59 | em vez disso. 60 | 61 | 62 | 63 | 64 | 96 | 134 | 157 |
RuimBom
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 |
158 | -------------------------------------------------------------------------------- /src/10-time.md: -------------------------------------------------------------------------------- 1 | # Use `"time"` para lidar com o tempo 2 | 3 | O tempo é complicado. Suposições incorretas frequentemente feitas sobre o tempo incluem o seguinte. 4 | 5 | 1. Um dia tem 24 horas. 6 | 2. Uma hora tem 60 minutos. 7 | 3. Uma semana tem 7 dias. 8 | 4. Um ano tem 365 dias. 9 | 5. [E muitos outros](https://infiniteundo.com/post/25326999628/falsehoods-programmers-believe-about-time) 10 | 11 | Por exemplo, *1* significa que adicionar 24 horas a um instante de tempo nem sempre resultará em um novo dia no calendário. 12 | 13 | Portanto, sempre use o pacote [`"time"`] ao lidar com o tempo, pois ele ajuda a lidar com essas suposições incorretas de maneira mais segura e precisa. 14 | 15 | [`"time"`]: https://golang.org/pkg/time/ 16 | 17 | ## Use `time.Time` para instantes de tempo 18 | 19 | Use [`time.Time`] ao lidar com instantes de tempo e os métodos em `time.Time` ao comparar, adicionar ou subtrair tempo. 20 | 21 | [`time.Time`]: https://golang.org/pkg/time/#Time 22 | 23 | 24 | 25 | 26 | 43 |
RuimBom
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 |
44 | 45 | ## Use `time.Duration` para períodos de tempo 46 | 47 | Use [`time.Duration`] ao lidar com períodos de tempo. 48 | 49 | [`time.Duration`]: https://golang.org/pkg/time/#Duration 50 | 51 | 52 | 53 | 54 | 81 |
RuimBom
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 |
82 | 83 | Voltando ao exemplo de adicionar 24 horas a um instante de tempo, o método que 84 | usamos para adicionar tempo depende da intenção. Se quisermos a mesma hora do 85 | dia, mas no próximo dia do calendário, devemos usar [`Time.AddDate`]. No entanto, 86 | se quisermos um instante de tempo garantido para ser 24 horas após o tempo 87 | anterior, devemos usar [`Time.Add`]. 88 | 89 | [`Time.AddDate`]: https://golang.org/pkg/time/#Time.AddDate 90 | [`Time.Add`]: https://golang.org/pkg/time/#Time.Add 91 | 92 | ```go 93 | newDay := t.AddDate(0 /* anos */, 0 /* meses */, 1 /* dias */) 94 | maybeNewDay := t.Add(24 * time.Hour) 95 | ``` 96 | 97 | ## Use `time.Time` e `time.Duration` com sistemas externos 98 | 99 | Use `time.Duration` e `time.Time` em interações com sistemas externos sempre que 100 | possível. Por exemplo: 101 | 102 | - Flags da linha de comando: [`flag`] suporta `time.Duration` via 103 | [`time.ParseDuration`] 104 | - JSON: [`encoding/json`] suporta a codificação de `time.Time` como uma string [RFC 3339] 105 | através do seu método [`UnmarshalJSON`] 106 | - SQL: [`database/sql`] suporta a conversão de colunas `DATETIME` ou `TIMESTAMP` 107 | para `time.Time` e vice-versa se o driver subjacente o suportar 108 | - YAML: [`gopkg.in/yaml.v2`] suporta `time.Time` como uma string [RFC 3339], e 109 | `time.Duration` via [`time.ParseDuration`]. 110 | 111 | [`flag`]: https://golang.org/pkg/flag/ 112 | [`time.ParseDuration`]: https://golang.org/pkg/time/#ParseDuration 113 | [`encoding/json`]: https://golang.org/pkg/encoding/json/ 114 | [RFC 3339]: https://tools.ietf.org/html/rfc3339 115 | [`UnmarshalJSON`]: https://golang.org/pkg/time/#Time.UnmarshalJSON 116 | [`database/sql`]: https://golang.org/pkg/database/sql/ 117 | [`gopkg.in/yaml.v2`]: https://godoc.org/gopkg.in/yaml.v2 118 | 119 | Quando não for possível usar `time.Duration` nessas interações, use 120 | `int` ou `float64` e inclua a unidade no nome do campo. 121 | 122 | Por exemplo, como `encoding/json` não suporta `time.Duration`, a unidade 123 | é incluída no nome do campo. 124 | 125 | 126 | 127 | 128 | 147 |
RuimBom
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 |
148 | 149 | Quando não for possível usar `time.Time` nessas interações, a menos que uma 150 | alternativa seja acordada, use `string` e formate os carimbos de data e hora conforme definido em 151 | [RFC 3339]. Este formato é usado por padrão por [`Time.UnmarshalText`] e está 152 | disponível para uso em `Time.Format` e `time.Parse` através de [`time.RFC3339`]. 153 | 154 | [`Time.UnmarshalText`]: https://golang.org/pkg/time/#Time.UnmarshalText 155 | [`time.RFC3339`]: https://golang.org/pkg/time/#RFC3339 156 | 157 | Embora isso geralmente não seja um problema na prática, tenha em mente que o 158 | pacote `"time"` não suporta a análise de carimbos de data e hora com segundos bissextos 159 | ([8728]), nem considera segundos bissextos em cálculos ([15190]). Se 160 | você comparar dois instantes de tempo, a diferença não incluirá os segundos bissextos 161 | que possam ter ocorrido entre esses dois instantes. 162 | 163 | [8728]: https://github.com/golang/go/issues/8728 164 | [15190]: https://github.com/golang/go/issues/15190 165 | -------------------------------------------------------------------------------- /src/57-test-table.md: -------------------------------------------------------------------------------- 1 | # Tabelas de Teste 2 | 3 | Testes baseados em tabelas com [subtestes] podem ser um padrão útil para escrever testes 4 | e evitar a duplicação de código quando a lógica central do teste é repetitiva. 5 | 6 | Se um sistema em teste precisa ser testado contra _múltiplas condições_ em que 7 | partes específicas das entradas e saídas mudam, um teste baseado em tabela deve 8 | ser usado para reduzir a redundância e melhorar a legibilidade. 9 | 10 | [subtestes]: https://blog.golang.org/subtests 11 | 12 | 13 | 14 | 15 | 84 |
RuimBom
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 |
85 | 86 | Tabelas de teste facilitam a adição de contexto às mensagens de erro, reduzem a lógica duplicada 87 | e permitem a adição de novos casos de teste. 88 | 89 | Seguimos a convenção de que a fatia de structs é referida como `tests` 90 | e cada caso de teste como `tt`. Além disso, incentivamos a explicitação das entradas e saídas 91 | para cada caso de teste com os prefixos `give` e `want`. 92 | 93 | ```go 94 | tests := []struct{ 95 | give string 96 | wantHost string 97 | wantPort string 98 | }{ 99 | // ... 100 | } 101 | 102 | for _, tt := range tests { 103 | // ... 104 | } 105 | ``` 106 | 107 | ## Evite Complexidade Desnecessária em Testes de Tabela 108 | 109 | Testes de tabela podem ser difíceis de ler e manter se os subtestes contiverem afirmações condicionais 110 | ou outra lógica de ramificação. Testes de tabela **NÃO** devem ser usados sempre que 111 | houver lógica complexa ou condicional dentro dos subtestes (ou seja, lógica complexa dentro do loop `for`). 112 | 113 | Testes de tabela grandes e complexos prejudicam a legibilidade e a manutenção, pois os leitores dos testes podem 114 | ter dificuldade em depurar falhas que ocorrem. 115 | 116 | Testes de tabela como esse devem ser divididos em várias tabelas de teste ou várias 117 | funções individuais `Test...`. 118 | 119 | Alguns ideais a serem buscados são: 120 | 121 | - Focar na unidade mais estreita de comportamento. 122 | - Minimizar a "profundidade do teste" e evitar afirmações condicionais (veja abaixo). 123 | - Garantir que todos os campos da tabela sejam usados em todos os testes. 124 | - Garantir que toda a lógica de teste seja executada para todos os casos da tabela. 125 | 126 | Nesse contexto, "profundidade do teste" significa "dentro de um teste dado, o número de 127 | afirmações sucessivas que requerem que as afirmações anteriores sejam verdadeiras" (semelhante 128 | à complexidade ciclomática). 129 | Ter testes "mais rasos" significa que há menos relacionamentos entre 130 | afirmações e, mais importante, que essas afirmações são menos propensas 131 | a serem condicionais por padrão. 132 | 133 | Concretamente, os testes de tabela podem se tornar confusos e difíceis de ler se usarem várias ramificações 134 | (e.g., `shouldError`, `expectCall`, etc.), usarem muitos `if` para 135 | expectativas específicas de simulação (e.g., `shouldCallFoo`), ou colocarem funções dentro da 136 | tabela (e.g., `setupMocks func(*FooMock)`). 137 | 138 | No entanto, ao testar comportamentos que apenas 139 | mudam com base na entrada alterada, pode ser preferível agrupar casos semelhantes 140 | juntos em um teste de tabela para ilustrar melhor como o comportamento muda em todas as entradas, 141 | em vez de dividir unidades comparáveis em testes separados 142 | e torná-los mais difíceis de comparar e contrastar. 143 | 144 | Se o corpo do teste for curto e direto, 145 | é aceitável ter um único caminho de ramificação para casos de sucesso versus falha 146 | com um campo de tabela como `shouldErr` para especificar expectativas de erro. 147 | 148 | 149 | 150 | 151 | 230 |
RuimBom
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 |
231 | 232 | Essa complexidade torna mais difícil alterar, entender e comprovar a 233 | correção do teste. 234 | 235 | Embora não haja diretrizes rigorosas, a legibilidade e a manutenibilidade devem 236 | sempre ser as principais considerações ao decidir entre Testes de Tabela e testes separados 237 | para múltiplas entradas/saídas em um sistema. 238 | 239 | ## Testes Paralelos 240 | 241 | Testes paralelos, assim como alguns loops especializados (por exemplo, aqueles que iniciam 242 | goroutines ou capturam referências como parte do corpo do loop), 243 | devem tomar cuidado para atribuir explicitamente variáveis de loop dentro do escopo do loop para 244 | garantir que elas contenham os valores esperados. 245 | 246 | ```go 247 | tests := []struct{ 248 | give string 249 | // ... 250 | }{ 251 | // ... 252 | } 253 | 254 | for _, tt := range tests { 255 | tt := tt // for t.Parallel 256 | t.Run(tt.give, func(t *testing.T) { 257 | t.Parallel() 258 | // ... 259 | }) 260 | } 261 | ``` 262 | 263 | No exemplo acima, devemos declarar uma variável `tt` com escopo para a iteração do loop 264 | devido ao uso de `t.Parallel()` abaixo. 265 | Se não fizermos isso, a maioria ou todos os testes receberá um valor inesperado para 266 | `tt`, ou um valor que muda enquanto estão sendo executados. 267 | 268 | 269 | --------------------------------------------------------------------------------