Afirmar o mal? [fechadas]


199

Os Gocriadores do idioma escrevem :

Go não fornece afirmações. Eles são inegavelmente convenientes, mas nossa experiência tem sido de que os programadores os usam como muleta para evitar pensar em manipulação e relatório de erros adequados. O tratamento adequado de erros significa que os servidores continuam a operação após erros não fatais, em vez de travarem. O relatório de erros adequado significa que os erros são diretos e diretos, evitando que o programador interprete um grande rastreamento de falha. Erros precisos são particularmente importantes quando o programador que está vendo os erros não está familiarizado com o código.

Qual é a sua opinião sobre isso?


4
tangente: Go é uma linguagem invulgarmente opinativa. Isso não é necessariamente uma coisa ruim. No entanto, isso significa que você deve ter suas opiniões com um grão maior de sal. Isso também significa que, se você discordar, estará rangendo os dentes ao usar o idioma. Como evidência de como o Go se apega a suas opiniões, apesar da realidade, considere que você precisa recorrer à magia da reflexão para determinar se duas coleções são iguais.
Allyourcode 4/15

@allyourcode Se você está se referindo reflect.DeepEqual, certamente não precisa . É conveniente, mas com o custo do desempenho (os testes de unidade são um bom caso de uso). Caso contrário, você poderá implementar qualquer verificação de igualdade apropriada para sua "coleção" sem muita dificuldade.
Igor Dubinskiy

1
Não, não é disso que estou falando. Não existe fatia1 == fatia2 sem reflexão. Todos os outros idiomas têm um equivalente a esta operação super básica. A única razão pela qual a Go não faz é preconceito.
Allyourcode

Você pode comparar duas fatias sem reflexão usando um forloop no Go (assim como C). Ele iria ser muito bom ter operações fatia genéricos, embora a comparação fica complicada quando os ponteiros e estruturas estão envolvidos.
Kbolino

Respostas:


321

Não, não há nada de errado assertcontanto que você o use como pretendido.

Ou seja, deveria ser para capturar casos que "não podem acontecer" durante a depuração, em oposição ao tratamento normal de erros.

  • Afirmar: Uma falha na própria lógica do programa.
  • Tratamento de erros: Entrada incorreta ou estado do sistema que não é devido a um erro no programa.

109

Não, nem gotonem assertsão maus. Mas ambos podem ser mal utilizados.

Afirmar é para verificações de sanidade. Coisas que devem matar o programa se não estiverem corretas. Não para validação ou como substituto para o tratamento de erros.


como usar gotosabiamente?
precisa saber é o seguinte

1
@ ar2015 Encontre um dos padrões absurdamente inventados que algumas pessoas recomendam evitar gotopor razões puramente religiosas, depois use apenas gotoao invés de ofuscar o que você está fazendo. Em outras palavras: se você pode provar que realmente precisa goto, e a única alternativa é encenar uma carga inútil de andaimes que, no final das contas, faz a mesma coisa, mas sem alterar a Polícia de Goto ... basta usar goto. Obviamente, uma pré-condição disso é a parte "se você puder provar que realmente precisa goto". Muitas vezes, as pessoas não. Isso ainda não significa que é inerentemente uma coisa ruim.
underscore_d

2
gotoé usado em kernel do Linux para limpeza de código
malat

61

Por essa lógica, os pontos de interrupção também são maus.

As declarações devem ser usadas como um auxiliar de depuração e nada mais. "Mal" é quando você tenta usá-los em vez de manipular erros.

Existem declarações para ajudar você, o programador, a detectar e corrigir problemas que não devem existir e verificar se suas suposições permanecem verdadeiras.

Eles não têm nada a ver com o tratamento de erros, mas, infelizmente, alguns programadores os abusam como tal e os declaram "maus".


40

Eu gosto de usar afirmar muito. Acho muito útil quando estou criando aplicativos pela primeira vez (talvez para um novo domínio). Em vez de fazer uma verificação de erro muito sofisticada (que consideraria a otimização prematura), codifico rapidamente e adiciono muitas afirmações. Depois de saber mais sobre como as coisas funcionam, reescrevo e removo algumas das declarações e as altero para melhorar o tratamento de erros.

