Go: proposta: deixar "if err! = nil" sozinho?

Criado em 28 jun. 2019  ·  314Comentários  ·  Fonte: golang/go

A proposta # 32437 do Go2 adiciona uma nova sintaxe à linguagem para tornar o padrão if err != nil { return ... } menos complicado.

Existem várias propostas alternativas: # 32804 e # 32811, pois o original não é universalmente amado.

Para lançar outra alternativa na mistura: por que não mantê-la como está ?

Passei a gostar da natureza explícita da construção if err != nil e, como tal, não entendo por que precisamos de uma nova sintaxe para isso. É tão ruim assim?

FrozenDueToAge Proposal Proposal-Hold error-handling

Comentários muito úteis

deve haver apenas uma maneira de fazer uma coisa

Todos 314 comentários

Eu apoio isso. Eu realmente gosto de como decorar cada erro antes de retorná-lo adiciona documentação legível por humanos à fonte (geralmente formatamos nossos erros como "não poderia [o que estou fazendo nestas linhas de código]: [erro anterior]") e também para os usuários erros de leitura.

Os erros gerados dessa forma são extremamente informativos e muito mais fáceis de ler do que os rastreamentos de pilha. Erros impressos que incluem rastreamentos de pilha geralmente presumem que você tem acesso imediato às fontes (os administradores podem não ter esse acesso) e realmente conhece o seu caminho no código.

Erros sem qualquer forma de contexto ou rastreamento (a string "EOF") são absolutamente inúteis. Acho que ter atalhos que facilitam o retorno de erros simples fará com que os programas Go imprimam muitos erros inúteis.

Devemos promover e apoiar erros de decoração com contexto, talvez com novas regras de veterinário e lint.

Também gosto da verificação de erro explicitamente. try é confuso e o retorno implícito é estranho.

Acho que, em vez de repensar os erros, poderíamos tentar uma abordagem alternativa para tornar essas verificações mais curtas.

Aqui está um exemplo com o qual não concordo necessariamente:

value, err := foo()
return err if err != nil

Isso permitiria uma abordagem mais curta, mas ainda explícita. E permitiria adicionar contexto!

Dito isso, ifs inline são uma coisa do Ruby e não parecem muito Goish, mas isso é apenas um brainstorming. Talvez encontremos outra coisa.


EDITAR: adicionei uma proposta para isso aqui: https://github.com/golang/go/issues/32860

deve haver apenas uma maneira de fazer uma coisa

[...] Por que não manter como está?

Acho que é justo dizer que todos nós sabemos a resposta para isso. Você só precisa ler uma das várias propostas para descobrir a resposta se, sinceramente, não souber.

IMO, há muito poucos detalhes aqui para termos uma discussão focada (ou seja, não acho que se qualifique como uma proposta) e logo se transformará em outro galpão de bicicletas cheio de idiotices e ideias que tornam o código menos legível .

Tanto isso.

Provavelmente entrei no Go por causa desse tratamento explícito de erros. Ele fica em algum lugar entre o try-catch implícito que muitas linguagens usam e tipos de função como Option ou Maybe, que favorece o retorno ao usuário e o tratamento explícito.

Não tenho certeza se uma nova construção realmente resolveria isso. Se você envolveu if err := nil em uma função auxiliar como esta, pode ajudar um pouco (perdoe meu Go enferrujado):

func handleErr(err error, cb func(error)) {
        if err := nil {
                cb(err)
        }
}

Mas o problema que torna essa função auxiliar menos útil geralmente é o sistema de tipos, que é um tópico diferente.

Eu apoio isso. if err != nil { return err } não faz parte de nenhum código em nossa base de código. Portanto, tentar "macro" não faz nenhum sentido. Retornamos apenas erros agrupados, com uma mensagem que descreve o contexto.

Adicionar contexto por meio de adiamento também não faz sentido, pois queremos retornar diferentes mensagens de erro para distinguir os diferentes tipos de erros. Entretanto, try(fn(), "my error message: %w") pode ser útil. Mas mesmo assim, a construção if err != nil ainda pode ser preferível, por causa dos comprimentos de linha mais curtos.

Francamente, não quero um retorno implícito que try fornece. Se tivéssemos genéricos, eu preferiria muito mais uma solução que usasse o comportamento monádico.

type Result<T> interface {
  Expect(err error) T
  OrElse(defaultValue T) T
}

func From<T>(value T, err error) Result<T> { ... }

Para mim, isso é muito mais limpo do que o integrado que está sendo proposto atualmente, embora outras alterações sejam necessárias para o acima, uma vez que você teria uma proliferação de métodos que retornaram (valor, erro) e Resultado

A proposta try atual, não tendo como decorar explicitamente os erros, não atende às minhas necessidades. Não consigo imaginar usá-lo. Francamente, também pode ser chamado de code_smell .

Pode não fazer sentido alterá-lo, porque o problema errado está tentando ser resolvido.

O código com o qual estamos familiarizados não é o tratamento de erros.

if err != nil {
  return err
}

Este é o tratamento do erro nil . Em nenhum ponto desse padrão o valor de um erro é tratado.

Se eu fosse demonstrar isso em uma linguagem diferente, Ruby.

begin
 some_method_that_raises_an_error
rescue => e # catch any exception
  retry e        # throw it up again
end

Isso retransmite o mesmo comportamento do código golang. Quando detectamos que ocorreu uma exceção e a aumentamos novamente. Nós apenas jogamos na pilha.

Em golang, nós return it.

Onde está ocorrendo o verdadeiro _ tratamento de erros_?

Todos nós já passamos por experiências semelhantes de falha desse padrão. Por exemplo, receber um erro file not found e, em seguida, gastar muito tempo rastreando o _thrower_ original desse erro.

É por isso que acredito que a proposta try (e outras) são falhas. Não temos um bom padrão para realmente lidar com os erros.

Eu vi err.Error() verificação de string, afirmações de tipo, etc. para realmente inspecionar o erro.
Precisamos de um padrão para essa inconsistência. Parece que xerrs pode estar resolvendo isso, mas também não parece concluído ainda.

Eu apóio manter err! = Nil verificar como está.

Sempre que procuro uma base de código Go considerável, me pergunto como posso reduzir parte do clichê. Eu sempre volto para:

  • Esses codepatas existem de uma forma ou de outra.
  • Mesmo que você não seja obrigado a ocultar o codepath, dar às pessoas a oportunidade de ocultá-lo tornará esse comportamento padrão (porque aparentemente ainda medimos o quão difícil é usar uma linguagem por contagem de linha).
  • Se o comportamento padrão esconder codepaths, então eu estaria procurando por novos bugs de "limpeza ausente".
  • O significado e os padrões para os erros retornados são diversos o suficiente para que esta proposta capturasse apenas uma parte do problema percebido
  • Se apenas uma parte for capturada, então certamente teremos um monte de soluções
  • Com um monte de soluções viria a tentação de ter alguma mágica adaptativa de caso de uso para enrolá-los
  • Que se isso fosse realmente um problema, as pessoas seriam livres para criar sua própria solução simples ou usar algum padrão adotado em massa. Eu não vi nada parecido com isso. Talvez eu simplesmente não tenha olhado com atenção o suficiente.

O rastreador de problemas é útil para muitas coisas, mas não é útil para uma discussão detalhada de um tópico complexo. O rastreador de problemas não fornece threading e responder a uma mensagem específica é estranho. Já que não há nenhuma proposta real aqui, apenas uma resposta a outras propostas, eu realmente encorajo você a levar esta discussão para a lista de e-mail de golang-nuts.

Se me permite, acredito que esta seja a resposta. Esta nova proposta de erro está em conflito direto com os objetivos da linguagem.

A razão pela qual eu amo o golang é por causa de sua simplicidade e uso claro do fluxo de controle. Uma das coisas que mais desprezo em Java é a construção try throw. É tão nojento. Encoraja o terrível tratamento de erros. Enviar exceções na pilha de chamadas é um método horrível e nojento de lidar com o fluxo de controle. Além disso, incentiva a embrulhar tudo em um cheque gigante e encerrar o dia, em vez de uma autodocumentação e tratamento explícito de cada situação de erro.

If err! = Nil encoraja um bom tratamento de erros, é autodocumentado e encoraja uma boa documentação quanto ao caso específico e, honestamente, é uma das coisas que eu mais amo em go. Fazer com que esse novo fluxo de controle seja interrompido, usando retornos e parâmetros confusos e um tanto ambíguos, e semântica confusa não está no espírito da linguagem que passei a adorar.

O verbosidade não é uma coisa ruim. O detalhamento desnecessário é, mas eu diria que o tratamento de erros de go não é desnecessário. Faz parte do charme da linguagem.

Não poderia concordar mais. O tratamento explícito de erros é uma das melhores características da linguagem IMO. Sempre sinto que muitos que se incomodam com isso ainda não estão acostumados.

Não é bom que as questões sejam separadas, mas estou pensando que duas opiniões se fundem como uma opinião neste caso.

  1. Não gostamos de nova sintaxe (tente ou nova sintaxe if-err)
  2. De qualquer forma, não queremos adicionar uma nova sintaxe

Os ícones de votação do GitHub não podem interpretar o segundo.

O tratamento explícito de erros em go é uma das razões pelas quais adoro golang. Eu não entendo por que qualquer desenvolvedor go iria querer isso de outra forma. Acho que a proposta de adicionar uma nova sintaxe vem principalmente de pessoas que se sentem confortáveis ​​em usar a sintaxe usada em outras linguagens. pode levar algum tempo para se acostumar, mas funciona perfeitamente assim que você se acostumar.

Escrevi # 32811 e apoio mais esta proposta ... Prefiro deixar o tratamento de erros sozinho. Acho que as reações dos emojis a essa proposta dizem muito.

Eu pessoalmente concordo em deixar o tratamento do erro como está. Uma das coisas que gosto no Go é que a linguagem é mínima e, de modo geral, tem uma maneira de fazer as coisas. Adicionando uma nova sintaxe para tratamento de erros, criaremos um mundo onde x% do código usa o método atual e y% usa o novo método. Isso irá, entre outras questões já discutidas, criar bases de código inconsistentes. Pessoalmente, não acho que o valor da nova sintaxe de tratamento de erros valha a pena compensar, já que considero a sintaxe existente suficiente / suficiente.

Como alguém que é novo no Golang, uma das coisas que acho revigorantes sobre a linguagem é o tratamento explícito de erros. Trabalhei muito em Java, Ruby, Python e Node, e lidar com erros é muito mais trabalhoso do que em Go. Eu preferiria ver o 'caminho' claro dos erros, do que tê-lo implícito para mim por alguma construção de linguagem que o torne mais vago.

ˋRetorne ... se ... ˋ a sugestão de @andreynering é, na verdade, bastante inteligente, imho. Mantém o código explícito (sem quebra de fluxo de controle oculto) enquanto corta o clichê (uma linha).

Concordo, deixe if err != nil sozinho.

Eu prefiro o formato atual. É um padrão claro e fácil de ensinar. Trazer novos engenheiros é simples, pois eles podem aprender um padrão simples e repeti-lo. Ele também pede aos usuários que pelo menos considerem o erro no contexto atual, garantindo que pelo menos o engenheiro esteja reconhecendo que um erro pode ocorrer aqui e eu preciso pensar sobre o que fazer.

Eu escrevi # 32804 e prefiro ver as coisas NÃO mudarem. Se o seu código for longo, é porque ele faz muitas coisas. Se você tem muitos códigos de tratamento de erros, é porque está fazendo um bom trabalho ao tratar de todos os seus casos.

Por favor, não vamos adicionar coisas apenas por adicionar coisas.

Eu gosto da simplicidade do tratamento de erros como está.

Expect é apenas um anagrama para exceto, e prefiro não usá-lo. Obrigado por começar.

Por favor, não mude meu Santo Graal.

Houve um feedback esmagador da comunidade, solicitando um tratamento de erros mais simplificado (da pesquisa anual). A Go Team está agora tratando desse problema.

@icholy Claro, mas as propostas atuais deixam muito a desejar. Todos eles parecem ofuscar o tratamento de erros, reverter para mais implementações do estilo try / catch / finally, fazer bolhas no tratamento de erros fora do contexto ou, de outra forma, torná-lo mais complicado. Já que Go deveria ser uma linguagem simples, acho que muitos de nós esperávamos por uma opção simples. Não vi nenhum de que goste pessoalmente, então acho que a melhor opção é manter o padrão atual.

Uma reclamação era ter que digitá-lo, mas praticamente todo editor tem atalhos para inserir trechos de código, então realmente não é um grande problema. Talvez seja minha experiência ter usado Go desde a versão anterior à 1.0, mas gosto da simplicidade e não me importo com a redundância.

@kevineaton você acha que try é complicado?

Eu concordo totalmente com isso. Não estou nem mesmo pessoalmente convencido de que precisamos fazer alguma coisa - concordo que os cheques de if err != nil parecem estranhos à primeira vista, mas não vi nada proposto que realmente resolva o problema sem violar amplamente o que acontece é popular para.

@icholy depois de passar dez anos escrevendo Java e Python antes de Go, acho que pode ser. Acho que você se depara com a captura de exceções do Pokémon, ou o encadeamento de múltiplas exceções, e de outra forma introduzindo ainda mais sobrecarga e clichê. Eu não voltaria a esse estilo de tratamento de erros se pudesse evitá-lo, já que quase sempre causava dores de cabeça e confusão, ESPECIALMENTE ao ensinar. Eu também ensino ciência da computação, além do meu trabalho diário como arquiteto de software, então sou inclinado a ensinar novos desenvolvedores e ser mentor. Eu escolheria Go e é um tratamento de erros simples em vez de um tratamento de erros potencialmente mais complicado ou matizado a qualquer momento.

O rastreador de problemas é útil para muitas coisas, mas não é útil para uma discussão detalhada de um tópico complexo.

_Isso_ não é a verdade. Mas aqui estamos.

if err != nil não desaparecerá se try for adicionado. Acredito que try adicionará alguma clareza aos caminhos de código que são pesados ​​no encaminhamento de erros ou onde muitos erros diferentes podem ser resumidos facilmente em um manipulador de erros de adiamento. . Eu realmente não vejo como try encoraja a não manipulação de erros muito mais do que um monte de if-err-return-err vazios. É fácil ignorar realmente lidar com os erros, independentemente de try estar lá ou não. Eu acho que try é uma das melhores sugestões para tratamento de erros porque parece que será fácil ler o código que está usando.

Meus dois centavos não solicitados, simplesmente não parece muito "Vá". É muito mágico e vamos usar construções implícitas em vez de explícitas.

Do FAQ de Go

Por que Go não tem a operadora?:?
_Não há operação de teste ternário no Go. Você pode usar o seguinte para obter o mesmo resultado: _

if expr {
   n = trueVal
} else {
    n = falseVal
}

O motivo?: Está ausente do Go é que os designers da linguagem viram a operação usada com muita frequência para criar expressões impenetravelmente complexas. A forma if-else, embora mais longa, é inquestionavelmente mais clara. Uma linguagem precisa de apenas uma construção de fluxo de controle condicional .

@ianlancetaylor

O rastreador de problemas é útil para muitas coisas, mas não é útil para uma discussão detalhada de um tópico complexo. O rastreador de problemas não fornece threading e responder a uma mensagem específica é estranho. Já que não há nenhuma proposta real aqui, apenas uma resposta a outras propostas, eu realmente encorajo você a levar esta discussão para a lista de e-mail de golang-nuts.

Você pode responder a uma mensagem específica. Acabei de responder ao seu. :)

Como não há proposta real aqui, apenas uma resposta a outras propostas,

Uma proposta para mim significa um apelo à mudança. Este problema específico é anti-mudança. Você propõe que criemos uma proposta para _não_ alterar o tratamento de erros? Acho que o sistema de propostas é ótimo, mas deixa o status quo pouco representado.

depois de passar dez anos escrevendo Java e Python ... Também ensino ciência da computação além do meu trabalho diário como arquiteto de software

@kevineaton, você acabou de chupar seu próprio pau?

Este problema funciona como uma longa enquete em um lugar semi-oficial onde basicamente qualquer um pode votar facilmente a favor ou contra as propostas.

Não alterar o idioma para remover if err != nil é uma proposta perfeitamente cromulenta que basicamente não precisa de nenhum detalhe adicionado. Não tenho certeza de qual é o problema. Não, não é terrível, longo e difícil de grocar. Isso não significa que seja errado, ruim ou insuficiente.

+1, se nada melhor, o bom será uma informação de rastreamento de pilha realmente boa (sem coisas de dança de frames), acho que x/errors já conseguiu isso, mas, eu adoraria algo rápido em um futuro próximo, como marcar func s usando a palavra-chave throws que retornaria uma palavra-chave error + try , evitando o erro var shadowing (que eu pessoalmente odeio), algo assim:

func a() (int) throws {
  throw &someError{}
}

anInt, err := try a()

@icholy Isso foi incrivelmente desnecessário. Este é um lugar para discussão e a comunidade Go deve ser uma comunidade acolhedora. Não há lugar para esse tipo de observação. Acredito que Sócrates tinha algo a dizer sobre insultos em um debate.

O tratamento de erros atual está sujeito a erros humanos. É fácil esquecer de verificar err no momento. Se já houver alguma verificação no escopo (e na maioria das vezes há), o compilador não terminará com unused variable . O tratamento de erros deve ser estrito - você _ um erro ou verifica - nenhum tiro na perna deve ser possível.

@kevineaton você acha que try é complicado?

try é um cheiro de código. Ele força o recuo em todo o bloco de código, em vez de apenas em um ponto. Além disso, a natureza "bolha" do tratamento de exceções cria de fato um comportamento não determinístico no código e em vários pontos de saída.

A beleza de usar múltiplos valores de retorno em vez de try é que há um valor para verificar quando sua função é concluída e um ponto de saída de sua função (a menos, é claro, usando instruções de guarda ou outros retornos explícitos).

try blocos de

@fillest Embora torne o código um pouco menos legível, acho que isso seria um valor agregado em termos de segurança / tratamento de erros explícito. Se você olhar para trás, para os objetivos originais de como lidamos com erros em Go, acho que seria uma boa iteração para evitar a classe de bug que você cita enquanto segue o espírito de explicitamente ser bom.

O tratamento de erros atual está sujeito a erros humanos. É fácil esquecer de verificar o erro no momento. Se já houver alguma verificação no escopo (e na maioria das vezes há), o compilador não terminará com a variável não utilizada. O tratamento de erros deve ser estrito - você _ um erro ou verifique - nenhum tiro de perna deve ser possível.

@fillest A mudança proposta para o tratamento de erros torna o "disparo de pernas" mais fácil e os erros são mais pronunciados porque podem ser manuseados preguiçosamente.

Eu parei de usar Go por causa da falta de genéricos, propensão clichê, GC, falta de limites de recursos / contabilidade e carga de trabalho gerada a partir de noobs PHP que não entendiam o que um compilador faz. Haskell, C # e outros resolveram muito bem o tratamento de erros ... a proposta do Go 2 parece boa se tiver tratamento de caso explícito como antes (incerto).

O tratamento de erros é o cerne da programação. Modelar a lógica de negócios (por mais complexa que seja) é sempre mais simples do que responder às condições inválidas que essa lógica gera. Simplesmente encaminhar um erro é um cheiro de código. Eu gostaria que Go não encorajasse esse comportamento, mas promovesse padrões de gerenciamento de erros. Os iniciantes costumam se confundir com todo esse código de tratamento de erros porque não perceberam o quão central é o gerenciamento de erros.

Concordo plenamente, já que try builtin não ajudará a quebrar erros e adicionar informações a eles, mesmo que por um momento.

Antes de reescrever com try :

_, err := doSomething()
if err != nil {
    return nil, errors.Wrap(err, "failed to do something")
}

_, err = doOtherThing()
if err != nil {
  return nil, errors.Wrap("failed to do the other thing")
}

Imagine o que acontecerá depois de reescrever com try .

Como try já atua como uma função de 1 argumento colocando seu argumento entre parênteses, ele pode aceitar um segundo argumento que é o código de empacotamento de erro.

try(extract_value(try(get_data(1), errors.Wrap(err, "failed to get data")), errors.Wrap(err, "failed to get data")))

Onde o err deveria ser introduzido implicitamente (de forma higiênica). Então, se try fosse usado como uma função de 1 argumento, ele apenas retornaria seu erro inalterado.

Eu concordo, a única coisa de "açúcar sintático" que pode tornar o tratamento de erros um pouco mais simples é nos deixar fazer algo como o seguinte quando temos vários retornos de nossas funções ... sublinhados seriam apenas valores padrão de quaisquer que sejam os tipos de retorno

if err != nil {
    return _, _, err
}

@sorenvonsarvort não me parece tão ruim:

var errContext string 

defer func() {
  // err is a named return
  if err != nil {
    err = fmt.Errorf("%v: %w", errContext, err)
  }
}()

errContext = "failed to do something"
_ := try(doSomething())

errContext = "failed to do other thing"
_ := try(doOtherThing())

Pelo meu entendimento, você ainda pode usar if err != nil { ... } se estiver mais claro para aquela seção específica do código.

try brilha em outros casos. Imagine algo mais parecido com:

func trySomeComplexOp() (r result, err error) {
  a := try(step1())
  b := try(step2(a))
  c, d := try(step3(b))
  return try(lastStep(c, d)), nil
}

Código como o código acima pode ser muito mais limpo do que se você tivesse que espalhar em blocos de if err != nil . Go tem tudo a ver com "legibilidade linear", então acho que try sai bem nesse sentido.

Houve um feedback esmagador da comunidade, solicitando um tratamento de erros mais simplificado (da pesquisa anual). A Go Team está agora tratando desse problema.

Esta é uma minoria vocal e aposto que uma boa parte deles nem sequer usa Go

@sirkon em que você está baseando essa afirmação?

@sorenvonsarvort não me parece tão ruim:

Código como o código acima pode ser muito mais limpo do que se você tivesse que polvilhar em blocos de if err != nil . Go tem tudo a ver com "legibilidade linear", então acho que try sai bem nesse sentido.

Na Rússia, chamamos isso de «экономия на спичках». Use o google tradutor para obter um significado.

Para aqueles neste tópico que ainda não o fizeram, recomendo a leitura deste comentário sobre o problema da proposta original try . Ele discute as melhores práticas gerais do contexto de erro e como elas podem ser expressas com try .

Acho que talvez o contexto de erro tenha se tornado um pouco dogmatizado na comunidade Go. Sei que pessoalmente me apaixonei por isso e contextualizei demais meus erros, resultando em mensagens muito longas, repetitivas e difíceis de ler. Há muitas nuances em relação a quando contextualizar os erros e quando não.

Eu gosto que try seja basicamente um atalho e reduz parte do código clichê. Mas perdemos a capacidade de envolver os erros com informações extras. No entanto, a seguinte alteração pode corrigir isso:

f := try(os.Open(filename))

torna-se

f := try(os.Open(filename), "open data file")

Claro, se você precisar fazer muito mais do que isso, a maneira "completa" de fazer um cheque err != nil ainda está disponível.

Concordo, mas vou respeitar o pedido da equipa go de ter mais experiência com a mudança antes de ter uma opinião final.

Mas minha experiência preliminar com a mudança parece confirmar que ela é realmente desnecessária. Eu tenho 2 programas do "mundo real" com cerca de 10k linhas cada e executando o tryhard em ambos os programas que nenhum deles se beneficiaria com essa mudança. Isso é facilmente explicado pelo fato de que ambos sempre adicionam contexto aos erros. Eu tenho outros programas de "brinquedo" menores em Go e tryhard encontrei 1 caso em que eu poderia ter usado try em um deles, mas é isso.

Admito que outras pessoas possam tratar os erros de maneira diferente da minha e admito a possibilidade de que try possa ser usado de forma positiva. O próprio código-fonte tryhard tem alguns casos consecutivos de return err , que se fosse usar try eu não acho que a legibilidade seria comprometida tanto. Mas eu só temo os usos indevidos, porque eles afetarão a legibilidade. Um bom exemplo é fornecido aqui . E então determinar o que é um bom uso ou não será outra história.

Além disso, gosto de como as pessoas geralmente podem apenas ler o código go, mesmo que não programem o go. Isso fará com que eles tenham que aprender a mágica que try faz, especialmente porque faz uma coisa diferente das outras try eles viram em outras línguas. Isso também vale para novos usuários do idioma, é apenas mais um recurso que eles terão que aprender em um idioma que se orgulha de ser simples por ter "apenas os recursos de que você precisa".

Vamos esperar e ver. Vou fazer mais experiências com essa mudança, mas não tenho certeza se ela vai mudar minha posição.

Houve um feedback esmagador da comunidade, solicitando um tratamento de erros mais simplificado (da pesquisa anual). A Go Team está agora tratando desse problema.

@icholy Como eu disse, gosto de adicionar contexto aos erros. Nesse sentido, "Tratamento de erros mais simplificado" para mim significa melhores maneiras de fornecer esse contexto e obter informações dele. Por exemplo, com todo o contexto que adicionei aos meus erros, deve ser trivial perguntar "o contexto" se o erro foi causado por um tempo limite. Mas isso não. Normalmente você tem que recorrer ao uso de pkg/error , fazer suas próprias "estruturas de erro" e / ou criar métodos para trabalhar com elas que, dependendo da implementação, podem recorrer a pesquisas de strings. Prefiro ver algo que me poupe de fazer estruturas e métodos inteiros do que coisas que me poupe um único if muito ocasionalmente, na melhor das hipóteses. E como foi dito anteriormente, você pode realmente chamá-lo de "tratamento de erros" quando essa alteração está basicamente fornecendo uma maneira mais conveniente de não realmente tratar o erro?

você acha que try é complicado?

Isoladamente, try não é complicado, mas as alterações de idioma não são consideradas isoladamente. Considerar:

  • Aumento da carga cognitiva com o aprendizado da semântica de um novo
  • Redução na legibilidade devido ao uso de try porque é mais curto, nos casos em que if err != nil {return ... errors.Wrap() } deveria ter sido usado no lugar

Repito os sentimentos acima de que a simplicidade (ter uma maneira única de verificar erros) é mais importante do que uma maneira breve de verificar os erros.

Não é a maneira certa de lidar com um erro é através dessa declaração global e se isso é algo a ser ignorado, como as situações de pânico são tratadas? Eu apoiaria um melhor tratamento de erros como outros paradigmas de programação tratam os erros hoje, mas não para ignorar

Não vejo nenhum problema com a proposta try ?
Se vc quiser usar o comportamento antigo ou precisar de outra maneira de lidar com o erro, por que não reutilizar a maneira antiga? Ninguém enfiando uma faca em seu pescoço pressiona você usa a sintaxe try ?
try sintaxe de

E se gofmt fosse alterado de forma que uma instrução se os blocos permanecessem em uma linha?

Dessa forma, poderíamos ter erros agrupados levando apenas uma linha para tratar, em vez de três na maioria dos casos. Isso reduziria o espaço vertical ocupado pelo tratamento de erros e faria parecer que o tratamento de erros não está ocupando mais da metade do espaço vertical na função média.

Seria assim:

// we already have an err in scope
err = frub.Confozzle(foo, bar, baz)
if err != nil { return errors.Wrap(err, "confozzling didn't work") }

Acho que talvez o contexto de erro tenha se tornado um pouco dogmatizado na comunidade Go. Sei que pessoalmente me apaixonei por isso e contextualizei demais meus erros, resultando em mensagens muito longas, repetitivas e difíceis de ler. Há muitas nuances em relação a quando contextualizar os erros e quando não.

O empacotamento de erros e as coisas de empilhamento / impressão de erros tornariam muito mais simples. Acho que haverá uma boa solução para isso com o tempo, mas não agora. Pessoalmente, prefiro esperar com a introdução de recursos go2 mais poderosos até que o polimorfismo paramétrico seja resolvido porque pode ser potencialmente útil ao projetar o resto dos recursos

Eu concordo totalmente em deixar como está. É um pouco prolixo, mas é bastante simples de seguir.

Se eu pudesse apenas reduzir

if err := foo.x(a, b); err != nil {
    return err
}

if err := foo.y(); err != nil {
    return err
}

if err := foo.z(c); err != nil {
    return err
}

para algo como

if err := check foo.x(a, b), foo.y(), foo.z(c); err != nil {
    return err
}

talvez também seria ótimo sem alterar o idioma IMHO demais.

@henvic Acho que o problema com isso é que você está presumindo que deseja lidar com os três erros da mesma maneira. Go força você a pensar sobre como lidar com cada erro individualmente. Ter verificações de erros separadas torna isso claro. Agregá-los em um faz com que o desenvolvedor tenha que voltar cognitivamente e verificar se esses erros _realmente_ devem ser tratados da mesma maneira? Acho que com esta proposta perdemos clareza e forçamos o pensamento em torno dos erros por menos alguns toques no teclado.

@sanbornm , você está certo. Eu concordo.

Este check também poderia ser um operador checkNonZero que receberia apenas um argumento de retorno e retornaria no primeiro valor diferente de zero (como nulo). Mas além de ser muito vago, e afetar ainda mais a linguagem. Isso daria apenas uma dica de que você não deveria usá-lo se estivesse esperando, digamos, io.EOF. Outra possibilidade seria, talvez, contar com go vet para, pelo menos, informá-lo dos casos mais comuns (como io.EOF ao ler de um tubo) ... mas essa ideia não parece boa em tudo para mim.

Não vejo nenhum problema com a proposta try ?
Se vc quiser usar o comportamento antigo ou precisar de outra maneira de lidar com o erro, por que não reutilizar a maneira antiga? Ninguém enfiando uma faca em seu pescoço pressiona você usa a sintaxe try ?
try sintaxe de

Todos nós vivemos em comunidade. Todos os dias eu trabalho com muitos códigos escritos por outras pessoas. Portanto, a mudança no idioma me afetará mesmo se eu não o usar.

Em grandes projetos, os piores logs são gerados por uma função que esquecemos que alguém escreveu, que chama uma biblioteca que chama uma biblioteca que chama uma biblioteca que jogou um new Exception() genérico que nada pegou até que um manipulador de exceção "Pokémon" fez e registrou um erro genérico. Os segundos piores são os mesmos, mas com um rastreamento de pilha inescrutável de várias centenas de linhas, com o qual acho que podemos eventualmente descobrir a causa (felizmente, apenas pesquisar por github.com/<us>/<ourproject> encontra a maioria das informações relevantes, mas às vezes há muito). Apesar do nome, "Exceções" não são nada excepcionais em grandes projetos Java.

Enquanto isso, mesmo quando há muito contexto redundante, strings de erro Go simples como "narf: Error unpoiting the zort: foo: Unexpected bar in baz: {\"ork\": \"morpork\"}" têm sido (na minha experiência) normalmente muito fáceis de interpretar, desde que tenhamos sido diligentes em incorporar o contexto importante em algum lugar o valor real do erro. Se um contexto importante estiver faltando, isso também é _também_ bastante óbvio. A "correção" nesses casos é adicionar mais contexto e esperar por outro erro, então não é perfeito, mas no geral ainda prefiro isso a rolar pelos rastreamentos de pilha e / ou depender das dependências das minhas dependências para "lançar "ou" aumentar "mensagens de erro lógicas. Eu realmente aprecio como o nome panic() parece impedir a maioria dos desenvolvedores Go de implementar tão liberalmente o que é essencialmente o mesmo recurso de linguagem. Não vamos fazer de error e panic a mesma coisa, afinal.

Ocasionalmente, me deparei com situações em que uma função tinha uma dúzia ou mais de modos de falha, e a grande maioria deles merecia a mesma mensagem de erro. A repetição realmente não me incomoda, mas geralmente há outra pessoa em minha equipe que incomoda, então nos comprometemos declarando um encerramento no início da função para lidar com esses erros comuns.

func foo(a, b, c SomeArgType) (x, y, z SomeReturnType, err error) {
  handleError := func(handleErr error) (x, y, z SomeReturnType, err error) {
    log.WithFields(logrus.Fields{
      "package": "foo",
      "func": "foo",
      "arguments": map[string]SomeArgType{"a": a, "b": b, "c": c},
      "error": handleErr,
    }).Error("Error fooing the bar")
    return reasonable, default, values, handleErr
  }

  err := doABunchOfThings()
  if err != nil {
    return handleError(err)
  }
}

O que, reconhecidamente, _ainda_ é uma solução imperfeita em alguns aspectos. Mas eu gosto que fazer isso tornou muito fácil para futuros desenvolvedores ainda entender quando e o que foo retorna, sem o fluxo de controle saltar muito.

Se de alguma forma este "problema" de repetição é extremamente comum em vários pacotes em vez de (como eu costumo ver) confinado a um punhado de funções irredutivelmente complexas em um pacote irredutivelmente complexo, um "functor" de todo o projeto provavelmente poderia ser usado para um fim semelhante, e (suspiro) se um conceito de tipos paramétricos acabar sendo adicionado à linguagem, você poderia esconder ainda mais detalhes atrás de uma fábrica-fábrica de tratamento de erros sem precisar depender de try / catch.

@thomasf

O empacotamento de erro e o empilhamento de quadro / impressão de erro tornariam muito mais direto

Eu concordo.

Pessoalmente, eu preferiria esperar com a introdução de recursos go2 mais poderosos até que o polimorfismo paramétrico seja resolvido porque ele pode ser potencialmente útil ao projetar o resto dos recursos

Algumas pessoas na proposta try discutiram a espera pelos genéricos, mas não está claro para mim como o polimorfismo paramétrico tornaria a proposta try diferente. Já está embutido, então não está confinado às limitações do que podemos expressar na linguagem.

@icholy

Houve um feedback esmagador da comunidade, solicitando um tratamento de erros mais simplificado (da pesquisa anual). A Go Team está agora tratando desse problema.

Apenas para responder a isso; também havia uma maioria no Reino Unido a favor do Brexit. Claro, a UE também traz algumas desvantagens às quais o público em geral respondeu. No entanto, uma vez que todas as alternativas foram levantadas, parecia que ficar na UE não seria tão ruim, afinal.

Agora, não é minha intenção politizar isso, e você pode discordar do que foi dito acima. Mas o que pretendo mostrar é que mesmo quando a maioria inicialmente considera algo um incômodo, ainda pode ser a melhor solução depois que todas as alternativas foram examinadas.

Não tenho uma opinião forte sobre o tratamento de erros, mas _poderia_ ser um argumento para deixar as coisas como estão.

Em um ambiente de codificação profissional, aproveitamos as práticas atuais de tratamento de erros para anotar sistemas de rastreamento e decorar logs. Como um aparte, o retorno implícito é como usar o pânico em uma função de biblioteca exportada, pois obscurece a legibilidade do controle de fluxo imediato.

@icholy

Houve um feedback esmagador da comunidade, solicitando um tratamento de erros mais simplificado (da pesquisa anual). A Go Team está agora tratando desse problema.

Apenas para responder a isso; também havia uma maioria no Reino Unido a favor do Brexit. Claro, a UE também traz algumas desvantagens às quais o público em geral respondeu. No entanto, uma vez que todas as alternativas foram levantadas, parecia que ficar na UE não seria tão ruim, afinal.

Você não precisa considerar a afirmação dessa pessoa a sério: a contagem de emojis mostra que as pessoas geralmente não gostam da proposta try e geralmente gostam dessa proposta do tipo “deixe como está”.

PS na minha prática, uma grande maioria das pessoas que não gostam do Go em seu domínio principal (serviços de rede, utilitários CLI) nem mesmo o usa. Portanto, prefiro ignorar suas opiniões.

Precisamos de opções melhores e menos controversas do que a proposta try .
Não vejo urgência para soluções precipitadas.

@velovix Acho que odeio polimorfismo paramétrico mais do que "manipulação" de erros de tentativa / captura, mas se ele se tornasse um recurso de linguagem, eu poderia ver algumas maneiras de contornar a necessidade de outro recurso de linguagem embutido.

Por um lado, quando o código que as pessoas não querem repetir é clichê como:

foo, err := Foo()
if err != nil {
  log(err)
}
bar, err := Bar(foo)
if err != nil {
  log(err)
}
// ...

Então, alguma combinação de funções paramétricas, inferência de tipo e padrões de projeto de objeto de estilo _talvez_ ou _opcional_ reduziriam diretamente (heh) o clichê sem recorrer a estranhas estratégias de fluxo de controle não linear:

func<T> DoWithErrorLogging(f func(any...) (T, error), args... any) T {
  t, err := f(args...)
  if err != nil {
    log(err)
  }
  return t
}
// ...
foo := DoWithErrorLogging(Foo)
bar := DoWithErrorLogging(Bar, foo)

IMO, tudo isso seria muito, muito pior do que Go1. Mas é melhor do que ter essas palavras-chave _plus_ try / catch no idioma.

Honestamente ... Do jeito que as coisas estão agora, acho que minhas alterações favoritas de "interrupção" para Go2 apenas resolveriam todos os pequenos inconvenientes em Go1, como os net/http padrões sendo globais compartilhados mutáveis ​​aninhados dentro de globais compartilhados mutáveis ​​(apenas tornar cleanhttp padrão do Hashicorp, basicamente), ou (*time.Timer).Reset() tendo um valor de retorno inútil que você apenas precisa saber, ou todo o pacote syscall . Go3 pode ser liberado quase imediatamente depois disso, com quaisquer tumores que as pessoas queiram desenvolver nele; Não vejo por que pequenas e grandes alterações de interrupção precisam ser feitas em uma única versão.

Sou a favor de try ... quando usado com moderação. Eu suspeito que projetos populares irão adicionar diretrizes sobre quando / se eles estão ok com o uso de try, e que projetos pequenos / novos / de uma pessoa às vezes irão sofrer de erros insatisfatórios - e, portanto, falta de uso - devido a try ing com muita freqüência. Esses projetos morrerão ou serão consertados ou bifurcados.

Eu realmente não vejo a adição de try à linguagem ser tão horrível. Se os piores temores das pessoas se mostrarem fundados, seu uso será desaprovado. Pessoas inexperientes o usarão indiscriminadamente, enquanto outras não. Não é o fim do mundo.

Se try for adicionado, provavelmente irei usá-lo em alguns casos. Esses casos são em que um erro é retornado, mas acho que é tão improvável que realmente haja um erro que não vejo razão para adicionar contexto e simplesmente retorno o erro no estado em que se encontra. Por exemplo, se acabei de criar um sistema de arquivos que preenche um disco que sei que tem 1 TB, posso ter certeza de que há espaço para criar um arquivo ou diretório de 1kb. Se isso falhar, não quero ignorar o erro - pode indicar um bug em outro lugar, falha de hardware, etc. No entanto, não vale a pena se esforçar para anotar cada erro insanamente improvável.

Eu não gosto de como try (..) é apenas colocar mais um par de parênteses / colchetes na pilha mental de um codificador para pensar ao digitar. E por mais tempo que eu possa imaginar!

Então, isso é melhor:
valor, err: = foo ()
retornar err se errar! = nulo

Mas isso ainda é tão comum. Então, eu gostaria se smt assim pudesse ser de alguma forma possível:

valor, verifique err: = foo ()

Se Go quiser ser uma linguagem legível, a capacidade de fazer coisas difíceis de ler ou entender deve ser minimizada.

Se Go quiser ter um bom tratamento de erros, deve encorajar os erros a terem contexto adicional à medida que sobem na pilha de chamadas. O requisito de usar adiar para lidar com erros parece confuso. E se o seu manipulador de erros tiver um erro? Defers são executados em ordem de pilha, precisamos declarar manipuladores ao contrário?

As declarações If são diretas e deixam pouco espaço para ambigüidade aqui. Eu sinto que try está resolvendo um problema em vez de um problema real de engenharia. Gosto desta proposta porque permite que a maioria silenciosa finalmente faça uma declaração sem compreender totalmente todas as facetas dessas propostas complexas.

@icholy Por favor, seja educado. Esteja ciente do Código de Conduta Gopher: https://golang.org/conduct. Obrigado.

