Nem sempre é nulo
"O quê? O que está escrito aqui?" você pergunta. Agora vou colocar tudo para fora.
Quando comecei a aprender o idioma, não achava que chegaria a esse caso restrito. Também não é racional modificar uma coleção iterável.
Por exemplo:
func Foo() error { var err *os.PathError = nil return err } func main() { err := Foo() fmt.Println(err) // <nil> fmt.Println(err == nil) // false }
WAT!
O que é uma interface?
Acesse o arquivo do pacote go runtime / runtime2.go e consulte:
type itab struct { // 40 bytes on a 64bit arch inter *interfacetype _type *_type ... }
Uma interface armazena o tipo de interface e o tipo de valor em si.
O valor de qualquer interface, não apenas o erro, é nulo no caso em que os valores e o tipo AND são nulos.
A função Foo retorna nil do tipo * os.PathError, comparamos o resultado com nil do tipo nil, a partir do qual sua desigualdade se segue.
Talvez muitos soubessem disso, mas poucas pessoas pensam em como entrar na prática.
Meu exemplo
type Response struct { Result ResponseResult `json:"result,omitempty"` Error *ResponseError `json:"error,omitempty"` } type ResponseError struct { Message string `json:"message"` } func (e *ResponseError) Error() string { return e.Message } ... func (s *NotificationService) NotifyIfError(w *ResponseWriter) error { ... var res handlers.Response _ = json.Unmarshal(body, &res) if res.Error == nil { return } return s.NotifyError(err) }
A resposta sempre tem um resultado ou erro.
Se houver um erro, nós o enviamos quando necessário através do serviço de notificação.
Dentro do serviço, o método Error é chamado e, como nosso valor é nulo, entramos em pânico.
O que fazer
Retorne uma interface estritamente de um tipo de interface.
Em caso de erro - tipo de erro.
- Adicionar uma declaração de erro de tipo
func (s *NotificationService) NotifyIfError(w *ResponseWriter) error { ... var res Response _ = json.Unmarshal(body, &res) var err error = res.Error return s.NotifyError(err) }
Essa técnica, para minha surpresa, também não funciona.
Acontece que, ao atribuir um valor à variável err, também passamos a ela as informações iniciais sobre o tipo, que não são nulas.
- Vamos tentar obter nosso tipo de fonte do tipo de interface e verificar seu valor.
func (s *NotificationService) NotifyIfError(w *ResponseWriter) error { ... if e, ok := err.(*ResponseError); ok && e == nil { return s.NotifyError(err) } return nil }
Sim, esta técnica funciona.
Mas, para ser sincero, não podemos dar ao luxo de verificar todos os tipos de erros que transmitiremos.
Podem ser todos os erros do driver do banco de dados, todos os nossos erros internos e outros tipos de lixo.
Qual é a opção mais racional que vejo:
func (s *NotificationService) NotifyIfError(w *ResponseWriter) error { var err error ... var res Response _ = json.Unmarshal(body, &res) if res.Error != nil { return s.NotifyError(err) } return nil }
Primeiro, declaramos uma variável de erro de tipo, como resulta do valor e do tipo nil.
E antes de passar nosso tipo e valor para essa variável, vamos verificar nosso tipo e seu valor em zero.
Isso nos permitirá não cair em pânico.
No final
Você pode ir ainda mais longe e implementar um erro “opcional” com o tipo Response, OptionalError ou ErrorOrNil, assim:
func (r *Response) ErrorOrNil() error { if r.Error == nil { return nil } return r.Error }
Nota
Nas notas do wiki do Go, a revisão de código é uma observação no tópico sobre a interface: em vez disso, retorne um tipo concreto e deixe o consumidor zombar da implementação do produtor.
Noto que as danças acima não são sobre isso.
Minhas anotações permitem que você não entre em pânico quando souber que deseja devolver juros e, em caso de erros, sempre desejará devolver a interface.
Mas se você puder retornar um determinado tipo, devolva-o.
Referências
go-internals
Eu sou
LinkedIn
Telegram
Twitter
Github