Skip to content

Instantly share code, notes, and snippets.

@gustavohenrique
Last active April 29, 2024 12:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gustavohenrique/0be5e07422471877d7333c724e65d5dc to your computer and use it in GitHub Desktop.
Save gustavohenrique/0be5e07422471877d7333c724e65d5dc to your computer and use it in GitHub Desktop.

Golang Styleguide

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.

Formatação do código

  • Utilize gofmt e goimports antes de fazer um commit, via make 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 da log 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"
)

Convenções e nomeclaturas

Não basta escrever um código bom. Ele precisa ser mantido limpo.

Nomes significativos

Use nomes que revelem seu propósito.
Exemplo de variável que guarda um determinado dia:

var diaPagamento int // Good
var d int            // Bad

Faça distinções significativas

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)

Use nomes pronunciáveis e sem abreviações

Se não for fácil de falar, vai ser difícil de entender:

var dataPagamento time.Time  // Good
var dtPagto time.Time        // Bad

Use structs privadas para implementar interfaces

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

Aproveite o mapeamento mental

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++ { ... }

Nomes de classes e métodos

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() {}

Padronize nomes de métodos

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)

Sempre maiúsculas

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(...)

Use termos utilizados pelos usuários e especialistas de negócios

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

Métodos e funções

Parâmetros e argumentos

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])

Verbos e palavras chaves

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

Encapsulamento de erros

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
}

Cascata de for/if

Evite usar for dentro de for e if dentro de if.

Ponteiros

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.

Comentários

Maus comentários

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()

Bons comentários

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.

Comentários Legais

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 byAuthor

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.

Comentários óbvios

Não faça nada do tipo.

// Retorna o nome
func GetNome() string {}

Idioma

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

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.

Formatação

  • 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

Exemplos

// 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))

Tratamento de Erros

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
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment