Deve-se verificar cada pequeno erro em C? [duplicado]

58

Como um bom programador, deve-se escrever códigos robustos que lidem com cada resultado de seu programa. No entanto, quase todas as funções da biblioteca C retornarão 0 ou -1 ou NULL quando houver um erro.

Algumas vezes é óbvio que a verificação de erros é necessária, por exemplo, quando você tenta abrir um arquivo. Mas muitas vezes ignoro a verificação de erros em funções como printf ou mesmo malloc porque não me parece necessário.

if(fprintf(stderr, "%s", errMsg) < 0){
    perror("An error occurred while displaying the previous error.");
    exit(1);
}

É uma boa prática simplesmente ignorar certos erros ou existe uma maneira melhor de lidar com todos os erros?

    
por Derek 朕會功夫 17.11.2015 / 00:59
fonte

7 respostas

27

Em geral, o código deve lidar com condições excepcionais onde quer que seja apropriado. Sim, esta é uma declaração vaga.

Em linguagens de nível mais alto com manipulação de exceção de software, isso geralmente é indicado como "capturar a exceção no método em que você pode realmente fazer algo a respeito". Se ocorreu um erro de arquivo, talvez você deixe que ele borbulhe a pilha para o código de interface do usuário que pode realmente informar ao usuário "seu arquivo falhou ao salvar no disco". O mecanismo de exceção engole efetivamente "cada pequeno erro" e o manipula implicitamente no local apropriado.

Em C, você não tem esse luxo. Existem algumas maneiras de lidar com erros, alguns dos quais são recursos de linguagem / biblioteca, alguns dos quais são práticas de codificação.

Is it a good practice to just ignore certain errors, or is there a better way to handle all the errors?

Ignorar certos erros? Talvez. Por exemplo, é razoável supor que a gravação na saída padrão não falhará. Se falhar, como você diria ao usuário? Sim, é uma boa ideia ignorar certos erros ou codificar defensivamente para evitá-los. Por exemplo, verifique se há zero antes de dividir.

Existem maneiras de lidar com todos ou pelo menos a maioria dos erros:

  1. Você pode usar saltos, semelhantes a gotos, para tratamento de erros . Embora seja um problema contencioso entre os profissionais de software, há usos válidos para eles, especialmente em código incorporado e de desempenho crítico (por exemplo, kernel do Linux).
  1. Em cascata em if s:

    if (!<something>) {
      printf("oh no 1!");
      return;
    }
    if (!<something else>) {
      printf("oh no 2!");
      return;
    }
    
  2. Teste a primeira condição, por exemplo abrindo ou criando um arquivo, então suponha que as operações subseqüentes sejam bem-sucedidas.

O código robusto é bom e deve-se verificar e manipular os erros. Qual método é o melhor para o seu código depende do que o código faz, quão crítica é uma falha, etc. e somente você pode realmente responder a isso. No entanto, esses métodos são testados em batalha e usados em vários projetos de código aberto, onde você pode dar uma olhada para ver como o código real verifica erros.

    
por 17.11.2015 / 02:22
fonte
15

However, almost all functions from the C library will return 0 or -1 or NULL when there's an error.

Sim, mas você sabe qual função você chamou, não é?

Na verdade, você tem muitas informações que podem ser colocadas em uma mensagem de erro. Você sabe qual função foi chamada, o nome da função que a chamou, quais parâmetros foram passados e o valor de retorno da função. Isso é muita informação para uma mensagem de erro muito informativa.

Você não precisa fazer isso para todas as chamadas de função. Mas a primeira vez que você vê a mensagem de erro "Ocorreu um erro ao exibir o erro anterior", quando o que você realmente precisava era de informações úteis, seria a última vez que você veria essa mensagem de erro, porque você vai mudar imediatamente a mensagem de erro para algo informativo que ajudará você a solucionar o problema.

    
por 17.11.2015 / 01:18
fonte
10

TLDR; você quase nunca deve ignorar erros.

A linguagem C não possui um bom recurso de tratamento de erros, deixando para cada desenvolvedor de biblioteca implementar suas próprias soluções. Línguas mais modernas têm excepções construídas, o que torna este problema em particular muito mais fácil de gerir.

Mas quando você está preso com C você não tem tais vantagens. Infelizmente, você simplesmente terá que pagar o preço toda vez que estiver chamando uma função que exista uma possibilidade remota de falha. Ou então você sofrerá conseqüências muito piores, como sobrescrever dados na memória sem querer. Então, como regra geral, você tem que verificar sempre os erros.

Se você não checar o retorno de fprintf , é muito provável que você deixe um bug por trás disso, no melhor dos casos, não faça o que o usuário espera e, pior ainda, exploda tudo durante o vôo. Não há desculpa para se enfraquecer dessa maneira.

No entanto, como desenvolvedor C, também é seu trabalho tornar o código fácil de manter. Então, às vezes, você pode ignorar com segurança os erros por questão de clareza se (e somente se) eles não representarem qualquer ameaça ao comportamento geral do aplicativo. .

