var client *http.Client // esse valor sempre será nil, pois o client atribuido nas
// condições abaixos não será o mesmo
if tracing {
client, err := createClientWithTracing()
if err != nil {
return err
}
log.Println(client)
} else {
client, err := createDefaultClient()
if err != nil {
return err
}
log.Println(client)
}
Alternativa para evitar shadowing
var client *http.Client
var err error
if tracing {
client, err = createClientWithTracing()
} else {
client, err = createDefaultClient()
}
if err != nil {
// Common error handling
}
Código com uma leitura facil demandam um esforço menor para serem mantidos funcionando e respeitando uma arquitetura prédeterminada. Um aspecto importante na dificuldade para a leitura de um código é o nível de aninhamento mantido nele.
func join(s1, s2 string, max int) (string, error) {
if s1 == "" {
return "", errors.New("s1 is empty")
} else {
if s2 == "" {
return "", errors.New("s2 is empty")
} else {
concat, err := concatenate(s1, s2)
if err != nil {
return "", err
} else {
if len(concat) > max {
return concat[:max], nil
} else {
return concat, nil
}
}
}
}
}
func join(s1, s2 string, max int) (string, error) {
if s1 == "" {
return "", errors.New("s1 is empty")
}
if s2 == "" {
return "", errors.New("s2 is empty")
}
concat, err := concatenate(s1, s2)
if err != nil {
return "", err
}
if len(concat) > max {
return concat[:max], nil
}
return concat, nil
}
Se um bloco if
retornar devemos evitar o uso do else
Uma função init
é utilizada para inicializar o estado de uma aplicação. Ela não contém nenhum argumento e não
retorna resultados. A inicialização de um pacote segue os seguintes passos:
init
é executadaAlgumas situações que devemos considerar para o uso de interfaces:
Na maioria dos casos deixe o cliente criar sua definição de interface baseada na implementação do produtor. Dessa forma o cliente pode definir a interface mais apropiada para o seu caso de uso.
Retornar interfaces obrigam aos clientes dependerem da mesma abstração, se um cliente precisar de uma abstação ele ainda pode realizar do seu lado. Retornar uma interface é tentar prever o futuro na esperança de que a abstração será útil para todos os clientes. Abstrações não devem ser forçadas na maioria dos casos mas descobertas pelos clientes.
Em golang uma propiedade de uma struct
é considerado incorporado se é declarado sem um nome:
type Foo struct {
Bar
}
Dessa forma métodos de Bar ficam disponiveis em Foo
Incoporar uma struct é sobre composição e não herança
Lidar com a evolução de uma api pode ser algo frustante, mudanças na api podem obrigar a mudança de acesso em todos os clientes do seu código para evitar isso podemos utilizar o padrão funcional de opções.
type options struct { // struct que contém todas as opções de configuração
port *int
}
type Option func(options *options) error // tipagem para funções de configuração
func WithPort(port int) Option { // a função de configuração que atualiza a struct de configuração
return func(options *options) error {
that updates the port
if port < 0 {
return errors.New("port should be positive")
}
options.port = &port
return nil
}
}
func NewServer(addr string, opts ...Option) { // argumento variadic que aceita uma lista de funções para configuração
var options options
for _, opt := range opts {
err := opt(&options)
if err != nil {
return nil, err
}
}
}
é uma má pratica a criação de um pacote util, common, e base
redis := redis.NewClient()
v, err := redis.Get("foo")
Todo elemento exportado deve ser documentado, a convenção é adicionar comentarios iniciando com o nome do elemento exportado.
// Customer is a customer representation.
type Customer struct{}
// Package math provides basic constants and mathematical functions.
//
// This package does not guarantee bit-identical results
// across architectures.
package math