Por causa das declarações, passo muito menos tempo codificando / depurando programas.

Também notei que as declarações me ajudam a pensar em muitas coisas que podem interromper meus programas.


31

Como informação adicional, go fornece uma função interna panic. Isso pode ser usado no lugar de assert. Por exemplo

if x < 0 {
    panic("x is less than 0");
}

panicimprimirá o rastreamento da pilha, portanto, de alguma forma, ele tem o objetivo de assert.


30

Eles devem ser usados ​​para detectar erros no programa. Entrada ruim do usuário.

Se usado corretamente, eles não são maus.


13

Isso surge muito, e acho que um problema que torna as defesas de afirmações confusas é que elas geralmente são baseadas na verificação de argumentos. Portanto, considere este exemplo diferente de quando você pode usar uma asserção:

build-sorted-list-from-user-input(input)

    throw-exception-if-bad-input(input)

    ...

    //build list using algorithm that you expect to give a sorted list

    ...

    assert(is-sorted(list))

end

Você usa uma exceção para a entrada, porque espera receber algumas vezes informações incorretas. Você afirma que a lista está classificada para ajudá-lo a encontrar um bug em seu algoritmo, o que, por definição, você não espera. A asserção está apenas na compilação de depuração, portanto, mesmo que a verificação seja cara, você não se importa de fazê-lo em todas as chamadas da rotina.

Você ainda precisa testar seu código de produção, mas essa é uma maneira diferente e complementar de garantir que seu código esteja correto. Os testes de unidade garantem que sua rotina atenda à sua interface, enquanto as asserções são uma maneira mais refinada de garantir que sua implementação esteja fazendo exatamente o que você espera.


8

As afirmações não são más, mas podem ser facilmente mal utilizadas. Concordo com a afirmação de que "as asserções costumam ser usadas como muleta para evitar pensar em manipulação e relatório de erros adequados". Eu já vi isso com bastante frequência.