Todos: além do meu comentário acima (https://github.com/golang/go/issues/32825#issuecomment-506740412), estejam atentos a https://golang.org/wiki/NoPlusOne. Não é útil fazer um comentário dizendo pouco mais do que "Concordo" ou "Discordo". Use os botões de emoji. Obrigado.

@sanbornm

(Concordo que é possível responder a uma mensagem; eu disse que era estranho, não impossível. E meu ponto sobre o encadeamento ainda permanece, pois este mini-encadeamento se perdeu em uma tempestade de outros comentários.)

Uma proposta para mim significa um apelo à mudança. Este problema específico é anti-mudança. Você propõe que criemos uma proposta para não alterar o tratamento de erros? Acho que o sistema de propostas é ótimo, mas deixa o status quo pouco representado.

Não é necessário criar a proposta A dizendo que a proposta B não deve ser adotada. Em vez disso, vote contra a proposta B. Para uma discussão detalhada da proposta B, use essa proposta ou a lista de mala direta.

(Eu entendo que a proposta B, neste caso, está bloqueada; o fato de esta proposta ter 77 comentários em menos de um dia mostra o porquê. Este nível de discussão simplesmente funciona melhor em uma lista de e-mails do que no rastreador de problemas.)

@ianlancetaylor

(Concordo que é possível responder a uma mensagem; eu disse que era estranho, não impossível. E meu ponto sobre o encadeamento ainda permanece, pois este mini-encadeamento se perdeu em uma tempestade de outros comentários.)

Muito justo, isso faz sentido. As listas de discussão são ótimas, mas pessoalmente acho mais fácil contribuir por meio do GitHub neste caso. Não tenho muito a dizer, a não ser que o tratamento de erros atual é ótimo e gostaria que continuasse o mesmo. Emoji / votos são ótimos para isso. Você provavelmente não quer 100 pessoas escrevendo "Por favor, deixe o tratamento de erros sozinho" na lista de discussão, onde 100 "votos" seriam suficientes.

Como esse problema está bloqueado, ele não pode mais ser "votado" com o Emojis. É por isso que acredito que este problema foi criado em primeiro lugar.

Um ponto secundário, mas relacionado, o gerenciamento de dependências não foi bem tratado. Dep funcionou muito bem e o mod go foi escolhido (do que parecia) do nada [1]. Eu entendo que é por isso que o sistema de propostas foi criado. Eu apenas sinto que o sistema de propostas neste caso pode sub-representar a comunidade se os problemas estiverem bloqueados e formos informados para ir para as listas de mala direta.

[1] https://twitter.com/_rsc/status/1022588240501661696

Edit: A equipe Go e a comunidade em sua maior parte fazem um trabalho incrível ouvindo o feedback da comunidade. Agradeço todo o trabalho que envolve. As pesquisas Go são um ótimo exemplo.

@sanbornm

Dep trabalhou bem

Precisa discordar aqui. Módulos Go finalmente resolveram aquele problema mal conhecido de “gobindata” com seu cache persistente por https://proxy.golang.org

Aquele cara do dep nem percebeu o problema e, em vez disso, estava brincando com uma “garantia” sofisticada via VCSes.

@sirkon Este é um pouco fora do tópico, mas você não precisa de nada disso se você for um vendedor de departamentos como Dep estava fazendo.

Como está, acho que prefiro deixar as coisas como estão, a menos que mais restrições sejam adicionadas, como 1 instrução try por linha. A razão é considerar este exemplo da proposta - parece bastante inofensivo info := try(try(os.Open(file)).Stat()) mas ele vaza identificadores de arquivo além do escopo do fluxo de controle normal. Acho que veremos um aumento no vazamento de recursos de arquivo com implementações de io.Closer ou outra função de limpeza que as pessoas podem evitar em busca de um código mais compacto.

Talvez algumas pessoas considerem isso irrelevante porque f não estará mais ativo e, portanto, qualificado para GC imediatamente e, em algum ponto, o finalizador garantirá que f seja fechado. Eu acho que isso muda as convenções claras anteriores (com suporte para linter) de usar adiar hoje, que estão vinculadas a um bloco de funções. Quando o bloco de funções é encerrado, o recurso é liberado. Contar com o coletor de lixo não oferece nenhuma garantia de que você não esgotará os recursos (os padrões de limite de manipulação de arquivos abertos típicos podem ser entre 1k e 4k) - o que é facilmente excedido com um caminho de arquivo simples. Caminhada que não fecha os arquivos que ele estatísticas.

Em resumo, acho que essa sintaxe implementada oferece um risco sutil no gerenciamento de recursos em Go, uma vez que falta o ctor / dtor e depende de um mecanismo de GC de nível inferior bem removido dos blocos de código para evitar vazamentos de recursos. Transformar algo que parece inofensivo em uma condição de erro potencial (muitos arquivos abertos).

var n int
for _, name in try(os.Readdir(...)) {
   n += try(getSize(name))
}
func getSize(name string) (int, error) {
   return try(try(os.Open(name)).Stat()).Size
}

editar:
Para as restrições, eu realmente acho que se fosse válido apenas no lado direito de uma atribuição, seria melhor do que dizer 1 por linha, já que a, b := try(get("a")), try(get("b")) é razoável o suficiente. Mas ainda deixa a capacidade de fazer try(os.Open(name)).Stat() - que se você fizer try () nulo, mas apenas quando não estiver no RHS de uma atribuição, você ficará com algo que não é muito funcional como em tudo.

@cstockton uau, ótima descoberta!

Rust na verdade tem macro semelhante ( ? se bem me lembro) que faz exatamente o que este try pretendia fazer, mas eles têm um raii apropriado lá, então não é um problema nessa linguagem e um enorme buraco no nosso caso

@sanbornm sim, manter metade da Internet em seu repo parece mesmo uma ótima ideia.

Finishing Touch

Como alguém mencionou um utilitário que contaria o número de lugares onde try me economizaria tempo / esforço , decidi executá-lo em meus maiores projetos de trabalho, além de tudo o mais em meu antigo diretório GOPATH GitHub.

| Projeto | LOC * | tentar candidatos |
| ---------- | ------ | ---------------- |
| cal1 | 2047 | 3 |
| pump1 | 1030 0 |
| docs1 | 4576 | 8
| hugoutil | 604 1
| tudo o mais | 8452 | 23

  • Go code apenas, excluindo comentários, de acordo com o utilitário cloc .

Lembre-se de que o conteúdo de "todo o resto" inclui hacks rápidos e código que escrevi quando estava aprendendo Go.

Minha conclusão geral é que, pelo menos para mim, a proposta try não ajudaria a simplificar meu tratamento de erros em nenhum grau que valha a pena.

O maior motivo pelo qual adoro go é que suas especificações restringem os codificadores a um pequeno subconjunto de sintaxe disponível para outras linguagens. Por ser um conjunto de recursos tão pequeno, é fácil aprender todo o conjunto de recursos. Um futuro desenvolvedor provavelmente pode olhar meu código e saber o que eu fiz. Cada coisa nova adicionada à linguagem diminui a chance de que o futuro desenvolvedor saiba disso. O extremo da ladeira escorregadia é uma linguagem cuja complexidade torna difícil de grocar, como C ++ ou scala.
Eu gostaria de ver nenhuma adição de sintaxe para ir 1. Coloque-os no go 2 em vez disso.

@miekg adicione este link https://github.com/golang/go/issues/32825#issuecomment -506882164 à proposta. O exemplo desqualifica completamente toda a ideia desta palavra-chave try recente.

image

Eu concordo totalmente em deixar como está. É um pouco prolixo, mas é bastante simples de seguir.

Se eu pudesse apenas reduzir

if err := foo.x(a, b); err != nil {
  return err
}

if err := foo.y(); err != nil {
  return err
}

if err := foo.z(c); err != nil {
  return err
}

para algo como

if err := check foo.x(a, b), foo.y(), foo.z(c); err != nil {
  return err
}

talvez também seria ótimo sem alterar o idioma IMHO demais.

Se você estava falando sobre um tipo "Talvez", ele requer primeiro o tipo de variante.

Vamos manter if err != nil funcionando, é claro, não é tão detalhado e faz sentido no fluxo do código. Conforme você lê o código com esta construção, você sabe o que ela fará.
Vamos mantê-lo, não vamos adicionar try

Quando eu leio o código, quero que as linhas que fazem o trabalho sejam claramente legíveis, sem ou com um mínimo de material de tratamento de erros.

As 3 letras 'err' no mesmo nível são aceitáveis ​​para mim. Eu não gostaria de alguma função de 'verificação' envolvendo o código importante, porque o código importante estaria em um segundo nível (lembra-se de lisp?), E eu não gostaria de 'tentar' uma linha antes, porque o código importante viria indentado e na segunda linha.

res, err: = begin_job ()
se errar! = nulo {
handle_error ()
}

err = continue_job (res)
se errar! = nulo {
handle_error ()
}

Com este código, você pode ler o fluxo do caso de não erro lendo as primeiras linhas dos blocos (como leio os títulos dos documentos quando tenho que ler rapidamente)

Como alguém mencionou um utilitário que contaria o número de lugares onde try me economizaria tempo / esforço , decidi executá-lo em meus maiores projetos de trabalho, além de tudo o mais em meu antigo diretório GOPATH GitHub.

Projeto LOC * testar candidatos
cal1 2047 3
pump1 1030 0
docs1 4576 8
hugoutil 604 1
todo o resto 8452 23

  • Go code apenas, excluindo comentários, de acordo com o utilitário cloc .

Acho que try é mais necessário em programas maiores. Apenas desenhando de memória, eu acho que programas com tamanhos LOC em torno de 15-20k e superiores precisam deles mais porque é quando você pode começar a obter camadas que só precisam passar erros porque são apropriadamente especificadas e tratadas em um sistema fechado por tanto do lado de envio quanto de recebimento. No entanto, depende muito do tipo de programa. Eu provavelmente não usaria muito try em programas menores também

Acho que tentar é mais necessário em programas maiores.

Bom ponto. Tentei tryhard em heptio / contour, 28,7k linhas de texto de origem, tryhard encontrou 12 substituições.

Acho que tentar é mais necessário em programas maiores.

Bom ponto. Tentei tryhard em heptio / contour, 28,7k linhas de texto de origem, tryhard encontrou 12 substituições.

UAU! 12 vs 28,7 mil linhas, isso realmente precisa de uma palavra-chave dedicada!

Bem, estou mais interessado em seu ponto de vista sobre isso :

stat := try(try(os.Open(fileName)).Stat())

Acho que é mais comum se o seu programa for um pouco mais monolítico e não fizer parte de uma integração de serviço entre muitos serviços. Quando eu apenas procuro por fmt.errorf ou errors no github daquele repositório ( heptio/contour ), há poucos resultados, então é difícil obter uma visão geral rápida. Mas como eu disse que provavelmente varia muito de programa para programa, mesmo para programas maiores.

Digamos que você tenha um único programa que não use muitas bibliotecas externas. Então você pode ter um AuthorizationError específico (e você sabe que todos os erros retornados são específicos o suficiente com quaisquer erros IO já tratados e agrupados) que já contém os metadados do usuário e pode ser propagado sem alterar algumas camadas sem muitas alterações nas coisas que realmente precisam para tratá-los até a camada de solicitação.

Acho que é mais comum se o seu programa for um pouco mais monolítico e não fizer parte de uma integração de serviço entre muitos serviços. Quando eu apenas procuro por fmt.errorf ou errors no github daquele repositório, há poucos resultados, então é difícil obter uma visão geral rápida. Mas como eu disse, provavelmente varia muito de programa para programar, mesmo para programas maiores.

Digamos que você tenha um único programa que não use muitas bibliotecas externas. Então você pode ter um AuthorizationError específico que já contém os metadados do usuário e pode ser propagado sem alterar algumas camadas sem muitas alterações nas coisas que realmente precisam tratá-los até a camada de solicitação.

Você não tem uma ideia. As anotações são para encontrar facilmente a forma como o erro ocorreu. Da mesma forma, temos os.NotExist mas isso é apenas uma boa dica sobre o caminho do erro.

@thomasf aqui é outro ponto de dados, de uma cópia de trabalho de vários anos de juju / juju,

529628 linhas de origem, tryhard encontrou 1763 (0,3%) substituições.

Sim claro. Uma vez que você se envolveu com ambos, eles provavelmente não são grandes exemplos de maneiras diferentes de escrever programas. Não tenho tempo nem para tentar o programa tryhard atm e muito menos para executá-lo adequadamente em fontes variadas (o que pode ser impossível de coletar de qualquer maneira porque omite o código-fonte fechado se estiver sendo coletado via github)

529628 linhas de origem, tryhard encontrou 1763 (0,3%) substituições.

Como alguém (carece de fontes) disse sabiamente, try não torna mais fácil lidar com os erros. Isso torna mais fácil não lidar com eles.

Se você analisar o código e encontrar muitas substituições de try , tudo o que lhe diz é que o código não faz nada com os erros, exceto retorná-los. Provavelmente não é um bom código. Devemos tornar mais fácil para as pessoas serem preguiçosas e não se preocuparem com erros? Não é por isso que Go não tem exceções, para evitar exatamente isso?

Não vou tomar posição sobre isso. No entanto, o que estou descobrindo são suas poucas evidências de apoio para sugerir que

uma. há muitos locais onde try é aplicável a bases de código go existentes
b. o tratamento de erros em geral constitui uma parte significativa do SLOC, com base em minhas próprias medições e nos números discutidos pela equipe de Go, ref https://youtu.be/RIvL2ONhFBI?t=440 timecode 07:26

Se você analisar o código e encontrar muitas substituições de try , tudo o que lhe diz é que o código não faz nada com os erros, exceto retorná-los. Provavelmente não é um bom código. Devemos tornar mais fácil para as pessoas serem preguiçosas e não se preocuparem com erros? Não é por isso que Go não tem exceções, para evitar exatamente isso?

  1. Você está fazendo julgamentos de valor sobre o código teórico sobre o qual nada sabe, o que não é um bom hábito.
  2. Não vejo como try é mais fácil de usar indevidamente do que o que temos agora, go não tem recursos para forçar o tratamento de erros e é muito fácil já ignorá-lo. Para mim, try significa tornar o código mais fácil de ler SE ele não precisar lidar com erros.

Talvez não haja necessidade de try porque não será necessário em muitos lugares, mas vamos entreter a ideia e apresentar casos de uso em vez de ficarmos mal-humorados com isso ...

Não consigo pensar em muitas situações práticas como eu o usaria, mas também não existe ainda, então é difícil dizer se eu projetaria algumas coisas de forma diferente se existisse. Uma coisa que vem à mente é que eu poderia fazer é agrupar uma série de ações em uma função anônima como esta usando tentar e ainda manipular o erro antes de retorná-lo ao chamador. Isso poderia tornar alguns códigos muito mais legíveis.

var v1, v3 string
if err := func() error {
    try(onething())
    v = try(twothing())
    try(otherthing())
    v3 = try(somethingg())
}(); err != nil {
  ... handle error...
}

Acho que pode ser uma boa ideia neste momento escrever um site para manter os dados de tryhard em pacotes diferentes e visualizá-los. Talvez modificar um pouco golang / gddo (godoc.org) pode fazer o trabalho.

Prefiro deixar if err != nil sozinho. Mas se tivermos que adicionar algo para tratamento de erros, aqui está uma nova proposta que adiciona a palavra-chave throws para ele.

32852

Sem repetir alguns dos argumentos já apresentados aqui, eu concordo com o sentimento de deixar if err != nil como está.

A perspectiva que posso oferecer é a seguinte: como alguém que ensinou Go a centenas de novatos (tanto em programação quanto em Go de outras linguagens), if err != nil nunca foi um problema para eles. Os programadores experientes em meus workshops acham isso incomum no início, mas rapidamente aprendem a amar a natureza explícita do tratamento de erros em Go.

Existem preocupações maiores que podemos abordar no idioma e a reação clara da comunidade a esse problema diz que if err != nil não é uma delas.

Go é perfeito por muitos motivos. O principal deles é “if err! = Nil”. Pode parecer prolixo, mas para as pessoas que estão aprendendo a codificar, é mais fácil depurar seu código e corrigir o código.

@davecheney

Não vou tomar posição sobre isso. No entanto, o que estou descobrindo são suas poucas evidências de apoio para sugerir que

uma. há muitos locais onde try é aplicável a bases de código go existentes
b. o tratamento de erros em geral constitui uma parte significativa do SLOC, com base em minhas próprias medições e nos números discutidos pela equipe de Go, ref https://youtu.be/RIvL2ONhFBI?t=440 timecode 07:26

Temo que, no clima atual, quaisquer exemplos que encontrarmos sejam simplesmente descartados como "bem, esse provavelmente não é um bom código".

Aqui está um exemplo:

llorllale:~/go/src/github.com/hyperledger/fabric$ cloc --exclude-dir=vendor .
    2406 text files.
    2256 unique files.                                          
    3130 files ignored.

http://cloc.sourceforge.net v 1.60  T=6.69 s (272.8 files/s, 58350.9 lines/s)
--------------------------------------------------------------------------------
Language                      files          blank        comment           code
--------------------------------------------------------------------------------
Go                             1751          54365          34149         294005
YAML                             35            547           2171           2060
Bourne Shell                     26            354            325           1312
make                              3            135             96            418
CSS                               1             40             14            140
HTML                              3              7              5             63
Python                            1             50            103             57
Bourne Again Shell                1              1              6             50
Java                              3              7              4             26
XML                               2              1              4              2
--------------------------------------------------------------------------------
SUM:                           1826          55507          36877         298133
--------------------------------------------------------------------------------
llorllale:~/go/src/github.com/hyperledger/fabric$ tryhard -l . | grep -v vendor | less | wc -l
1417

Para ser justo, os dados sobre o número de locais encontrados por tryhard podem ser confundidos por convenções que exigem erros de empacotamento. Por exemplo, se a convenção da sua empresa é

if err != nil {
   return errors.Wrap(err) 
} 

...
if err != nil {
   return errgo.Notef(err, "error doing x") 
} 

isso não seria relatado por tryhard.

Temos essa convenção em minha empresa. Fazer uma simples pesquisa e substituição para reverter para os retornos de erro simples fornece os seguintes resultados:

Language                             files          blank        comment           code
---------------------------------------------------------------------------------------
Go                                    2488          40317          15901         297038

tryhard relata 2736 substituições, mas fazendo uma revisão manual do empacotamento restante parece que há uma subestimação por volta de 1850, então eu estimaria um total de ~ 4500 try uso em nossa base de código de 300k linhas.

(Pessoalmente, sou a favor da atual explicitação do tratamento de erros e não me importo.)

Por exemplo, se a convenção da sua empresa é
[embrulhe erros com uma mensagem personalizada]
isso não seria relatado por tryhard.

Esse é o ponto - a proposta try simplifica apenas if err != nil return err devoluções sem formatação, não oferece suporte a erros de empacotamento com uma mensagem e contexto personalizados.

A única repetitividade de if err != nil eu acredito que poderia ser corrigida tendo que especificar também os valores zero dos outros valores de retorno. A linguagem pode ser atualizada para suprimir isso. Por exemplo:

No Go de hoje, se eu tiver uma função com esta assinatura:

func add(x, y string) (int, error)

Em algum lugar da função, eu teria que escrever:

func add(x, y string) (int, error) {
    // ...
    if err != nil {
        return 0, err
    }

Forçando o escritor a repetir os mesmos valores zero em toda a função.

Seria muito mais fácil (e com pouco custo de verbosidade e legibilidade do erro) se a linguagem pudesse preencher automaticamente os valores zero para os valores de retorno sem erro:

func add(x, y string) (int, error) {
    // ...
    if err != nil {
        return ..., err
    }
    // ...
}
func main() {
    add("8", "beep") // returns 0, error(`strconv.ParseInt: parsing "beep": invalid syntax`)
}

Posso dizer por experiência com muitos códigos que interagem com consultas e chamadas do banco de dados, ter que repetir os valores zero nas funções é o único aspecto negativo do tratamento de erros no estilo Go. Caso contrário, concordo com o sentimento desta proposta: deixe if err != nil sozinho!

Observação: sim, valores de retorno nomeados podem _sort of_ realizar isso (https://play.golang.org/p/MLV8Y52HUBY), mas depois de implementar algumas funções em minhas próprias bases de código usando essa técnica, fui lembrado de quanto custa os valores de retorno nomeados -gun são; Sempre acabo ocultando o valor de retorno nomeado.

Por exemplo, se a convenção da sua empresa é
[embrulhe erros com uma mensagem personalizada]
isso não seria relatado por tryhard.

Esse é o ponto - a proposta try simplifica apenas if err != nil return err devoluções sem formatação, não oferece suporte a erros de empacotamento com uma mensagem e contexto personalizados.

É verdade, estava pensando na variante que permitisse adicionar uma string descritiva. A grande maioria (~ 4000/4500) de nossos retornos de erro são sem contexto errgo.Mask(err) , que eu estava considerando equivalente a sem descrição try() , mas atualmente seria uma redução em funcionalidade já que errgo adiciona informações de pilha e try não (ainda).

@ianlancetaylor há uma proposta aqui. @miekg está propondo que você, como um dos líderes de nossa linguagem, não busque mais a substituição de if err != nil por alguma outra construção que contradiga o espírito de tratamento de erros conforme decidido pelos autores originais do Go. Para mim, pessoalmente, parece que você está tentando afirmar a falta de importância dessa pergunta, movendo-a para golang-nuts vez de tratá-la como nossas outras propostas. Essa pode não ser sua intenção, mas é o impacto que sinto.

Nosso método de tratamento de erros é único e acredito que seja um valor agregado enorme em relação a outras linguagens. Mudou completamente a maneira como penso sobre os erros nos sistemas que construo e, como resultado, me tornei um engenheiro de software mais forte. Não quero que cedamos à minoria barulhenta, ou estranhos, no interesse de conseguir mais desenvolvedores Go. Acho que devemos adotar linhas rígidas em certas coisas, com a maneira que escolhemos para lidar com os erros sendo uma delas porque, no final das contas, nos torna melhores ao negociar a brevidade do código.

Esta é uma oportunidade para a equipe dentro do Google construir mais confiança e fé com a comunidade ou continuar a trajetória em que estamos atualmente, que não é boa para o idioma, o ecossistema ou seus usuários.

Peço que a Go Team aceite esta proposta como está, enquanto continua a buscar outras iterações de linguagem não relacionadas que são um valor agregado mais claro.

O rastreador pode não ter threading, mas eu pessoalmente prefiro ter a garantia de que esta proposta seja respondida em uma capacidade oficial e não relegada ao grupo do Google, onde pode desaparecer silenciosamente na obscuridade.

O assunto também já foi discutido no grupo do Google.

A versão atual do # 32437 não é satisfatória. O try () embutido esconde muitos caminhos de execução para olhos não treinados. A proposta original com check and handle era muito compreensível e a palavra-chave check se destacou.

Agora, o try () embutido parece uma função - não é óbvio que ele pode alterar o fluxo de controle. Também temos pânico (), mas está (acredito) sempre em uma linha própria, tem um nome de destaque e seu uso é escasso. try () por outro lado, pode se esconder dentro de uma expressão complexa.

@theckman Robert projetou as primeiras iterações de Go with Rob e Ken, e Robert e Russ se juntaram à equipe desde o início. Eles têm trabalhado no Go desde o início. Acho que podemos confiar que eles saberão se uma proposta "contradiz o espírito de tratamento de erros conforme decidido pelos autores originais do Go".

Não gosto do princípio de uma proposta que congelaria o tratamento de erros como é hoje. Tal proposta proibiria todas as propostas futuras sobre este tópico.

Por que não apenas aceitar iterar o design em vez disso? Tínhamos a proposta de verificar / manipular. Mas algumas desvantagens foram discutidas. Isso levou à proposta try. Algumas desvantagens desta proposta são discutidas agora. Talvez isso leve a outra proposta melhor, até que a abordagem certa seja encontrada.

Nosso método de tratamento de erros é único

O tratamento de erros em Rust é conceitualmente semelhante ao que fazemos em Go (erros são valores, fluxo de controle explícito, exceto que usamos vários valores de retorno quando eles usam tipos de soma). Rust teve o mesmo problema que Go com o tratamento de erros detalhado. Isso levou Rust a adicionar o try! macro e, eventualmente, o? operador. Eu diria que a comunidade Rust é ainda mais rígida do que a comunidade Go em relação ao tratamento de erros (os RFCs e as discussões de tratamento de erros são esclarecedores). Eles descobriram uma maneira de diminuir a verbosidade do tratamento de erros sem a ladeira escorregadia do mau tratamento de erros. Tenho certeza que nós também podemos.

a trajetória em que estamos atualmente e que não é boa para o idioma, o ecossistema ou seus usuários

Do que você está falando? Go está constantemente melhorando. É incrível ter acesso a uma linguagem tão grande, ferramentas e documentação gratuitamente (como na liberdade de expressão).

@theckman Robert projetou as primeiras iterações de Go with Rob e Ken, e Robert e Russ se juntaram à equipe desde o início. Eles têm trabalhado no Go desde o início. Acho que podemos confiar que eles saberão se uma proposta "contradiz o espírito de tratamento de erros conforme decidido pelos autores originais do Go".

Não gosto do princípio de uma proposta que congelaria o tratamento de erros como é hoje. Tal proposta proibiria todas as propostas futuras sobre este tópico.

Por que não apenas aceitar iterar o design em vez disso? Tínhamos a proposta de verificar / manipular. Mas algumas desvantagens foram discutidas. Isso levou à proposta try. Algumas desvantagens desta proposta são discutidas agora. Talvez isso leve a outra proposta melhor, até que a abordagem certa seja encontrada.

Nosso método de tratamento de erros é único

O tratamento de erros em Rust é conceitualmente semelhante ao que fazemos em Go (erros são valores, fluxo de controle explícito, exceto que usamos vários valores de retorno quando eles usam tipos de soma). Rust teve o mesmo problema que Go com o tratamento de erros detalhado. Isso levou Rust a adicionar o try! macro e, eventualmente, o? operador. Eu diria que a comunidade Rust é ainda mais rígida do que a comunidade Go em relação ao tratamento de erros (os RFCs e as discussões de tratamento de erros são esclarecedores). Eles descobriram uma maneira de diminuir a verbosidade do tratamento de erros sem a ladeira escorregadia do mau tratamento de erros. Tenho certeza que nós também podemos.

a trajetória em que estamos atualmente e que não é boa para o idioma, o ecossistema ou seus usuários

Do que você está falando? Go está constantemente melhorando. É incrível ter acesso a uma linguagem tão grande, ferramentas e documentação gratuitamente (como na liberdade de expressão).

A história do desenvolvimento de Rust mostra que os caras por trás dele não tinham ideia do que estavam fazendo. Eles basicamente copiaram os princípios de processamento de erros de Haskell, mas eles não são uma boa combinação para a programação imperativa (do mundo real?). A macro ? deles é apenas uma solução alternativa para o sistema de processamento de erros inicialmente falhado.

@ianlancetaylor há uma proposta aqui. @miekg está propondo que você, como um dos líderes de nossa linguagem, não busque mais a substituição de if err != nil por alguma outra construção que contradiga o espírito de tratamento de erros conforme decidido pelos autores originais do Go. Para mim, pessoalmente, parece que você está tentando afirmar a falta de importância dessa pergunta, movendo-a para golang-nuts vez de tratá-la como nossas outras propostas. Essa pode não ser sua intenção, mas é o impacto que sinto.

Nosso método de tratamento de erros é único e acredito que seja um valor agregado enorme em relação a outras linguagens. Mudou completamente a maneira como penso sobre os erros nos sistemas que construo e, como resultado, me tornei um engenheiro de software mais forte. Não quero que cedamos à minoria barulhenta, ou estranhos, no interesse de conseguir mais desenvolvedores Go. Acho que devemos adotar linhas rígidas em certas coisas, com a maneira que escolhemos para lidar com os erros sendo uma delas porque, no final das contas, nos torna melhores ao negociar a brevidade do código.

Esta é uma oportunidade para a equipe dentro do Google construir mais confiança e fé com a comunidade ou continuar a trajetória em que estamos atualmente, que não é boa para o idioma, o ecossistema ou seus usuários.

Peço que a Go Team aceite esta proposta como está, enquanto continua a buscar outras iterações de linguagem não relacionadas que são um valor agregado mais claro.

Eles não podem fazer nada sério com o sistema de tipo atual dos anos 60. Eles precisam finalmente pegar emprestado as ideias dos anos 80 em seu Go 2.0

Do que você está falando? Go está constantemente melhorando. É incrível ter acesso a uma linguagem tão grande, ferramentas e documentação gratuitamente (como na liberdade de expressão).

@ngrilly, essa última parte é provavelmente para uma discussão mais ampla. Sem inviabilizar esta proposta, mas encerrando esse comentário, há um sentimento crescente de desalinhamento entre os usuários e a liderança na comunidade / ecossistema.

Para o resto da discussão, não acho que adicionar mais sobrecarga cognitiva à sintaxe seja uma vitória. Estou feliz que eles encontraram algo que funcionou para eles, não somos eles.

Acabei de abrir uma proposta para a declaração if inline: https://github.com/golang/go/issues/32860

Referência: https://github.com/golang/go/issues/32825#issuecomment -506707539

O quão melhor este mundo se tornaria se todos enviando suas propostas sobre qualquer novo recurso do golang 2.0 que eles adorariam ter, também fornecessem um branch de seu fork de https://github.com/golang/go (e qualquer outro repositórios necessários) que implementa essa proposta.

Você não concorda?

@ av86743 Parece estar além do escopo desta proposta. Apresente uma proposta sugerindo esse curso de ação.

Eu vejo alguns desafios nisso, como o risco de muito esforço desperdiçado antes que alguém rejeite com base em algo no próprio documento de proposta. Então você gastou todo esse tempo em uma bifurcação que nem será revisada.

que tal esta sintaxe:

# call error handler
try callFunction(), errorHandler()

# error handler with anonymous function
variable := try callSomething(), func(err *Error) { # error handling }

@theckman Peço desculpas se parecer que minha sugestão de mover esta discussão para outro lugar parece que não é importante. Expliquei minhas razões em meu pedido e acredito que elas ainda estão de pé. A equipe Go considera as discussões da lista de e-mails, bem como as propostas.

Já que você mencionou "os autores originais do Go", acho que vale a pena ressaltar que a proposta de "teste" foi feita por @griesemer, que é um dos três autores originais do Go.

Eu concordo totalmente com esta proposta, acho que a única coisa que precisa ser mudada é ir fmt, make go fmt para permitir uma instrução if de linha.

Eu realmente quero uma linha de

if err != nil { return wrappedErr{err} }

em vez de três linhas de

if err != nil {
    return wrappedErr{err}
}

@ av86743 Parece estar além do escopo desta proposta. Apresente uma proposta sugerindo esse curso de ação.

@theckman: Você está me dizendo o que fazer, e isso não é apenas educado, é muito rude. Você pode tentar se posicionar da maneira que escolher, no entanto, nem eu nem, presumivelmente, ninguém aqui é seu macaco "vá buscar" para pular quando você diz isso.

Eu vejo alguns desafios nisso, como o risco de muito esforço desperdiçado antes que alguém rejeite com base em algo no próprio documento de proposta. Então você gastou todo esse tempo em uma bifurcação que nem será revisada.

Seria apenas um "esforço desperdiçado" para [... _descrição em uma linguagem inteiramente apropriada, omitida por uma questão de brevidade_ ...].

Para um programador, no entanto, isso seria um exercício trivial, mas útil e, ao mesmo tempo, um serviço à comunidade Go.

@ av86743 Acho que o que você sugeriu é uma ideia interessante e não queria que ela se perdesse como um comentário em um problema não relacionado. Se você não tem interesse em persegui-lo em uma capacidade oficial para consideração, peço desculpas por defender que você levante uma questão separada.

Mesmo que essa proposta específica venha de @griesemer , acho difícil acreditar que ele esteja fervendo de raiva interior por dez anos sobre a verbosidade de retornos de erros não embrulhados em Go. Ele é, no entanto, um excelente engenheiro, e os engenheiros apresentam soluções para os problemas (percebidos); é muito difícil detê-los. _Gostamos_ de resolver problemas. Basta cheirar um problema para começarmos a ter todos os tipos de ideias. Uma vez que esse processo de pensamento esteja bastante avançado, é difícil para qualquer um de nós abandonar nossas soluções putativas emocionalmente e considerar que _talvez não seja realmente um problema, afinal_. Afinal, tivemos um bebê, intelectualmente falando, e não é fácil simplesmente abandoná-lo.

Pelo que vale a pena, minha suspeita particular é que o processo de raciocínio da equipe Go sobre isso foi algo como:

  1. Go é extremamente popular e amplamente utilizado, por isso milhares de pessoas têm, naturalmente, comentários, sugestões e reclamações sobre ele.
  2. Você só pode resistir por um certo tempo. A equipe Go sente uma enorme pressão para fazer _algo_ sobre todo o barulho da comunidade.
  3. Isto é algo.

que tal adicionar uma função catch () em adiar para pegar se a tentativa foi encontrada algum erro como recover ().
exemplo:

func doSomthing()(err error){
    //return error
}

func main(){
    defer func(){
        if err:=catch();err!=nil{
            //found error
        }
    }()

    try doSomthing()
}

em muitas funções

func Foo() (err error) {
    defer func(){
        if err:=catch();err!=nil{
            //found error
        }
    }()

   try{
    file1 := open("file1")
    defer file1.Close()
    file2 := open("file2")
    defer file2.Close()
   }
   //without try
    file3,err := open("file3")
    defer file3.Close()
 }

que tal adicionar uma função catch () em adiar para pegar se a tentativa foi encontrada algum erro como recover ().
exemplo:

func doSomthing()(err error){
    //return error
}

func main(){
    defer func(){
        if err:=catch();err!=nil{
            //found error
        }
    }()

    try doSomthing()
}

em muitas funções

func Foo() (err error) {
    defer func(){
        if err:=catch();err!=nil{
            //found error
        }
    }()

   try{
  file1 := open("file1")
  defer file1.Close()
  file2 := open("file2")
  defer file2.Close()
   }
   //without try
    file3,err := open("file3")
    defer file3.Close()
 }

Como isso ajuda a lidar com cada erro de maneira separada?

Alguns esclarecimentos:

  1. A proposta try não apresenta uma nova sintaxe nem uma nova palavra-chave, ao contrário do que algumas pessoas têm afirmado neste tópico. Ele meramente apresenta um novo built-in, sobre a alteração mínima que alguém pode ser capaz de fazer para adicionar uma nova funcionalidade. Por favor, seja preciso ao discutir isso, porque é importante. Há uma grande diferença entre adicionar uma nova sintaxe e uma nova palavra-chave e um embutido. O primeiro é uma mudança importante, este último uma adição relativamente pequena. O que a proposta try sugere é uma adição relativamente pequena.

  2. Eu concordo com @ianlancetaylor que esta discussão é melhor realizada em outro lugar (nozes de golang). Não há nenhuma proposta aqui.

  3. Na verdade, @bitfield , não tenho nenhuma "raiva interna sobre a verbosidade dos retornos de erros não embalados em Go" , obrigado :-) Mas acho que a verificação de erros é mais detalhada do que talvez necessário; e o fato de que esse mesmo sentimento foi levantado pela comunidade repetidamente é um claro indicador de que nós (a equipe Go) não estamos sozinhos com essa crença. Eu não chegaria a dizer que há muita pressão para fazer "algo" - estamos trabalhando nisso há muito tempo e estamos muito satisfeitos em esperar pela abordagem "certa" .

A proposta try é sobre a solução mínima que encontramos (com ajuda significativa de contribuições da comunidade) que aborda o problema de verificação de erros. A proposta try é muito explícita sobre o fato de que _não ajudará em nada_ se cada teste de erro exigir o tratamento do erro de alguma maneira específica. try só ajuda quando todos os erros em uma função são testados e tratados da mesma maneira (e então recomendamos usar defer ), ou quando eles são simplesmente retornados. É difícil ser mais explícito aqui, mas vamos repetir o que afirma a try não ajudará em todos os cenários de erro. Isso ajuda em um número significativo de casos. Para todo o resto, use instruções if .

@griesemer try é muito sujeito a erros: não há RAII no Go, então não podemos simplesmente deixar a função em muitos casos.

@sirkon , não tenho certeza de como RAII é relevante para esta discussão. try substitui os padrões existentes de if ..., err := f(); err != nil { return ..., err } por um ... := try(f()) . Se havia um bug de liberação de recursos usando try , então certamente existia de antemão também. A introdução de try não melhora nem evita o bug de liberação de recursos.

@sirkon , não tenho certeza de como RAII é relevante para esta discussão. try substitui os padrões existentes de if ..., err := f(); err != nil { return ..., err } por um ... := try(f()) . Se havia um bug de liberação de recursos usando try , então certamente existia de antemão também. A introdução de try não melhora nem evita o bug de liberação de recursos.

Leia o tópico, havia um exemplo:

info := try(try(os.Open(fileName)).Stat())

@sirkon Já vi esse exemplo várias vezes. Eu concordo que é interessante. Mas vamos pensar um pouco mais sobre isso. O try embutido proposto é basicamente um açúcar sintático para um certo tipo de clichê encontrado no código Go. Portanto, podemos converter esse exemplo para o código original.

    f, err := os.Open(fileName)
    if err != nil {
        return err
    }
    info, err := f.Stat()
    if err != nil {
        return err
    }

Esse código tem o mesmo bug. Certamente já vi um código assim. Não é óbvio para mim que o try embutido torne esse bug mais fácil de escrever ou mais difícil de ver.

[Parece que @ianlancetaylor chegou antes de mim.]

@sirkon Este bug já é possível, try ou não - Go não o impede de escrever código incorreto. Ou inverter a situação, usar um código incorreto como uma razão pela qual try não deveria ser permitido não é um argumento convincente. Em vez disso, go vet deve sinalizar casos problemáticos.

defer é o idioma Go para limpar quando uma função retorna, e isso funciona bem. A abordagem correta aqui, claro, seria:

f := try(os.Open(filename))
defer f.Close()
info := try(f.Stat())

compare isso com:

f, err := os.Open(filename)
if err != nil {
   return ..., err
}
defer f.Close()
info, err := f.Stat()
if err != nil {
   return ..., err
}

Usando try a fonte concentra-se na preocupação principal: obter as informações de um arquivo. Usando a abordagem tradicional, a maior parte do código-fonte se preocupa com possíveis erros; e é tudo igual. Mesmo se quisermos decorar o erro, a abordagem try funciona perfeitamente:

defer errd.Wrap(&err, "failed to do X for %s", filename)
f := try(os.Open(filename))
defer f.Close()
info := try(f.Stat())

usando algo como um pacote errd (consulte a edição # 32676).

@griesemer
Minha futura auto-revisão de código ainda continua gritando que o mecanismo de fluxo de controle deve estar em sua própria linha. Esta abordagem pode ser válida (sem aplicação) dentro da proposta atual? Além da legibilidade, a refatoração para uma lógica de tratamento de erros mais detalhada é facilitada.

defer errd.Wrap(&err, "failed to do X for %s", filename)
f, err:= os.Open(filename)
try(err) // check is so much a better term
defer f.Close()
info, err := f.Stat()
try(err)

Além disso, essa abordagem para handle parece ótima aqui, mas vários adiamentos não farão uma bagunça? Ou haverá uma espécie de sync.Once com escopo de função (não vi um esclarecimento no problema errd)? Em caso afirmativo, as funções anônimas teriam seu próprio escopo? E sombreando ... eesh - quem vem primeiro, o que vem depois?

Parece que haverá dois "modos" de escrever código Go. Diga que não é assim.

@griesemer Embora você esteja certo, o bug também é possível hoje, sinto que ele se tornará mais prevalente no futuro com a implementação atual do try. Alguém vindo de quase _qualquer_ linguagem popular que eu possa (). Think.Of (Has) Chaining (). Of (Methods) arraigados neles para o melhor ou para o pior. Todas essas linguagens fornecem seus próprios idiomas que tornam os padrões naturais e seguros. Eles atingiram um bloqueio imediato com Go, pois o sistema de tipos os força a atribuir todos os valores com uma condição de falha associada, simplesmente não há um padrão razoável para evitar isso (ou a proposta try não existiria).

Se esta proposta for aceita, eles serão uma forma de evitar o boiler plate if err , permitindo que escrevam o código de uma forma que lhes seja familiar. Exceto que eles terão quase uma década de código Go em respostas stackoverflow, postagens de blog etc. que foram escritos antes de try ser criado. Eles aprenderão rapidamente a simplesmente abandonar a instrução err e if com try. Eles querem um tamanho de arquivo para que possam colar o código envolvido em try até que possam acessar o campo que desejam, exatamente como Stat() na proposta de teste. É um padrão ao qual eles estão acostumados, então é natural que o apliquem enquanto escrevem Go. Dado que o Go OS mais direcionado trata tudo como um arquivo, é justo presumir que mais vazamentos de recursos ocorrerão.

O que me leva ao motivo pelo qual discordo veementemente da afirmação "você já pode fazer isso hoje" - porque simplesmente não pode. Claro - você pode vazar um identificador de arquivo. Mas Go não dá a um programador a oportunidade de ignorar o identificador no escopo e, portanto, também vazar o arquivo. Cada identificador f ignorado é um identificador de arquivo que vazou. O uso do recurso _requer_ que certas expressões idiomáticas proeminentes no ecossistema Go sejam quebradas. Assim, a introdução do recurso conforme projetado hoje aumenta comprovadamente o risco de vazamento de recursos no Go.

Dito isso, como mencionei em https://github.com/golang/go/issues/32825#issuecomment -506882164 Eu na verdade apoiaria try se alguns pequenos ajustes fossem feitos, acho que a mudança seria um bem-vinda adição à linguagem. Tudo o que acho que o try precisa é torná-lo válido apenas no RHS de uma atribuição e não permitir que o valor de retorno seja endereçável. Faça com que os "bons" exemplos de uso de try (tendem a ser um try por linha) sejam a "única" maneira de usar try, ou seja:

info := try(try(os.Open(filename)).Stat()) // compiler error
f := try(os.Open(filename)) // valid
// we were forced to assign f, so we still have an identifier to Close (serve linters and users alike)
defer f.Close()
info := try(f.Stat())
a, b := try(strconv.Atoi("1")), try(strconv.Atoi("2")) // also valid 
a, b := try(strconv.Atoi("1"), strconv.Atoi("2")) // maybe?
a, b := try strconv.Atoi("1"), strconv.Atoi("2")

Acho que isso vai se encaixar naturalmente melhor na linguagem, manter todos os benefícios atuais de try (exceto aninhá-los, se você considerar isso um benefício) sem nenhuma das desvantagens. Não creio que aninhar ou tentar favoreça ninguém, poupa muito pouco, mas oferece possibilidades ilimitadas de abusos. Não estou me sentindo particularmente mal hoje, então isso é o melhor que posso fazer:

total := try(try(os.Open(filename)).Stat()).Size() + try(strconv.Atoi(try(ioutil.ReadAll(os.Stdin))))

Mas _nós_ pensaremos no pior, se você nos deixar.

@daved Colocar try(err) em uma segunda linha é totalmente compatível com a proposta try : try simplesmente quer um argumento que avalie um ou mais valores onde o último valor é de digite error , que fica naturalmente satisfeito quando você escreve try(err) .

Não tenho certeza se entendi sua preocupação em relação a defer - se houver necessidade de manipuladores diferentes, defer pode não ser a escolha certa; em vez disso, o tradicional if pode ser necessário (conforme especificado na proposta).

@cstockton Concordo que try aninhados podem ser muito problemáticos; mas também acredito que se tivéssemos try , a maior parte do código se pareceria com os exemplos (válidos) que

Por uma questão de estilo, não colocamos restrições como a que você está preferindo na linguagem - usamos go vet para isso. No final, para o software escrito, o efeito é o mesmo. Mas, por não tê-lo na língua, não estamos nos amarrando. É difícil acertar essas restrições e elas tornam a especificação desnecessariamente complicada. É simplesmente muito mais fácil ajustar go vet e torná-lo mais inteligente à medida que aprendemos mais do que ajustar o idioma.

@griesemer Obrigado pelo esclarecimento. No exemplo de código, se a primeira linha fosse var err error , a quebra afetaria potencialmente os dois erros verificados? Já vi comentários sobre o sombreamento ser uma preocupação com a qual podemos lidar no futuro. Como isso / pode se relacionar com isso?

que tal adicionar uma função catch () em adiar para pegar se a tentativa foi encontrada algum erro como recover ().
exemplo:

func doSomthing()(err error){
    //return error
}

func main(){
    defer func(){
        if err:=catch();err!=nil{
            //found error
        }
    }()

    try doSomthing()
}

em muitas funções

func Foo() (err error) {
    defer func(){
        if err:=catch();err!=nil{
            //found error
        }
    }()

   try{
    file1 := open("file1")
    defer file1.Close()
    file2 := open("file2")
    defer file2.Close()
   }
   //without try
    file3,err := open("file3")
    defer file3.Close()
 }

Como isso ajuda a lidar com cada erro de maneira separada?

como outros usuários comprometidos

func Foo() (err error) {
    defer func(){
        if err:=catch();err!=nil{
            //found error
        }
    }()

    file1 :=try open("file1")
    defer file1.Close()
    file2 :=try open("file2")
    defer file2.Close()

        //without try
       file3,err := open("file3")
       defer file3.Close()
 }

@daved Nesses exemplos, a suposição era que err é o nome do resultado error . try sempre definirá essa variável de erro de resultado, independentemente do nome (ou ausência de um nome). Se você tem uma variável local chamada err então é uma variável diferente. Se você quiser se referir ao erro do resultado, ele deverá ter um nome diferente. Observe que isso já não é válido:

func f(...) (..., err error) {
   var err error // << err already declared
   ...

Por outro lado, se você escrever:

func f(...) (..., err error) {
   a, b, ... err := g() // redeclaration of err
   ...

o err na atribuição é simplesmente o mesmo nomeado na lista de parâmetros de resultado. Não há nada diferente aqui do que já acontecia há muito tempo.

PS: Provavelmente devemos parar de sequestrar esse problema para discussões de try e voltar para a proposta original - ela será desbloqueada e aberta para discussão amanhã (1º de julho) novamente.

@godcong Uma função catch() (ou semelhante) só permitirá que você obtenha o erro, não configurá-lo (e geralmente deseja-se definir o erro da função envolvente em uma função adiada operando como um manipulador de erro) . Ele poderia funcionar fazendo catch() retornar um *error que é o endereço do valor de retorno de erro da função envolvente. Mas por que não usar apenas o nome do resultado do erro em vez de adicionar um novo mecanismo à linguagem? Veja também a proposta try onde isso é discutido.

Além disso, consulte o PS acima .

@griesemer

É difícil ser mais explícito aqui, mas vamos repetir o que afirma a proposta: tentar não ajudará em todos os cenários de erro. Isso ajuda em um número significativo de casos. Para todo o resto, use instruções if.

Acho que essa é exatamente a falha fatal da proposta try() : onde antes havia uma e apenas uma maneira de fazer a verificação de erros, agora serão duas, mescladas por todo o código-base. Além disso, pelo menos para a base de código em que estou trabalhando, menos de 20% de if err != nil pode ser substituído por try() , que embora não seja insignificante, não parece valer a pena o suficiente para criar um dividido em estilos de tratamento de erros.

Pessoalmente, eu teria preferido uma construção de tratamento de erros que fosse poderosa o suficiente para substituir 95% de todos os if err != nil casos. Acho que é o que muitas pessoas também gostariam.

@griesemer Concordo que as pessoas aprenderão e o ferramental será uma obrigação, pois os guias de estilo, boas práticas, exemplos, documentação e assim por diante a que você se refere estarão todos desatualizados. Acho que está claro que try, conforme proposto atualmente, apresenta novas maneiras sutis de escrever software incorreto. O que não está claro é como descartar esse fato sob a premissa de que as pessoas sempre podem escrever software incorreto é um contra-argumento válido?

Vou mudar os ângulos aqui, qual é o caso de uso para aninhar declarações try que seja forte o suficiente para os efeitos colaterais potenciais que descrevi? Como o código Go hoje se beneficia ao permitir que um grupo de parênteses separados por tentativa e aninhado em cadeia apareça descontroladamente em qualquer lugar? Meu palpite é que não e acho que ninguém pediu para tentar o aninhamento, ele veio com a proposta porque foi implementado como uma função. Você não deseja adicionar nenhuma restrição, como remover o aninhamento / ser endereçável para limitar o abuso de aninhamento ou erros sutis porque tornaria a especificação da linguagem mais complexa. O tema aqui é para evitar a introdução de complexidade na linguagem ou para adicionar uma maneira melhor de lidar com os erros?

Porque se o objetivo aqui é não tornar a especificação da linguagem mais complexa, a escolha é clara: não adicionar uma nova função com retornos e parâmetros genéricos, é aninhada arbitrariamente, fornece fluxo de controle e altera a aridade dos valores que é fornecida ( mas apenas se eles satisfizerem uma interface interna específica) e provavelmente mais do que estou esquecendo, por exemplo, uma função com complexidade sem precedentes. Se o objetivo é melhorar o tratamento de erros, acho que isso deveria ser feito sem introduzir novas maneiras de produzir erros.

@sirkon Já vi esse exemplo várias vezes. Eu concordo que é interessante. Mas vamos pensar um pouco mais sobre isso. O try embutido proposto é basicamente um açúcar sintático para um certo tipo de clichê encontrado no código Go. Portanto, podemos converter esse exemplo para o código original.

    f, err := os.Open(fileName)
    if err != nil {
        return err
    }
    info, err := f.Stat()
    if err != nil {
        return err
    }

Esse código tem o mesmo bug. Certamente já vi um código assim. Não é óbvio para mim que o try embutido torne esse bug mais fácil de escrever ou mais difícil de ver.

É óbvio para mim o fluxo tradicional "mais lento" deixa mais espaço para notar que o arquivo deve ser fechado e isso try provoca esse tipo de vazamento, pois as pessoas tendem a preferir atalhos em vez de caminhos longos.

@godcong Uma função catch() (ou semelhante) só permitirá que você obtenha o erro, não configurá-lo (e geralmente deseja-se definir o erro da função envolvente em uma função adiada operando como um manipulador de erro) . Ele poderia funcionar fazendo catch() retornar um *error que é o endereço do valor de retorno de erro da função envolvente. Mas por que não usar apenas o nome do resultado do erro em vez de adicionar um novo mecanismo à linguagem? Veja também a proposta try onde isso é discutido.

Além disso, consulte o PS acima .

O sistema de tipos de Go ficou preso nos anos 60 e, portanto, naturalmente incapaz de lidar bem com casos extremos. Se você tivesse visão de futuro o suficiente para pegar emprestadas as ideias dos anos 80, você teria métodos para controlar fluxos sutis sujeitos a erros. Você está tentando construir um edifício de vidro e metal em uma vila medieval agora: o ruim é que essas vilas medievais não têm eletricidade e encanamentos de água, portanto, você também não terá.

Será interessante ver até que ponto quaisquer recursos de erro novos e aprimorados serão empregados no próprio golang/go . Se for assim.

Também será interessante ver se go2 fmt terá uma opção de saída simples go1.x .

Por experiência própria, desde que adicionei contexto ao erro retornado por:

import "github.com/pkg/errors"
func caller(arg string) error {
  val, err := callee(arg)
  if err != nil {
    return errors.Warpf(err, "failed to do something with %s", arg)
  }

  err = anotherCallee(val)
  if err != nil {
    return errors.Warpf(err, "failed to do something with %s", val)
  }

  return nil
}

a equipe de suporte raramente precisa da minha opinião para problemas que surgem na produção.

IMHO, acredito que melhorar a manipulação de erros não é reduzir o código clichê, mas fornecer uma maneira mais conveniente de adicionar contexto aos erros. Ainda não consigo encontrar uma boa maneira sensata de usar try ().

Pode adicionar contexto em adiar:

func caller(arg string) (err error) {
  defer func() {
    switch t := err.(type) {
      case CalleeErr:
        err = errors.Wrapf(err, "failed to do something with %s", arg)
      case AnotherCalleeErr:
        err = errors.Wrapf(err, "failed to do something with %s", val)
    }
  }()

  val := try(callee(arg))
  try(anotherCallee(val)
  return nil
}

Não parece economizar muita digitação, mas sacrificamos a legibilidade e o desempenho.

Pode acabar usando try () desta forma:

func caller(arg string) error {
    val, err := callee(arg)
    try(errors.Warpf(err, "failed to do something with %s", arg))

    err = anotherCallee(val)
    try(errors.Warpf(err, "failed to do something with %s", val))

    return nil
  }

Reduz algumas linhas, só isso.

para mim, a maioria das soluções para esse problema parece quebrar a única coisa que eu pensei que fosse separada de outras linguagens que usam exceções: o mecanismo de tratamento de erros não é usado como fluxo de controle. A maioria dessas soluções adiciona alguma forma de fluxo de controle (verificar / manipular, tentar, capturar, esperar), ponto em que eu acho que a equipe Go pode também adicionar exceções e encerrar o dia.

Embora eu adorasse se pudéssemos ter um caso especial de if e return que se assemelha a rubi

return err if err != nil

PS Perdoe minha inexperiência em design de linguagem, se eu disse algo estúpido, fique à vontade para apontar e me educar.

que tal adicionar uma função catch () em adiar para pegar se a tentativa foi encontrada algum erro como recover ().
exemplo:

func doSomthing()(err error){
    //return error
}

func main(){
    defer func(){
        if err:=catch();err!=nil{
            //found error
        }
    }()

    try doSomthing()
}

em muitas funções

func Foo() (err error) {
    defer func(){
        if err:=catch();err!=nil{
            //found error
        }
    }()

   try{
  file1 := open("file1")
  defer file1.Close()
  file2 := open("file2")
  defer file2.Close()
   }
   //without try
    file3,err := open("file3")
    defer file3.Close()
 }

Como isso ajuda a lidar com cada erro de maneira separada?

como outros usuários comprometidos

func Foo() (err error) {
    defer func(){
        if err:=catch();err!=nil{
            //found error
        }
    }()

  file1 :=try open("file1")
  defer file1.Close()
  file2 :=try open("file2")
  defer file2.Close()

        //without try
       file3,err := open("file3")
       defer file3.Close()
 }

No seu exemplo, você lida com todos os erros pelo mesmo deffer. O que acontece se você quiser adicionar uma mensagem personalizada e informações personalizadas ao erro?

@ianlancetaylor Alguém sugeriu aumentar o operador ": =" para suportar "retornos inferidos" - Basicamente, comporte-se exatamente como tentar sem uma chamada de função. Isso resolveria muitas preocupações que tenho visto em ambos os lados:

  • try como um nome para o recurso tem sido controverso, enquanto implementarmos isso como uma função, estaremos presos a dar a ele um nome pelo qual não tenho certeza se alguém se sentirá 100% bem a respeito.
  • try faz um número sem precedentes de coisas, tem entrada como append() e afeta o fluxo de controle como panic() enquanto toma o lugar de um padrão onipresente ( if err != nil ) .
  • try aninhamento de
  • try é implementado como uma função para manter a compatibilidade com versões anteriores

Acho que se simplesmente inferirmos retornos como fazemos tipos, as coisas parecem concisas e parecem mais "Go" como:

f, err := os.Open(filename)
if err != nil {
   return ..., err
}
defer f.Close()

info, err := f.Stat()
if err != nil {
   return ..., err
}

_Insira aqui a descrição correta da ciência da computação para esse comportamento_, até então se contentar com retornos inferidos por meio de declarações de variáveis ​​curtas :

f := os.Open(filename)
defer f.Close()
info := f.Stat()

O que parece muito mais organizado do que:

f := try(os.Open(filename))
defer f.Close()
info := try(f.Stat())

Isso resolve todas as preocupações que listei acima, embora (na minha opinião) me sinta um pouco mais "goste" e mantenha a compatibilidade com versões anteriores. Parece um pouco mais fácil de explicar também, o retorno "implícito" na chamada de função para try() parece realmente fora de lugar dado o significado onipresente de try em todas as outras linguagens. Não posso falar 100% para implementação, mas parece que isso poderia ser feito com praticamente o mesmo esforço? O AssignStmt poderia ter um campo adicionado que especifica quais expressões no LHS omitiram seus valores de erro para informar a mesma compilação de back-end como um builtin?

Gosto da verificação de erros no estado em que se encontra, mas se esse é realmente um problema que deve ser resolvido, acho que uma nova palavra-chave é provavelmente uma solução melhor. Qualquer solução provavelmente envolverá mudanças no fluxo de controle, e é melhor tornar isso o mais óbvio possível.

Neste exemplo, a condição on é avaliada toda vez que err é definida.

func example() (foo int, err error) {
    on err != nil {
        return foo, err
    }

    foo, err = calcThis()
    foo, err = calcThat(foo)

    return
}

Isso funciona sem declarar nomes para valores de retorno na assinatura da função também.

func example() (*int, error) {
    var err error

    on err != nil {
        return nil, err
    }

    foo, err = calcThis()
    foo, err = calcThat(&foo)

    return &foo, nil
}

Isso também pode ser definido várias vezes. Aqui está um exemplo inventado:

func example() (*int, error) {
    var err error

    on err != nil {
        return nil, err
    }

    foo, err = calcThis()

    on err != nil {
        return &foo, err
    }

    foo, err = calcThat(&foo)

    return
}

Aos meus olhos, isso mantém o estilo e a legibilidade de Go, ao mesmo tempo que deixa claro o que está acontecendo em cada etapa (uma vez que você entende o paradigma), uma vez que está inserindo efetivamente essa condição após cada ocorrência de err sendo definida.

Você pode até fazer o seguinte:

func example() (foo int, err error) {
    var message string

    on err != nil {
        return foo, errors.Wrap(err, message)
    }

    message = "failed to calc this"
    foo, err = calcThis()

    message = "failed to calc that"
    foo, err = calcThat(foo)

    return
}

Finalmente, isso tem aplicabilidade além do tratamento de erros. Quer retornar se foo == 0? Outro exemplo inventado:

func example(i int) bool {
    on x == 0 {
        return false
    }

    x = calcSomeInt(i)
    return true
}

@cstockton

Eu estava prestes a protestar que seria um pouco fácil omitir erros dessa forma, mas:

  • Isso já é um "pegadinha" com funções que retornam erros _apenas_, como json.Unmarshal . Os bons revisores de código do IME aprendem a cuidar disso muito rapidamente.
  • Ainda seria um erro de sintaxe em funções que não retornam valores de erro, como http.Handler métodos.
  • Se você soubesse como lidar com o erro, estaria pensando sobre isso, o anteciparia e provavelmente capturaria o valor do erro de qualquer maneira _para que_ pudesse lidar com ele.
  • Se sua intenção era apenas devolver quaisquer erros encontrados para torná-los problema de outra pessoa (o chamador), isso consegue, com a pequena desvantagem de que você perde a oportunidade de incluir explicitamente mais anotações para o contexto.

Então, depois de pesar todos os prós em que posso pensar, e não notar qualquer lado negativo óbvio, estou ... em cima do muro. Suspeito que haja desvantagens não óbvias que não considerei. Mas estou começando a gostar de onde isso vai dar, por pouco que isso valha para alguém.

Espero que esta seja a maneira certa de responder e não estou cometendo um
faux-pas sério.

Em 01/07/19, Chris Stockton [email protected] escreveu:

@ianlancetaylor Alguém sugeriu aumentar o operador ": =" para oferecer suporte
"retornos inferidos" - Basicamente se comporta exatamente como tentar sem uma função
ligar. [...]

O que acho intrigante neste caso é que temos algo
análogo ao paradigma da "vírgula OK", onde agora omitir o "err"
cessionário é permitido em algumas circunstâncias bem definidas. Que vale a pena
notando, mas claramente não é suficiente por si só para tornar isso um válido
proposição.

Lucio.

Este bug já é possível, tente ou não - Go não o impede de escrever códigos ruins. Ou, invertendo a situação, usar um código incorreto como uma razão pela qual a tentativa não deve ser permitida não é um argumento convincente. Em vez disso, go vet deve sinalizar casos problemáticos.

@griesemer Não concordo. Embora possa salvar pressionamentos de tecla, a aritmética de ponteiros foi excluída do Go com a premissa de que torna mais fácil escrever códigos corrompidos e corrompidos. Acho que esse é o mesmo tipo de recurso que tornará os bugs mais difíceis de detectar.

data := try(ioutil.ReadAll(try(os.Open("foo.txt"))))

O exemplo típico com try usa exemplos como o acima, que para mim obviamente vaza um descritor de arquivo. (O fato de Go procurar e fechar esses descritores no tempo de execução sem o conhecimento dos usuários na implementação atual é algo que pode nos dar conforto, mas há um exemplo melhor de qualquer maneira).

data := try(ioutil.ReadAll(try(http.Get("http://example.com/")).Body))

O código acima é lido como um código correto, mas ignora o requisito de que o corpo da resposta seja lido por completo e, em seguida, fechado no caminho feliz. Se você olhar por muito tempo, a elegância nojenta do exemplo incorreto deve deixar claro que veremos esses tipos de bugs no futuro.

Como alguém que revisa mais do que escreve código neste ponto, eu preferiria que o Go não adicionasse recursos que tornassem o erro tão tentador.

@ jesse-amano O uso do operador de atribuição realmente impede que esse caso seja possível, sem uma instrução de atribuição explícita, o seguinte se comporta exatamente como hoje, ou seja:

json.Unmarshal(...)
(http.Handler)(h).ServeHTTP(w, r)

Já os valores que apenas retornam um erro são elegíveis para serem retornados como return json.Unmarshal(...) e também podem ser representados na forma mais compacta, uma vez que não há necessidade de um valor existir fora do bloco if.

if err := json.Unmarshal(...); err != nIl {
    return err
} 

O que acho intrigante neste caso é que temos algo análogo ao paradigma da "vírgula OK", onde agora omitir o cessionário "errar" é permitido sob algumas circunstâncias bem definidas. Vale a pena notar, mas claramente não é suficiente por si só para tornar esta uma proposição válida.

O comportamento seria idêntico ao tentar sem parênteses ou aninhamento e encadeamento arbitrários. Acho que será difícil encontrar algo que a maioria das pessoas ache natural sem quebrar a linguagem. Admito que, uma vez que parece que os autores de Go estão bastante determinados a adicionar este tipo de recurso ao Go, estou realmente procurando alternativas porque estou absolutamente convencido de que try em sua forma atual não é uma boa opção para Go. Esta é a coisa mais próxima que posso pensar que não quebrará o BC, mas talvez ainda não pareça certo para pessoas suficientes. De qualquer forma, espero que a proposta try seja negada ou alguém apareça com algo que muito mais pessoas possam concordar.

edit: @ jesse-amano Eu perdi completamente o seu ponto, desculpe! Suponho que estar dentro de uma função que não retorna um erro exibiria um erro de compilação de incompatibilidade de contagem de atribuição típico? Imagino que try provavelmente apresentaria um novo tipo de mensagem de erro do compilador para isso.

@beoran Em relação a https://github.com/golang/go/issues/32825#issuecomment -507126700: O tratamento de erros já é diferente de situação para situação: às vezes retornamos o erro inalterado, às vezes retornamos um erro decorado, às vezes agimos após um erro, e às vezes nós (podemos corretamente) ignorar um erro. A única coisa que todos eles compartilham (exceto quando ignoramos o erro) é o teste err != nil (que já podemos fazer de mais de uma maneira). Por mais que seja bom para um novo recurso de linguagem capturar todos esses casos (ou 95% deles), tal construção tem grande probabilidade de interferir de maneiras não ortogonais com outras construções de controle que já temos. Ou seja, o melhor que podemos esperar é tornar alguns desses casos (talvez 20%, talvez 50% deles) melhores.

@cstockton Com relação a https://github.com/golang/go/issues/32825#issuecomment -507136111: Se try aninhados são o único bloqueio restante e go vet não é bom o suficiente , Acho que podemos considerar a proibição de try 's aninhados - isso seria fácil. Mas no momento eu acho que ainda estamos na fase FUD (medo, incerteza e dúvida), com praticamente ninguém tendo realmente experimentado try seriamente. Observo que as pessoas que o fizeram relataram positivamente.

PS: O problema try está aberto novamente para feedback. Vamos continuar por aí.

@cstockton Oh, absolutamente. Para esclarecer, o que eu queria era que _já_ é uma má prática chamar funções como json.Unmarshal sem capturar o valor do erro na maioria dos casos, mas geralmente não é considerada uma má prática para defer file.Close() vez disso de defer func() { err = file.Close(); if err != nil { ... }; }() , e aprendemos a diferenciar facilmente. Assim será (provavelmente) com a mudança proposta. Inicialmente, estremeci ao pensar em alguém usando inocentemente foo := strconv.ParseFloat(bar, 64) quando pretendia lidar com o erro, mas após uma breve consideração, não acho realmente que seja um problema, afinal.

@sirkon Sobre https://github.com/golang/go/issues/32825#issuecomment -507167388: Por favor, deixe tais declarações claramente não qualificadas fora da discussão - elas não têm lugar aqui. Só para constar, muitas idéias em Go são, na verdade, dos anos 80 (pacotes, compilação separada, etc.) em vez dos anos 60 e muitas são muito mais novas (goroutines, interfaces). Essas ideias ainda podem parecer antigas, mas resistiram ao teste do tempo (pelo menos o pouco tempo que CS existe).

@turtleDev Em relação a https://github.com/golang/go/issues/32825#issuecomment -507231353: Go trata de exceções e é feito com panic e defer e recover - simplesmente não o chamamos de "tratamento de exceções" porque esse termo vem com uma conotação que é enganosa para Go. Mas só para ficar claro, Go pode fazer tudo o que raise com try-catch pode fazer, e muito mais (porque em contraste com try-catch , defer pode ser usado condicionalmente). Obrigado.

@sorenvonsarvort Em relação a https://github.com/golang/go/issues/32825#issuecomment -507268293: Se quiser uma decoração de erro diferente para cada caso, use uma instrução if . Por favor, consulte o documento de design. Esta pergunta já foi respondida muitas vezes até agora. Obrigado.

@cstockton Sobre https://github.com/golang/go/issues/32825#issuecomment -507306652: Sim, pensamos em tal mecanismo. Especificamente, pensamos em "virar a mesa" e em vez de fornecer try , apenas forneça handle , que declara um manipulador de erros. Se um manipulador estiver presente (e somente então), alguém simplesmente deixaria de lado err no LHS de uma atribuição e seria verificado implicitamente (como com um try invisível). Parece bom, mas também é completamente invisível, o que é uma grande bandeira vermelha. Queremos que o tratamento de exceções seja explicitamente visível no código, em todos os lugares. Sem isso, seria quase impossível ler o código e ver onde a verificação de erros está acontecendo.

@griesemer obrigado por esclarecer. mas pânico e recuperação têm casos de uso diferentes e, na maioria das vezes, são muito difíceis de encontrar em qualquer base de código de produção. isso o deixa com apenas uma quantidade limitada de controle de construções de fluxo. adicionar uma nova construção quebraria essa consistência, pois agora você tem um novo controle de construção de fluxo que faz algo como retornar.

@ matthew-noken Sobre https://github.com/golang/go/issues/32825#issuecomment -507323973: Você está propondo uma ideia interessante; ele se parece muito com o mecanismo de ponto de observação do depurador. Existem algumas perguntas que precisam ser respondidas: O bloco on tem que retornar (eu suspeito que sim, porque senão você entrará no terreno do código espaguete)? Alguém pode ter mais de uma instrução on ? Quão complicada pode ser a condição on (ela terá que ser avaliada em cada atribuição)? Observe que não podemos ter expressões arbitrárias porque temos que identificar a variável exclusivamente com a instrução on . Além disso, um tanto anátema em Go: a construção on implica código invisível a ser executado em outro lugar.

Se você quiser explorar isso mais, sugiro discutir isso em outro lugar (nozes de golang ou uma nova proposta diferente). Obrigado.

@com relação a https://github.com/golang/go/issues/32825#issuecomment -507345821:

A aritmética de ponteiros foi excluída do Go com base na premissa de que torna mais fácil escrever código inválido e quebrado

Na verdade, ele foi excluído porque tornaria a coleta de lixo difícil ou impossível (e sim, também se pode escrever código inseguro). Mas o ponto mais importante aqui é que havia evidências e experiência concretas que apoiaram essa decisão.

Não há experiência e nenhuma evidência de que try aninhados serão predominantes ou comuns. Mas consulte https://github.com/golang/go/issues/32825#issuecomment -507358397.

@turtleDev Em relação a https://github.com/golang/go/issues/32825#issuecomment -507368167: A panic é _exatamente_ uma exceção e recover dentro de uma função adiada é essencialmente catch . Eles podem ser mais difíceis de encontrar no código de produção porque no Go não recomendamos que você escreva seu código usando exceções; eles só devem ser usados ​​em circunstâncias excepcionais.

Com relação ao número de estruturas de fluxo de controle: A proposta é muito clara que try é simplesmente um açúcar sintático, nada mais.

Tentei responder a alguns dos comentários recentes neste tópico sobre este tópico. Mas vamos continuar novos comentários sobre o try proposta na actual try edição # 32437 (agora desbloqueado novamente a partir de hoje); e deixe este assunto reservado para a discussão leave err != nil alone . Obrigado.

@cstockton Outro comentário sobre https://github.com/golang/go/issues/32825#issuecomment -507306652: Se implementamos isso, então começando com

    func f() int { ... }
    ...
    x := f()

e mudando para

    func f() (int, error) { ... }

significaria que o comportamento de x := f() seria repentina e silenciosamente muito diferente.

Fiz alguns experimentos semelhantes ao que @lpar fez em todos os nossos repositórios go. Os resultados estão nesta essência: https://gist.github.com/freeformz/55abbe5da61a28ab94dbb662bfc7f763

@ianlancetaylor Na verdade, acho que isso funcionaria muito bem na maioria dos casos e tornaria a introdução de relatórios de erro melhores muito menos impactante. Considere exemplos completos para os dois casos principais, primeiro o chamador retorna um erro:

func f() int { ... }
func a() error {
    x := f() // if f() is updated to return an error, we get automatic error propagation by default
    ...
}

func b() {
    x := f() // if f() is updated to return an error, we get the same compiler error 
    // assignment mismatch: 1 variable but pkg.f returns 2 values
}

Acho que, no caso comum, esse é um bom benefício para essa abordagem, os casos em que isso cria um problema que acredito já são frágeis. Só consigo pensar em um que poderia ser realmente desagradável é criar um deadlock em um mutex:

func (t *T) a() error {
   t.mu.Lock()
   x := f() // if f() is updated to return an error, we get automatic error propagation by default
   if x > 15 {
     t.mu.Unlock()
     return errors.New("t > 15")
   }
   ...
}

Mas acho que o código que é escrito assim já é suscetível a deadlocks se depender de uma chamada de biblioteca externa para ter um estado de programa válido. Se for armazenado em um escopo que pode viver além do pânico, ele pode travar se a mesma biblioteca introduzir um pânico em tempo de execução via NPE. Além disso, um motivador principal para escrever códigos como esse é o custo histórico dos adiamentos que vivem na pilha. Com a melhoria de desempenho dos adiadores que vivem na pilha, tal código não é realmente necessário. Acho que qualquer derivação desse tipo de erro é facilmente corrigida.

Finalmente, como os argumentos de apoio à lacuna de "tentar" com ferramentas também podem ser aplicados aqui. Dada a crescente adoção de go-modules, temos uma boa oportunidade de injetar uma etapa de "atualização de biblioteca" para colocar essas mudanças na frente do usuário de forma clara.

@griesemer

Em relação ao número de estruturas de fluxo de controle: A proposta é muito clara de que try é simplesmente açúcar sintático, nada mais.

Perdoe-me, mas try não será uma macro (como C), portanto, para o usuário final, é apenas mais um controle de declaração de fluxo.

Acredito não ter argumentos objetivos neste ponto, então vou admitir que talvez precisemos de um melhor tratamento de erros, mas acho que try pode não ser a melhor solução.

Novamente, essas são minhas opiniões e estou apenas representando-as. Tenho certeza de que a equipe Go pensou muito mais nisso do que eu.

Lado: Parece-me estranho que este problema tenha 1335 votos positivos, enquanto a proposta try (# 32437) tenha apenas 279 votos negativos. Eu esperaria que as pessoas que votaram a favor, votassem negativamente na proposta try , para que os sentimentos da comunidade sobre ela fiquem mais evidentes, porque essas duas propostas são mutuamente exclusivas.

@griesemer

O tratamento de erros já é diferente de situação para situação: às vezes retornamos o erro inalterado, às vezes retornamos um erro decorado, às vezes agimos sobre um erro e às vezes (podemos corretamente) ignorar um erro.

Concordo aí, isso é óbvio.

A única coisa que todos eles compartilham (exceto quando ignoramos o erro) é o teste err != nil (que já podemos fazer de mais de uma maneira). Por mais que seja bom para um novo recurso de linguagem capturar todos esses casos (ou 95% deles), tal construção tem grande probabilidade de interferir de maneiras não ortogonais com outras construções de controle que já temos. Ou seja, o melhor que podemos esperar é tornar alguns desses casos (talvez 20%, talvez 50% deles) melhores.

A declaração try() proposta também "interfere" com if e return de maneiras não ortogonais, então eu diria que esse argumento não está correto. Algumas pessoas aqui não gostam de try() por esse motivo, mas eu discordo. Go não é Oberon, é simples, mas não minimalista, Go é mais prático.

Discordamos que vale a pena nos preocuparmos com uma construção de linguagem que, como você mesmo admitiu, tem apenas uso e aplicabilidade limitados e que já pode ser feito corretamente com if e return . Acho que muitas pessoas, como eu, que concordaram com este tópico, ficam tão desapontadas com try() que preferem não tê-lo de jeito nenhum. Mesmo que não seja ortogonal com o retorno, um try() mais poderoso e geralmente útil é provavelmente o que a maioria dos programadores de Go gostaria de ver.

@beoran ,

Você escreveu que gostaria de um try() "mais poderoso", "mais útil em geral".

@griesemer mencionou 4 situações:

  1. Retorna o erro inalterado
  2. Retorne um erro decorado
  3. Agir de acordo com um erro
  4. Ignore um erro

try() resolve 1 por design: é literalmente um atalho para if err != nil { return ..., err } .

As construções de linguagem existentes resolvem 3 e 4. Já podemos agir sobre um erro com if err != nil { ... } e será difícil encontrar uma estrutura mais concisa nesse caso. Já podemos ignorar um erro com _ .

Isso nos deixa com 2 (retornar um erro decorado). A proposta try() sugere o uso de uma instrução defer para decorar o erro ou, se cada erro deve ser decorado de forma diferente, use uma construção if err != nil { ... } padrão.

O raciocínio é bem explicado nesta parte da proposta :

Nossa primeira iteração desta proposta foi inspirada por duas ideias de Partes-chave do tratamento de erros , que é usar um operador integrado em vez de um operador e uma função Go comum para tratar um erro, em vez de uma nova construção de linguagem de tratamento de erros. Em contraste com aquela postagem, nosso manipulador de erros tinha a assinatura de função fixa func(error) error para simplificar as coisas. O manipulador de erros seria chamado por try na presença de um erro, pouco antes de try retornar da função envolvente. Aqui está um exemplo:

handler := func(err error) error {
        return fmt.Errorf("foo failed: %v", err)  // wrap error
}

f := try(os.Open(filename), handler)              // handler will be called in error case

Embora essa abordagem permitisse a especificação de manipuladores de erros definidos pelo usuário eficientes, ela também abriu muitas questões que não tinham respostas obviamente corretas: O que aconteceria se o manipulador fosse fornecido, mas fosse nulo? try entrar em pânico ou tratá-lo como um manipulador de erros ausente? E se o manipulador for invocado com um erro não nulo e retornar um resultado nulo? Isso significa que o erro foi “cancelado”? Ou a função envolvente deve retornar com um erro nulo? Também não estava claro se permitir um manipulador de erros opcional levaria os programadores a ignorar completamente o tratamento de erros adequado. Também seria fácil fazer o tratamento adequado de erros em qualquer lugar, mas perder uma única ocorrência de try . E assim por diante.

A próxima iteração removeu a capacidade de fornecer um manipulador de erros definido pelo usuário em favor do uso de defer para empacotamento de erros. Essa parecia uma abordagem melhor porque tornava os manipuladores de erros muito mais visíveis no código. Essa etapa eliminou todas as questões sobre funções opcionais como manipuladores de erros, mas exigia que os resultados dos erros fossem nomeados se o acesso a eles fosse necessário (decidimos que estava tudo bem).

[...]

Se determinarmos no futuro que ter alguma forma de função de tratamento de erro fornecida explicitamente, ou qualquer outro parâmetro adicional para esse assunto, é uma boa ideia, é trivialmente possível passar esse argumento adicional para uma chamada try.

Você discorda desse raciocínio?

Conheço essa parte da proposta e discordo dela, pois o fato de as perguntas terem sido abertas devido à ideia de passar por um manipulador de erros, não significa que não precisamos desse recurso. Significa apenas que temos que pensar bem para responder a essas perguntas de maneira razoável.

Além disso, agora, lidamos com todos os 4 casos de uso de erro com uma instrução if err != nil , que é amplamente compreendida, no idioma Go consistente. Usar apenas try() para o caso 1, e possivelmente para o caso 2, se não nos importamos com a sobrecarga de fazer o empacotamento de erros em uma instrução defer, significa que o código para tratamento de erros será dividido em if e try inconsistentemente, e se o tratamento de erros mudar, teremos que alternar entre um e outro.

Uma versão mais poderosa de try() , que poderia ser usada em todos os casos, nos permitiria usar try() quase sempre, e isso se tornaria o novo idioma de tratamento de erros consistente para Go.

No entanto, com try() como está agora, não é amplamente aplicável o suficiente, e simplesmente não há conveniência suficiente para introduzir a inconsistência mencionada no tratamento de erros em nossas bases de código. É por isso que não me sinto impressionado com a proposta atual de try() e acho que não fazer nada é melhor.

@beoran Acho que os casos 1 e 2 têm em comum retornar um erro da função (respectivamente sem e com decoração), enquanto os casos 3 e 4 não (respectivamente atuam sobre um erro e ignoram um erro). Acho que o foco de 'try ()' está nos casos 1 e 2.

E se a proposta try() pudesse ser melhorada para lidar com os casos 1 e 2, aceitando uma função de manipulador opcional? Claro, isso requer encontrar respostas razoáveis ​​para as perguntas listadas na proposta, mas parece tratável. Você apoiaria algo assim?

Aqui está eu. Se o caso é que queremos que os usuários verifiquem os erros, provavelmente devemos adicionar os erros verificados (como exceções verificadas). Dessa forma, seremos o mais explícitos possível e o usuário saberá que está verificando todos os erros.

Com toda a seriedade, se eu fosse o responsável por essas decisões, eu realmente gostaria de redirecionar o operador Kotlin ? ou algo como o comportamento da ferrugem unwrap() .

Qualquer um desses eu acho que seria uma melhoria:

getFile()?.getPath()?.toString()

onde você recebe nil volta se houver um erro ao longo do caminho ou

get_file().unwrap().get_path().unwrap().lossy_to_string()

onde explode no meio se houver um erro. Rust lida com o segundo tendo uma expressiva função match que permite fazer uma pesquisa exaustiva dos erros e lidar com cada um separadamente, todos retornando um valor de algum tipo na cadeia.

Em 02/07/19, Nicolas Grilly [email protected] escreveu:

@beoran , acho que os casos 1 e 2 têm em comum o retorno de um erro do
função (respectivamente sem e com decoração), enquanto os casos 3 e 4
não (respectivamente, agir sobre um erro e ignorar um erro). Eu acho 'try () `
o foco está nos casos 1 e 2.

Estou um pouco desapontado por não ver mais discussão sobre minha sugestão
que é a parte "retorno" do "retorno de erro" que precisa ser
abordada, não a parte do "erro".

Então, novamente, talvez eu devesse ter seguido uma abordagem mais oficial. Eu estou
simplesmente não sou bom em formular propostas, tendo a passar por todo o
Lugar, colocar.

Os casos 1 e 2 são, em minha opinião, mais bem atendidos por um comando de "falha"
palavra-chave que indica claramente (depois de algum tempo se acostumando) a
mudança no fluxo do programa e que não está sujeito a qualquer
tradição inconveniente no que diz respeito à sua sintaxe completa.

O que se segue disso, no entanto, gostemos ou não, é que
o posicionamento de "err" como o último parâmetro de retorno é em breve
tornar-se lei em vez de convenção. Esse é um dos muitos "não intencionais" ou
consequências "colaterais" que precisam ser levadas em consideração.

Haverá muitas outras consequências, das mínimas a
desastroso, alguns que apenas oportunisticamente pegam carona no
proposta. Eu pessoalmente erraria por excesso de cautela,
entender por que a Go Team e Robert em particular estão buscando
apoio e sem dúvida estão feridos que a resistência acabou por ser tão
excelente.

É um choque de ideologias políticas, talvez precisemos cavar muito
mais fundo para buscar as raízes reais que precisam de alguma atenção de jardinagem.

@lootch Você tem uma preocupação particular com o erro que precisa ser o último parâmetro de retorno para try funcionar? Isso já não é uma convenção de fato?

(Como um aparte, não estamos "magoados" com a resistência - principalmente espantados com a reação exagerada.)

@griesemer desculpe a analogia com o carro

Só quero escrever um comentário rápido dizendo que tendo escrito muito Go (tenho usado Go desde seu primeiro lançamento público em 2009 - veja meu github), gostaria de melhorar a ergonomia em torno do tratamento de erros. Embora a explicitação do tratamento de erros do Go seja boa, também é uma dor no código sequencial. A falta de introspecção e digitação em torno dos próprios valores reais (o que está sendo abordado em uma proposta diferente) não melhora, de fato, a resiliência do programa.

Eu fiquei irritado o suficiente com isso alguns anos atrás. Na verdade, escrevi um pouco de açúcar em torno do pânico e da recuperação para permitir que eles funcionem (principalmente) como exceções não verificadas: https://hackthology.com/exceptions-for-go-as-a-library. html . Na verdade, usar esses invólucros é uma má ideia, por causa de onde a comunidade está. No entanto, continuo a acreditar que melhorar a ergonomia em torno do tratamento de erros é uma vitória.

Parece loucura que as pessoas defendam com veemência manter uma forma extremamente prolixa, mas inútil, de propagar as condições de erro. Eu escrevi e encontrei bugs reais em meu código (e no código de outros) onde as pessoas bagunçaram a condição if err != nil e criaram bugs. Não há realmente nenhuma razão para esses bugs serem escritos. A verificação estática pode ajudar, mas não elimina esses bugs.

Apoio as propostas para melhorar a ergonomia em torno do tratamento de erros.

O processo de proposta é para, para citar https://golang.org/s/proposal , "Propondo mudanças para acontecer".
Este problema não está propondo uma mudança, então realmente é um erro de categoria.
Se alguém registrou uma proposta intitulada "deixe a seleção em paz", nós simplesmente a fecharíamos como não sendo uma proposta.

Mas, na verdade, esse problema é apenas uma extensão da discussão de outros problemas, como o # 32437, portanto, vou deixá-lo em aberto como um bom lugar para discutir "não fazer nada".

Eu marquei Proposta-Espera para indicar que não há uma decisão específica aqui, mais como uma meta-decisão.

Lado: Parece-me estranho que este problema tenha 1335 votos positivos, enquanto a proposta try (# 32437) tem _apenas_ 279 votos negativos.

A proposta de teste estava bloqueada no momento em que tomei conhecimento dela, então não pude votar contra ela. Suponho que é também por isso que esta proposta foi apresentada para começar.

Em 02/07/19, Robert Griesemer [email protected] escreveu:

@lootch Você tem uma preocupação particular com o erro que precisa ser o último
parâmetro de retorno para try funcionar? Isso já não é de fato
convenção?

Eu faço no sentido de que se isso se tornou "de fato" gravado na pedra, então
essa porta deve ser aberta e tornar "erro" um opcional, especial
tipo de argumento e aceitar isso, porque para cada resultado certo lá
são mais frequentemente do que muitos errados e podem muito bem ser
abordada de uma forma especial.

Uma vez que a ideia de que a declaração de "retorno" precisa ser investigada
mais profundamente (e a proposta try parece sugerir que
direção), então a condição de erro não é mais "apenas um valor e
A abordagem de Go começa a se assemelhar ao melhor castelo de cartas construído para
data, mas um castelo de cartas, no entanto.

Eu sou um usuário relutante de alguns dos truques mais inteligentes de Go (eu tenho
mencionado em outro lugar que x ++ e x - não apresentam - principalmente, eu escorrego
às vezes - no meu código), então "tentar" continuará sendo um daqueles para mim,
mas, em princípio, não faço objeções à sua introdução, apenas sinto
que tanto foi dito, e ainda assim todas as desvantagens possíveis podem não
foram revelados (as consequências não intencionais do que vejo se tornando
eventual decisão política). Colocar isso à prova pode ser bom
Ou ruim.

O que vejo é que o pânico / recuperação teve uma má reputação e a injustiça disso
está voltando para nos assombrar. Novamente, eu ainda não usei nenhum e eu
temo o dia que tenho de fazer porque não sou o biscoito mais afiado do pote
e eu acharei muito difícil, mas o pânico / recuperação foi encorajado
para as raras ocasiões em que é adequado (nenhum deles tratado por
Rob Pike em sua apresentação maravilhosa sobre erros sendo apenas retorno
valores, se bem me lembro), seria muito mais claro para tudo isso
Go já lida com erros tão bem quanto o esperado, sem a necessidade
para explorar o pântano de opções possíveis disponíveis além de sua
limites.

Dito isso, faz muito sentido colocar a tentativa à prova, é, depois
tudo, um recurso opcional. Mas o único efeito colateral é isso
a troca é sobre: ​​quais serão as consequências do "fato"
funções de tratamento de erros atraentes para ter um argumento do tipo "erro"
no final de sua lista de parâmetros de retorno? Como isso mudará o
percepção de que "erros são apenas valores"? Como isso vai se encaixar com
o agora muito mais análogo paradigma "vírgula-OK"? O que mais será isso
novo princípio dar origem a?

Acima de tudo, acho que a montanha que está sendo feita deste pequeno morro é
indicativo de que Go atingiu um marco de mudança de vida. Quase
certamente, suposições que eram verdadeiras em 2013, podem muito bem não mais
segurar. Não que isso seja novidade para ninguém, mas sugere que Go2 pode
não ser tão maravilhoso ser compatível com Go1 como tem sido
proposto com muita firmeza (na minha opinião).

@lootch Obrigado pela sua resposta detalhada. Na verdade, é muito difícil estender uma linguagem existente de uma maneira compatível com versões anteriores e, portanto, eu concordo com você que, se essa compatibilidade com versões anteriores não fosse exigida, talvez alguém pudesse fazer as coisas de forma diferente. Em breve (assim que os módulos se tornarem comum) seremos capazes de fazer alterações de idioma que não requeiram compatibilidade com versões anteriores.

Como você disse, try é um mecanismo opcional e - acho que vale a pena repetir - apesar de todo o alarido em torno dele, um mecanismo muito simples, facilmente explicado usando a funcionalidade Go existente - é apenas que podemos ' t escrever try como uma função no Go nós mesmos (e os genéricos também não ajudariam, por falar nisso). Se pudéssemos, tenho certeza de que seria bastante comum ver funções auxiliares como try para tratamento de erros no código existente. Afinal, é exatamente disso que se trata a conversa de Rob Pike sobre os erros serem valores: é programar com erros.

Ainda é muito surpreendente para mim que haja tanto alvoroço em relação à adição de uma função auxiliar muito pequena que todos estão livres para ignorar, mas as pessoas estão pensando seriamente em mudanças significativas de linguagem, como on error que realmente sugerem fortemente um estilo específico de tratamento de erros no idioma.

Obrigado novamente por sua contribuição. Tudo isso é muito interessante.

Não tenho certeza se esta proposta é o lugar certo para discuti-la, mas como já existe uma discussão vívida sobre a palavra-chave try neste tópico, irei considerá-la apenas no tópico por enquanto :)

Eu me pergunto se o pessoal do Google estaria disposto a implementar a palavra-chave try em seu repositório Golang interno e, subsequentemente, converter as bases de código existentes do Google para usar essa palavra-chave. Ao mantê-lo apenas interno, permitiria revertê-lo facilmente (ou seja, o ônus recai sobre uma única empresa, e não sobre toda a comunidade).

Isso permitiria fazer um pequeno estudo de caso sobre esse recurso em uma base de código de alto sloc do mundo real. O Facebook fez algo semelhante com os recursos do PHP no passado e, dessa forma, eles foram capazes de ajustar certas funcionalidades antes de propô-las para a comunidade em geral.

Se você fosse escrever um estudo de caso sobre como a palavra-chave try foi usada _na prática_ e qual valor ela agregou na vida real, isso poderia fornecer um caso atraente. Se (hipoteticamente) não fornecesse nenhum valor do mundo real, também seria valioso saber. Às vezes, algo parece muito bom no papel, mas não funciona na prática - ou vice-versa.

@Freeaqingme Já produzimos um CL provisório que mostra como try pode se parecer na biblioteca std: https://go-review.googlesource.com/c/go/+/182717 (pode ser falso positivos, mas se houver, são muito raros). Estamos pensando em talvez desenvolver uma ferramenta que permita a conversão de bases de código em ambas as direções.

Você também é encorajado a usar tryhard em sua base de código e relatar de volta.

Obrigado.

@griesemer Posso não ter sido claro. Presumo que o Google use sua própria versão do Go internamente. Minha sugestão seria aplicar o patch que você vinculou acima à compilação interna do Google e, em seguida, converter (partes) da base de código do Google - não apenas stdlib, mas especialmente os projetos internos que são escritos em Go. Se os engenheiros do Google o usassem por alguns meses em situações da vida real, isso forneceria bons insights sobre como ele funcionaria na prática.

Obviamente, também posso aplicá-lo e usá-lo em minhas próprias bases de código (e também poderia). Mas eu sou apenas um desenvolvedor com uma base de código relativamente pequena, então isso não seria tão representativo se muitos funcionários do Google o usassem.

Pessoalmente, ainda estou em dúvida sobre esse recurso. Por um lado, agradeço o fato de que isso pode me poupar alguns toques no teclado a cada vez. Mas às vezes posso ser preguiçoso (afinal, sou humano) e aninho o máximo de declarações try que posso. Agora vou ser um pouco mais disciplinado, se esse recurso estiver presente. Mas mesmo se eu fosse, ainda há uma chance de que as bibliotecas externas abusem / abusem dessa palavra-chave enquanto minha base de código sofre com isso (em termos de depuração, extensibilidade dessas bibliotecas).

@Freeaqingme Entendi. Certamente poderíamos executar try em repositórios internos. Não tenho certeza se podemos converter e converter de volta - há um custo significativo envolvido aqui. Além disso, só pudemos relatar de forma agregada (estatísticas) sobre este experimento, pois não poderíamos relatar detalhes internos. Ou seja, a comunidade não teria uma maneira fácil de verificar nossas afirmações. Finalmente, a base de código do Google pode não ser representativa.

Mas obrigado, entendi.

@griesemer Eu

Eu entendo que você não seria capaz de relatar sobre projetos individuais do Google ou infraestrutura interna. No entanto, algumas anedotas poderiam ser compartilhadas, talvez. Mas o que eu pessoalmente consideraria muito mais valioso são alguns Googlers relatando como isso funcionou para eles. Sem entrar em detalhes, afirmações como "Eu esperava X, mas quando encontrei casos como A e BI descobri que Y", eu acharia muito valioso. Eu não encontraria necessidade de verificar esse tipo de relatório.

Finalmente, a base de código do Google pode não ser representativa.

Pode ser, pode não ser. Mas há muitas pessoas trabalhando no Google, então eu acho que a maior parte do código-base não é baseado em como um único indivíduo decidiu escrevê-lo, mas sim uma culminação das contribuições de muitos contribuidores diferentes (funcionários). Nesse sentido, espero que as coisas sejam tão representativas quanto poderiam ser. Provavelmente não existe uma base de código 100% representativa.

Mantenha-o como está, até que encontremos uma solução melhor, o try não é o que estávamos procurando. Não há necessidade de tomar ações tendenciosas sobre isso, vá com calma, Autores. Eu acredito fortemente, pelo que falo com esquilos ao redor do mundo todos os dias, que a maioria da comunidade Go não adota a proposta de try atm.

A introdução de uma nova sintaxe significa que todos precisam atualizar sua versão Go. Ainda estou usando Go 1.10 porque meu fluxo de trabalho se baseia no fato de que posso go get coisas e, em seguida, usá-las a partir da linha de comando (meu GOPATH está em meu PATH ) . Recentemente, tive um problema ao tentar go get a biblioteca de outra pessoa que usa módulos. Recebi um erro informando que .../v2 não estava disponível. Isso significa que já existe uma divisão no código (pense em Python 2 e 3). Para mim, existe um mundo antes do Go 1.11 e depois do Go 1.11. Isso é muito chato e introduzir uma nova sintaxe para algo que funciona tão bem quanto o tratamento de erros no Go não é uma boa troca. Ele introduz mais fragmentação.

Em 04/07/19, gonutz [email protected] escreveu:

A introdução de uma nova sintaxe significa que todos precisam atualizar seus Go
versão. Ainda estou usando o Go 1.10 porque meu fluxo de trabalho é baseado no fato
que posso go get coisas e, em seguida, usá-los a partir da linha de comando (meu
GOPATH está na minha PATH ). Recentemente, tive um problema ao tentar go get
biblioteca de outra pessoa que usa módulos. Recebi um erro de que .../v2 era
não disponível. Isso significa que já existe uma divisão no código (pense
Python 2 e 3). Para mim, existe um mundo antes do Go 1.11 e depois do Go 1.11.
Isso é muito chato e introduzir uma nova sintaxe para algo que funciona
bem como o tratamento de erros no Go não é uma boa troca. Isto
introduz mais fragmentação.

Se serve de consolo, estou exatamente na mesma posição em relação
Módulos Go. Eu não encontrei tempo e oportunidade para me familiarizar
com eles, então vou continuar com Go1.10 também. Talvez devesse ser
uma pesquisa que vale a pena ter.

Lucio.

-
Você está recebendo isso porque foi mencionado.
Responda a este e-mail diretamente ou visualize-o no GitHub:
https://github.com/golang/go/issues/32825#issuecomment -508372318

-
Lucio De Re
2 Piet Retief St
Kestell (Estado Livre Oriental)
9860 África do Sul

Telefone: +27 58 653 1433
Celular: +27 83 251 5824
FAX: +27 58 653 1435

Sou um novo desenvolvedor de golang (ainda estou aprendendo sobre go). Eu acho que o tratamento de erros atual é bom porque nos faz gerenciá-los facilmente. Como um desenvolvedor Android, acho que try-catch é mais difícil de gerenciar nosso erro do que if err != nil{ } em golang. E acho que o tratamento de erros explícito é sempre melhor do que o tratamento de erros implícitos.

PS. Desculpe pelo meu idioma.

leave it alone

Não está quebrado ....

Ame o meme, @ Daniel1984 :-)

A propósito, a proposta try deixa if err != nil sozinho; apenas fornece uma opção adicional onde faz sentido.

Sou de opinião que try não deve ser incluído. Sobre a inclusão de try :

Pró

  • Os programadores diminuem o número de pressionamentos de tecla que fazem.
  • Os programadores podem ter um atalho para retornar da função atual à la macro.
  • Não é obrigatório.
  • Seria comumente usado.
  • É claro onde a mágica está acontecendo (ao contrário de throws do Java).
  • Os olhos não ficam mais vidrados ao ver o mar de nil cheques.
  • Funciona melhor com implementações simples.

Vigarista

  • try adiciona um método duplicado para uma operação existente.
  • Para try retornar da função atual é inesperado, também conhecido como mais mágica.
  • Ele adiciona inconsistências à verificação de erros.
  • Programadores sem experiência em Go não vão entender.
  • Não altera o tratamento de erros.
  • Menos clareza (incompatibilidade entre o retorno da função e o valor da expressão).
  • É difícil até mesmo descrever o que está acontecendo em try em palavras.

@griesemer é exatamente disso que eu não gosto. As coisas deveriam ser simples, eu não gostaria de adicionar complexidade à linguagem apenas para ter 2 maneiras de conseguir a mesma coisa. Existem padrões para evitar esse if err != nil verbosidade. https://www.ardanlabs.com/blog/2019/07/an-open-letter-to-the-go-team-about-try.html

A proposta # 32437 do Go2 adiciona uma nova sintaxe à linguagem para tornar o padrão if err != nil { return ... } menos complicado.

Existem várias propostas alternativas: # 32804 e # 32811, pois o original não é universalmente amado.

Para lançar outra alternativa na mistura: por que não mantê-la como está ?

Passei a gostar da natureza explícita da construção if err != nil e, como tal, não entendo por que precisamos de uma nova sintaxe para isso. É tão ruim assim?

Muito isso. O código explícito é o código correto. Os horrores que vi com manipuladores de exceção são suficientes para me afastar para sempre dessa terrível construção ilegível.

Parece haver um grande atraso, com as pessoas começando a comentar apenas
agora e não se pode deixar de ficar com a impressão de que é recente
notícias para eles.

Isso também precisa ser levado em consideração. O boato claramente não é tão
rápido como se pode pensar.

Lucio.

E mude o gofmt para permitir instruções if de uma única linha, uma bola de mão simples e erro até o
função de chamada. Isso removeria muita desordem.

Ao invés de:

err = myFunction ()
se errar! = nulo {
return err
}

Permitir:

err = myFunction ()
if err! = nil {return err}

A propósito, a proposta try deixa if err! = Nil sozinho; apenas fornece uma opção adicional onde faz sentido.

Essa justificativa precisa é como Go se torna mais um C ++, C #, Scala, Kotlin, etc. "Bem, você não precisa usá-lo se não quiser" é como as linguagens cheias de recursos são criadas.

Editar - isso pode ter parecido errado. Não estou dizendo que try vai transformar Go em uma linguagem cheia de recursos. Estou dizendo que a justificativa aqui é um pouco falha

@deanveloper Você tem um exemplo claro de comportamento de erro de difícil compreensão com "try":
https://github.com/golang/go/issues/32437#issuecomment -498932961

Mesmo que seja um pouco repetitivo, concordo com a OP.
Além disso, todas as alternativas propostas apresentam uma complexidade inútil.

Será legal omitir as chaves quando você tiver apenas uma linha dentro do escopo

@enlouquecer

Isso significa que já existe uma divisão no código (pense em Python 2 e 3). Para mim, existe um mundo antes do Go 1.11 e depois do Go 1.11.

Sou um programador Python de longa data e a suposta "divisão" que você mencionou em relação aos módulos Go não é nada comparada ao desastre da migração do Python 2 para o Python 3.

Isso é muito chato e introduzir uma nova sintaxe para algo que funciona tão bem quanto o tratamento de erros no Go não é uma boa troca. Ele introduz mais fragmentação.

try é uma função embutida na proposta. É totalmente compatível com versões anteriores. Se seu código já usa try como um identificador, então seu identificador irá sombrear o try embutido.

@pongsatornw

Eu acho que o tratamento de erros atual é bom porque nos faz gerenciá-los facilmente. Como um desenvolvedor de Android, acho que try-catch é mais difícil de gerenciar nosso erro do que if err! = Nil {} em golang. E acho que o tratamento de erros explícito é sempre melhor do que o tratamento de erros implícitos.

Você leu a proposta na íntegra? try é apenas uma função incorporada que ajuda a fatorar a repetição de if err != nil { return ..., err } . A lógica geral de tratamento de erros no Go permanece a mesma. Ainda é explícito, os erros ainda são valores e não há try-catch (também conhecido como exceções).

@kroppt

  • try adiciona um método duplicado para uma operação existente.

try está apenas fatorando código repetitivo. É o mesmo com append . Podemos escrever nós mesmos cada vez que acrescentamos elementos a uma fatia, mas é mais fácil chamar append .

  • Ele adiciona inconsistências à verificação de erros.

Você pode manipular uma fatia "manualmente" usando [...:...] ou você pode usar append , dependendo do que você faz. Não há inconsistência. Eles são apenas ferramentas diferentes para tarefas diferentes. O mesmo para erros, com if err != nil { ... } simples, ou com try , dependendo da tarefa em mãos.

  • Para try retornar da função atual é inesperado, também conhecido como mais mágica.

É inesperado porque é novo. Nós nos acostumamos à medida que o usamos mais. E não acho que seja mágico; a especificação de try é muito direta.

  • Programadores sem experiência em Go não vão entender.
  • É difícil até mesmo descrever o que está acontecendo em try em palavras.

Programadores sem experiência em Go não entenderão chan , defer , 'go , iota , panic , recover , <- , type assertions, and many other things either without reading the documentation. try` é fácil em comparação com a maior parte disso.

  • Não altera o tratamento de erros.

Talvez isso seja uma coisa boa, de acordo com esquilos pedindo para deixar if err != nil sozinho ;-)

@marcopeereboom

O código explícito é o código correto. Os horrores que vi com manipuladores de exceção são suficientes para me afastar para sempre dessa terrível construção ilegível.

try tem absolutamente nada a ver com o tratamento de exceções. Você leu a proposta completa? Não há nada comparável ao tratamento de exceções como em Java ou Python, por exemplo. try é explícito. Os erros devem ser mencionados nas assinaturas das funções. Os erros devem ser tratados no site da chamada. Não há desenrolamento da pilha. Etc.

@ gale93

Será legal omitir as chaves quando você tiver apenas uma linha dentro do escopo

Acho que a maioria dos esquilos teve o mesmo pensamento, e li propostas semelhantes muitas vezes no rastreador de problemas. Mas é uma mudança muito maior do que try . Não seria razoável fazer isso apenas para a instrução if . Portanto, você teria que alterar isso em todos os lugares em que um bloqueio fosse aceito. Sem o { marcando o início do bloco, você deve especificar uma maneira de delimitar o final da expressão condicional. Você tem que atualizar a gramática, o analisador, gofmt, etc. Isso mudaria completamente a superfície da linguagem.

@ngrilly
É importante moderar e manter a linguagem simples.

Alguns dos argumentos que você usou podem justificar uma grande mudança nas especificações. Não existem apenas pontos positivos aqui.

Estou avaliando a decisão com base em se ela ajudaria ou prejudicaria, não necessariamente cumprindo totalmente uma certa filosofia. Você está correto ao dizer que algumas coisas nas especificações violam certos princípios nos quais go foi fundado, mas é tudo sobre moderação. Essa mudança não tem um impacto positivo o suficiente para eu tolerar os negativos.

Olá @kroppt ,

manter a linguagem simples é importante

Eu concordo e acho que todos nós nos esforçamos para isso.

Estou avaliando a decisão com base em se ela ajudaria ou prejudicaria, não necessariamente cumprindo totalmente uma certa filosofia.

Acho que estamos todos avaliando try base em seus benefícios e custos. A discussão é sobre como definir e encontrar um consenso baseado em fatos sobre esses benefícios e custos, que é o que tentei fazer em meu comentário anterior.

Você está correto ao dizer que algumas coisas nas especificações violam certos princípios sobre os quais go foi fundado

Nos últimos anos, tenho lido quase tudo que a equipe de Go publicou sobre Go e seu design, e In não entendo o que você quer dizer. Não acho que a proposta try viole os princípios fundamentais do Go.

@ngrilly
https://talks.golang.org/2012/splash.article descreve alguns dos conceitos por trás do que torna go diferente - clareza e simplicidade, entre outros. Acho que esse é o conflito que alguns de nós vemos com essa nova mudança. É mais simples, mas é menos claro. Para mim, parece que o ganho em simplicidade é menor do que a perda de clareza. Talvez eu esteja errado e apenas seja cauteloso.

@kroppt Já li este artigo dezenas de vezes ;-) Não concordo com a ideia de tentar ofuscar o código. try é apenas uma função embutida usada para fatorar algum código repetitivo. Fazemos isso o tempo todo como programadores. Quando identificamos um padrão repetitivo, o fatoramos em uma nova função. Do contrário, teríamos uma função main () longa com todo o nosso código embutido nela.

@ngrilly
O que você está descrevendo está na minha seção "profissional":

  • Os programadores diminuem o número de pressionamentos de tecla que fazem.
  • Os programadores podem ter um atalho para retornar da função atual à la macro.
  • Os olhos não ficam mais vidrados ao ver o mar de nil cheques.

Novamente, não vejo sentido em mencionar a aplicação universal de um princípio quando não o estamos aplicando universalmente aqui.

Não concordo com a ideia de que tentar ofuscar o código

O objetivo da mudança é ofuscar / ocultar / simplificar / representar o código - caso contrário, veríamos o bloco de verificação de erro original. A questão é se torna menos claro o significado.

Acho que go originalmente atingiu um grande equilíbrio de simplicidade, a ponto de contribuir em vez de diminuir a clareza. Eu não posso explicar como eles fizeram isso, mas try na minha opinião não.

Não acho que devemos considerar a verbosidade um problema. O código precisa ser lido e entendido por humanos - cujo tempo é mais caro do que computadores - e _entendê-lo_ tende a ser a parte difícil e demorada.

Acho que a estrutura de indentação do tratamento de erros go me ajuda a acompanhar o que está acontecendo. Cada verificação de erro é explícita. A maioria dos erros não tratados também são explícitos. Isso torna o código fácil de entender, para mim.

Eu também acho que, embora if err != nil cheques possam parecer entediantes, eu não preciso realmente _tipá-los_. Eu apenas peço ao meu editor para fazer isso.

@kroppt

O objetivo da mudança é ofuscar / ocultar / simplificar / representar o código - caso contrário, veríamos o bloco de verificação de erro original.

Mas você pode usar este argumento para qualquer chamada de função! Se eu chamar strings.HasPrefix("foobar", "foo") , isso ofuscará o código? Você prefere escrever e ler l := len("foo"); len("foobar") >= l && s[0:l] == "foo" ?

@rossmcf

Cada verificação de erro é explícita.

try ainda está verificando explicitamente o erro. É a razão de ser da tentativa.

Eu também acho que, embora as verificações if err! = Nil possam parecer entediantes, não preciso digitá-las de fato.

Não é tedioso digitar. É tedioso de ler, quando temos as mesmas 3 linhas em todos os lugares. É uma repetição, e nós, como programadores, geralmente tendemos a fatorar isso. Talvez try tenha outras desvantagens, mas acho que não.

tentar ainda está verificando explicitamente o erro
A diferença entre a abstração try e strings.HasPrefix é que try retorna implicitamente.
Ao ler o código go, sei que o fluxo de execução permanece dentro da minha função até que eu:

  • leia o colchete de fechamento de uma função sem tipos de retorno
  • leia as palavras-chave return , panic
  • leia syscall.Exit(code)
    Com try , eu não conseguia ler o código da mesma maneira. Explorar as linhas visualmente e ver zero declarações de "retorno" não significaria mais "todas essas linhas são executadas ou um bloco ou o programa termina".

@ngrilly Você pode responder a mais de uma pessoa em um post Para sua informação, 10 respostas em algumas horas com 5 em uma linha em um ponto torna difícil acompanhar a discussão. Depois de ler suas postagens além de algumas falácias, não vi nenhum novo argumento concreto formado descrevendo os benefícios de tentar. Eu vi um único benefício: prevenir a digitação de if err != nil . Isso tem o custo de introduzir novas maneiras de vazar recursos , a capacidade de escrever código menos conciso e o pior de tudo permite aninhar ou tentar .

Eu sinto que a especificação e os argumentos formados pelos proponentes são enganosos, atualmente fornece apenas os exemplos de melhor caso, sem mostrar nenhum dos exemplos de pior caso. Ele não avalia ou menciona as desvantagens negativas acima ou quaisquer fatores atenuantes em potencial. Isso não justifica porque não limita a implementação do try ao uso proposto e demonstrado. A ferramenta tryhard está sendo usada para mostrar um código mais compacto que subjetivamente fornece melhor estética para algumas pessoas, sem uma ferramenta trytoohard que mostra todas as capacidades do que ela pode fazer, por exemplo, tentativa profundamente aninhada afirmações. Finalmente, o próprio nome é associado de forma ubíqua no mundo da programação com exceções, permite que ele seja aninhado e relacionado a erros e o coloque adjacente a uma recuperação não relacionada e pânico, deixando as coisas simplesmente parecendo fora do lugar. Eu acredito e confio na capacidade dos autores de Go de inventar algo melhor.

Há muito custo aqui para justificar com um único valor agregado que vejo regurgitado nas respostas dos proponentes: "Não preciso mais digitar if err != nil " - o que foi defendido com fervor e arraigado junto com os erros é valores por toda a comunidade Go. Temos quase uma década de código escrito usando if err != nil - que alguns dos avanços tecnológicos mais notáveis ​​(docker, k8s, ...) ao mesmo tempo, todos usam com grande sucesso.

Para encerrar, if err != nil não é um fardo esconder com um embutido, mas algo que todos devemos reconhecer como um ingrediente central para o sucesso das linguagens. Mesmo que aceitemos coletivamente que é um fardo, a barreira para removê-lo deve ser alta e inflexível. No momento, muitos aspectos da tentativa são um meio-termo.

Tenho opiniões sobre qual método é mais fácil, mas são opiniões. A tentativa dada é simples e as verificações explícitas atuais são simples. Ótimo, ambas as formas são simples. O problema para mim é que aumenta a carga cognitiva do leitor e do escritor de qualquer código. Agora, ambos precisam interpretar várias maneiras de fazer as coisas. E o redator tem que escolher como fazer as coisas e / ou arriscar fazer as coisas de maneira diferente do resto do pacote ou projeto em que está trabalhando. Se try estivesse substituindo a verificação explícita, isso ainda aumentaria a carga cognitiva por causa dos retornos implícitos como outra coisa a analisar.

_Pondo tudo isso de lado por um momento e considerando que agora temos duas formas igualmente simples de lidar com os erros, ainda temos um problema: _ Simples não é mais fácil . E isso abre a porta para todas as coisas que foram projetadas para evitar.

A barra para adicionar algo assim deve ser muito maior, deve ser experimental por muito tempo para provar que é melhor com dados de campo.

@cstockton

Você pode responder a mais de uma pessoa em um post FYI, 10 respostas em algumas horas com 5 em uma linha em um ponto torna difícil acompanhar a discussão.

Ian sugeriu 7 dias atrás mover esta discussão para golang-nuts exatamente por esta razão (nenhuma maneira de responder a um comentário específico, nenhum tópico), sugestão que foi descartada para garantir que a discussão seria "oficial". Temos o que pedimos.

@therealplato

A diferença entre a abstração try e strings.HasPrefix é que try retorna implicitamente.

Isso é verdade. Ao ler uma função e procurar por pontos de "saída", teremos que procurar return, panic, log.Panic, os.Exit, log.Fatal, etc ... e tentar. Isso é um problema? O número de pontos de saída em uma função permanecerá o mesmo e ainda será explicitamente marcado, com ou sem tentativa.

leia as palavras-chave retorno, entre em pânico

pânico não é uma palavra-chave; é uma função embutida. Se vamos criticar a proposta da equipe de Go, que provavelmente é mais competente em design de linguagem do que qualquer um de nós, então, pelo menos, devemos fazer o favor de definir as coisas de maneira adequada. 😉

Ao ler uma função e procurar por pontos de "saída", teremos que procurar return, panic, log.Panic, os.Exit, log.Fatal, etc ... e tentar. Isso é um problema?

É um problema, porque try pode aparecer literalmente em qualquer lugar onde uma expressão possa aparecer. Cada ponto de saída no Go pode _somente_ ocorrer como uma única instrução, try é a única coisa que pode aparecer como uma expressão.

@ngrilly

Ian sugeriu 7 dias atrás mover esta discussão para golang-nuts exatamente por esta razão (nenhuma maneira de responder a um comentário específico, nenhum tópico), sugestão que foi descartada para garantir que a discussão seria "oficial". Temos o que pedimos.

começar mensagem

@ user1
responder 1

@ user2
resposta 2

mensagem final

Isso é o que significava.

@cstockton

Eu vi um único benefício: evitar a digitação de if err! = Nil.

try evita a digitação e leitura repetitiva de if err != nil { return ..., err } (formatado em 3 linhas), não apenas if err != nil .

Isso tem o custo de introduzir novas maneiras de vazar recursos, a capacidade de escrever código menos conciso e, o pior de tudo, permite aninhar ou tentar.

O risco de vazamento de recursos que você mencionou pode ser evitado com vet e lint .

Sobre "código menos conciso", o objetivo de try é escrever um código mais conciso, então não entendo seu argumento.

O risco de aninhamento excessivo de chamadas de função não é específico de try . Qualquer chamada de função pode ser excessivamente aninhada. As revisões de código e linting ajudarão, como sempre.

Eu sinto que a especificação e os argumentos formados pelos proponentes são enganosos, atualmente fornece apenas os exemplos de melhor caso, sem mostrar nenhum dos exemplos de pior caso.

Talvez esse sentimento seja mútuo. Somos todos esquilos adoráveis; não vamos cair em juízo de valor ;-)

Vejo regurgitado nas respostas dos proponentes: "Não preciso mais digitar if err! = Nil"

Novamente, não preciso mais digitar l := len("foo"); len("foobar") >= l && s[0:l] == "foo" .
Posso usar strings.HasPrefix("foobar", "foo") vez disso.
Como é tão diferente com try ?
Eu li antes que você aceitaria um try "restrito" que se chamaria check e proibiria o aninhamento.

Estamos chegando a uma década de código escrito usando if err! = Nil - que alguns dos avanços tecnológicos mais notáveis ​​(docker, k8s, ...) ao mesmo tempo, todos usam com grande sucesso.

Também temos muitos códigos excelentes escritos em C, C ++, Java, etc. Com essa linha de raciocínio, não teríamos Go.

Ao ler as discussões sobre o tratamento de erros no Go, não achei que todos estivessem na mesma página em relação à proposta try , então decidi escrever uma postagem no blog que demonstra como try pode ser usado: https://faiface.github.io/post/how-to-use-try/

Discussão associada no Reddit: https://www.reddit.com/r/golang/comments/c9eo3g/how_to_use_try_faiface_blog/

Sei que esse problema é contra try , mas espero que minha postagem traga novas perspectivas :)

Go está preso e lutando entre o mágico ou lógico para a ideia de simples.

Prós:

  • Reduzir clichê
  • Simples
  • Padrão existente entre outras línguas
  • Opcional

Contras:

  • Curva de aprendizado
  • Magia subjacente
  • Novos tipos de bugs
  • Go é opinativo e pragmático com if != nil mas você poderia usar try

Eu me sinto como esta comunidade esp. aqui difere contra as pessoas votadas no Go Survey [1] .
Os eleitores podem não escolher isso como uma preocupação principal, em vez de deixar para considerações futuras.
Mas foi considerado como tendo um impacto devido à sua localização.

IMO, recurso de adição de linguagem é antigo e a forma de programação moderna está adicionando mais recursos aos editores, ou seja, Emmet ou fragmentos de linguagem, dobramento e coloração de código, refatoração e formatação, depuração e teste, sugerindo solução para um erro e citando godoc ou estouro de pilha , UI sobre o código-fonte e deixe o código-fonte detalhado
codificar dobrar if err != nil em um try

@ngrilly

try impede a digitação e leitura repetitiva de if err! = nil {return ..., err} (formatado em 3 linhas), não apenas if err! = nil.

Você acredita que eu disse "impede você de digitar if err! = Nil" significa que eu tinha esquecido completamente que também lemos o código que digitamos?

O risco de vazamento de recursos que você mencionou pode ser evitado com veterinário e fiapos.

Eu vinculei uma discussão por que acho que o veterinário e o fiapo não são uma opção razoável aqui.

Sobre "código menos conciso", o objetivo é escrever um código mais conciso, então não entendo seu argumento.

Sim, você leu o link de que "a capacidade de escrever códigos menos concisos " realmente apontado para você pode ter entendido meu argumento. _Observe, levar um pouco de tempo para entender os argumentos das pessoas com quem você está participando da discussão é o primeiro passo para apresentar informações capazes de fazer com que elas cedam à sua opinião._

O risco de aninhamento excessivo de chamadas de função não é específico para tentar. Qualquer chamada de função pode ser excessivamente aninhada. As revisões de código e linting ajudarão, como sempre.

Interessante, vamos decompô-lo:

1) O risco de aninhamento excessivo de chamadas de função não é específico para tentar.

Sim, todos aqui entendem como as funções funcionam.

2) Qualquer chamada de função pode ser aninhada demais.

Sim, todos aqui entendem como as funções funcionam.

3) As revisões de código e linting ajudarão, como sempre.

Sim, você já fez o argumento do lint e o "argumento das revisões de código" é outro controle de linguagem externo que foi feito nas postagens que vinculei a você .

Talvez esse sentimento seja mútuo. Somos todos esquilos adoráveis; não vamos cair em juízo de valor ;-)

