Técnicas de tratamento de erros Go [fechado]


108

Estou apenas começando com Go. Meu código está começando a ter muito disso:

   if err != nil {
      //handle err
   }

ou isto

  if err := rows.Scan(&some_column); err != nil {
      //handle err
  }

Existem boas expressões idiomáticas / estratégias / melhores práticas para verificar e lidar com erros no Go?

EDIT para esclarecer: não estou reclamando ou sugerindo que a equipe Go venha com algo melhor. Estou perguntando se estou fazendo certo ou se perdi alguma técnica que a comunidade criou. Obrigado a todos.


4
Não, não há realmente. Esse é um tópico frequentemente discutido e sensato. Houve muitas propostas de evolução também. A resposta da equipe parece ser que isso não deve ser um problema em um código bem escrito.
Denys Séguret


Observe que esta questão relacionada não é realmente a mesma. As respostas são muito específicas.
Denys Séguret

Também há uma razão para esse aborrecimento: torna mais difícil escrever um programa rapidamente, mas também torna mais difícil criar bugs simplesmente relançando os erros.
Denys Séguret

Você pode encontrar Andrew Gerrand e Brad Fitzpatrick escrevendo o início de um cliente HTTP / 2 em Go de maneira mais ou menos semelhante youtube.com/watch?v=yG-UaBJXZ80
Supreet Sethi

Respostas:


61

Seu código é idiomático e, em minha opinião, é a melhor prática disponível. Alguns discordariam com certeza, mas eu diria que esse é o estilo visto em todas as bibliotecas padrão em Golang . Em outras palavras, os autores de Go escrevem o tratamento de erros dessa maneira.


12
"Os autores do Go escrevem o tratamento de erros dessa maneira." Parece bom para mim.
gmoore

"Alguns discordariam com certeza" : não tenho certeza se alguém diria que não é a melhor prática disponível hoje. Alguns pedem sintaxe sugar ou outras mudanças, mas hoje eu não acho que nenhum programador sério iria verificar os erros de outra forma.
Denys Séguret

@dystroy: OK, alguns dizem " é sux ", outros chamam de "erros são tratados em valores de retorno. Estilo dos anos 70". e assim por diante ;-)
zzzz

2
@jnml Manipular erros dessa forma é uma questão de design de linguagem, que é um tópico altamente controverso. Felizmente, existem dezenas de idiomas para escolher.
fuz

4
O que me mata é como o mesmo padrão é usado para cada chamada de função. Isso torna o código um tanto barulhento em alguns lugares e é apenas gritar por açúcar sintático para simplificar o código sem perder nenhuma informação, que é essencialmente a definição de concisão (que acredito ser um atributo superior do que a verbosidade, mas isso é discutivelmente controverso ponto). O princípio é bom, mas a sintaxe deixa muito a desejar IMHO. No entanto reclamar é proibido, então vou beber meu kool-aid agora ;-)
Thomas

30

Seis meses depois que essa pergunta foi feita, Rob Pike escreveu uma postagem no blog intitulada Erros são valores .

Lá ele argumenta que você não precisa programar da maneira apresentada pelo OP e menciona vários lugares na biblioteca padrão onde eles usam um padrão diferente.

É claro que uma instrução comum envolvendo um valor de erro é testar se ele é nulo, mas há inúmeras outras coisas que se pode fazer com um valor de erro, e a aplicação de algumas dessas outras coisas pode tornar seu programa melhor, eliminando muito do boilerplate que surge se cada erro é verificado com uma instrução if rotineira.

...

Use a linguagem para simplificar o tratamento de erros.

Mas lembre-se: faça o que fizer, verifique sempre os seus erros!

É uma boa leitura.


Obrigado! Vou verificar isso.
gmoore

O artigo é incrível, basicamente apresenta um objeto que pode estar em estado de falha e, se estiver, simplesmente ignorará tudo o que você fizer com ele e permanecerá em estado de falha. Para mim parece quase-mônada.
Waterlink

@Waterlink Sua declaração não tem sentido. Tudo que tem um estado é quase-mônada, se você olhar um pouco mais estreito. Compará-lo com en.wikipedia.org/wiki/Null_Object_pattern é mais útil, eu acho.
user7610

