Estou no meio do desenvolvimento de uma nova linguagem de programação para resolver alguns requisitos de negócios, e essa linguagem é direcionada a usuários iniciantes. Portanto, não há suporte para o tratamento de exceções no idioma, e eu não esperaria que eles o usassem, mesmo que eu o adicionasse.
Cheguei ao ponto em que tenho que implementar o operador de divisão e estou pensando em como lidar melhor com uma divisão por erro zero?
Parece que tenho apenas três maneiras possíveis de lidar com esse caso.
- Ignore o erro e produza
0
como resultado. Registrando um aviso, se possível. - Adicione
NaN
como um valor possível para números, mas isso levanta questões sobre como lidar comNaN
valores em outras áreas do idioma. - Encerre a execução do programa e relate ao usuário que ocorreu um erro grave.
A opção 1 parece a única solução razoável. A opção 3 não é prática, pois esse idioma será usado para executar a lógica como um cron noturno.
Quais são minhas alternativas para lidar com um erro de divisão por zero e quais são os riscos com a opção nº 1.
fonte
reject "Foo"
foi implementado, mas simplesmente que rejeita um documento se ele contiver a palavra-chaveFoo
. Tento facilitar a leitura do idioma usando termos com os quais o usuário está familiarizado. Fornecer ao usuário sua própria linguagem de programação permite que ele adicione regras de negócios sem depender da equipe técnica.Respostas:
Eu recomendaria fortemente contra o nº 1, porque apenas ignorar erros é um perigoso anti-padrão. Isso pode levar a erros difíceis de analisar. Definir o resultado de uma divisão por zero como 0 não faz sentido algum, e a execução contínua do programa com um valor sem sentido vai causar problemas. Especialmente quando o programa está sendo executado sem supervisão. Quando o intérprete do programa percebe que há um erro no programa (e uma divisão por zero é quase sempre um erro de design), anulá-lo e manter tudo como está é geralmente preferido em vez de encher seu banco de dados com lixo.
Além disso, é improvável que você tenha sucesso ao seguir completamente esse padrão. Mais cedo ou mais tarde, você encontrará situações de erro que simplesmente não podem ser ignoradas (como falta de memória ou estouro de pilha) e você precisará implementar uma maneira de finalizar o programa de qualquer maneira.
A opção 2 (usando NaN) seria um pouco de trabalho, mas não tanto quanto você imagina. Como lidar com o NaN em cálculos diferentes está bem documentado no padrão IEEE 754, portanto você provavelmente pode fazer o que o idioma em que o seu intérprete está escrito faz.
A propósito: Criar uma linguagem de programação utilizável por não programadores é algo que tentamos fazer desde 1964 (Dartmouth BASIC). Até agora, não tivemos sucesso. Mas boa sorte de qualquer maneira.
fonte
PHP
tem sido uma má influência para mim.NaN
no idioma de um iniciante, mas em geral, ótima resposta.Isso não é uma boa idéia. Em absoluto. As pessoas começarão dependendo disso e, se você precisar corrigi-lo, quebrará muito código.
Você deve manipular o NaN da mesma forma que os tempos de execução de outros idiomas: qualquer cálculo adicional também produz NaN e todas as comparações (mesmo NaN == NaN) produzem false.
Eu acho que isso é aceitável, mas não necessariamente amigável para iniciantes.
Esta é a melhor solução que eu acho. Com essas informações em mãos, os usuários devem ser capazes de lidar com 0. Você deve fornecer um ambiente de teste, especialmente se ele for executado uma vez por noite.
Há também uma quarta opção. Faça da divisão uma operação ternária. Qualquer um desses dois funcionará:
fonte
NaN == NaN
serfalse
, então você terá que adicionar umaisNaN()
função para que os usuários são capazes de detectarNaN
s.isNan(x) => x != x
. Ainda assim, quando vocêNaN
aparecer em seu código de programação, não deve começar a adicionarisNaN
verificações, mas rastrear a causa e fazer as verificações necessárias lá. Portanto, é importante paraNaN
propagar completamente.NaN
s são majoritariamente contra-intuitivos. No idioma de um iniciante, eles estão mortos na chegada.1/0
- você precisa fazer algo com isso. Não há resultado possivelmente útil além deInf
ouNaN
- algo que propague o erro ainda mais no programa. Caso contrário, a única solução é parar com um erro neste momento.Encerre o aplicativo em execução com extremo prejuízo. (Ao fornecer informações de depuração adequadas)
Em seguida, instrua seus usuários a identificar e manipular condições em que o divisor pode ser zero (valores inseridos pelo usuário etc.)
fonte
No Haskell (e similar no Scala), em vez de lançar exceções (ou retornar referências nulas), o wrapper digita
Maybe
eEither
pode ser usado. ComMaybe
o usuário, é possível testar se o valor obtido é "vazio" ou ele pode fornecer um valor padrão ao "desembrulhar".Either
é semelhante, mas pode ser usado retorna um objeto (por exemplo, uma string de erro) descrevendo o problema, se houver algum.fonte
error "some message"
função que está sendo avaliada.Haskell
não permite que expressões puras gerem exceções.Outras respostas já consideraram os méritos relativos de suas idéias. Proponho outro: use a análise básica de fluxo para determinar se uma variável pode ser zero. Então você pode simplesmente não permitir a divisão por variáveis que são potencialmente zero.
Como alternativa, tenha uma função de asserção inteligente que estabelece invariantes:
Isso é tão bom quanto gerar um erro de tempo de execução - você evita completamente operações indefinidas - mas tem a vantagem de que o caminho do código nem precisa ser atingido para que a falha em potencial seja exposta. Isso pode ser feito da mesma maneira que as datilografias comuns, avaliando todas as ramificações de um programa com ambientes de digitação aninhados para rastrear e verificar os invariantes:
Além disso, ele se estende naturalmente ao alcance e
null
verificação, se o seu idioma tiver esses recursos.fonte
def foo(a,b): return a / ord(sha1(b)[0])
. O analisador estático não pode inverter o SHA-1. O Clang tem esse tipo de análise estática e é ótimo para encontrar erros rasos, mas há muitos casos que ele não consegue lidar.O número 1 (insira o zero que não pode ser carregado) é sempre ruim. A escolha entre # 2 (propagar NaN) e # 3 (finalizar o processo) depende do contexto e, idealmente, deve ser uma configuração global, como é o Numpy.
Se você estiver fazendo um cálculo grande e integrado, propagar o NaN é uma péssima idéia, porque eventualmente se espalhará e infectará todo o cálculo - quando você olha para os resultados pela manhã e vê que eles são todos NaN, você ' teria que jogar fora os resultados e começar de qualquer maneira. Teria sido melhor se o programa terminasse, você recebesse uma ligação no meio da noite e a corrigisse - em termos de número de horas perdidas, pelo menos.
Se você está fazendo muitos cálculos pouco independentes (como cálculos de redução de mapa ou embaraçosamente paralelos) e pode tolerar que uma porcentagem deles seja inutilizável devido aos NaNs, provavelmente essa é a melhor opção. Encerrar o programa e não fazer os 99% que seriam bons e úteis por causa dos 1% malformados e divididos por zero pode ser um erro.
Outra opção, relacionada aos NaNs: a mesma especificação de ponto flutuante IEEE define Inf e -Inf, e eles são propagados de maneira diferente do NaN. Por exemplo, tenho certeza de que Inf> qualquer número e -Inf <qualquer número, que seria o que você queria se sua divisão por zero acontecesse porque o zero era apenas um número pequeno. Se suas entradas são arredondadas e sofrem de erro de medição (como medições físicas feitas manualmente), a diferença de duas grandes quantidades pode resultar em zero. Sem a divisão por zero, você teria conseguido um número grande e talvez não se importe com o tamanho. Nesse caso, In e -Inf são resultados perfeitamente válidos.
Também pode estar formalmente correto - basta dizer que você está trabalhando com reais estendidos.
fonte
Claro que é prático: é responsabilidade dos programadores escrever um programa que realmente faça sentido. Dividir por 0 não faz sentido. Portanto, se o programador estiver executando uma divisão, também é sua responsabilidade verificar previamente se o divisor não é igual a 0. Se o programador não executar essa verificação de validação, deverá perceber esse erro assim que possível, e resultados de computação desnormalizados (NaN) ou incorretos (0) simplesmente não ajudarão nesse sentido.
A opção 3 é a que eu recomendaria a você, por ser a mais direta, honesta e matematicamente correta.
fonte
Parece-me uma má idéia executar tarefas importantes (por exemplo, "cron noturno") em um ambiente em que os erros são ignorados. É uma péssima ideia tornar isso um recurso. Isso exclui as opções 1 e 2.
A opção 3 é a única solução aceitável. As exceções não precisam fazer parte do idioma, mas fazem parte da realidade. Sua mensagem de encerramento deve ser o mais específica e informativa possível sobre o erro.
fonte
O IEEE 754 realmente tem uma solução bem definida para o seu problema. Tratamento de exceção sem usar
exceptions
http://en.wikipedia.org/wiki/IEEE_floating_point#Exception_handlingDessa forma, todas as suas operações fazem sentido matematicamente.
\ lim_ {x \ to 0} 1 / x = Inf
Na minha opinião, seguir o IEEE 754 faz mais sentido, pois garante que seus cálculos sejam tão corretos quanto em um computador e que você também seja consistente com o comportamento de outras linguagens de programação.
O único problema que surge é que Inf e NaN contaminarão seus resultados e seus usuários não saberão exatamente de onde o problema está vindo. Dê uma olhada em uma linguagem como Julia que faz isso muito bem.
O erro de divisão é propagado corretamente através das operações matemáticas, mas no final o usuário não sabe necessariamente de qual operação o erro ocorre.
edit:
Não vi a segunda parte da resposta de Jim Pivarski, que é basicamente o que estou dizendo acima. Minha culpa.fonte
O SQL, facilmente a linguagem mais usada por não programadores, faz o número 3, pelo que vale a pena. Na minha experiência observando e ajudando não programadores a escrever SQL, esse comportamento geralmente é bem compreendido e facilmente compensado (com uma declaração de caso ou algo semelhante). Ajuda que a mensagem de erro recebida tenda a ser bastante direta, por exemplo, no Postgres 9, você recebe "ERRO: divisão por zero".
fonte
Eu acho que o problema é "direcionado a usuários iniciantes. -> Portanto, não há suporte para ..."
Por que você acha que o tratamento de exceções é problemático para usuários iniciantes?
O que é pior? Tem um recurso "difícil" ou não sabe por que algo aconteceu? O que poderia confundir mais? Uma falha com um dump principal ou "Erro fatal: dividir por zero"?
Em vez disso, acho que é MUITO melhor procurar GRANDES erros de mensagem. Em vez disso, faça o seguinte: "Cálculo incorreto, divida 0/0" (ou seja: sempre mostre os DADOS que causam o problema, não apenas o tipo de problema). Veja como o PostgreSql faz os erros da mensagem, que são ótimos IMHO.
No entanto, você pode procurar outras maneiras de trabalhar com exceções, como:
http://dlang.org/exception-safe.html
Eu também tenho o sonho de construir uma linguagem e, nesse caso, acho que misturar um Talvez / Opcional com Exceções normais poderia ser o melhor:
fonte
Na minha opinião, seu idioma deve fornecer um mecanismo genérico para detectar e manipular erros. Os erros de programação devem ser detectados no momento da compilação (ou o mais cedo possível) e normalmente devem levar ao encerramento do programa. Os erros resultantes de dados inesperados ou errôneos ou de condições externas inesperadas devem ser detectados e disponibilizados para a ação apropriada, mas permitem que o programa continue sempre que possível.
As ações plausíveis incluem (a) encerrar (b) solicitar ao usuário uma ação (c) registrar o erro (d) substituir um valor corrigido (e) definir um indicador a ser testado no código (f) invocar uma rotina de tratamento de erros. Qual destes você disponibiliza e de que forma são as escolhas que precisa fazer.
De acordo com minha experiência, erros comuns de dados, como conversões defeituosas, divisão por zero, excesso e valor fora do intervalo, são benignos e, por padrão, devem ser tratados substituindo um valor diferente e definindo um sinalizador de erro. O (não programador) usando essa linguagem verá os dados com defeito e entenderá rapidamente a necessidade de verificar se há erros e resolvê-los.
[Por exemplo, considere uma planilha do Excel. O Excel não encerra sua planilha porque um número excedeu o limite ou o que for. A célula obtém um valor estranho e você descobre o porquê e o corrige.]
Então, para responder à sua pergunta: você certamente não deve terminar. Você pode substituir o NaN, mas não deve torná-lo visível, apenas certifique-se de que o cálculo seja concluído e gere um valor alto estranho. E defina um sinalizador de erro para que os usuários que precisam dele possam determinar que ocorreu um erro.
Divulgação: criei exatamente uma implementação dessa linguagem (Powerflex) e resolvi exatamente esse problema (e muitos outros) na década de 1980. Houve pouco ou nenhum progresso em idiomas para não-programadores nos últimos 20 anos, e você atrairá muitas críticas por tentar, mas eu realmente espero que você tenha sucesso.
fonte
Gostei do operador ternário, no qual você fornece um valor alternativo, caso o denumerador seja 0.
Mais uma ideia que eu não vi é produzir um valor geral "inválido". Um geral "essa variável não tem um valor porque o programa fez algo ruim", que carrega um rastreamento de pilha completo consigo. Então, se você usar esse valor em qualquer lugar, o resultado será novamente inválido, com a nova operação tentada no topo (ou seja, se o valor inválido aparecer em uma expressão, a expressão inteira produzirá inválida e nenhuma chamada de função será tentada; uma exceção seria ser operadores booleanos - verdadeiro ou inválido é verdadeiro e falso e inválido é falso - também pode haver outras exceções). Depois que esse valor não é mais referenciado em nenhum lugar, você registra uma boa descrição longa de toda a cadeia em que as coisas estavam erradas e continua os negócios como de costume. Talvez envie o rastreio por email para o líder do projeto ou algo assim.
Algo como a mônada Talvez, basicamente. Também funcionará com qualquer outra coisa que possa falhar, e você pode permitir que as pessoas construam seus próprios inválidos. E o programa continuará sendo executado enquanto o erro não for muito profundo, o que é realmente desejado aqui, eu acho.
fonte
Existem duas razões fundamentais para uma divisão por zero.
Para 1. você deve comunicar aos usuários que eles cometeram um erro, porque eles são os responsáveis e eles que sabem melhor como remediar a situação.
Para 2. Isso não é culpa do usuário, você pode apontar o dedo para algoritmo, implementação de hardware, etc., mas não é culpa do usuário, portanto, você não deve encerrar o programa ou mesmo lançar uma exceção (se permitido, o que não é nesse caso). Portanto, uma solução razoável é continuar as operações de maneira razoável.
Eu posso ver a pessoa que fez essa pergunta no caso 1. Então você precisa se comunicar novamente com o usuário. Usando qualquer padrão de ponto flutuante, Inf, -Inf, Nan, IEEE não se encaixa nessa situação. Estratégia fundamentalmente errada.
fonte
Não o permita no idioma. Ou seja, não permita a divisão por um número até que não seja comprovadamente zero, geralmente testando-o primeiro. Ou seja.
fonte
int
permite valores zero, mas o GCC ainda pode determinar onde no código as entradas específicas não podem ser zero.Ao escrever uma linguagem de programação, você deve aproveitar o fato e tornar obrigatório incluir uma ação para o dispositivo por estado zero. a <= n / c: 0 div por ação zero
Sei que o que acabei de sugerir é adicionar um 'goto' ao seu PL.
fonte