Não entendo? A proposta não tem exemplos da capacidade total fornecida pela implementação, apenas o uso pretendido. A ferramenta tryhard usada para ajudar a medir os efeitos da proposta usa try da forma mais limitada e razoável, este é um fato simples.

Novamente, não preciso mais digitar l: = len ("foo"); len ("foobar")> = l && s [0: l] == "foo".
Posso usar strings.HasPrefix ("foobar", "foo") em vez disso.
Como é tão diferente com try?

Eu faço o meu melhor para extrair uma posição de cada visão oposta, caso contrário, não posso formar um argumento para desmontá-la. Eu realmente não consigo ver isso aqui, sinto muito. Vou interpretar isso da única maneira que posso entender: literalmente.

Em que ( strings.HasPrefix ) é tão diferente com try ?

strings.HasPrefix

func HasPrefix

função HasPrefix (s, prefix string) bool

HasPrefix testa se a string s começa com prefixo.

Experimente

func try é uma nova função integrada chamada try com assinatura (pseudo-código)

func try(expr) (T1, T2, … Tn)

onde expr representa uma expressão de argumento de entrada (geralmente uma chamada de função) produzindo n + 1 valores de resultado dos tipos T1, T2, ... Tn e erro para o último valor. Se expr for avaliado como um único valor (n é 0), esse valor deve ser do tipo erro e try não retorna um resultado. Chamar try com uma expressão que não produz um último valor de erro de tipo leva a um erro em tempo de compilação.

