¿Se debe verificar cada pequeño error en C? [duplicar]

58

Como buen programador, uno debe escribir códigos robustos que manejen cada uno de los resultados de su programa. Sin embargo, casi todas las funciones de la biblioteca C devolverán 0 o -1 o NULL cuando haya un error.

A veces es obvio que se necesita una comprobación de errores, por ejemplo, cuando intenta abrir un archivo. Pero a menudo ignoro la verificación de errores en funciones como printf o incluso malloc porque no me parece necesario.

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

¿Es una buena práctica simplemente ignorar ciertos errores, o hay una mejor manera de manejar todos los errores?

    
pregunta Derek 朕會功夫 17.11.2015 - 00:59

7 respuestas

27

En general, el código debe tratar con condiciones excepcionales siempre que sea apropiado. Sí, esta es una declaración vaga.

En los idiomas de nivel superior con manejo de excepciones de software, esto se suele decir como "detectar la excepción en el método en el que realmente se puede hacer algo al respecto". Si se produjo un error en el archivo, tal vez deje que se acumule la pila en el código de la interfaz de usuario que puede decirle al usuario que "su archivo no se guardó en el disco". El mecanismo de excepción efectivamente absorbe "cada pequeño error" y lo maneja de manera implícita en el lugar apropiado.

En C, no tienes ese lujo. Hay algunas formas de manejar los errores, algunas de las cuales son características de lenguaje / biblioteca, algunas de las cuales son prácticas de codificación.

  

¿Es una buena práctica simplemente ignorar ciertos errores, o hay una   ¿Mejor manera de manejar todos los errores?

¿Ignorar ciertos errores? Tal vez. Por ejemplo, es razonable suponer que la escritura en la salida estándar no fallará. Si falla, ¿cómo le dirías al usuario, de todos modos? Sí, es una buena idea ignorar ciertos errores, o codificar defensivamente para prevenirlos. Por ejemplo, compruebe si hay cero antes de dividir.

Hay formas de manejar todos los errores, o al menos la mayoría de ellos:

  1. Puede usar saltos, similares a gotos, para el manejo de errores . Si bien es un tema polémico entre los profesionales de software, existen usos válidos para ellos, especialmente en el código incrustado y de rendimiento crítico (por ejemplo, kernel de Linux).
  1. En cascada if s:

    if (!<something>) {
      printf("oh no 1!");
      return;
    }
    if (!<something else>) {
      printf("oh no 2!");
      return;
    }
    
  2. Probar la primera condición, por ejemplo, abriendo o creando un archivo, luego asuma que las operaciones subsiguientes tienen éxito.

El código robusto es bueno, y uno debe verificar y manejar los errores. El método que mejor se adapte a su código depende de lo que haga el código, qué tan crítico es un fallo, etc., y solo usted puede responderlo de verdad. Sin embargo, estos métodos se prueban en el campo de la batalla y se usan en varios proyectos de código abierto, donde puedes echar un vistazo para ver cómo el código real comprueba los errores.

    
respondido por el user22815 17.11.2015 - 02:22
15
  

Sin embargo, casi todas las funciones de la biblioteca C devolverán 0 o -1 o NULL cuando haya un error.

Sí, pero sabes a qué función llamaste, ¿no?

Usted realmente tiene mucha información que podría poner en un mensaje de error. Usted sabe qué función se llamó, el nombre de la función que lo llamó, qué parámetros se pasaron y el valor de retorno de la función. Eso es mucha información para un mensaje de error muy informativo.

No tiene que hacer esto para cada llamada de función. Pero la primera vez que vea el mensaje de error "Ocurrió un error al mostrar el error anterior", cuando lo que realmente necesitaba era información útil, será la última vez que vea ese mensaje de error allí, porque inmediatamente cambiará. el mensaje de error a algo informativo que le ayudará a solucionar el problema.

    
respondido por el Robert Harvey 17.11.2015 - 01:18
10

TLDR; casi nunca deberías ignorar los errores.

El lenguaje C carece de una buena función de manejo de errores que deja que cada desarrollador de bibliotecas implemente sus propias soluciones. Los lenguajes más modernos tienen excepciones incorporadas que hacen que este problema en particular sea mucho más fácil de manejar.

Pero cuando estás atascado con C, no tienes tales ventajas. Desafortunadamente, simplemente tendrá que pagar el precio cada vez que llame a una función en la que existe una posibilidad remota de fallo. O bien, sufrirá consecuencias mucho peores, como sobrescribir datos en la memoria involuntariamente. Por lo tanto, como regla general, siempre debe comprobar si hay errores.

Si no verifica la devolución de fprintf , es muy probable que deje un error que, en el mejor de los casos, no hará lo que el usuario espera y el peor de los casos explotará por completo durante el vuelo. No hay excusa para minarse de esa manera.

Sin embargo, como desarrollador de C, también es su tarea hacer que el código sea fácil de mantener. Por lo tanto, a veces puede ignorar los errores de forma segura por motivos de claridad si (y solo si) no representan una amenaza para el comportamiento general de la aplicación .