@ user7610, Obrigado por um feedback. Eu só posso concordar.
Waterlink de

2
Pike: "Mas lembre-se: faça o que fizer, verifique sempre os seus erros!" - isso é tão anos 80. Erros podem ocorrer em qualquer lugar, parar de sobrecarregar os programadores e adotar exceções para o bem de Pete.
Slawomir,

22

Eu concordaria com a resposta do jnml de que ambos são códigos idiomáticos e adicionaria o seguinte:

Seu primeiro exemplo:

if err != nil {
      //handle err
}

é mais idiomático ao lidar com mais de um valor de retorno. por exemplo:

val, err := someFunc()
if err != nil {
      //handle err
}
//do stuff with val

Seu segundo exemplo é uma boa abreviatura ao lidar apenas com o errvalor. Isso se aplica se a função retornar apenas um errorou se você ignorar deliberadamente os valores retornados diferentes de error. Por exemplo, às vezes é usado com as funções Readere Writerque retornam um intdo número de bytes gravados (às vezes informações desnecessárias) e um error:

if _, err := f.Read(file); err != nil {
      //handle err
}
//do stuff with f

A segunda forma é conhecida como o uso de uma instrução de inicialização if .

Portanto, com relação às melhores práticas, até onde eu sei (exceto pelo uso do pacote "errors" para criar novos erros quando você precisar deles), você cobriu praticamente tudo o que precisa para saber sobre os erros no Go!

EDIT: Se você descobrir que realmente não pode viver sem exceções, você pode imitá-los com defer, panic&recover .


4

Fiz uma biblioteca para agilizar o tratamento de erros e canalizar uma fila de funções Go.

Você pode encontrá-lo aqui: https://github.com/go-on/queue

Possui uma variante sintática compacta e detalhada. Aqui está um exemplo para a sintaxe curta:

import "github.com/go-on/queue/q"

func SaveUser(w http.ResponseWriter, rq *http.Request) {
    u := &User{}
    err := q.Q(                      
        ioutil.ReadAll, rq.Body,  // read json (returns json and error)
    )(
        // q.V pipes the json from the previous function call
        json.Unmarshal, q.V, u,   // unmarshal json from above  (returns error)
    )(
        u.Validate,               // validate the user (returns error)
    )(
        u.Save,                   // save the user (returns error)
    )(
        ok, w,                    // send the "ok" message (returns no error)
    ).Run()

    if err != nil {
       switch err {
         case *json.SyntaxError:
           ...
       }
    }
}

Esteja ciente de que há uma pequena sobrecarga de desempenho, uma vez que faz uso de reflexão.

Além disso, este não é um código go idiomático, então você vai querer usá-lo em seus próprios projetos ou se sua equipe concordar em usá-lo.


3
Só porque você pode fazer isso, não significa que seja uma boa ideia. Parece o padrão da Cadeia de Responsabilidade , mas talvez seja mais difícil de ler (opinião). Eu sugeriria que não é um "Go idiomático". Interessante, entretanto.
Steven Soroka

2

Uma "estratégia" para lidar com erros em golang e em outras linguagens é propagar erros continuamente pela pilha de chamadas até que você esteja alto o suficiente na pilha de chamadas para lidar com esse erro. Se você tentou lidar com esse erro muito cedo, provavelmente acabará repetindo o código. Se você lidar com isso tarde demais, você quebrará algo em seu código. Golang torna esse processo muito fácil, pois torna muito claro se você está lidando com um erro em um determinado local ou propagando-o.

Se você for ignorar o erro, um simples _ revelará esse fato muito claramente. Se você estiver lidando com isso, então exatamente qual caso do erro você está lidando é claro, pois você verificará na instrução if.

Como as pessoas disseram acima, um erro é na verdade apenas um valor normal. Isso o trata como tal.


2

Os deuses Go publicaram um "projeto de projeto" para tratamento de erros no Go 2. O objetivo é mudar o idioma dos erros:

Visão geral e design

Eles querem feedback dos usuários!

Wiki de feedback

Resumidamente, parece:

func f() error {
   handle err { fmt.Println(err); return err }
   check mayFail()
   check canFail()
}

ATUALIZAÇÃO: o projeto de rascunho recebeu muitas críticas, então eu elaborei Requisitos a serem considerados para o tratamento de erros do Go 2 com um menu de possibilidades para uma eventual solução.