O built-in try só pode ser usado dentro de uma função com pelo menos um parâmetro de resultado onde o último resultado é do tipo erro. Chamar try em um contexto diferente leva a um erro em tempo de compilação.

Invocar try com uma chamada de função f () como em (pseudo-código)

x1, x2, … xn = try(f())

turns into the following (in-lined) code:

t1, … tn, te := f()  // t1, … tn, te are local (invisible) temporaries
if te != nil {
        err = te     // assign te to the error result parameter
        return       // return from enclosing function
}
x1, … xn = t1, … tn  // assignment only if there was no error

Em outras palavras, se o último valor produzido por "expr", do tipo erro, for nulo, try simplesmente retorna os primeiros n valores, com o erro nil final removido. Se o último valor produzido por "expr" não for nulo, a variável de resultado de erro da função envolvente (chamada err no pseudo-código acima, mas pode ter qualquer outro nome ou não ser nomeada) é definida para esse valor de erro não nulo e a função envolvente retorna. Se a função envolvente declarar outros parâmetros de resultado nomeados, esses parâmetros de resultado manterão qualquer valor que possuam. Se a função declara outros parâmetros de resultado não nomeados, eles assumem seus valores zero correspondentes (que é o mesmo que manter o valor que já possuem).

Se try for usado em uma atribuição múltipla como nesta ilustração, e um erro não nulo for detectado, a atribuição (para as variáveis ​​definidas pelo usuário) não será executada e nenhuma das variáveis ​​no lado esquerdo do atribuições são alteradas. Ou seja, try se comporta como uma chamada de função: seus resultados só estão disponíveis se try retornar ao site da chamada real (em vez de retornar da função envolvente). Como consequência, se as variáveis ​​do lado esquerdo forem nomeadas como parâmetros de resultado, o uso de try levará a um resultado diferente do código típico encontrado hoje. Por exemplo, se a, b e err forem todos parâmetros de resultado nomeados da função envolvente, este código

