1 pontos por GN⁺ 2025-06-04 | 1 comentários | Compartilhar no WhatsApp
  • A verbosidade no tratamento de erros em Go está entre as principais reclamações dos usuários há muito tempo
  • Várias propostas de melhoria sintática (como check/handle, try, operador ? etc.) foram discutidas e testadas, mas todas foram rejeitadas por falta de consenso suficiente na comunidade
  • O amplo impacto sobre código, ferramentas, documentação etc. causado por mudanças na linguagem, junto com o princípio de preservar a simplicidade característica de Go, são fatores centrais nessa decisão
  • A clareza do modelo atual, a facilidade de depuração e a preferência de parte dos usuários tornam fraca a justificativa para introduzir uma mudança sintática
  • Não há planos de mudar a sintaxe de tratamento de erros em um futuro previsível, e as propostas relacionadas serão encerradas sem investigação adicional

Levantando o problema da verbosidade no tratamento de erros em Go

  • Uma das reclamações antigas sobre Go é que a sintaxe de tratamento de erros é excessivamente verbosa
  • Um exemplo representativo é o padrão if err != nil, que aparece repetidamente no código
  • Quanto mais chamadas de API um programa exige, mais esse padrão se destaca, chegando ao ponto de haver mais código de tratamento de erros do que lógica real
  • Em pesquisas anuais com usuários, essa reclamação continua aparecendo entre as principais menções

Consulta com a comunidade e propostas iniciais

  • A equipe de Go sempre valorizou o feedback da comunidade e continuou pesquisando formas de melhorar o tratamento de erros
  • Em 2018, nas discussões do projeto Go 2, Russ Cox organizou formalmente os pontos centrais do problema de tratamento de erros
    • Surgiu a proposta do mecanismo check e handle de Marcel van Lohuizen
    • A proposta incluía comparação com linguagens semelhantes e análise de várias alternativas
  • Embora essa abordagem realmente tornasse o código mais conciso, ela não foi adotada por aumentar a complexidade

A proposta try e o que veio depois

  • Em 2019, foi apresentada a proposta da função embutida try, bem mais simplificada
    • Apenas a funcionalidade de check foi levada para o código, sem handle
    • A proposta foi criticada por esconder o fluxo de controle e acabou descartada em meio à reação negativa da comunidade
  • Essa experiência deixou claro o risco de propostas prontas sem feedback suficiente
    • Em mudanças de grande porte, ficou evidente a importância de ouvir opiniões mais amplas ainda na fase inicial de design

Novas tentativas e várias propostas

  • Inúmeras variações e abordagens alternativas de tratamento de erros continuaram surgindo na comunidade
    • Ian Lance Taylor organizou o cenário em uma umbrella issue, e exemplos continuaram sendo reunidos no Go Wiki, em blogs e em outros espaços
  • Em 2024, surgiu uma proposta para aplicar o operador ? emprestado de Rust
    • Em pequenos testes de usabilidade, houve feedback de que ele parecia intuitivo, mas, mais uma vez, não se chegou a consenso diante da diversidade de opiniões

O impasse da discussão e a conclusão

  • Houve mais de três propostas formais e informais, e centenas de propostas da comunidade, mas todas foram rejeitadas por falta de empatia coletiva e consenso suficiente
  • Nem mesmo o grupo interno de arquitetos de Go tem alinhamento sobre a direção a seguir
  • Até que haja mudança no cenário ou formação de um consenso especial, foi decidido interromper as tentativas de alterar a sintaxe de tratamento de erros

Principais argumentos em defesa da manutenção do modelo atual

  • Se açúcar sintático tivesse sido incluído no design inicial da linguagem, talvez não houvesse controvérsia, mas hoje existe um ecossistema acostumado a esse modelo há 15 anos de uso
  • A introdução de nova sintaxe inevitavelmente traria preocupação com a diferença de estilo entre código antigo e novo e a quebra de consistência entre usuários existentes e novos
  • Isso também está alinhado com a filosofia de design de Go de não fazer a mesma coisa de várias maneiras e com o princípio de priorizar simplicidade e consistência
    • A permissão de redeclaração na declaração curta de variáveis (:=) também é uma mudança secundária surgida por causa do tratamento de erros
  • A sintaxe explícita de tratamento de erros (via if) tem vantagens intuitivas para leitura de código, depuração e definição de breakpoints
  • Mudanças na linguagem também representam um peso grande em termos de escopo e custo real da alteração, afetando código, documentação, ferramentas e mais

