Evitar linhas excessivamente longas
Evite usar linhas de código que exijam dos leitores rolagem horizontal ou girem excessivamente o documento.
Recomendamos limitar o comprimento da linha para 99 caracteres. Os autores devem quebrar a linha antes desse limite, mas não é uma regra rígida. É permitido que o código exceda esse limite.
Consistência
Algumas das normas delineadas neste documento baseiam-se em julgamentos subjetivos, cenários ou contextos. No entanto, o aspecto mais crucial é manter a consistência.
Um código consistente é mais fácil de manter, mais racional, exige menos custos de aprendizagem e é mais fácil de migrar, atualizar e corrigir erros quando novas convenções surgem ou erros ocorrem.
Por outro lado, incluir múltiplos estilos de código completamente diferentes ou conflitantes em uma base de código leva a um aumento nos custos de manutenção, incerteza e vieses cognitivos. Tudo isso resulta diretamente em velocidade mais lenta, revisões de código dolorosas e um número maior de bugs.
Ao aplicar essas normas a uma base de código, recomenda-se fazer alterações no nível do pacote (ou superior). Aplicar estilos múltiplos no nível de subpacote viola as preocupações acima.
Agrupar Declarações Semelhantes
A linguagem Go suporta o agrupamento de declarações semelhantes.
Não recomendado:
import "a"
import "b"
Recomendado:
import (
"a"
"b"
)
Isso também se aplica a declarações de constantes, variáveis e tipos:
Não recomendado:
const a = 1
const b = 2
var a = 1
var b = 2
type Area float64
type Volume float64
Recomendado:
const (
a = 1
b = 2
)
var (
a = 1
b = 2
)
type (
Area float64
Volume float64
)
Apenas agrupe declarações relacionadas juntas e evite agrupar declarações não relacionadas.
Não recomendado:
type Operation int
const (
Add Operation = iota + 1
Subtract
Multiply
EnvVar = "MY_ENV"
)
Recomendado:
type Operation int
const (
Add Operation = iota + 1
Subtract
Multiply
)
const EnvVar = "MY_ENV"
Não há restrições sobre onde usar o agrupamento. Por exemplo, você pode usá-los dentro de uma função:
Não recomendado:
func f() string {
red := color.New(0xff0000)
green := color.New(0x00ff00)
blue := color.New(0x0000ff)
...
}
Recomendado:
func f() string {
var (
red = color.New(0xff0000)
green = color.New(0x00ff00)
blue = color.New(0x0000ff)
)
...
}
Exceção: Se as declarações de variáveis estiverem adjacentes a outras variáveis, especialmente dentro de declarações locais de função, elas devem ser agrupadas juntas. Execute isso mesmo para variáveis não relacionadas declaradas juntas.
Não recomendado:
func (c *client) request() {
caller := c.name
format := "json"
timeout := 5*time.Second
var err error
// ...
}
Recomendado:
func (c *client) request() {
var (
caller = c.name
format = "json"
timeout = 5*time.Second
err error
)
// ...
}
Agrupamento de Importações
As importações devem ser agrupadas em duas categorias:
- Biblioteca padrão
- Outras bibliotecas
Por padrão, esse é o agrupamento aplicado pelo goimports.
Não recomendado:
import (
"fmt"
"os"
"go.uber.org/atomic"
"golang.org/x/sync/errgroup"
)
Recomendado:
import (
"fmt"
"os"
"go.uber.org/atomic"
"golang.org/x/sync/errgroup"
)
Nome do Pacote
Ao nomear um pacote, siga estas regras:
- Tudo em minúsculas, sem letras maiúsculas ou underscores.
- Na maioria dos casos, não é necessário renomear ao importar.
- Curto e conciso. Lembre-se de que o nome é totalmente qualificado em todos os lugares em que é usado.
- Evite plurais. Por exemplo, use
net/url
em vez denet/urls
. - Evite usar "comum", "util", "compartilhado" ou "lib". Isso não é informativo o suficiente.
Nomenclatura de Funções
Seguimos a convenção da comunidade Go de usar MixedCaps para nomes de funções. Uma exceção é feita para agrupar casos de teste relacionados, onde o nome da função pode conter sublinhados, como: TestMinhaFuncao_OQueEstaSendoTestado
.
Alias de Importação
Se o nome do pacote não corresponder ao último elemento do caminho de importação, um alias de importação deve ser usado.
import (
"net/http"
client "exemplo.com/client-go"
trace "exemplo.com/trace/v2"
)
Em todos os outros casos, os alias de importação devem ser evitados, a menos que haja um conflito direto entre as importações. Não Recomendado:
import (
"fmt"
"os"
nettrace "golang.net/x/trace"
)
Recomendado:
import (
"fmt"
"os"
"runtime/trace"
nettrace "golang.net/x/trace"
)
Agrupamento e Ordem de Funções
- As funções devem ser classificadas aproximadamente na ordem em que são chamadas.
- Funções no mesmo arquivo devem ser agrupadas por receptor.
Portanto, as funções exportadas devem aparecer primeiro no arquivo, colocadas após as definições de struct
, const
e var
.
Um newXYZ()
/NewXYZ()
pode aparecer após definições de tipo, mas antes dos métodos restantes do receptor.
À medida que as funções são agrupadas por receptor, as funções de utilidade geral devem aparecer no final do arquivo. Não Recomendado:
func (s *algo) Custo() {
return calcularCusto(s.pesos)
}
type algo struct{ ... }
func calcularCusto(n []int) int {...}
func (s *algo) Parar() {...}
func novoAlgo() *algo {
return &algo{}
}
Recomendado:
type algo struct{ ... }
func novoAlgo() *algo {
return &algo{}
}
func (s *algo) Custo() {
return calcularCusto(s.pesos)
}
func (s *algo) Parar() {...}
func calcularCusto(n []int) int {...}
Reduzir Aninhamento
O código deve reduzir o aninhamento manipulando erros/casos especiais o mais cedo possível e retornando ou continuando o loop. Reduzir o aninhamento reduz a quantidade de código em vários níveis.
Não Recomendado:
for _, v := range dados {
if v.F1 == 1 {
v = processar(v)
if err := v.Chamar(); err == nil {
v.Enviar()
} else {
return err
}
} else {
log.Printf("v inválido: %v", v)
}
}
Recomendado:
for _, v := range dados {
if v.F1 != 1 {
log.Printf("v inválido: %v", v)
continue
}
v = processar(v)
if err := v.Chamar(); err != nil {
return err
}
v.Enviar()
}
Else Desnecessário
Se uma variável está sendo definida em ambas as ramificações de um if, pode ser substituída por uma única declaração if.
Não Recomendado:
var a int
if b {
a = 100
} else {
a = 10
}
Recomendado:
a := 10
if b {
a = 100
}
Declaração de Variável de Nível Superior
No nível superior, use a palavra-chave var
padrão. Não especifique o tipo, a menos que difira do tipo da expressão.
Não Recomendado:
var _s string = F()
func F() string { return "A" }
Recomendado:
var _s = F()
// Como F retorna explicitamente um tipo string, não precisamos especificar explicitamente o tipo para _s
func F() string { return "A" }
Especifique o tipo se ele não corresponder exatamente ao tipo necessário para a expressão.
type meuErro struct{}
func (meuErro) Error() string { return "erro" }
func F() meuErro { return meuErro{} }
var _e error = F()
// F retorna uma instância do tipo meuErro, mas precisamos do tipo error
Use '_' as a Prefix for Unexported Top-level Constants and Variables
Para vars
e consts
de topo de nível não exportados, adicione o prefixo de sublinhado _
para indicar explicitamente sua natureza global quando usados.
Razão básica: Variáveis e constantes de topo de nível têm escopo de pacote. O uso de nomes genéricos pode facilmente levar ao uso acidental do valor errado em outros arquivos.
Não recomendado:
// foo.go
const (
defaultPort = 8080
defaultUser = "user"
)
// bar.go
func Bar() {
defaultPort := 9090
...
fmt.Println("Porta padrão", defaultPort)
// Não veremos um erro de compilação se a primeira linha de
// Bar() for deletada.
}
Recomendado:
// foo.go
const (
_defaultPort = 8080
_defaultUser = "user"
)
Exceção: Valores de erro não exportados podem usar o prefixo err
sem sublinhado. Consulte a nomenclatura de erros.
Incorporação em Estruturas
Tipos incorporados (como mutex) devem ser colocados no topo da lista de campos dentro da estrutura e devem ter uma linha vazia separando os campos incorporados dos campos regulares.
Não recomendado:
type Client struct {
version int
http.Client
}
Recomendado:
type Client struct {
http.Client
version int
}
A incorporação deve fornecer benefícios tangíveis, como adição ou aprimoramento de funcionalidade de uma maneira semanticamente apropriada. Deve ser usada sem qualquer impacto adverso no usuário. (Veja também: Evite incorporar tipos em estruturas públicas)
Exceções: Mesmo em tipos não exportados, Mutex não deve ser usado como campo incorporado. Consulte também: O valor zero do Mutex é válido.
Incorporação não deve:
- Existir exclusivamente por motivos estéticos ou de conveniência.
- Dificultar a construção ou uso do tipo externo.
- Afetar o valor zero do tipo externo. Se o tipo externo tiver um valor zero útil, ainda deve haver um valor zero útil após a incorporação do tipo interno.
- Ter o efeito colateral de expor funções ou campos não relacionados do tipo interno incorporado.
- Expor tipos não exportados.
- Afetar o formato de clonagem do tipo externo.
- Alterar a API ou semântica do tipo externo.
- Incorporar o tipo interno em um formato não padrão.
- Expor detalhes de implementação do tipo externo.
- Permitir que os usuários observem ou controlem o tipo interno.
- Alterar o comportamento geral de funções internas de uma forma que possa surpreender os usuários.
Em resumo, incorpore consciente e propositadamente. Um bom teste é: "Todos esses métodos/campos exportados do tipo interno serão diretamente adicionados ao tipo externo?" Se a resposta for alguns
ou não
, não incorpore o tipo interno - use campos em vez disso.
Não recomendado:
type A struct {
// Ruim: A.Lock() e A.Unlock() agora estão disponíveis
// Não fornece benefício funcional e permite que o usuário controle detalhes internos de A.
sync.Mutex
}
Recomendado:
type countingWriteCloser struct {
// Bom: Write() é fornecido no nível externo para um propósito específico
// e delega o trabalho para o Write() do tipo interno.
io.WriteCloser
count int
}
func (w *countingWriteCloser) Write(bs []byte) (int, error) {
w.count += len(bs)
return w.WriteCloser.Write(bs)
}
Declarações de Variáveis Locais
Se uma variável for explicitamente definida com um valor, a forma de declaração de variável curta (:=
) deve ser usada.
Não recomendado:
var s = "foo"
Recomendado:
s := "foo"
No entanto, em alguns casos, o uso da palavra-chave var
para valores padrão pode ser mais claro.
Não recomendado:
func f(list []int) {
filtered := []int{}
for _, v := range list {
if v > 10 {
filtered = append(filtered, v)
}
}
}
Recomendado:
func f(list []int) {
var filtered []int
for _, v := range list {
if v > 10 {
filtered = append(filtered, v)
}
}
}
nil é uma fatia válida
nil
é uma fatia válida com um comprimento de 0, o que significa:
- Você não deve retornar explicitamente uma fatia com um comprimento de zero. Em vez disso, retorne
nil
.
Não recomendado:
if x == "" {
return []int{}
}
Recomendado:
if x == "" {
return nil
}
- Para verificar se uma fatia está vazia, sempre use
len(s) == 0
em vez denil
.
Não recomendado:
func isEmpty(s []string) bool {
return s == nil
}
Recomendado:
func isEmpty(s []string) bool {
return len(s) == 0
}
- As fatias de valor zero (fatias declaradas com
var
) podem ser usadas imediatamente sem chamarmake()
.
Não recomendado:
nums := []int{}
// ou, nums := make([]int)
if add1 {
nums = append(nums, 1)
}
if add2 {
nums = append(nums, 2)
}
Recomendado:
var nums []int
if add1 {
nums = append(nums, 1)
}
if add2 {
nums = append(nums, 2)
}
Lembre-se de que, embora uma fatia nil seja uma fatia válida, ela não é igual a uma fatia com um comprimento de 0 (uma é nil e a outra não é), e elas podem ser tratadas de maneira diferente em diferentes situações (por exemplo, serialização).
Escopo estreito de variáveis
Se possível, tente estreitar o escopo das variáveis, a menos que entre em conflito com a regra de redução de aninhamento.
Não recomendado:
err := os.WriteFile(name, data, 0644)
if err != nil {
return err
}
Recomendado:
if err := os.WriteFile(name, data, 0644); err != nil {
return err
}
Se o resultado de uma chamada de função fora da instrução if precisar ser usado, não tente estreitar o escopo.
Não recomendado:
if data, err := os.ReadFile(name); err == nil {
err = cfg.Decode(data)
if err != nil {
return err
}
fmt.Println(cfg)
return nil
} else {
return err
}
Recomendado:
data, err := os.ReadFile(name)
if err != nil {
return err
}
if err := cfg.Decode(data); err != nil {
return err
}
fmt.Println(cfg)
return nil
Evitar parâmetros sem nome
Parâmetros pouco claros em chamadas de função podem prejudicar a legibilidade. Quando o significado dos nomes dos parâmetros não é óbvio, adicione comentários no estilo C (/* ... */
) aos parâmetros.
Não recomendado:
// func printInfo(name string, isLocal, done bool)
printInfo("foo", true, true)
Recomendado:
// func printInfo(name string, isLocal, done bool)
printInfo("foo", true /* isLocal */, true /* done */)
Para o exemplo acima, uma abordagem melhor poderia ser substituir os tipos bool
por tipos personalizados. Dessa forma, o parâmetro pode potencialmente suportar mais do que apenas dois estados (verdadeiro/falso) no futuro.
type Regiao int
const (
RegiaoDesconhecida Regiao = iota
Local
)
type Status int
const (
StatusPronto Status = iota + 1
StatusFeito
// Talvez tenhamos um StatusEmProgresso no futuro.
)
func printInfo(name string, regiao Regiao, status Status)
Usar literais de string brutas para evitar escapar
Go suporta o uso de literais de string brutas, que são indicados por " ` " para representar strings brutas. Em cenários em que é necessária a escape, devemos usar essa abordagem para substituir as strings escapadas manualmente, que são mais difíceis de ler.
Elas podem abranger várias linhas e incluir aspas. O uso dessas strings pode evitar as strings escapadas manualmente, que são mais difíceis de ler.
Não recomendado:
wantError := "unknown name:\"test\""
Recomendado:
wantError := `unknown error:"test"`
Inicializar structs
Inicializar estruturas usando nomes de campo
Ao inicializar uma estrutura, os nomes dos campos devem quase sempre ser especificados. Atualmente, isso é aplicado por go vet
.
Não recomendado:
k := User{"John", "Doe", true}
Recomendado:
k := User{
FirstName: "John",
LastName: "Doe",
Admin: true,
}
Exceção: Quando há 3 ou menos campos, os nomes dos campos em tabelas de teste podem ser omitidos.
tests := []struct{
op Operation
want string
}{
{Add, "add"},
{Subtract, "subtract"},
}
Omitir campos de valor zero em estruturas
Ao inicializar uma estrutura com campos nomeados, a menos que um contexto significativo seja fornecido, ignore os campos com valor zero. Ou seja, vamos configurar automaticamente esses campos com valores zero.
Não recomendado:
user := User{
FirstName: "John",
LastName: "Doe",
MiddleName: "",
Admin: false,
}
Recomendado:
user := User{
FirstName: "John",
LastName: "Doe",
}
Isso ajuda a reduzir as barreiras de leitura, omitindo os valores padrão no contexto. Especifique apenas valores significativos.
Inclua o valor zero onde os nomes dos campos fornecem um contexto significativo. Por exemplo, casos de teste em um teste baseado em tabela podem se beneficiar de nomear os campos, mesmo que sejam valores zero.
tests := []struct{
give string
want int
}{
{give: "0", want: 0},
// ...
}
Use var
para estruturas de valor zero
Se todos os campos de uma estrutura forem omitidos na declaração, use var
para declarar a estrutura.
Não recomendado:
user := User{}
Recomendado:
var user User
Isso distingue estruturas de valor zero daquelas com campos de valor não zero, semelhante ao que preferimos ao declarar uma slice vazia.
Inicializar referências de estrutura
Ao inicializar referências de estrutura, use &T{}
em vez de new(T)
para torná-lo consistente com a inicialização da estrutura.
Não recomendado:
sval := T{Name: "foo"}
// inconsistente
sptr := new(T)
sptr.Name = "bar"
Recomendado:
sval := T{Name: "foo"}
sptr := &T{Name: "bar"}
Inicializar mapas
Para um mapa vazio, use make(..)
para inicializá-lo, e o mapa é preenchido de forma programática. Isso torna a inicialização do mapa diferente da declaração em aparência e também permite adicionar dicas de tamanho após make
.
Não recomendado:
var (
// m1 é seguro para leitura e escrita;
// m2 entra em pânico ao escrever
m1 = map[T1]T2{}
m2 map[T1]T2
)
Recomendado:
var (
// m1 é seguro para leitura e escrita;
// m2 entra em pânico ao escrever
m1 = make(map[T1]T2)
m2 map[T1]T2
)
A declaração e inicialização parecem muito semelhantes. | A declaração e inicialização parecem muito diferentes.
Onde possível, forneça o tamanho da capacidade do mapa durante a inicialização, veja Especificação de Capacidade de Mapa para detalhes.
Além disso, se o mapa contiver uma lista fixa de elementos, use literais de mapa para inicializar o mapa.
Não recomendado:
m := make(map[T1]T2, 3)
m[k1] = v1
m[k2] = v2
m[k3] = v3
Recomendado:
m := map[T1]T2{
k1: v1,
k2: v2,
k3: v3,
}
A diretriz básica é usar literais de mapa para adicionar um conjunto fixo de elementos durante a inicialização. Caso contrário, use make
(e, se possível, especifique a capacidade do mapa).
Formato de String para Funções do Tipo Printf
Se você declarar a string de formato de uma função do tipo Printf
fora de uma função, defina-a como uma constante const
.
Isso ajuda o go vet
a realizar análise estática na string de formato.
Não recomendado:
msg := "valores inesperados %v, %v\n"
fmt.Printf(msg, 1, 2)
Recomendado:
const msg = "valores inesperados %v, %v\n"
fmt.Printf(msg, 1, 2)
Nomeando Funções no Estilo Printf
Ao declarar funções no estilo Printf
, garanta que o go vet
possa detectar e verificar a string de formato.
Isso significa que você deve usar nomes de função no estilo Printf
predefinidos sempre que possível. O go vet
verificará isso por padrão. Para mais informações, consulte a Família Printf.
Se os nomes predefinidos não puderem ser usados, termine o nome selecionado com f
: Wrapf
em vez de Wrap
. O go vet
pode solicitar que nomes específicos no estilo Printf sejam verificados, mas o nome deve terminar com f
.
go vet -printfuncs=wrapf,statusf