Esse guia tem como objetivo manter um estilo de codificação consistente dentro de uma organização com foco em melhorar a legibilidade, a manutenção e a colaboração do código.
- Utilize
gofmt
egoimports
antes de fazer um commit, viamake lint
- O código dever ser indentado com tabs. Configure .editorconfig para aplicar suas preferências no seu editor
- Utilize
CamelCase
para variables, types, functions e constants - Utilize line length de 130 caracteres
- Utilize a lib
logger
da aplicação ao invés dalog
stdlib - Não deixe
fmt.Print*
esquecido no código - Os imports podem ser separados em até 3 partes, com uma linha de espaçamento. Na primeira parte, imports das stdlibs. Na segunda, imports de libs de terceiros. Na terceira, imports de outros packages dentro do sistema
// Good:
import (
"strings"
"fmt"
"github.com/google/uuid"
"myapp/src/shared/configurator"
"myapp/src/domain/models"
)
// Bad:
import (
"fmt"
"github.com/google/uuid"
"myapp/src/shared/configurator"
"myapp/src/domain/models"
"strings"
)
Não basta escrever um código bom. Ele precisa ser mantido limpo.
Use nomes que revelem seu propósito.
Exemplo de variável que guarda um determinado dia:
var diaPagamento int // Good
var d int // Bad
Não utilize números para distinguir funções ou variáveis:
// Good
found := FindCompanyById("1")
saved := Create(company)
// Bad
company1 := FindCompanyById("1")
company2 := Create(company)
Se não for fácil de falar, vai ser difícil de entender:
var dataPagamento time.Time // Good
var dtPagto time.Time // Bad
Não enfeite interfaces. No caso abaixo, o I é apenas uma distração. Use o nome da struct que vai implementar a interface, com o mesmo nome dela, mas a letra inicial em minúscula, pois assim se mantém privada.
// Good
type UserRepository interface {}
type userRepository struct {} // implementation
// Bad
type IUserRepository interface {}
type UserRepository struct {} // implementation
Variáveis de uma letra como i, j, k, c são tradicionalmente utilizadas dentro de laços e por isso são traduzidas mentalmente por programadores como variáveis auxiliares. Não há problema em utilizá-las.
for i := 0; i < len(items); i++ { ... }
Classes e objetos devem ter nomes com substantivo, como Cliente, TelaCadastro, Endereco.
Métodos devem ser verbos como salvar, excluir, deletar.
// Good
type BoletoService struct {}
func (b BoletoService) Imprimir() {}
// Bad
type GerarBoletoService struct {}
func (b BoletoService) Imprime() {}
Não use palavras diferentes para o mesmo conceito. Se na struct UserRepository
foi criado o método ReadOne
, entao na struct Address
faz mais sentido o método ReadOne
do que GetBy
.
// Good
user := userRepository.ReadOneByID("1")
address := addressRepository.ReadOneByUser(user)
// Bad
user := userRepository.ReadOneByID("1")
address := addressRepository.GetByUser(user)
Atributos, sufixos ou prefixos abaixo, devem ser sempre escrito em uppercase.
// Good
const URL = ""
typ Some struct {
ID string
CNPJ string
}
service.FindByID(...)
// Bad
const Url = ""
typ Some struct {
Id string
Cnpj string
}
service.FindById(...)
Utilzie os termos comuns mesmo que não sejam exatamente os termos corretos. Principalmente se é um termo comum ao seu idioma e não possui uma tradução exata ou sua tradução não é utilizada com frequência pelos usuários e especialistas de negócios.
Por exemplo, um método de pagamento conhecido como "carnê" que, na verdade seria boleto.
// Good
const PaymentTypeCarne = 1
// Bad
const PaymentTypeBill = 1
A quantidade ideal de parâmetros é zero. Até dois é aceitável, salvo raros casos até 3. Mas acima disso é extremamente desaconselhável.
Se necessário enviar muitas informações para um método, passe através de objetos.
// Good
type File struct {
Name string
Path string
Content []byte
}
func saveXml(file File) {}
// Bad
func saveXml(name, path string, content []byte])
O nome do método e seu argumento devem formar um belo par verbo/substantivo. O ideal é que a chamada ao método explique a intenção do que é esperado.
// Awesome
func (r userService) Create(user User) {}
userService.Create(user)
// Good
func (r userService) CreateUser(user User) {}
userService.CreateUser(user)
// Bad
func (r userService) InsertUser(user User) {}
userService.InsertUser(user) // Insert não é um termo apropriado para um service pois é um termo de banco de dados
Aplicações que são consumidas por outras precisam fornecer um bom entendimento sobre erros ocorridos para seus consumidores.
Devemos encapsular os erros na origem, através de um CustomError, inserindo um código que pode ser utilizado por serviços REST ou gRPC.
type User struct {
ID string `db:"id"`
Name string `db:"name"`
Email string `db:"email"`
}
func (r userRepository) ReadOne(user User) (User, error) {
query := "select id, name, email from users where id=$1"
var found User
err := r.store.Exec(query, user.ID).Get(&found)
if err != nil {
return user, customerror.NotFound(err, "usuario nao encontrado")
}
return found, nil
}
Evite usar for dentro de for e if dentro de if.
Evite usar ponteiros a menos que realmente sejam necessários.
A mutabilidade de ponteiros e o fato deles poderem ter valor nil, podem ocasionar erros inesperados em tempo de execução.
Não comente um código ruim. Reescreva-o.
Um dos maiores motivos para escrita de comentários é o código ruim. Comentários escritos para tentar explicar um código confuso e desorganizado devem ser evitados. Refatore o código e deixe-o mais expressivo. Qual código é mais fácil de entender? Assim:
// Verifica se o funcionario está apto a receber todos os benefícios.
if funcionario.motivoDemissao != 1 && funcionario.idade > 65
Ou assim?
if funcionario.estaAptoReceberTodosBeneficios()
Algumas vezes comentários são necessários ou benéficos. Por exemplo, se você está escrevendo uma API ou um JAR utilitário que vão ser utilizados por outros, os comentários podem complementar a documentação. Javadocs nesses casos são bem-vindos, mas não exagere.
Algumas vezes somos forçados a escrever comentários por razões legais. Em projetos de código aberto (Open Source) por exemplo, dependendo da licença escolhida, é necessário inserir em cada arquivo de código fonte um resumo da licença no topo do arquivo.
Comentários que informam o nome do autor só fazem sentido se você é o único que pode alterar o código. Se trabalha em time então outro programador pode alterar seu código. Se o código foi escrito para uma empresa ou cliente, então não é só seu. Se deseja rastreabilidade, saber quem criou ou alterou, então os logs do controlador de versão (svn/git) podem fornecer essas informações.
Não faça nada do tipo.
// Retorna o nome
func GetNome() string {}
O idioma utilizado depende do contexto do negócio. Existem tipos de negócios globais e outros regionais.
No cenário de uma empresa de contabilidade brasileira, por exemplo, muitos dos termos utilizados só fazem sentido no Brasil, ou seja, são regionais.
Devemos utilizar os mesmos termos utilizados pelos especialistas em negócio ou inglês quando o código não depende dos termos corporativos.
// Good
type Empresa struct {
Nome string
CNPJ string
}
/* Create é um verbo genérico para cadastro em algum sistema, então fica em inglês */
empresaService.Create(empresa)
/* Calcular PIS é o termo usado por negócios */
impostoService.CalcularPis(notas)
/* S3 não é um termo de negócio e não faz distinção do tipo de arquivo para o upload */
s3Store.Upload(file)
// Bad
type Company struct {
Name string
CNPJ string
}
companyService.Create(company)
taxService.CalculatePis(invoices)
s3Store.Enviar(arquivo)
Logs devem ser estruturados utilizando para contar uma história, usando a aplicação como sujeito. O tempo verbal deve ficar no presente, antes de determinada ação, e no passado, após uma ação executada. Devemos enriquece-lo, sempre que possível, com informações extras, usando chave/valor. Os levels utilizados são:
- ERROR: para erros críticos que podem atrapalhar a utilização da aplicação.
- INFO: para descrever a sequência de uma execução para facilitar o entendimento do fluxo.
- DEBUG: para ajudar na resolução dos problemas. Pode conter o valores de structs, variáveis e respostas de APIs.
- Devem ser curtos e objetivos
- Utilizar chave/valor para facilitar o filtro de busca
- Em minúsculo
- Conter apenas caracteres ASCII, nada de acentuação ou emojis
- Level ERROR deve conter os dados de input antes do erro acontecer
- Level ERROR deve conter a chave "error" contendo a mensagem principal de erro
- Devem exibir o correlationID, se houver
- Devem exibir o ID de usuário que iniciou o fluxo, se houver
- Formato JSON ou plain text
// Good
log.Info("vou calcular a apuracao do pis", "cnpj", cnpj, "competencia", "competencia")
log.Info("pis calculado com valor="+valor, "cnpj", cnpj, "competencia", competencia)
log.Info("vou gerar darf no valor="+valor, "cnpj", cnpj, "competencia", competencia)
log.Error("falhei ao gerar darf no valor="+valor, "cnpj", cnpj, "competencia", competencia, "error", err.Error())
log.Debug("response do integra contador", "cnpj", cnpj, "competencia", competencia, "response", toJSON(response))
// Bad
log.Info(fmt.Sprintf("Vou calcular apuracao do imposto PIS para cnpj=%s e competencia=%s", cnpj, competencia))
log.Info("Sucesso. imposto calculado")
log.Info("vou gerar darf no integra contador...")
log.Error("erro", err)
log.Debug(toJSON(response))
O tratamento de erros depende do contexto mas em geral erros devem ser tratados sempre que possível. Em alguns casos, podem ser ignorados, por exemplo, ao tentar converter uma string para número, retornar zero em caso de erro.
Utilizamos um CustomError que deve ser chamado na origem do erro e ele contém a lógica para gerar os códigos de acordo com o tipo de situação.
import "myproject/src/domain/types/customerror"
func (r SomeRepository) ReadOneByID(id string) (User, error) {
found, err := execSQL(...)
if err != nil {
return found, customerror.NotFound(err, id+" nao encontrado")
}
return found, nil
}