Pessoalmente, gosto de usar afirmações porque documentam as suposições que eu poderia ter feito ao escrever meu código. Se essas suposições forem quebradas ao manter o código, o problema poderá ser detectado durante o teste. No entanto, faço questão de remover todas as declarações do meu código ao fazer uma compilação de produção (por exemplo, usando #ifdefs). Ao eliminar as asserções na construção da produção, elimino o risco de alguém usá-las como muletas.

Há também outro problema com afirmações. As asserções são verificadas apenas no tempo de execução. Mas geralmente é o caso que a verificação que você gostaria de executar poderia ter sido executada em tempo de compilação. É preferível detectar um problema no momento da compilação. Para programadores C ++, o boost fornece BOOST_STATIC_ASSERT, que permite fazer isso. Para programadores C, este artigo ( texto do link ) descreve uma técnica que pode ser usada para executar asserções em tempo de compilação.

Em resumo, a regra de ouro que sigo é: Não use asserções em uma construção de produção e, se possível, use apenas asserções para coisas que não podem ser verificadas no tempo de compilação (ou seja, devem ser verificadas no tempo de execução).


5

Eu admito ter usado afirmações sem considerar o relatório de erros adequado. No entanto, isso não tira que eles sejam muito úteis quando usados ​​corretamente.

Eles são especialmente úteis se você deseja seguir o princípio "Crash Early". Por exemplo, suponha que você esteja implementando um mecanismo de contagem de referência. Em certos locais do seu código, você sabe que a refcount deve ser zero ou um. E também suponha que, se o refcount estiver errado, o programa não travará imediatamente, mas durante o próximo ciclo de mensagens, nesse momento, será difícil descobrir por que as coisas deram errado. Uma declaração teria sido útil na detecção do erro mais próximo de sua origem.


5

Prefiro evitar código que faça coisas diferentes na depuração e liberação.

Quebrar o depurador em uma condição e ter todas as informações de arquivo / linha é útil, mas também a expressão exata e o valor exato.

Ter uma declaração que "avalie a condição apenas na depuração" pode ser uma otimização de desempenho e, como tal, útil apenas em 0,0001% dos programas - onde as pessoas sabem o que estão fazendo. Em todos os outros casos, isso é prejudicial, pois a expressão pode realmente alterar o estado do programa:

assert(2 == ShroedingersCat.GetNumEars()); faria o programa fazer coisas diferentes na depuração e liberação.

Nós desenvolvemos um conjunto de macros de declaração que lançariam uma exceção e as faríamos na versão debug e release. Por exemplo, THROW_UNLESS_EQ(a, 20);lançaria uma exceção com a mensagem what () com arquivos, linhas e os valores reais de a, e assim por diante. Somente uma macro teria poder para isso. O depurador pode ser configurado para interromper o 'lançamento' do tipo de exceção específico.


4
90% por cento das estatísticas usadas nos argumentos são falsas.
João Portela

5

Não gosto de afirmações intensamente. Eu não iria tão longe quanto dizer que eles são maus.

Basicamente, uma afirmação fará o mesmo que uma exceção não verificada faria, a única exceção é que a afirmação (normalmente) não deve ser mantida para o produto final.

Se você criar uma rede de segurança para si mesmo durante a depuração e a construção do sistema, por que negaria essa rede de segurança para seu cliente, para o suporte técnico ou para qualquer pessoa que possa usar o software que está construindo no momento. Use exceções exclusivamente para afirmações e situações excepcionais. Ao criar uma hierarquia de exceção apropriada, você poderá discernir muito rapidamente um do outro. Só que desta vez a declaração permanece no local e pode fornecer informações valiosas em caso de falha que, de outra forma, seria perdida.

Portanto, entendo perfeitamente os criadores do Go removendo completamente as declarações e forçando os programadores a usar exceções para lidar com a situação. Há uma explicação simples para isso: as exceções são apenas um mecanismo melhor para o trabalho, por que ficar com as afirmações arcaicas?


O Go não possui exceções. O motivo usual para usar uma afirmação em vez de uma exceção é porque você deseja que ela seja elidida na implantação por motivos de desempenho. Afirmações não são arcaicas. Sinto muito, mas esta resposta não é remotamente útil para a pergunta original, nem correta.
Nir Friedman


3

Recentemente, comecei a adicionar algumas declarações ao meu código, e é assim que tenho feito:

Divido mentalmente meu código em código de limite e código interno. Código de limite é um código que lida com a entrada do usuário, lê arquivos e obtém dados da rede. Nesse código, solicito entrada em um loop que sai somente quando a entrada é válida (no caso de entrada interativa do usuário) ou lança exceções no caso de dados corrompidos irrecuperáveis ​​de arquivos / rede.

Código interno é tudo o resto. Por exemplo, uma função que define uma variável na minha classe pode ser definida como

void Class::f (int value) {
    assert (value < end);
    member = value;
}

e uma função que obtém entrada de uma rede pode ser lida da seguinte forma:

void Class::g (InMessage & msg) {
    int const value = msg.read_int();
    if (value >= end)
        throw InvalidServerData();
    f (value);
}

Isso me dá duas camadas de cheques. Qualquer coisa em que os dados sejam determinados no tempo de execução sempre recebe uma exceção ou tratamento imediato de erros. No entanto, esse check-in extra Class::fcom a assertdeclaração significa que, se algum código interno for chamado Class::f, ainda tenho uma verificação de integridade. Meu código interno pode não passar um argumento válido (porque eu posso ter calculado a valuepartir de uma série complexa de funções), então eu gosto de ter a asserção na função de configuração para documentar que, independentemente de quem está chamando a função, valuenão deve ser maior que ou igual a end.

Isso parece se encaixar no que estou lendo em alguns lugares, que afirmações devem ser impossíveis de violar em um programa que funcione bem, enquanto exceções devem ser para casos excepcionais e errôneos que ainda são possíveis. Como, em teoria, estou validando todas as informações, não deve ser possível que minha afirmação seja acionada. Se for, meu programa está errado.


2

Se as afirmações sobre as quais você está falando significam que o programa vomita e depois existe, as afirmações podem ser muito ruins. Isso não quer dizer que eles estejam sempre a coisa errada a usar, eles são um construto que é facilmente usado mal. Eles também têm muitas alternativas melhores. Coisas assim são boas candidatas a serem chamadas de más.

Por exemplo, um módulo de terceiros (ou qualquer módulo realmente) quase nunca deve sair do programa de chamada. Isso não dá ao programador responsável o controle sobre o risco que o programa deve assumir naquele momento. Em muitos casos, os dados são tão importantes que até salvar dados corrompidos é melhor do que perdê-los. As declarações podem forçar você a perder dados.

Algumas alternativas para afirmações:

  • Usando um depurador,
  • Console / banco de dados / outro log
  • Exceções
  • Outros tipos de tratamento de erros

Algumas referências:

Mesmo as pessoas que defendem a afirmação acham que devem ser usadas apenas no desenvolvimento e não na produção:

Essa pessoa diz que as declarações devem ser usadas quando o módulo potencialmente corromper dados que persistem após uma exceção ser lançada: http://www.advogato.org/article/949.html . Este é certamente um ponto razoável, no entanto, um módulo externo nunca deve prescrever a importância dos dados corrompidos para o programa de chamada (saindo "para" eles). A maneira correta de lidar com isso é lançando uma exceção que deixa claro que o programa agora pode estar em um estado inconsistente. E como os bons programas consistem principalmente em módulos (com um pouco de código de cola no executável principal), afirmações são quase sempre a coisa errada a se fazer.


1

assert é muito útil e pode poupar muito tempo de retorno quando erros inesperados ocorrem ao interromper o programa nos primeiros sinais de problemas.

Por outro lado, é muito fácil abusar assert.

int quotient(int a, int b){
    assert(b != 0);
    return a / b;
}

A versão correta e correta seria algo como:

bool quotient(int a, int b, int &result){
    if(b == 0)
        return false;

    result = a / b;
    return true;
}

Então ... a longo prazo ... no quadro geral ... Devo concordar que isso assertpode ser abusado. Eu faço isso o tempo todo.


1

assert está sendo abusado por manipulação de erros porque é menos digitado.

Portanto, como designers de linguagem, eles devem ver que o tratamento adequado de erros pode ser feito com digitação ainda menor. Excluir a declaração porque seu mecanismo de exceção é detalhado não é a solução. Oh, espere, o Go também não tem exceções. Que pena :)