Es el mismo problema que hacer esto:

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

Si ve que no hay buenos comentarios dentro del bloque catch vacío, esto es ciertamente un problema. No hay razón para pensar que una llamada a malloc() se ejecutará con éxito el 100% del tiempo.

    
respondido por el Alex 17.11.2015 - 02:38
9

La pregunta no es realmente específica del idioma, sino del usuario. Piensa en la pregunta desde la perspectiva del usuario. El usuario hace algo, como escribir el nombre del programa en una línea de comando y presionar Intro. ¿Qué espera el usuario? ¿Cómo pueden saber si algo salió mal? ¿Pueden permitirse el lujo de interceder si se produce un error?

En muchos tipos de código, tales controles son excesivos. Sin embargo, en el código crítico de seguridad de alta confiabilidad, como aquellos para reactores nucleares, la verificación de errores patológicos y las rutas de recuperación planificadas son parte de la naturaleza diaria del trabajo. Se considera que vale la pena el costo de tomarse el tiempo para preguntar "¿Qué sucede si X falla? ¿Cómo vuelvo a un estado seguro?" En códigos menos confiables, como los de los videojuegos, puede salir con mucho menos control de errores.

Otro enfoque similar es ¿cuánto puedes mejorar realmente en el estado al detectar el error? No puedo contar la cantidad de programas de C ++ que orgullosamente detectan excepciones, solo para reprobarlos porque en realidad no sabían qué hacer con ellos ... pero sabían que debían hacer el manejo de excepciones. Tales programas no ganaron nada con el esfuerzo extra. Solo agregue el código de comprobación de errores que cree que puede manejar la situación mejor que simplemente no verificar el código de error. Dicho esto, C ++ tiene reglas específicas para manejar excepciones que ocurren durante el manejo de excepciones para poder capturarlas de una manera más significativa (y con eso, quiero decir terminate () para asegurarse de que la pira funeraria que usted ha construido para usted se ilumine) en su propia gloria)

¿Qué tan patológico puedes llegar? Trabajé en un programa que definía un "SafetyBool" cuyos valores verdaderos y falsos se escogieron cuidadosamente para tener una distribución uniforme de 1 y 0, y se eligieron para que cualquiera de una serie de fallas de hardware (un solo bit flips, bus de datos). las trazas que se rompen, etc.) no causaron que un booleano se malinterprete. No hace falta decir que no diría que esta es una práctica de programación de propósito general que se utilizará en cualquier programa antiguo.

    
respondido por el Cort Ammon 17.11.2015 - 05:20
6
  • Los diferentes requisitos de seguridad requieren diferentes niveles de corrección. En el software de control de aviación o automóvil, se verifican todos los valores de retorno, cf. MISRA.FUNC.UNUSEDRET . En una prueba de concepto rápida que nunca abandona su máquina, probablemente no.
  • La codificación cuesta tiempo. Demasiados controles irrelevantes en software que no son críticos para la seguridad es un esfuerzo que se gasta mejor en otros lugares. Pero, ¿dónde está el punto óptimo entre la calidad y el costo? Eso depende de las herramientas de depuración y la complejidad del software.
  • El manejo de errores puede oscurecer el flujo de control e introducir nuevos errores. Me gustan bastante las funciones de envoltura de Stevens de la "red" de Richard que al menos informan errores.
  • El manejo de errores puede, rara vez, ser un problema de rendimiento. Pero la mayoría de las llamadas a la biblioteca de C demorarán tanto que el costo de verificar el valor de retorno es inmensamente pequeño.
respondido por el Peter A. Schneider 17.11.2015 - 14:50
3

Un poco de un resumen sobre la pregunta. Y no es necesariamente para el lenguaje C.

Para programas más grandes tendría una capa de abstracción; Tal vez una parte del motor, una biblioteca o dentro de un marco. A esa capa no le importará el clima si obtiene datos válidos o la salida tendrá un valor predeterminado: 0 , -1 , null etc.

Luego hay una capa que sería su interfaz con la capa abstracta, que haría un montón de manejo de errores y quizás otras cosas como inyecciones de dependencia, escucha de eventos, etc.

Y luego tendría su capa de implementación concreta en la que realmente establece las reglas y maneja la salida.

Así que mi opinión sobre esto es que a veces es mejor excluir completamente el manejo de errores de una parte del código porque esa parte simplemente no hace ese trabajo. Y luego tenga algún procesador que evalúe la salida y señale un error.

Esto se hace principalmente para separar las responsabilidades que conducen a la legibilidad del código y una mejor escalabilidad.

    
respondido por el Creative Magic 17.11.2015 - 03:55
0

En general, a menos que tenga una buena razón para que no compruebe esa condición de error, debería hacerlo. La única buena razón que se me ocurre para no verificar una condición de error es cuando no se puede hacer algo significativo si falla. Sin embargo, esta es una barra muy difícil de cumplir, porque siempre hay una opción viable: salir con gracia.

    
respondido por el Clever Neologism 17.11.2015 - 16:23

Lea otras preguntas en las etiquetas