a, b, err = f()
if err != nil {
        return
}

sempre definirá a, b e err, independentemente se f () retornou um erro ou não. Em contraste

a, b = try(f())

deixará aeb inalterados em caso de erro. Embora essa seja uma diferença sutil, acreditamos que casos como esse sejam raros. Se o comportamento atual for esperado, mantenha a instrução if.

Eles são diferentes porque todo o texto encontrado na descrição de try não está presente em strings.HasPrefix. Uma pergunta melhor seria como são semelhantes, ao que eu responderia que ambos compartilham alguns aspectos das Chamadas e nada mais.

Eu li antes que você aceitaria uma tentativa "restrita" que seria chamada de verificação e proibiria o aninhamento.

Que bom que você leu meu argumento central contra try: a implementação não é restritiva o suficiente. Acredito que a implementação deve corresponder a todos os exemplos de uso das propostas que sejam concisos e fáceis de ler.

_Ou_ a proposta deve conter exemplos que correspondam à implementação para que todas as pessoas que a considerem possam ser expostas ao que inevitavelmente aparecerá no código Go. Junto com todos os casos extremos que podemos enfrentar ao solucionar problemas de software menos do que idealmente escrito, o que ocorre em qualquer idioma / ambiente. Ele deve responder a perguntas como a aparência dos rastreamentos de pilha com vários níveis de aninhamento. Os locais dos erros são facilmente reconhecíveis? E quanto aos valores de método, literais de função anônima? Que tipo de rastreamento de pilha o abaixo produz se a linha que contém as chamadas para fn () falhar?