1
Nada mal :-) Exceções ou não, asserções ou não, os fãs do Go ainda estão falando sobre o quão curtos são os códigos.
Moshe Revah

1

Tive vontade de chutar a cabeça do autor quando vi isso.

Uso afirmações o tempo todo no código e eventualmente as substituo quando escrevo mais código. Uso-os quando não escrevi a lógica necessária e quero ser alertado quando executo o código em vez de escrever uma exceção que será excluída à medida que o projeto se aproxima da conclusão.

Exceções também se misturam com o código de produção mais facilmente, o que eu não gosto. Uma afirmação é mais fácil de notar do quethrow new Exception("Some generic msg or 'pretend i am an assert'");


1

Meu problema com essas respostas na defesa de afirmação é que ninguém especifica claramente o que a torna diferente de um erro fatal regular e por que uma afirmação não pode ser um subconjunto de uma exceção . Agora, com isso dito, e se a exceção nunca for detectada? Isso torna uma afirmação por nomenclatura? E, por que você desejaria impor uma restrição no idioma que pode ser gerada uma exceção que / nada / possa lidar?


Se você olhar para a minha resposta. Meu uso é diferenciar 'exceções' (afirma) que eu quero me livrar de usado para depuração vs exceções que eu mantenho. Por que eu iria querer me livrar deles? porque sem eles trabalhando, não estaria completo. Exemplo é se eu lidar com 3 casos e o quarto é todo. Posso pesquisar com facilidade a afirmação para encontrá-los no código e saber que está incompleto, em vez de usar uma exceção que pode ser acidentalmente capturada (por outro programador) ou difícil dizer se é uma exceção ou uma verificação lógica que devo resolver no código.