1

A maioria na indústria segue as regras padrão mencionadas na documentação do golang Tratamento de erros e Go . E também ajuda na geração de documentos para o projeto.


Esta é essencialmente uma resposta apenas com link. Eu sugeriria que você adicionasse algum conteúdo à resposta para que, se o link se tornar inválido, sua resposta ainda seja útil.
Neo de

obrigado pelo valioso comentário.
pschilakanti

0

Abaixo está minha opinião sobre como reduzir o tratamento de erros no Go, um exemplo é para obter parâmetros de URL HTTP:

(Padrão de design derivado de https://blog.golang.org/errors-are-values )

type HTTPAdapter struct {
    Error *common.AppError
}

func (adapter *HTTPAdapter) ReadUUID(r *http.Request, param string, possibleError int) uuid.UUID {
    requestUUID := uuid.Parse(mux.Vars(r)[param])
    if requestUUID == nil { 
        adapter.Error = common.NewAppError(fmt.Errorf("parameter %v is not valid", param),
            possibleError, http.StatusBadRequest)
    }
    return requestUUID
}

chamá-lo para vários parâmetros possíveis seria o seguinte:

    adapter := &httphelper.HTTPAdapter{}
    viewingID := adapter.ReadUUID(r, "viewingID", common.ErrorWhenReadingViewingID)
    messageID := adapter.ReadUUID(r, "messageID", common.ErrorWhenReadingMessadeID)
    if adapter.Error != nil {
        return nil, adapter.Error
    }

Isso não é uma solução mágica; a desvantagem é que, se você teve vários erros, só poderá obter o último erro.

Mas, neste caso, é relativamente repetitivo e de baixo risco, portanto, posso obter apenas o último erro possível.


-1

Você pode limpar seu código de tratamento de erros para erros semelhantes (uma vez que os erros são valores, você deve ter cuidado aqui) e escrever uma função que você chama com o erro passado para tratar o erro. Você não terá que escrever "if err! = Nil {}" toda vez. Novamente, isso só resultará na limpeza do código, mas não acho que seja a maneira idiomática de fazer as coisas.

Novamente, só porque você pode , não significa que você deve .


-1

goerr permite lidar com erros com funções

package main

import "github.com/goerr/goerr"
import "fmt"

func ok(err error) {
    if err != nil {
        goerr.Return(err)
        // returns the error from do_somethingN() to main()
        // sequence() is terminated
    }
}

func sequence() error {
    ok(do_something1())
    ok(do_something2())
    ok(do_something3())

    return nil /// 1,2,3 succeeded
}
func do_something1() error { return nil }
func do_something2() error { return fmt.Errorf("2") }
func do_something3() error {
    fmt.Println("DOING 3")
    return nil
}

func main() {
    err_do_something := goerr.OR1(sequence)

    // handle errors

    fmt.Println(err_do_something)
}

Ick. Complicar / ocultar a lógica de tratamento de erros como essa não é uma boa ideia IMO. O código resultante (que precisa de pré-processamento de origem por goerr) é mais difícil de ler / raciocinar sobre o código idiomático Go.
Dave C

-4

Se você quer um controle preciso dos erros, essa pode não ser a solução, mas para mim, na maioria das vezes, qualquer erro é um obstáculo.

Então, eu uso funções.

func Err(err error) {
    if err!=nil {
        fmt.Println("Oops", err)
        os.Exit(1)
    }
}

fi, err := os.Open("mmm.txt")
Err(err)

Essas mensagens devem ir para em stderrvez de stdout, então use apenas log.Fatal(err)ou log.Fatalln("some message:", err). Uma vez que quase nada além de maintomar essa decisão de encerrar todo o programa (ou seja, retornar erros de funções / métodos, não abortar), no caso raro, isso é o que você deseja fazer, é mais limpo e melhor fazê-lo explicitamente (ou seja, if err := someFunc(); err != nil { log.Fatal(err) }) em vez de uma função "auxiliar" que não está clara sobre o que está fazendo (o nome "Err" não é bom, não dá nenhuma indicação de que pode encerrar o programa).
Dave C de

Aprendeu coisas novas! Obrigado @DaveC
Gon
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.