É o mesmo problema de fazer isso:

try
{
    run();
} catch (Exception) {
    // comments expected here!!!
}

Se você ver que sem bons comentários dentro do bloco catch vazio, isso é certamente um problema. Não há razão para pensar que uma chamada para malloc() será executada com sucesso 100% do tempo.

    
por 17.11.2015 / 02:38
fonte
9

A pergunta não é específica do idioma, mas específica do usuário. Pense na questão do ponto de vista do usuário. O usuário faz alguma coisa, como digitar o nome do programa em uma linha de comando e apertar enter. O que o usuário espera? Como eles podem dizer se algo deu errado? Eles podem se permitir interceder se ocorrer um erro?

Em muitos tipos de código, essas verificações são exageradas. No entanto, em códigos críticos de segurança de alta confiabilidade, como os de reatores nucleares, a verificação de erros patológicos e os caminhos de recuperação planejados fazem parte da natureza do dia-a-dia do trabalho. Considera-se que vale a pena gastar o tempo para perguntar "O que acontece se o X falhar? Como volto para um estado seguro?" Em um código menos confiável, como os de videogames, você pode obter muito menos erros na verificação de erros.

Outra abordagem semelhante é quanto você pode realmente melhorar o estado, capturando o erro? Eu não posso contar o número de programas em C ++ que orgulhosamente capturam exceções, apenas para apenas recolocá-los porque eles realmente não sabem o que fazer com eles ... mas eles sabiam que deveriam fazer o tratamento de exceções. Esses programas não ganharam nada com o esforço extra. Adicione apenas o código de verificação de erros que você acha que pode realmente manipular a situação melhor do que simplesmente não verificar o código de erro. Dito isto, o C ++ tem regras específicas para lidar com exceções que ocorrem durante o tratamento de exceções para capturá-las de maneira mais significativa (e com isso, quero dizer chamando terminate () para ter certeza de que a pira funerária que você construiu para si em sua própria glória)

Como patológico você consegue? Eu trabalhei em um programa que definia um "SafetyBool" cujos valores verdadeiro e falso eram cuidadosamente escolhidos para ter uma distribuição par de 1s e 0s, e eles eram escolhidos de modo que qualquer uma de várias falhas de hardware (single bit flips, data bus traços ficarem quebrados, etc.) não fez com que um booleano fosse mal interpretado. Escusado será dizer que eu não afirmaria que esta é uma prática de programação de propósito geral a ser usada em qualquer programa antigo.

    
por 17.11.2015 / 05:20
fonte
6
  • Diferentes requisitos de segurança exigem diferentes níveis de correção. No software de controle de aviação ou de automóveis, todos os valores de retorno serão verificados, cf. MISRA.FUNC.UNUSEDRET . Em uma rápida prova de conceito que nunca sai da sua máquina, provavelmente não.
  • A codificação custa tempo. Muitas verificações irrelevantes em softwares não críticos para segurança são um esforço melhor gasto em outro lugar. Mas onde está o ponto ideal entre qualidade e custo? Isso depende das ferramentas de depuração e da complexidade do software.
  • O tratamento de erros pode obscurecer o fluxo de controle e introduzir novos erros. Eu gosto bastante das funções de wrapper de Richard "network" Stevens que, pelo menos, relatam erros.
  • O tratamento de erros raramente pode ser um problema de desempenho. Mas a maioria das chamadas à biblioteca C demorará tanto que o custo de verificar um valor de retorno é imensamente pequeno.
por 17.11.2015 / 14:50
fonte
3

Um pouco de resumo sobre a questão. E não é necessariamente para a linguagem C.

Para programas maiores, você teria uma camada de abstração; talvez uma parte do mecanismo, uma biblioteca ou dentro de uma estrutura. Essa camada não se importaria com o tempo em que você obtém dados válidos ou a saída seria um valor padrão: 0 , -1 , null etc.

Depois, há uma camada que seria a sua interface com a camada abstrata, que geraria muitos erros e talvez outras coisas, como injeções de dependência, audição de eventos, etc.

E, mais tarde, você teria sua camada de implementação concreta onde você realmente definiria as regras e lidaria com a saída.

Então, minha opinião sobre isso é que às vezes é melhor excluir completamente o tratamento de erros de uma parte do código, porque essa parte simplesmente não faz esse trabalho. E então ter algum processador que avalie a saída e aponte para um erro.

Isso é feito principalmente para separar as responsabilidades que levam à legibilidade do código e melhor escalabilidade.

    
por 17.11.2015 / 03:55
fonte
0

Em geral, a menos que você tenha uma boa razão para não verificar essa condição de erro, você deve. A única boa razão pela qual posso pensar em não verificar uma condição de erro é quando você não pode fazer algo significativo se falhar. Esta é uma barra muito difícil de encontrar, porque há sempre uma opção viável: sair normalmente.

    
por 17.11.2015 / 16:23
fonte