fn := func(n int) (int, error) { ... }
return try(func() (int, error) { 
    mu.Lock()
    defer mu.Unlock()
    return try(try(fn(111111)) + try(fn(101010)) + try(func() (int, error) {
       // yea...
    })(2))
}(try(fn(1)))

Estou bem ciente de que haverá muitos códigos razoáveis ​​escritos, mas agora estamos fornecendo uma ferramenta que nunca existiu antes: a capacidade de escrever código potencialmente sem um fluxo de controle claro. Então, eu quero justificar porque nós permitimos isso em primeiro lugar, eu nunca quero perder meu tempo depurando este tipo de código. Porque eu sei que vou, a experiência me ensinou que alguém fará isso se você permitir. Esse alguém geralmente é um eu desinformado.

Go fornece as maneiras menos possíveis para outros desenvolvedores e eu perdermos o tempo uns dos outros, limitando-nos a usar as mesmas construções mundanas. Não quero perder isso sem um benefício avassalador. Não acredito que "porque try é implementado como uma função" seja um benefício esmagador. Você pode fornecer um motivo para isso?

Não perca tempo com esse problema de tratamento de erros, dê-nos os genéricos e criaremos algo como Rust's Result.

Go está preso e lutando entre o mágico ou lógico para a ideia de simples.

Prós:

  • Reduzir clichê
  • Simples
  • Padrão existente entre outras línguas
  • Opcional

Contras:

  • Curva de aprendizado
  • Magia subjacente
  • Novos tipos de bugs
  • Go é opinativo e pragmático com if != nil mas você poderia usar try

Eu me sinto como esta comunidade esp. aqui difere contra as pessoas votadas no Go Survey [1] .
Os eleitores podem não escolher isso como uma preocupação principal, em vez de deixar para considerações futuras.
Mas foi considerado como tendo um impacto devido à sua localização.

IMO, recurso de adição de linguagem é antigo e a forma de programação moderna está adicionando mais recursos aos editores, ou seja, Emmet ou fragmentos de linguagem, dobramento e coloração de código, refatoração e formatação, depuração e teste, sugerindo solução para um erro e citando godoc ou estouro de pilha , UI sobre o código-fonte e deixe o código-fonte detalhado
codificar dobrar if err != nil em um try

Fui eu que votei no tratamento de erros mais rígido, sem a possibilidade de esquecer de processar um erro. Não para tentar.

Precisaremos de muito mais do que genéricos para refazer qualquer coisa remotamente parecida com o tipo Result Rust. Mesmo _se_ o tipo Result pudesse ser feito exclusivamente com genéricos, os programadores iniciantes precisariam conhecer os genéricos antes mesmo de poderem tratar um erro adequadamente ou retornar um erro de uma função "da maneira Result "

@deanveloper , o que

@txgruppi Posso concordar que os genéricos devem ter maior prioridade. Só estava tentando dizer que não acho que os genéricos sejam um bom substituto para o tratamento de erros.

@deanveloper , na minha opinião este problema de tratamento de erros é apenas cosmético, as pessoas estão gastando tempo discutindo algo que é estável e funciona bem apenas porque você precisa digitar alguns códigos extras. Apenas aprenda como escrever um código melhor e resolver isso com alterações de design.
Antes que alguém diga que Genéricos são tão simples de consertar com um código melhor: erros em tempo de compilação ...

pode ser resolvido com um trecho ou uma macro do teclado? então não é um problema.

@txgruppi

Apenas aprenda como escrever um código melhor e resolver isso com alterações de design.

70% de todo o código de tratamento de erros na biblioteca padrão é atualmente adequado para try como Robert Griesemer descobriu usando sua ferramenta tryhard . Outros seriam elegíveis com alterações no código, como usar a função (ainda não existente) fmt.HandleErrorf . Espero que você não queira chamar a biblioteca padrão de código ruim.

pode ser resolvido com um trecho ou uma macro do teclado? então não é um problema.

É também sobre a leitura do código. É por isso que não gostamos de thing.Thing thing = new thing.Thing(thing.THING);

@faiface , 'if err! = nil' está começando a desenvolver software de qualidade? Acho que não.
A falta de Genéricos está atrapalhando o desenvolvimento de software de qualidade? Sim, ele é.

A maneira como vejo as coisas é: não tenho conhecimento suficiente para implementar os genéricos, então preciso de alguém para implementá-los, mas essa coisa de tratamento de erros é apenas uma perda de tempo para aquelas mentes que podem tornar os genéricos uma realidade. Não sou contra esse tratamento de erros porque é uma coisa ruim, sou contra porque há coisas mais importantes para resolver.

@faiface a biblioteca padrão não é uma boa representação do código Go real. Isso ocorre porque é muito mais provável que a biblioteca padrão simplesmente ignore os erros sem adicionar contexto, por exemplo io/ioutil nunca realmente precisa decorar os erros, ela pode simplesmente ignorar o erro que ocorreu em io . Robert Griesemer também admitiu que o stdlib não é exatamente o melhor representante do código Go da vida real, no entanto, estou no celular agora e não quero ficar procurando pelo comentário. Tenho certeza de que foi relativamente perto de sua postagem original do tryhard.

@deanveloper @faiface Quando executado no Go Corpus :

--- stats ---
 401679 (100.0% of  401679) func declarations
  97496 ( 24.3% of  401679) func declarations returning an error
 991348 (100.0% of  991348) statements
 217490 ( 21.9% of  991348) if statements
  88891 ( 40.9% of  217490) if <err> != nil statements
    485 (  0.5% of   88891) <err> name is different from "err" (-l flag lists details)
  59500 ( 66.9% of   88891) return ..., <err> blocks in if <err> != nil statements
  29391 ( 33.1% of   88891) complex error handler in if <err> != nil statements; cannot use try (-l flag lists details)
    596 (  0.7% of   88891) non-empty else blocks in if <err> != nil statements; cannot use try (-l flag lists details)
  52810 ( 59.4% of   88891) try candidates (-l flag lists details)

Portanto, no código da vida real, 40% das instruções if são escritas para verificação de erros e try pode eliminar 59% delas fora da caixa.

Eu concordo. Estou bem com if err! = Nil. É simples e limpo para funções que retornam valores de erro únicos. Também gosto do pacote de erros e de suas funções causa / agrupamento quando o contexto do erro é importante. Usar erros personalizados com uma propriedade de código (até onde eu sei) requer que você faça uma declaração de tipo ou use algo no lugar da interface de erro padrão. De qualquer forma, nunca me peguei lendo ou escrevendo código Go e sentindo qualquer tipo de aborrecimento com a forma como o tratamento de erros funciona atualmente. Os incômodos que encontrei são casos em que vários erros podem ocorrer como resultado do processamento de uma coleção de itens. No entanto, este é um problema de design e não um problema de linguagem.

Observe que quando digo "quando o contexto do erro é importante", estou me referindo a uma situação em que talvez tenha ocorrido um soluço na rede e então desejo tentar novamente, ou talvez os resultados de uma chamada do tipo "localizar" não tenham retornado resultados porque houve na verdade, nenhum, ou meu dedo inquieto adicionou um 's' aleatório à minha consulta SQL, o que a está fazendo explodir (isso acontece comigo com frequência ... Eu provavelmente deveria ser verificado para ver se há danos nos nervos).

Em 05/07/19, Nicolas Grilly [email protected] escreveu:

@kroppt já li dezenas de vezes ;-) Não concordo com a ideia de que tentar é
ofuscando o código. try é apenas uma função embutida usada para fatorar alguns
código repetitivo. Fazemos isso o tempo todo como programadores. Quando identificamos um
padrão repetitivo, nós o fatoramos em uma nova função. Se não, nós
teria uma função main () longa com todo o nosso código embutido nela.

Estou tentado a chamá-lo de hipócrita, mas respeito o de Ian Lance Taylor
esforços sobre-humanos para manter a discussão educada e eu realmente não posso
veja o que alguém ganharia mentindo intencionalmente neste fórum.

Dito isso, "quando identificamos um padrão repetitivo, o fatoramos em
uma nova função. "Claro, mas não fornecendo uma construção conflitante
que, neste estágio final do desenvolvimento de Go, vem com dois
recursos ocultos: o primeiro é tratar funções cujo "retorno
a lista de argumentos termina com um error valor "como especial (ou tudo o mais
como um erro semântico) e o segundo fornece um fluxo de controle oculto
desvio que é análogo, mas não inteiramente idêntico a um "retorno"
demonstração.

Não importa os aros inflamados que o uso de "adiar" introduz para lidar
com usos mais misteriosos de "try - the pseudo function". Alguém
em outro lugar, disse aproximadamente "Não quero encontrar try no código
Eu li ". Eu sinto o mesmo e isso não deve ser varrido sob o
tapete.

Afirmei que é o aspecto de "retorno" do "retorno de erro" que
precisa ser tratada, e a proposta "on err" chega mais perto de
esse princípio, mas também distorce um pouco as regras. O meu também
sugestão de "falha" (move o último argumento para ser o primeiro, que é
me deixando infeliz).

Mais profundamente, o que nenhuma linguagem com a possível exceção do SNOBOL,
que estou familiarizado deu o salto que Rob Pike descreveu como
"erros são valores" no grau que Go tem, mas algo se perdeu
no processo: uma "condição" de erro "não" é um valor. Bem-sucedido
a conclusão de uma função é um caso especial e, portanto, cada um é possível
fracasso.

Cada um (e isso se aplica à conclusão bem-sucedida, da qual pode haver
mais de um também) precisa ser tratado por seu mérito, mas nós
insista que a função invocada deve nos dizer sua opinião sobre
a qualidade do preenchimento de forma abreviada, algo que tem
foi feito para sempre e Rob mostrou ser um equívoco.

Para ilustrar o ponto, considere os valores de retorno de um leitor:
io.EOF é um caso especial que às vezes é um sucesso e às vezes um
falha, mas pelos padrões Go é orgulhosamente um erro ("io.Err! = nulo").
Teremos alguma maneira de abreviar isso também? Quase com certeza
não, porque estamos bastante acostumados a "perdoar" seu "erro".

Há muito tempo espero por uma saída de loop para transmitir um "status" semelhante ou
código de condição (uma pesquisa pode terminar com um valor encontrado ou uma falha, como
você diz qual, se deseja fazer coisas diferentes? Você adiciona um teste,
onde o conhecimento já existe - mesmo problema, diferente
contexto).

Estas são melhorias reais para as linguagens tradicionais: redução da caldeira
placa é um absurdo, em comparação.

E a comparação com o operador ternário é igualmente válida: se?:
não é permitido "para que não seja abusado", então tente não deve ser permitido,
qualquer um, pelo menos por esses motivos.

Francamente, tentar é uma brincadeira. Isso torna o Go mais atraente para o errado
público. Além do perigo em fazer isso - quem quer as pessoas erradas
para se juntar à nossa comunidade? - há a questão do precedente e a questão
de consequências não intencionais. Eu diria "jogue fora" e vamos aceitar isso
ainda não chegamos a um ponto em que a compatibilidade com versões anteriores pode
ser desprezado, portanto, o tratamento de erros deve permanecer prolixo.

Estamos em pânico / recuperação e precisa ser promovido de terceira categoria
cidadão ao ajudante disposto que possa ser, com todas as explicações
que tornam os novatos (e eu, admito estar com medo disso) mais
confiante em usá-lo.

A construção "adiar" no tratamento de erros (que adotei -
sem perceber sua importância em outro lugar - consistentemente na finalização
Transações SQL: tx.Rollback / tx.Commit) foi uma revelação para mim.
Pode haver mais princípios que podem ser aprendidos "dentro" do escopo de
o que Go já oferece: vamos ficar dentro dessa caixa por enquanto.

Uma coisa, de improviso, seria passar para um relatório de erro
função uma lista de "métodos de erro" a serem executados no convencional
condições (io.EOF, sql.ErrNoRows), em vez de relatar o resultado
como preto e branco. Mas eu não sou instruído nesses assuntos, minhas sugestões
são muito ingênuos, deixe os outros (Roger, você está ouvindo?) criarem semelhantes
ideias à fruição.

Lucio.

"Não é tedioso para digitar. É tedioso para ler, quando temos o mesmo
3 linhas em todos os lugares. É uma repetição, e nós, como programadores, geralmente
tendem a fatorá-los. Talvez a tentativa tenha outras desvantagens, mas não esta
um eu acho. "

Novamente, insincero ou, no mínimo, racionalizando. Eu admito que aqueles
três linhas são lidas muito mais vezes do que escritas, mas o
a dor que Griesemer almejava aliviar está na escrita, não na leitura

  • o que sem dúvida ele percebe como uma consequência benéfica.

Mas "err! = Nil" é um marcador muito familiar e ao "ler" - como
em oposição a "pesquisar" usando um editor, esse padrão é fácil de
local e fácil de cancelar. Fatorar não é o mesmo
liga em tudo. E o preço está errado.

"Nós, como programadores", tendemos a fatorar padrões mais complexos primeiro,
mesmo que ocorram raramente. Também sabemos que "if err! = Nil {return
err} "é compilado em uma sequência muito simples de instruções, se
se alguém não o fizer, deixe-os levantar a mão aqui. Um pode ser igualmente
confiante de que isso acontecerá com "try - the function"?

Lucio.

@lootch Ei cara, é realmente improdutivo chamar as pessoas de hipócritas, tenho quase certeza de que não são, já que as afirmações que você marcou como tal são bastante razoáveis.

Nós fatoramos padrões repetitivos como programadores, isso é absolutamente verdade.

Muitos códigos repetitivos tornam a leitura lenta, então também não vejo como isso é falso.

Seus contra-argumentos são basicamente "vamos lá, cara, isso não é grande coisa". Bem, para muitas pessoas é.

quem quer que as pessoas erradas se juntem à nossa comunidade?

Este é um portão super arrogante. Além disso, a ferramenta tryhard revelou que try é diretamente aplicável em muitas das bases de código Go atuais. Ele pode ser aplicado diretamente a 70% do código de tratamento de erros na biblioteca padrão. Com as mudanças no código (para usar decoração de erro usando defer , etc), acredito que será aplicável a mais de 80% do tratamento de erros em todo o código Go.

Concordo, estou ultrapassando o limite, aqui. Peço desculpas.

Acho que alguns de nós estão ficando malucos com essa discussão
está andando em círculos.

Em 7/7/19, Michal Štrba [email protected] escreveu:

@lootch Ei cara, é realmente improdutivo chamar as pessoas de falsa, eu sou
tenho certeza de que não são, pois as declarações que você marcou como tal são bonitas
razoável.

Nós fatoramos padrões repetitivos como programadores, isso é absolutamente
verdade.

Muitos códigos repetitivos tornam a leitura lenta, então não vejo como isso
insincero também.

Seus contra-argumentos são basicamente "vamos lá, cara, não é tal
grande coisa ". Bem, para muitas pessoas é.

quem quer que as pessoas erradas se juntem à nossa comunidade?

Este é um portão super arrogante. Além disso, a ferramenta tryhard revelou que
try é diretamente aplicável em muitas das bases de código Go atuais. Pode ser
aplicado diretamente a 70% do código de tratamento de erros na biblioteca padrão. Com
mudanças no código (para usar decoração de erro usando defer , etc), eu acredito
será aplicável a mais de 80% do tratamento de erros em todo o código Go.

-
Você está recebendo isso porque foi mencionado.
Responda a este e-mail diretamente ou visualize-o no GitHub:
https://github.com/golang/go/issues/32825#issuecomment -508971768

-
Lucio De Re
2 Piet Retief St
Kestell (Estado Livre Oriental)
9860 África do Sul

Telefone: +27 58 653 1433
Celular: +27 83 251 5824
FAX: +27 58 653 1435

@lootch Props sobre ser autoconsciente! Eu posso entender a frustração de ver a discussão andando em círculos.

Também vejo da mesma forma e estou do outro lado.

Talvez ambos os lados não estejam conseguindo se entender. Você já leu minha postagem do blog chamada Como usar o 'teste'? onde tento mostrar como usar 'try' pareceria na prática, fazendo o meu melhor para permanecer imparcial?

Em 7/7/19, Michal Štrba [email protected] escreveu:

[...]
Talvez ambos os lados estejam apenas falhando em entender o outro lado. Você já
leia minha postagem do blog chamada Como usar 'Experimente'? onde eu tento
mostre como usar 'try' seria na prática, fazendo o meu melhor para ficar
imparcial?

Confesso que não, desejo ardentemente nunca ter de :-)

Você considerou os aspectos que acho que Stockton levantou, onde apenas
as vantagens de tentar são exibidas e ele pede que o ponto fraco
ser revelado também? Temo que concordo com ele e - sem intenção de ofender -
que seu blog pode sofrer das mesmas deficiências.

Se não, então, por favor, me estimule, bons livros de leitura têm um lugar especial
na minha vida :-)

Lucio.

@lootch Fiz o meu melhor para mostrar o máximo possível de aspectos de 'tentativa' (ou seja, quando se aplica e quando não se aplica) em uma pequena quantidade de código e torná-lo o mais imparcial e realista possível. Mas é claro, não acredite apenas na minha palavra :)

Este foi o comentário mais votado na discussão associada do Reddit :

Este é um exemplo hipotético imparcial útil. Obrigado por adicionar algo construtivo à conversa que não seja apenas "uma merda".

@lootch Fiz o meu melhor para mostrar o máximo possível de aspectos de 'tentativa' (ou seja, quando se aplica e quando não se aplica) em uma pequena quantidade de código e torná-lo o mais imparcial e realista possível. Mas é claro, não acredite apenas na minha palavra :)

Este foi o comentário mais votado na discussão associada do Reddit :

Este é um exemplo hipotético imparcial útil. Obrigado por adicionar algo construtivo à conversa que não seja apenas "uma merda".

Função com o caminho do arquivo como argumento? Isso por si só seria um motivo pelo qual este código não seria aprovado em minha revisão. E se alguns campos estiverem faltando? Reordenado?

@sirkon Para o propósito de não ser muito longo, o exemplo é simplificado, é claro. As mudanças que seriam necessárias para corrigir os problemas que você levantou não afetam as práticas de tratamento de erros, porém, que é tudo o que importa lá.

As mudanças que seriam necessárias para corrigir os problemas que você levantou não afetam as práticas de tratamento de erros

Porque você disse isso?

  1. Comece pelo título do seu blog: deveria se chamar "como não escrever", porque, repito, usar o caminho do arquivo como parâmetro é uma prática muito ruim e, francamente, um código inteiro abaixo também.
  2. Você percebe isso
    go resp := Respondent{ Name: name, Gender: try(parseField(s, &line, "gender")), OS: try(parseField(s, &line, "os")), Lang: try(parseField(s, &line, "lang")), }
    irá produzir mensagens de erro ruins? Deve haver uma mensagem de erro de campo inesperada e, pelo menos, mensagem de erro de campos ausentes. O diagnóstico do seu script está abaixo do padrão.

PS Dê uma olhada em seus repositórios. Você percebe que Go é uma ferramenta ruim para suas tarefas? Você deve entender na prática de aplicação real do Go, os primeiros que verão os logs serão os engenheiros de operação, não os desenvolvedores. A mensagem de erro adequada pode ajudá-los a resolver o problema sozinhos.

@sirkon Vamos, não faça flamewars.

Você percebe que isso produzirá mensagens de erro ruins?

Eles são totalmente adequados ao modelo. O formato deve conter todos os campos e em ordem. A mensagem de erro diz isso muito claramente.

Se você deseja contestar a qualidade do código, por que não o reescreve de acordo com seus padrões de qualidade? Se você fizer isso, tentarei fazer o possível para reescrever seu código para usar try .

PS Dê uma olhada em seus repositórios. Você percebe que Go é uma ferramenta ruim para suas tarefas?

Você sugere outro para minhas tarefas? Eu usei alguns. A propósito, isso está fora do assunto.

@faiface

Você sugere outro para minhas tarefas? Eu usei alguns. A propósito, isso está fora do assunto.

Ferrugem? C ++?

@sirkon

Ferrugem? C ++?

Aqui vamos nós. Eu usei os dois antes de decidir por Go. Eu nunca olhei para trás.

@sirkon Uma das grandes falhas de try é que ele desencoraja a decoração de erros. O programador, neste caso, estava mostrando possíveis aplicações de try , então é claro que não haverá muita decoração de erro acontecendo.

Além disso, desacreditar as pessoas com base nos projetos em que trabalharam é totalmente fora do assunto e desnecessário. Você foi muito rude com seus comentários anteriores e quero que pelo menos esteja ciente disso.

@deanveloper Obrigado pelo comentário!

Por falar nisso

O programador, neste caso, estava mostrando possíveis aplicações de teste, então é claro que não haverá muita decoração de erro acontecendo.

Caso você esteja se referindo ao meu blog, na verdade há muitos erros de decoração acontecendo, mas não exatamente da mesma forma que @sirkon faria. Aqui estão algumas mensagens de erro do programa que usa try :

parse respondnts.txt: open respondnts.txt: no such file or directory
parse respondents.txt: line 12: parse field gender: expected "gender:"
parse respondents.txt: line 9: expected empty line
parse respondents.txt: line 4: parse field lang: EOF

@faiface Meu erro, eu deveria ter sido mais específico. try desencoraja a decoração de erros quando você deseja várias mensagens de erro na mesma função. Era possível fazer isso com check/handle draft e com as contrapropostas do "manipulador nomeado". Teria sido muito útil na instância específica apontada (onde você estava usando try ao inicializar uma estrutura) para poder adicionar decoração em torno de cada mensagem, mas infelizmente a proposta de teste torna isso um pouco difícil de fazer sem escrever sua própria função.

Verificar / manipular não teria ajudado muito no seu caso específico. Mas a ideia proposta de catch e outras contrapropostas de try teriam sido capazes de lidar com os erros e adicionar decoração adicional.

@deanveloper Bem, na maioria das vezes você precisa usar a mesma decoração para todos os erros em uma função porque as subfunções devem fornecer seu próprio contexto. No entanto, quando você precisa decorar as coisas de maneira diferente em uma única função, ainda há uma solução fácil com try:

..., err := functionThatCanFail(...)
try(errors.Wrapf(err, ...))

Ou simplesmente divida a função grande em várias pequenas.

@faiface aos meus olhos, nesse ponto, deve-se usar apenas if err != nil , mas acho que é uma questão de preferência.

No entanto, às vezes (como no caso de inicialização de estrutura), não é uma boa ideia dividir em várias funções. Estou ficando um pouco minucioso, embora eu acho.

Na verdade, não sou muito contra try , mas também não sou exatamente um grande apoiador. Acho que há outra solução melhor por aí.

@deanveloper

No entanto, às vezes (como no caso de inicialização de estrutura), não é uma boa ideia dividir em várias funções.

É verdade, mas também não é necessário decorá-los de forma diferente, porque toda a decoração específica necessária vem de parseField .

Acho que há outra solução melhor por aí.

Isso é bem possível! Se eu vir uma solução melhor, perderei try em um minuto :)

na maioria das vezes você precisa usar a mesma decoração para todos os erros dentro de uma função porque as subfunções devem fornecer seu próprio contexto

@faiface Eu discordo totalmente desta afirmação. Cada função é uma subfunção de outra na pilha de chamadas. Isso significa que tem as mesmas responsabilidades no fluxo de tratamento de erros (ou seja, fornecer contexto de erro para o escopo superior).