Melhorias alternativas e direção futura

  • O fortalecimento da biblioteca padrão (por exemplo, com a introdução de cmp.Or) pode reduzir parte do código repetitivo
  • IDEs e ferramentas de desenvolvimento, com dobramento de código, autocompletar, uso de LLMs etc., podem amenizar na prática parte dessa verbosidade
  • Em grupos importantes de usuários de Go (como participantes do evento Google Cloud Next), predomina uma visão negativa sobre a necessidade de mudar a linguagem
    • Quanto mais a pessoa usa Go, menos tende a sentir esse problema de verbosidade na prática

Argumentos a favor da necessidade de melhorar a sintaxe

  • Com base no feedback dos usuários, ainda existe demanda por melhorias na sintaxe de tratamento de erros
  • Uma sintaxe de tratamento de erros que não apenas reduza caracteres, mas aumente a clareza também pode contribuir para melhorar qualidade e segurança do código
  • É necessário pesquisar com mais precisão o tratamento de erros que realmente exerce um papel relevante, e não apenas a verificação simples de erros

Conclusão final e política daqui para frente

  • Reconhecendo que até agora não houve consenso relevante nem mudança prática, declara-se que, em um futuro previsível, todas as discussões e propostas de mudança sintática na linguagem para tratamento de erros serão interrompidas
  • O processo anterior de discussão e pesquisa acabou contribuindo indiretamente para melhorias no ecossistema e nos processos de Go
  • Se no futuro surgir uma definição mais clara do problema e um consenso mais sólido, a discussão poderá ser retomada
  • Por ora, a diretriz será manter a robustez e a simplicidade do próprio Go em vez de investir em novas tentativas