A meu ver, essa é uma péssima idéia, no mesmo nível das aulas "seladas" e pelo mesmo motivo. Você está assumindo que as exceções que deseja manter são aceitáveis ​​para usos do seu código que você ainda não conhece. Todas as exceções passam pelos mesmos canais e, se o usuário não quiser capturá-las, pode optar por não. Se ele faz, ele deve ter a capacidade também. De qualquer forma, você está apenas fazendo suposições ou adiando sua prática com um conceito como uma afirmação.
Evan Carroll

Eu decidi que os cenários de exemplo são os melhores. Heres um simples. int func (int i) {if (i> = 0) {console.write ("O número é positivo {0}", i); } else {assert (false); // com preguiça de fazer ATM negativos} return i * 2; <- Como eu faria isso sem declarações e é uma exceção realmente melhor? e lembre-se, esse código será implementado antes do lançamento.

Claro que as exceções são melhores, digamos que eu recebo a entrada do usuário e ligo func()com um número negativo. Agora, de repente, com suas afirmações, você puxou o tapete debaixo de mim e não me deu uma chance de se recuperar, em vez de me dizer educadamente o que estou solicitando, não pode ser feito. Não há nada errado em acusar o programa de se comportar mal e emitir uma citação: o problema é que você está confundindo o ato de fazer cumprir uma lei e condenar o criminoso.
Evan Carroll

Esse é o ponto, você NÃO DEVE dizer o que o usuário deseja fazer não pode ser feito. O aplicativo deve terminar com o erro msg e quem estiver depurando se lembrará de algo que DEVE SER FEITO, mas não está. Você NÃO quer que seja tratado. Você quer uma rescisão e deve ser lembrado de que não lidou com esse caso. e é extremamente fácil ver quais casos restam, pois tudo o que você precisa fazer é procurar por uma declaração no código. Manipular o erro estaria errado, pois o código deve poder fazê-lo assim que estiver pronto para produção. Permitir que um programador o pegue e faça outra coisa está errado.

1

Sim, afirmações são más.

Muitas vezes, eles são usados ​​em locais onde o tratamento adequado de erros deve ser usado. Acostume-se a escrever o tratamento adequado de erros de qualidade de produção desde o início!

Geralmente eles atrapalham a escrita de testes de unidade (a menos que você escreva uma afirmação personalizada que interaja com seu equipamento de teste). Isso geralmente ocorre porque eles são usados ​​onde o tratamento adequado de erros deve ser usado.

Principalmente, eles são compilados a partir das versões do release, o que significa que nenhum dos "testes" deles está disponível quando você está executando o código que realmente lança; dado que em situações multithread, os piores problemas geralmente aparecem apenas no código do release, isso pode ser ruim.

Às vezes, são uma muleta para projetos quebrados; isto é, o design do código permite que um usuário o chame de uma maneira que não deva ser chamado e a afirmação "impede" isso. Corrija o design!

Eu escrevi sobre isso mais no meu blog em 2005 aqui: http://www.lenholgate.com/blog/2005/09/assert-is-evil.html


0

Não tanto mal como geralmente contraproducente. Há uma separação entre verificação de erro permanente e depuração. A afirmação faz com que as pessoas pensem que toda a depuração deve ser permanente e causa grandes problemas de legibilidade quando muito usada. O tratamento permanente de erros deve ser melhor do que o necessário, e, como a afirmação causa seus próprios erros, é uma prática bastante questionável.


5
assert é bom para declarar as pré-condições na parte superior de uma função e, se claramente escrito, atua como parte da documentação da função.
6266 Peter Cordes

0

Eu nunca uso assert (), exemplos geralmente mostram algo como isto:

int* ptr = new int[10];
assert(ptr);

Isso é ruim, eu nunca faço isso, e se o meu jogo estiver alocando um monte de monstros? Por que eu deveria travar o jogo? Em vez disso, você deve lidar com os erros normalmente, então faça algo como:

CMonster* ptrMonsters = new CMonster[10];
if(ptrMonsters == NULL) // or u could just write if(!ptrMonsters)
{
    // we failed allocating monsters. log the error e.g. "Failed spawning 10 monsters".
}
else
{
    // initialize monsters.
}

14
newnunca volta nullptr, joga.
David Stone

Observe que você pode usar std :: nothrow para isso.
Mark3 #
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.