Imagine uma função que anexa dois blocos de dados a um único arquivo. Como você distinguiria qual desses anexos falhou se você quase não retornou uma instrução 'não foi possível gravar no arquivo'?

Somos todos criaturas preguiçosas. Eu também preferiria fazer algo de uma vez por todas, se pudesse. E sim, quando comecei minha aventura com Go, considerei o tratamento de erros um pouco complicado. Depois de alguns anos de prática, minha visão mudou 180 graus. Acredito que o tratamento de erros atual em Go promove uma programação responsável e um bom design. IMHO seria uma grande falha em adicionar outro mecanismo que prejudica esta abordagem.

@mklimuk Uma parte importante do meu comentário é "na maioria das vezes". O exemplo que você forneceu provavelmente é melhor tratado por if err != nil . Como observado várias vezes, try não foi projetado para lidar com todas as situações, apenas as mais comuns.

E as evidências mostram que sim, pois 70% de todo o código de tratamento de erros na biblioteca padrão pode usar try pronto para uso e o mesmo vale para 59% de todo o código de tratamento de erros na selvagem.

@faiface bem o fato de que try pode substituir o tratamento de erros explícito não significa que deveria. No meu caso, retornar um erro sem adicionar contexto a ele não é 'a situação mais comum'. É o contrário :)

As pessoas que estão votando positivamente neste tópico estão preocupadas apenas com o fato de que esta nova declaração irá estragar todo o esforço por trás do design Go original (simplicidade, clareza, etc.) para tornar o código Go menos prolixo.

Claro, mas espero que você entenda que try não serve para retornar um erro sem um contexto. Na verdade, o caso mais comum de adição de contexto (um contexto por função) é amplamente simplificado por try :

func doSomething() (err error) {
    defer fmt.HandleErrorf(&err, "doing something")

    x := try(oneThing())
    try(anotherThing(x))
    // ...
}

A coisa a perceber é que na maioria das vezes, oneThing() e anotherThing() retornarão um contexto suficiente por conta própria, então envolvê-lo em um simples "doing something: ..." é completamente suficiente.

Como observação lateral, acho que poderíamos usar algumas convenções sobre _quem_ faz a decoração. No stdlib algumas funções fazem isso, ex copy: x to y ou similar, eu pessoalmente tenho deixado a decoração por conta do chamador, pois ele tem os argumentos.

Por exemplo, se eu tivesse um Copy() eu faria algo como return errors.Wrap(err, "writing") e o chamador usando Copy() iria embrulhar com errors.Wrapf(err, "copying from %v to %v", src, dst) ou similar.

Esses dois não se misturam e combinam muito bem e às vezes podem acabar com strings duplicadas. É melhor apenas dizer que o estilo stdlib é idiomático? Não me lembro de todos eles se comportando assim. Eu acho que essa é a única maneira de o exemplo de @faiface ser suficiente. Talvez eu tenha invertido o problema, no entanto, para mim parece mais limpo fazer menos e deixar as decisões para o chamador, especialmente "apenas no caso" de omitir informações contextuais.

Eu pessoalmente tenho deixado a decoração por conta do chamador, pois ele tem os argumentos.

sim. Por exemplo, considere uma função que analisa o corpo JSON de uma solicitação HTTP, verificando os cabeçalhos e assim por diante. Se ele for alimentado sintaticamente com um JSON inválido, sua responsabilidade - tudo o que ele pode fazer, na verdade - é relatar o erro. O _caller_ sabe qual parte da API estava tentando ser chamada e precisa decorar o erro de acordo antes de transmiti-lo pela cadeia ou emitir um erro HTTP.

Se suas funções forem realmente fatoradas em código de propósito geral que poderia ser usado em vários lugares, elas não terão as informações necessárias para decorar o erro. Ao contrário, se eles têm todo o contexto, provavelmente não são funções que fazem sentido como funções autônomas, você está apenas criando funções para quebrar o código e torná-lo mais organizado do que realmente é.

@lpar Mind está dando um exemplo específico disso?

Já dei um exemplo específico? Considere se sua função parseJSON realmente conhecia o contexto e era capaz de decorar para qual ponto de extremidade da API e fluxo de atividade ela estava analisando o corpo. Isso sugeriria que era específico para aquele terminal ou que você estava passando as informações apenas para poder usá-las para encerrar erros.

@lpar Ok, então esse é outro exemplo onde if err != nil permanecerá em uso. Ou você divide sua lógica em várias funções.

Mas entenda que dar um exemplo onde try não é apropriado não é um argumento contra try . try não pretende substituir todo o tratamento de erros, apenas os casos mais comuns.

Screenshot 2019-07-07 at 6 30 42 PM

@abejide001 a try não é o tradicional "try / catch" de muitas outras linguagens, é muito mais semelhante à macro try! em Rust. Bom meme embora lol

Ops - postou para o problema errado. Movido para https://github.com/golang/go/issues/32437#issuecomment -509024693.

Recentemente, postei uma proposta # 32968 que se baseia em meu desacordo com a perigosa capacidade de aninhamento que a macro try possui. Embora eu espere que não haja falhas graves, como autor não sou a pessoa certa para ver nenhuma. Portanto, gostaria de pedir ao meu campo _não tente_ (você :) para ver, avaliar e comentar sobre ele.


Excerto:

  • _A macro check não é de uma linha: ela ajuda muito onde muitos
    verificações usando a mesma expressão devem ser realizadas nas proximidades._
  • _Sua versão implícita já compila no playground._

Restrições de design (atendidas)

É embutido, não se aninha em uma única linha, permite muito mais fluxos do que try e não tem expectativas sobre a forma de um código dentro dele. Não incentiva retornos nus.

exemplo de uso

// built-in 'check' macro signature: 
func check(Condition bool) {}

check(err != nil) // explicit catch: label.
{
    ucred, err := getUserCredentials(user)
    remote, err := connectToApi(remoteUri)
    err, session, usertoken := remote.Auth(user, ucred)
    udata, err := session.getCalendar(usertoken)

  catch:               // sad path
    ucred.Clear()      // cleanup passwords
    remote.Close()     // do not leak sockets
    return nil, 0, err // dress before leaving
}
// happy path

// implicit catch: label is above last statement
check(x < 4) 
  {
    x, y = transformA(x, z)
    y, z = transformB(x, y)
    x, y = transformC(y, z)
    break // if x was < 4 after any of above
  }

Espero que isso ajude, aproveite!

Mas entenda que dar um exemplo onde try não é apropriado não é um argumento contra try . try não pretende substituir todo o tratamento de erros, apenas os casos mais comuns.

E de acordo com as estatísticas que postei antes , eles não são os casos mais comuns em meu código. Os casos mais comuns em meu código são erros sendo empacotados antes do retorno. Portanto, try só seria apropriado para uma porcentagem de um único dígito dos meus retornos de erro, na melhor das hipóteses (*), e é por isso que acho que precisamos de algo melhor.

(*) E, de fato, estou inclinado a pensar que as instâncias nuas de err return são provavelmente erros que devem ser corrigidos.

Concordo totalmente, deixe "if err! = Nil" sozinho.

@abejide001 a try não é o tradicional "try / catch" de muitas outras linguagens, é muito mais semelhante à macro try! em Rust. Bom meme embora lol

Isso por si só é uma preocupação para mim, Go já é uma linguagem estranha para os recém-chegados, e agora devemos explicar por que try tem uma lógica sob medida. FWIW, não acho que dizer "Rust fez isso" seja uma boa razão para justificar a adição de qualquer coisa em uma linguagem - simplesmente não é muito conhecido.

Como eu não estava dizendo isso para justificar o recurso, estava apenas dizendo para esclarecer o que o recurso fazia. Estou meio no meio da proposta try .

Não que isso faça muita diferença, mas também é um recurso do Swift, embora com uma palavra-chave em vez de uma macro.

Parece que há alguma confusão sobre o que exatamente o try está tentando alcançar. IMHO, o problema não é gravar vários blocos if que verificam se há erros. Você os escreve uma vez e pronto. O problema é ler o código que contém vários desses blocos. Lemos muito mais do que escrevemos. E esses blocos ofuscam o código real porque se entrelaçam a ele. Pior ainda, na maioria das vezes eles são quase exatamente os mesmos, com apenas uma pequena diferença de string em algum lugar dentro do bloco if.

Eu, pessoalmente, preferia o antigo rascunho de controle de verificação, mas pelo menos faz um bom trabalho separando erros e caminhos de negócios. E podemos finalmente ser capazes de ter um único contexto de escopo de função em vez de para cada chamada, que atualmente tem uma boa chance de repetir a mesma coisa que o erro pai.

@icholy escreveu:

Houve um feedback esmagador da comunidade, solicitando um tratamento de erros mais simplificado (da pesquisa anual). A Go Team está agora tratando desse problema.

Acabei de pesquisar a pesquisa aqui: https://blog.golang.org/survey2018-results

Aparentemente, a pergunta era: "Qual é o maior desafio que você enfrenta pessoalmente usando Go hoje?" com possível resposta "Tratamento de erros".

Eu seriamente me pergunto como, com base nessa pergunta + resposta, foi deduzido que uma sintaxe mais breve era necessária. Eu também poderia ter respondido 'tratamento de erros', mas de forma alguma gostaria de ver outra sintaxe. Se eu tivesse marcado essa opção na pesquisa, teria pensado em permitir que os erros sejam agrupados, forneça rastreamentos de pilha, etc.

Minha sugestão seria recuar em todas as propostas de tratamento de erros (efetivamente o que @miekg estava sugerindo). E primeiro determine o que realmente é o que a comunidade deseja, documente isso. Em seguida, descubra por que é isso que eles querem. E só depois comece a procurar maneiras de conseguir isso.

Acabei de analisar a proposta try, mas a menos que esteja faltando alguma coisa, ela deixa de dizer _why_ que está sendo proposta, além de "eliminar as declarações padrão if [...}". Mas não há nenhuma menção sobre por que a eliminação dessas declarações padronizadas se é necessária.

Eu definitivamente concordo com o acima. Vamos ver se as novas alterações de valores de erro ajudam a auxiliar no tratamento de erros de reclamações que as pessoas têm com Go. Então, podemos ver se uma sintaxe mais breve é ​​necessária.

As pessoas aqui estão argumentando contra try porque acham que todos os erros retornados devem ser anotados. A realidade é que, no corpus atual de código (incluindo a biblioteca padrão), uma alta porcentagem de verificações de erro tem retornos de erro ~ nua ~ não anotados e se beneficiaria de try . Sua crença de como o código DEVE ser não tem nada a ver com a maneira como o código É . Poupe-me de seu dogma.

@icholy Ignorar erros indica que o desenvolvedor não se preocupa com esse erro. O erro é insignificante ou é considerado impossível pelo chamador. Se for esse o caso, "tentar" é tão inútil quanto, o chamador simplesmente não envolveria a função em um "teste".

Minha sugestão seria recuar em todas as propostas de tratamento de erros (efetivamente o que @miekg estava sugerindo). E primeiro determine o que realmente é o que a comunidade deseja, documente isso. Em seguida, descubra por que é isso que eles querem. E só depois comece a procurar maneiras de conseguir isso.

Eu concordo totalmente com isso. Vejo muitas divergências básicas sobre qual funcionalidade qualquer melhoria no tratamento de erros do Go deveria suportar. Cada pedaço diferente de funcionalidade que as pessoas mencionam está desencadeando um ciclo de bicicletas em relação a sua nomenclatura e sintaxe, de modo que a discussão não vai a lugar nenhum.

Eu gostaria de saber com mais detalhes o que a comunidade Go mais ampla realmente deseja de qualquer novo recurso de tratamento de erros proposto.

Eu fiz uma pesquisa listando um monte de recursos diferentes, partes da funcionalidade de tratamento de erros que vi as pessoas proporem. Eu cuidadosamente _omitei_ qualquer nomenclatura ou sintaxe proposta e, é claro, tentei tornar a pesquisa neutra em vez de favorecer minhas próprias opiniões.

Se as pessoas quiserem participar, aqui está o link, abreviado para compartilhamento:

https://forms.gle/gaCBgxKRE4RMCz7c7

Todos os participantes devem ser capazes de ver os resultados resumidos. Então, talvez, quando tivermos uma ideia melhor do que as pessoas realmente desejam, seremos capazes de ter uma discussão inteligente sobre se a proposta try fornece essas coisas. (E então talvez até comece a discutir a sintaxe.)

@ lane-c-wagner você está tentando dizer que retornar um erro não anotado é o mesmo que não retorná-lo? editar: corrigiu o comentário anterior

@icholy Ah eu entendi mal. Quando você disse "simples", pensei que quisesse dizer "_" erros ignorados.

Esta proposta argumenta que nenhuma ação deve ser uma ação válida. Essa mudança afeta todos os usuários da linguagem porque eles lêem o código. Assim, uma pesquisa identificando o maior obstáculo ainda precisa perguntar à comunidade se vale a pena corrigir esse obstáculo. Esta proposta é a avaliação mais próxima de tal questão.

Por favor, pare de dizer "que todos são livres para ignorar" try . Lemos o código escrito por outros.

@ tv42 Não sei se você está se dirigindo a mim aqui , mas eu também disse isso e você tem razão . Culpado pela acusação. Tentarei ser mais cuidadoso com generalizações como essa. Obrigado.

@griesemer sua pesquisa estava em falta. Votei a favor do tratamento de erros, mas o problema que me referia era a segurança total de tipos, não a verbosidade. É melhor você cometer outro apenas sobre erros.

E ainda quero tipos de soma.

Esta é uma proposta sobre a forma como o gofmt formata atualmente if err! = Nil

(Esta não é uma opinião sobre a proposta try ().)

Quando uma instrução if retorna um valor de erro de uma linha não nulo, como:

err := myFunc()
if err != nil {
    return err
}

gofmt poderia relaxar sua própria regra de instrução if e formatá-la em uma linha como esta:

err := myFunc()
if err != nil { return err }

Três linhas de código de tratamento de erros tornam-se apenas uma linha. Menos desordem. Mais fácil de seguir o fluxo do programa.

Será necessário algum julgamento sobre onde traçar os limites (trocadilhos) com isso
mudança de regra gofmt. Pode incluir alguma decoração, como:

err := myFunc()
if err != nil { return fmt.Errorf("myFunc() blew up! %v", err }

Mas o tratamento de erros multilinhas elaborado deve permanecer como está: multilinhas e claro e explícito.

A proposta _try_ foi retirada: https://github.com/golang/go/issues/32437#issuecomment -512035919

Alguém genérico?

Esta é uma proposta sobre a forma como o gofmt formata atualmente if err! = Nil

Eu tentei isso, mas o código é ainda mais ilegível desse jeito do que com a formatação de várias linhas. tentar é muito melhor do que essa solução.

IMO, o problema aqui não é como o tratamento de erros é executado, mas se ele é ignorado . Não seria possível deixar a sintaxe if err != nil como está, mas restringir a ignorância dos retornos de Error ? Como torná-lo um aviso / erro do compilador com opção de deseverity para o código legado.

IMO, o problema aqui não é como o tratamento de erros é executado, mas se ele é ignorado . Não seria possível deixar a sintaxe if err != nil como está, mas restringir a ignorância dos retornos de Error ? Como torná-lo um aviso / erro do compilador com opção de deseverity para o código legado.

Muitas pessoas querem um linter mostrando erros ignorados.

Eu prefiro cometer um erro grave, mas olhando para as toneladas de legado já escrito, o linter também é justo.

acho https://github.com/kisielk/errcheck valioso por me contar sobre erros não tratados @plyhun @sorenvonsarvort

Como visto na discussão em # 32437, esta proposta foi aceita por enquanto. Fechando. Se o problema surgir novamente, uma nova proposta pode ser aberta.

Estou começando a pensar que uma das razões pelas quais muitas das propostas parecem não se encaixar bem é porque, na verdade, estão tentando resolver dois problemas diferentes ao mesmo tempo. Por um lado, é verdade que ter blocos de err != nil depois de quase todas as chamadas de função pode quebrar o fluxo do código de uma forma estranha, embora certamente tenha suas vantagens, mas acho que isso é apenas metade do problema. O outro problema é que lidar com várias devoluções, independentemente de haver erros envolvidos ou não, pode ser bastante desajeitado.

Múltiplas funções de retorno parecem muito, muito diferentes das funções de retorno único, apesar da diferença aparentemente pequena entre as duas. É como se houvesse restrições extras na chamada de funções que usam mais de um argumento. É muito estranho lidar com isso às vezes. Quando você chama uma função com vários valores de retorno, quase sempre precisa fazer isso em sua própria linha, e ela, combinada com := , é frequentemente a principal fonte de vários problemas de sombreamento de variável que foram discutidos em outro lugar . Você não pode encadear chamadas de método a eles, não pode atribuir a partir deles diretamente a um campo de estrutura e uma nova variável na mesma linha e assim por diante.

Eu não sei. Talvez seja só eu. Mas eu uso Go há quase 10 anos e chamar funções com vários retornos ainda parece meio estranho para mim às vezes.

Obrigado!

Na verdade, há um problema com if err != nil , o escopo de err pode durar mais do que deveria. Quando você inline if isso resolve o problema, mas nem todos os casos podem ser inline.

if err := foo(); err != nil {
if _, err := bar(); err != nil {



md5-6a135eb952fe7b24b3389cb16d3244a1



a, err := bar()
if err != nil {



md5-d52f811d3e31bb368bd8045cfb2e93b4



var err error
baz.A, err = bar()
if err != nil {

A variável err não deve existir no escopo da função após a conclusão do bloco if err != nil {} . Aqui está minha proposta que se baseia na proposta de try() para corrigir o problema https://github.com/golang/go/issues/33161. Eu adoraria algum feedback construtivo.

A variável err não deve existir no escopo da função após a conclusão do bloco if err! = Nil {}.

por que "deveria" não existir após a conclusão do bloco if? O compilador pode otimizá-lo (se julgar necessário) e não há carga mental quando o bloco err: = stmt () \ nif err! = Nil {} é concluído porque quase sempre vêm juntos.

Ainda não examinei sua proposta em profundidade (embora Kudo seja por ter se esforçado para escrevê-la!). No entanto, como também descrevi em meu comentário acima, acho que mais pesquisas são necessárias em quaisquer problemas percebidos, antes de aprofundarmos em quaisquer propostas para resolvê-los.

Os erros if err != nil , principalmente porque já agimos como se não existisse.

No exemplo de CopyFile, existe r, err := os.Open(src) seguido por w, err := os.Create(dst) . O segundo err está obscurecendo o primeiro. Variáveis ​​de sombreamento geralmente são desaprovadas.

Existem também outras esquisitices. Se eu tiver err := foo() e mais tarde algo como bar.V, err = baz() , se o código for refatorado e eu não precisar mais de foo (), eu precisaria adicionar var err error antes de baz linha. . Não acho que refatorar um local diferente em uma função deva afetar outros lugares como esse.

Tecnicamente em

    r, err := os.Open(src)
    if err != nil {
        return ...
    }
    w, err := os.Create(dst)

a segunda instância de err não obscurece a primeira instância. Na verdade, eles são a mesma variável. Veja a discussão sobre redeclarar variáveis ​​em https://golang.org/ref/spec#Short_variable_declarations.

função doSomeThing () {
r, err: = os.Open (nome do arquivo)
panic (fmt.Errorf (err, "falha ao abrir o arquivo:% s", nome do arquivo)) //
É o pânico aqui.

}

Na quinta-feira, 10 de outubro de 2019 às 11h24, clearcode [email protected] escreveu:

Acho que podemos adicionar uma função buildin:

afirmar()

exemplo:

func doSomeThing () error {

r, err := os.Open(filename)
assert(err, "failed to open file: %s", filename) // in this step, just return the error

resp, err: = http.Get (someURL)
assert (err, "solicitação falhou")

}

e outra função que não retorna um erro:

função doSomeThing () {
r, err: = os.Open (nome do arquivo)
assert (err, "falha ao abrir o arquivo:% s", nome do arquivo) // É o pânico aqui.

}

então assert (error, args ... interface {}) é melhor do que: if err! = nil; {
return err}

-
Você está recebendo isso porque comentou.
Responda a este e-mail diretamente, visualize-o no GitHub
https://github.com/golang/go/issues/32825?email_source=notifications&email_token=AGUV7XQ5HO7GL3YP72R7BV3QN2N55A5CNFSM4H4DL33KYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEA2KWUI#issuecomment-540322641 ,
ou cancelar
https://github.com/notifications/unsubscribe-auth/AGUV7XS4JMK44QHIIR3RSGTQN2N55ANCNFSM4H4DL33A
.

A conclusão é que quero ver o erro real retornado no atual
função na linha atual.

Na sexta-feira, 11 de outubro de 2019 às 9h55, Aaaa Einai [email protected] escreveu:

função doSomeThing () {
r, err: = os.Open (nome do arquivo)
panic (fmt.Errorf (err, "falha ao abrir o arquivo:% s", nome do arquivo)) // É o pânico aqui.

}

Na quinta-feira, 10 de outubro de 2019 às 11h24, clearcode [email protected]
escreveu:

Acho que podemos adicionar uma função buildin:

afirmar()

exemplo:

func doSomeThing () error {

r, err := os.Open(filename)
assert(err, "failed to open file: %s", filename) // in this step, just return the error

resp, err: = http.Get (someURL)
assert (err, "solicitação falhou")

}

e outra função que não retorna um erro:

função doSomeThing () {
r, err: = os.Open (nome do arquivo)
assert (err, "falha ao abrir o arquivo:% s", nome do arquivo) // É o pânico aqui.

}

então assert (error, args ... interface {}) é melhor do que: if err! = nil; {
return err}

-
Você está recebendo isso porque comentou.
Responda a este e-mail diretamente, visualize-o no GitHub
https://github.com/golang/go/issues/32825?email_source=notifications&email_token=AGUV7XQ5HO7GL3YP72R7BV3QN2N55A5CNFSM4H4DL33KYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEA2KWUI#issuecomment-540322641 ,
ou cancelar
https://github.com/notifications/unsubscribe-auth/AGUV7XS4JMK44QHIIR3RSGTQN2N55ANCNFSM4H4DL33A
.

Francamente, não quero um retorno implícito que try fornece. Se tivéssemos genéricos, eu preferiria muito mais uma solução que usasse o comportamento monádico.

type Result<T> interface {
  Expect(err error) T
  OrElse(defaultValue T) T
}

func From<T>(value T, err error) Result<T> { ... }

Para mim, isso é muito mais limpo do que o integrado que está sendo proposto atualmente, embora outras alterações sejam necessárias para o acima, uma vez que você teria uma proliferação de métodos que retornaram (valor, erro) e Resultado

É tão parecido com Rust's Ok e Err.
Acho que if err != nil {} talvez seja um pouco melhor.

@Yanwenjiepy isso é intencional, sou grande fã do tipo Result do Rust.

Estou a menos de 10 minutos de aprender Go. A primeira coisa que notei no código que estava examinando foi essa cópia colada várias vezes:

someValue, err := someFunction();
if err != nil {
  panic(err)
}

Obviamente, não sou um especialista, mas pode ser útil que só tenha levado meu primeiro olhar para terminar neste tópico.

Isso porque você está procurando trechos de código para aprendizado. O código real precisa lidar com erros, não apenas entrar em pânico e travar.

É verdade, mas os erros podem (e geralmente devem) ser agrupados. É por isso que os blocos try / catch existem em outras linguagens. Por exemplo, o seguinte cheiraria muito menos a dinossauros para mim:

try {
  foo, throw err := someFunction();
  bar, throw err := foo.get();
  baz, throw err := bar.make();
  qux, throw err := baz.transform();
} catch(err) {
  // "Unable to foo bar baz qux."
  tryHarder();
}

Mais uma vez, leigo total. Mas o código são apenas símbolos e, se eles se repetirem o suficiente, você também pode criar um símbolo para isso. Este parece ser um símbolo que se repete com muita frequência.

Você pode querer dar uma olhada na postagem Erros são valores de Rob Pike para ver como você pode usar um auxiliar para mesclar erros e lidar com eles todos de uma vez. Na prática, capturar todas as exceções com uma única cláusula é considerado um estilo incorreto na maioria das linguagens que as possuem, porque você acaba ocultando informações sobre o que realmente aconteceu. (E se você estender o exemplo para quebrar as exceções individuais capturadas e não jogar fora essas informações, o código terminará tão longo quanto o equivalente em Go.)

Obrigado pelo link. O errWriter é uma solução totalmente aceitável.

É verdade, mas os erros podem (e geralmente devem) ser agrupados. É por isso que os blocos try / catch existem em outras linguagens. Por exemplo, o seguinte cheiraria muito menos a dinossauros para mim:

try {
  foo, throw err := someFunction();
  bar, throw err := foo.get();
  baz, throw err := bar.make();
  qux, throw err := baz.transform();
} catch(err) {
  // "Unable to foo bar baz qux."
  tryHarder();
}

Mais uma vez, leigo total. Mas o código são apenas símbolos e, se eles se repetirem o suficiente, você também pode criar um símbolo para isso. Este parece ser um símbolo que se repete com muita frequência.

Digamos que cada função retorne um tipo de erro sobreposto e você deve lidar com todos os resultados da função normalmente. Como escrever tryHarder ()?

try {
  foo, throw err := someFunction();  // err could be TypeA and TypeB
  bar, throw err := foo.get();       // err could be TypeB and TypeC
  baz, throw err := bar.make();      // err could be TypeA and TypeC
  qux, throw err := baz.transform(); // err could be TypeB and TypeD
} catch(err) {
  tryHarder(); // tell me how to handle each error?
}

Alguém levará apenas 1 minuto para entender o código abaixo:

foo, err := someFunction();  // err could be TypeA and TypeB
if err != nil {
 // handle err
}

bar, err := foo.get();       // err could be TypeB and TypeC
if err != nil {
  // handle err
}

baz, err := bar.make();      // err could be TypeA and TypeC
if err != nil {
  // handle err
}

qux, err := baz.transform(); // err could be TypeB and TypeD
if err != nil {
  // handle err
}

Digamos que cada função retorna um tipo de erro sobreposto e você deve lidar com todos os resultados da função normalmente

Nesse exemplo, você está totalmente correto.

Esta página foi útil?
0 / 5 - 0 avaliações

Questões relacionadas

michaelsafyan picture michaelsafyan  ·  3Comentários

rakyll picture rakyll  ·  3Comentários

enoodle picture enoodle  ·  3Comentários

stub42 picture stub42  ·  3Comentários

natefinch picture natefinch  ·  3Comentários