1 comentários

 
GN⁺ 2025-06-04
Comentários do Hacker News
  • Se alguém quiser sugerir facilmente que a equipe do Go poderia ter escolhido outras alternativas, gostaria muito que antes desse uma olhada no wiki Go2ErrorHandlingFeedback ou na busca de issues no GitHub. Quase todas as ideias já propostas foram discutidas seriamente, e, como usuário que aprecia a abordagem transparente da equipe do Go, tenho muito prazer em usar Go todos os dias

    • O rascunho do documento de design menciona C++, Rust e Swift, mas é difícil encontrar algo como do-notation/for-comprehensions/monadic-let de linguagens funcionais como Haskell/Scala/OCaml, que é o que eu estava procurando. A equipe do Go parece mestre em design de linguagem, mas no fim acaba esbarrando nas limitações de uma tipagem estática sem polimorfismo paramétrico, como Java, e não consegue chegar a uma solução para o problema de tratamento de erros. Acho que isso vem de uma limitação do projeto fundamental da linguagem

    • Mesmo sendo um documento escrito por pessoas inteligentes e experientes, é muito curioso que soluções como os mônadas Maybe/Either de Haskell e o operador bind (do-notation) não sejam mencionados em lugar nenhum. Na prática isso não é difícil nem pedante, e é uma forma muito elegante e comprovada de propagar erros com segurança, então não entendo por que a comunidade Go não tentou incorporar isso. Sou grato por essa página existir, mas é difícil entender como uma solução tão famosa foi ignorada

    • Quase toda linguagem oferece abordagens diversas e melhores, então fico curioso por que justamente no Go esse problema ganha tanto destaque. Não sei se é apenas falta de consenso, ou se existe alguma característica específica do Go que faz com que soluções de outras linguagens não se encaixem bem

    • Um fenômeno frequente nas críticas ao Go é que pessoas relativamente não especialistas tendem a partir do pressuposto de que os desenvolvedores de Go entendem menos de linguagens do que elas. Na realidade, na maioria dos casos, os desenvolvedores de Go têm muito mais experiência e sabem muito mais. O não especialista vê uma linguagem com muitos recursos e acha automaticamente que ela é melhor, mas ignora que o importante é equilibrar bem o conjunto como um todo

  • Acho que os usuários se beneficiam do conservadorismo do Go ao ser cauteloso com a adição de novos recursos à linguagem. No caso do Swift, há tantas mudanças de recursos que ele fica difícil de aprender e, mesmo em Macs novos, às vezes nem um projeto simples compila. Como palavras-chave continuam aumentando e mudando, o Swift perde em continuidade de uso; comparado a isso, a consistência é um ponto forte do Go

  • Uma vez, tive um caso excepcional em que uma função Go esperava que uma função interna gerasse erro, e, se ela não gerasse, aí sim a função deveria falhar. Nessa estrutura incomum, eu precisava ramificar com if err == nil, mas por hábito acabei escrevendo if err != nil, e demorei bastante para encontrar o erro por estar acostumado demais com o padrão de sempre. Fiquei pensando que, se houvesse suporte sintático no nível da linguagem para distinguir o uso frequente de if err != nil do uso raro de if err == nil, isso poderia reduzir esse tipo de engano

    • Sempre que uso if err == nil, adiciono um comentário // inverted para destacar o padrão. Seria bom se a linguagem tratasse isso automaticamente, mas, por enquanto, esse método já ajuda a tornar a distinção mais clara
    • Na verdade, isso é mais um argumento contra mudar a sintaxe. O padrão frequente if err == nil { return ... } pode acabar parecendo ainda mais estranho no código. Há quem prefira o tratamento de erros atual do Go justamente por ele ser claro e fácil de ler
    • A mesma confusão pode ocorrer com padrões como if fruit != "Apple", então o argumento é que isso, no fundo, não é um problema exclusivo de tratamento de erros, mas um problema geral de ramificação por estado. Erros, no fim, também são tratados como apenas mais um valor de estado
    • Em IDEs ou com certas configurações de fonte, talvez fosse possível renderizar if err != nil como se fosse um símbolo especial, fazendo-o se misturar mais ao fundo e deixando apenas usos diferentes, como if err == nil, em maior destaque, o que poderia ajudar a evitar erros no nível do editor
    • Também houve a sugestão de que o editor poderia exibir um padrão abreviado como if err … {, melhorando a legibilidade
  • Gosto do tratamento explícito de erros do Go. Eu simplesmente entendo que uma função ou sempre tem sucesso (minimal error) ou pode falhar. Se uma função pode falhar, isso precisa ser tratado antes de prosseguir para a próxima etapa. Em muitas linguagens, com tratamento por exceções, quando ocorre um erro ele vai subindo pela stack até ser capturado, o que muitas vezes só informa onde aconteceu, sem dar pistas realmente úteis. No Go, é possível ter opções claras: 1) ignorar o erro 2) retornar imediatamente quando houver erro 3) encapsular o erro adicionando informações úteis 4) interpretar um erro específico e ramificar com base nele (por exemplo, converter para 404). No Go2, eu gostaria de experimentar adicionar um tipo Result<Value, Failure> ou tipos de erro mais específicos e enumeráveis. Acho mais adequado introduzir isso no Go 2 por causa da compatibilidade com o Go 1

    • Pela minha experiência, a política de tratamento de erro deve ser decidida obrigatoriamente pelo chamador, e tratá-lo nas camadas inferiores da stack não é desejável. No fim, isso tende a virar um trabalho repetitivo de apenas encapsular o erro e passá-lo para cima
    • O “tratamento de erros do Go” na verdade já é oferecido pela maioria das linguagens, como linguagens funcionais, Rust e Java, e não por JavaScript ou Python. No fim, com generics, daria para implementar o estilo de tratamento de erros do Go em praticamente qualquer linguagem. Se a comparação para em JS ou Python, então isso é só um padrão comum
    • O ponto “se a função falhar, isso obrigatoriamente precisa ser tratado” é justamente onde o Go falha, segundo essa crítica. No Go, na prática, é possível ignorar completamente um valor de erro, então, se a meta é construir software realmente robusto, o estilo do Go pode até ser uma fraqueza
    • Há também a opinião amarga de que Go2 acabará ficando apenas como um “laboratório que nunca será lançado”
  • No começo eu não gostava da forma como o Go trata erros, mas depois de ler o post errors-are-values e começar a usar panic(err) nos lugares certos, passei a ficar bastante satisfeito. Para estados anormais que o código pai não deveria tratar diretamente, usar panic reduziu muito a quantidade de ramificações de erro no código. Essa forma de gerenciar erros tem ajudado bastante no trabalho real

    • Há uma réplica de que essa lógica, na verdade, não consegue defender o tratamento fraco de erros do Go, e que mesmo com melhorias as vantagens não desapareceriam
    • Também se menciona que o PHP permite tratamento de erro por níveis e supressão de erro no call site com o operador @, e que o bash tem técnicas de gerenciamento de erro como -e
    • Quando vi pela primeira vez o fluxo try/catch/finally em C#, achei inovador, mas hoje acabo preferindo uma lógica simples como a do Go. Mesmo com mais linhas de código (Loc), considero uma vantagem o fato de o fluxo ficar claro
    • Também se observa que o tratamento de erro baseado em sum types no Rust pertence ao paradigma de “errors are values”
  • Quando se diz que a verbosidade some rápido assim que você realmente trata o erro, isso faz surgir a dúvida se gerar manualmente um stack trace é mesmo “tratar” o erro. Pela definição do Go, então exceções também não seriam tratamento? Essa é uma contestação bem-humorada

    • Fico em dúvida se dezenas de linhas de stack trace são mesmo informação clara. Pessoalmente, acho um erro encapsulado em uma única linha muito mais eficiente, e isso também ajuda a manter os logs organizados. Em mais de 10 anos usando Go, nunca precisei de informações de stack tão verbosas, incluindo funções de runtime
  • Não gosto que este texto trate o problema do tratamento de erros no Go simplesmente como “a sintaxe é verbosa”. Acho que os problemas reais incluem: 1) erros podem ser omitidos silenciosamente ou ignorados por engano com facilidade 2) resultados de função não podem ser passados ou armazenados com facilidade como valores 3) erros aninhados como errors.Is se encaixam de forma estranha no sistema de tipos 4) fazer switching sobre erros é difícil 5) há muito uso de sentinel value na biblioteca padrão 6) a compatibilidade com generics é ruim, o que acaba gerando necessidade de pacotes

    • 90% dos programadores profissionais de Go escrevem casos de teste para cada ramificação de retorno de erro para bater cobertura, o que seria desnecessário em linguagens com exceções
    • Acho incorreto dizer que, neste texto, o problema principal foi apresentado como “It’s too verbose”. Mesmo mudando a sintaxe, a melhora substancial seria pequena
    • Há também quem veja como vantagem o fato de o Go mudar muito lentamente (até generics levou bastante tempo)
    • Como Googler, fiquei mais uma vez decepcionado com a decisão da equipe do Go
  • Em Elixir (e Erlang), funções normalmente retornam tuplas {:ok, result} ou {:error, description}. Graças à sintaxe with do Elixir, o tratamento de erros pode ficar agrupado na parte inferior do bloco, melhorando bastante a legibilidade. Se o Go adotasse algo parecido com uma instrução with, daria para melhorar a leitura deixando a execução seguir apenas quando o erro for nil e colocando um bloco de tratamento de erro no final

    • No Go, por causa do problema de consenso da comunidade, até recursos valiosos bem básicos como sum types, tratamento de erro e gerenciamento de pacotes são introduzidos muito lentamente. Generics levou 13 anos, tratamento de erros 16 anos, gerenciamento de pacotes 9 anos: a mudança é lenta demais. Cautela é importante, mas fica a frustração de que, ao buscar perfeição, as decisões acabam sempre adiadas
    • O padrão de retornos múltiplos do Go também pode ser visto como algo anormal dependendo da perspectiva. A crítica é que a única coisa realmente possível com uma função que retorna vários tipos é fazer atribuição a variáveis
  • Não entendo por que não seguem logo o estilo do Rust. Especialmente agora que existem generics, já seria possível implementar algo parecido sem muita demora. Não concordo com a crítica de que o operador ? do Rust, embora conveniente, incentivaria ignorar erros. Na prática, o Go está cheio de casos em que valores de erro podem ser ignorados sem nem gerar erro de compilação. Para realmente evitar esse tipo de engano, seria preciso forçar retornos com um tipo Result, no estilo do Rust. Se isso vira polêmica em nome da conveniência, então talvez também fosse coerente proibir panic, segundo essa visão mais forte

    • Há a opinião de que o Go não consegue introduzir Result porque não tem sum types e tem esse projeto peculiar de exigir zero value para todos os tipos
    • Sobre a afirmação de que um recurso de conveniência como o “operador ?” faria as pessoas “pararem de usar erros encapsulados”, há a contraposição de que daria perfeitamente para desenhar um recurso desses de forma a incentivar wrapping
    • Como ponto negativo de recursos voltados à conveniência (estilo Rust), explica-se que o fluxo de ramificação fica escondido em uma linha, breakpoints de depuração ficam mais difíceis de colocar, e o foco fica extremamente em bubbling em vez de enrich/handling; seria um tipo de sintaxe que o Go já rejeitou antes (por exemplo, o operador ternário)
    • Mesmo que se tente aplicar uma comparação direta com o estilo do Rust, surge a dúvida técnica sobre o que exatamente seria equivalente no Go
    • Houve também o feedback pedindo exemplo de código: depois da introdução de generics, o que exatamente teria sido implementado em estilo Rust?
  • Acho que linguagem não deve ser projetada marcando itens de checklist como no Rust ao discutir adoção de recursos; ela precisa ser desenhada dentro de uma consistência geral. Só porque todos os itens de uma lista foram marcados não significa que devam ser introduzidos, se isso não combinar com a essência da linguagem

    • O Rust acabou ganhando a imagem de uma linguagem de design by committee, com sintaxe difícil de ler e menos consistente
    • Há quem diga que não existe “solução perfeita”
    • Segundo os resultados da pesquisa, apenas 13% responderam que o único problema fatal do Go era o tratamento de erros, e não são poucos os usuários que preferem o estado atual. Veja os resultados da pesquisa