A linguagem Go do Google não tem exceções como escolha de design, e a fama do Linus do Linux chama as exceções de porcaria. Por quê?
A linguagem Go do Google não tem exceções como escolha de design, e a fama do Linus do Linux chama as exceções de porcaria. Por quê?
Respostas:
As exceções tornam realmente fácil escrever código onde uma exceção lançada quebrará invariantes e deixará objetos em um estado inconsistente. Eles essencialmente o forçam a lembrar que quase todas as afirmações que você faz podem potencialmente jogar e lidar com isso corretamente. Fazer isso pode ser complicado e contra-intuitivo.
Considere algo assim como um exemplo simples:
class Frobber
{
int m_NumberOfFrobs;
FrobManager m_FrobManager;
public:
void Frob()
{
m_NumberOfFrobs++;
m_FrobManager.HandleFrob(new FrobObject());
}
};
Supondo FrobManager
que delete
o FrobObject
, isso parece OK, certo? Ou talvez não ... Imagine então se FrobManager::HandleFrob()
ou operator new
lança uma exceção. Neste exemplo, o incremento de m_NumberOfFrobs
não é revertido. Portanto, qualquer pessoa que use essa instância de Frobber
terá um objeto possivelmente corrompido.
Este exemplo pode parecer estúpido (ok, eu tive que me esforçar um pouco para construir um :-)), mas, a conclusão é que se um programador não está pensando constantemente em exceções e certificando-se de que todas as permutações de estado sejam roladas sempre que houver lançamentos, você terá problemas dessa forma.
Por exemplo, você pode pensar nisso como você pensa em mutexes. Dentro de uma seção crítica, você confia em várias instruções para garantir que as estruturas de dados não sejam corrompidas e que outros threads não possam ver seus valores intermediários. Se qualquer uma dessas declarações simplesmente não funcionar, você terminará em um mundo de dor. Agora tire os bloqueios e a simultaneidade e pense em cada método assim. Pense em cada método como uma transação de permutações no estado do objeto, se quiser. No início da chamada do método, o objeto deve estar em estado limpo e, no final, também deve haver um estado limpo. No meio, a variável foo
pode ser inconsistente combar
, mas seu código acabará corrigindo isso. O que as exceções significam é que qualquer uma de suas declarações pode interrompê-lo a qualquer momento. A responsabilidade recai sobre você em cada método individual para acertar e reverter quando isso acontecer, ou ordenar suas operações para que os lançamentos não afetem o estado do objeto. Se você errar (e é fácil cometer esse tipo de erro), o chamador acaba vendo seus valores intermediários.
Métodos como o RAII, que os programadores de C ++ adoram mencionar como a solução definitiva para esse problema, ajudam muito na proteção contra isso. Mas eles não são uma bala de prata. Isso garante que você libere recursos imediatamente, mas não o livra de ter que pensar sobre a corrupção do estado do objeto e os chamadores vendo valores intermediários. Então, para muitas pessoas, é mais fácil dizer, por decreto de estilo de codificação, sem exceções . Se você restringir o tipo de código que escreve, é mais difícil introduzir esses bugs. Do contrário, é muito fácil cometer um erro.
Livros inteiros foram escritos sobre codificação segura de exceção em C ++. Muitos especialistas erraram. Se for realmente tão complexo e tiver tantas nuances, talvez seja um bom sinal de que você precisa ignorar esse recurso. :-)
O motivo de Go não ter exceções é explicado nas Perguntas frequentes sobre o design da linguagem Go:
As exceções são uma história semelhante. Vários designs de exceções foram propostos, mas cada um adiciona uma complexidade significativa à linguagem e ao tempo de execução. Por sua própria natureza, as exceções abrangem funções e talvez até goroutines; eles têm implicações abrangentes. Também há preocupação com o efeito que eles teriam nas bibliotecas. Eles são, por definição, excepcionais, mas a experiência com outras linguagens que os suportam mostra que eles têm um efeito profundo na biblioteca e na especificação de interface. Seria bom encontrar um design que os permitisse ser verdadeiramente excepcionais sem encorajar erros comuns a se transformarem em um fluxo de controle especial que requer que cada programador compense.
Como os genéricos, as exceções permanecem um problema em aberto.
Em outras palavras, eles ainda não descobriram como oferecer suporte a exceções no Go de uma forma que considerem satisfatória. Eles não estão dizendo que as exceções são ruins em si ;
ATUALIZAÇÃO - maio de 2012
Os designers de Go agora pularam da cerca. Seu FAQ agora diz o seguinte:
Acreditamos que as exceções de acoplamento a uma estrutura de controle, como no idioma try-catch-finally, resultam em código complicado. Também tende a encorajar os programadores a rotular muitos erros comuns, como a falha ao abrir um arquivo, como excepcionais.
Go tem uma abordagem diferente. Para tratamento de erros simples, os retornos de vários valores do Go tornam mais fácil relatar um erro sem sobrecarregar o valor de retorno. Um tipo de erro canônico, juntamente com outros recursos do Go, torna o tratamento de erros agradável, mas bastante diferente daquele em outras linguagens.
Go também possui algumas funções integradas para sinalizar e se recuperar de condições verdadeiramente excepcionais. O mecanismo de recuperação é executado apenas como parte do estado de uma função sendo destruído após um erro, o que é suficiente para lidar com a catástrofe, mas não requer estruturas de controle extras e, quando bem usado, pode resultar em código de tratamento de erros limpo.
Consulte o artigo Adiar, entrar em pânico e recuperar para obter detalhes.
Portanto, a resposta curta é que eles podem fazer isso de maneira diferente, usando o retorno de vários valores. (E eles têm uma forma de tratamento de exceção de qualquer maneira.)
... e a fama do Linus do Linux chamou de merda de exceções.
Se você quer saber por que Linus acha que as exceções são uma porcaria, a melhor coisa é procurar seus escritos sobre o assunto. A única coisa que rastreei até agora é esta citação que está embutida em alguns e-mails em C ++ :
"Todo o tratamento de exceções do C ++ está fundamentalmente quebrado. Está especialmente quebrado para kernels."
Você notará que ele está falando sobre exceções C ++ em particular, e não exceções em geral. (E exceções C ++ que aparentemente têm algumas questões que tornam complicado para usar corretamente.)
Minha conclusão é que Linus não chamou as exceções (em geral) de "merda" de forma alguma!
As exceções não são ruins em si, mas se você sabe que vão acontecer com frequência, elas podem ser caras em termos de desempenho.
A regra prática é que as exceções devem sinalizar condições excepcionais e que você não deve usá-las para controlar o fluxo do programa.
Eu discordo com "apenas lance exceções em uma situação excepcional." Embora geralmente seja verdade, é enganoso. As exceções são para condições de erro (falhas de execução).
Independentemente da linguagem que você usa, adquira uma cópia do Framework Design Guidelines : Conventions, Idioms, and Patterns for Reusable .NET Libraries (2ª edição). O capítulo sobre lançamento de exceções não tem par. Algumas citações da primeira edição (a 2ª no meu trabalho):
Existem páginas de notas sobre os benefícios das exceções (consistência da API, escolha do local do código de tratamento de erros, robustez aprimorada etc.). Há uma seção sobre desempenho que inclui vários padrões (Tester-Doer, Try-Parse).
Exceções e tratamento de exceções não são ruins. Como qualquer outro recurso, eles podem ser mal utilizados.
Da perspectiva do golang, acho que não ter tratamento de exceções mantém o processo de compilação simples e seguro.
Da perspectiva de Linus, eu entendo que o código do kernel é TUDO sobre casos extremos. Portanto, faz sentido recusar exceções.
Exceções fazem sentido no código onde não há problema em deixar a tarefa atual no chão e onde o código de caso comum tem mais importância do que o tratamento de erros. Mas eles requerem a geração de código do compilador.
Por exemplo, eles são bons na maioria dos códigos voltados para o usuário de alto nível, como códigos de aplicativos da Web e de desktop.
As exceções em si não são "ruins"; é a maneira como as exceções às vezes são tratadas que tende a ser ruim. Existem várias diretrizes que podem ser aplicadas ao lidar com exceções para ajudar a aliviar alguns desses problemas. Alguns deles incluem (mas certamente não estão limitados a):
Option<T>
vez de null
hoje em dia. Acabei de ser introduzido no Java 8, por exemplo, pegando uma dica do Guava (e outros).
Os argumentos típicos são que não há como saber quais exceções sairão de uma parte específica do código (dependendo da linguagem) e que elas são muito parecidas com goto
s, tornando difícil rastrear mentalmente a execução.
http://www.joelonsoftware.com/items/2003/10/13.html
Definitivamente, não há consenso sobre esse assunto. Eu diria que, do ponto de vista de um programador C hard-core como Linus, exceções são definitivamente uma má ideia. No entanto, um programador Java típico está em uma situação muito diferente.
setjmp
/ longjmp
, o que é muito ruim.
As exceções não são ruins. Eles se encaixam bem no modelo RAII do C ++, que é a coisa mais elegante do C ++. Se você já tem um monte de código que não é seguro para exceções, eles são ruins nesse contexto. Se você está escrevendo um software de baixo nível, como o sistema operacional Linux, eles são ruins. Se você gosta de bagunçar seu código com um monte de verificações de retorno de erro, então eles não ajudam. Se você não tem um plano para controle de recursos quando uma exceção é lançada (que os destruidores C ++ fornecem), então eles são ruins.
Um ótimo caso de uso para exceções é, portanto ...
Digamos que você esteja em um projeto e cada controlador (cerca de 20 controladores principais diferentes) estenda um único controlador de superclasse com um método de ação. Então, cada controlador faz um monte de coisas diferentes entre si chamando os objetos B, C, D em um caso e F, G, D em outro caso. As exceções vêm ao resgate aqui em muitos casos onde havia toneladas de código de retorno e CADA controlador estava lidando com isso de forma diferente. Eu destruí todo aquele código, lancei a exceção apropriada de "D", peguei no método de ação do controlador da superclasse e agora todos os nossos controladores são consistentes. Anteriormente, D estava retornando nulo para VÁRIOS casos de erro diferentes que desejamos informar ao usuário final, mas não foi possível e eu não fiz
sim, temos que nos preocupar com cada nível e qualquer limpeza / vazamento de recursos, mas em geral nenhum de nossos controladores tinha quaisquer recursos para limpar depois.
graças a Deus nós tínhamos exceções ou eu teria que refatorar e perder muito tempo em algo que deveria ser um simples problema de programação.
Teoricamente, eles são muito ruins. No mundo matemático perfeito, você não pode obter situações de exceção. Observe as linguagens funcionais, elas não têm efeitos colaterais, portanto, virtualmente não têm origem para situações comuns.
Mas, a realidade é outra história. Sempre temos situações que são "inesperadas". É por isso que precisamos de exceções.
Acho que podemos pensar em exceções como sintaxe de açúcar para ExceptionSituationObserver. Você apenas recebe notificações de exceções. Nada mais.
Com Go, acho que eles introduzirão algo que lidará com situações "inesperadas". Posso supor que eles tentarão fazer com que soe menos destrutivo como exceções e mais como lógica de aplicativo. Mas isso é apenas meu palpite.
O paradigma de tratamento de exceções do C ++, que forma uma base parcial para o do Java e, por sua vez, .net, apresenta alguns bons conceitos, mas também tem algumas limitações severas. Uma das principais intenções de design do tratamento de exceção é permitir que os métodos garantam que eles irão satisfazer suas pós-condições ou lançar uma exceção, e também garantir que qualquer limpeza que precise acontecer antes que um método possa sair, aconteça. Infelizmente, os paradigmas de tratamento de exceções de C ++, Java e .net falham em fornecer qualquer bom meio de lidar com a situação em que fatores inesperados impedem que a limpeza esperada seja realizada. Isso, por sua vez, significa que se deve correr o risco de ter tudo parando bruscamente se algo inesperado acontecer (a abordagem C ++ para lidar com uma exceção ocorre durante o desenrolamento da pilha),
Mesmo que o tratamento de exceções seja geralmente bom, não é irracional considerar inaceitável um paradigma de tratamento de exceções que falha em fornecer um bom meio para lidar com problemas que ocorrem durante a limpeza após outros problemas. Isso não quer dizer que uma estrutura não pudesse ser projetada com um paradigma de tratamento de exceções que pudesse garantir um comportamento sensato mesmo em cenários de falha múltipla, mas nenhuma das principais linguagens ou estruturas ainda pode fazer isso.
Não li todas as outras respostas, então isso pode já ter sido mencionado, mas uma crítica é que eles fazem com que os programas quebrem em longas cadeias, tornando difícil rastrear erros ao depurar o código. Por exemplo, se Foo () chama Bar () que chama Wah () que chama ToString (), então acidentalmente enviar os dados errados para ToString () acaba parecendo um erro em Foo (), uma função quase completamente não relacionada.
Ok, resposta chata aqui. Acho que realmente depende do idioma. Quando uma exceção pode deixar recursos alocados para trás, eles devem ser evitados. Em linguagens de script, eles simplesmente abandonam ou superam partes do fluxo do aplicativo. Isso é desagradável em si, mas escapar de erros quase fatais com exceções é uma ideia aceitável.
Para sinalização de erro, geralmente prefiro sinais de erro. Tudo depende da API, do caso de uso e da gravidade ou se o registro for suficiente. Também estou tentando redefinir o comportamento e, em throw Phonebooks()
vez disso. A ideia é que "Exceções" geralmente são becos sem saída, mas uma "Lista telefônica" contém informações úteis sobre recuperação de erros ou rotas de execução alternativas. (Não encontrei um bom caso de uso ainda, mas continue tentando.)
Para mim, a questão é muito simples. Muitos programadores usam o manipulador de exceções de forma inadequada. Mais recursos de linguagem é melhor. Ser capaz de lidar com exceções é bom. Um exemplo de mau uso é um valor que deve ser inteiro não verificado, ou outra entrada que pode ser dividida e não verificada para divisão de zero ... o tratamento de exceções pode ser uma maneira fácil de evitar mais trabalho e raciocínio, o programador pode querer fazer um atalho sujo e aplicar um tratamento de exceção ... A declaração: "um código profissional NUNCA falha" pode ser ilusória, se alguns dos problemas processados pelo algoritmo forem incertos por sua própria natureza. Talvez nas situações desconhecidas por natureza seja bom entrar em cena o tratador de exceções. Boas práticas de programação são uma questão de debate.