¿Qué herramientas o estándares se pueden usar para mejorar la confiabilidad del código C integrado?

Normalmente programo PIC en C, generalmente para convertidores de modo conmutado. He oído hablar de varias herramientas y estándares de análisis estático como MISRA C que se pueden usar para ayudar a mejorar la confiabilidad del código. Me gustaría saber más. ¿Qué estándares o herramientas podrían ser apropiados para mi contexto?

¿Qué tan ajustado estás en el lenguaje C?
Se me podría persuadir de cambiar a otra cosa si hubiera un muy buen caso para ello. Pero tendría que ser un muy buen caso.
"Un muy buen caso" para cambiar de C no se puede hacer rápidamente, y para el PIC, posiblemente todavía no. Para AVR, ARM o MSP430, valdría la pena echarle un vistazo a Ada (a pesar de la negatividad que atrae, ¡como puedes ver!) y para rel alto, vale la pena echarle un vistazo a SPARK.
Puede encontrar estos interesantes como información de antecedentes: SPARK vs MISRA-C spark-2014.org/entries/detail/… y este estudio de caso en curso: spark-2014.org/uploads/Nosegearpaper_1.pdf
Podría ser mejor invertir tiempo para justificar el cambio del PIC a algo moderno... Especialmente si está diseñando el tipo de sistemas de misión crítica para los que MISRA y SPARK estaban destinados originalmente.

Respuestas (5)

La validación del código integrado es complicada, especialmente cuando se trata de partes de recursos limitados como los PIC. A menudo no puede darse el lujo de codificar en casos de prueba debido a las limitaciones de memoria de la pieza y la programación a menudo "en tiempo real" que se realiza en este tipo de dispositivos.

Estas son algunas de mis pautas:

  1. Escriba una especificación si no hay una: si no está codificando contra una especificación, documente lo que se supone que debe hacer su código, cuáles son las entradas válidas, cuáles son las salidas esperadas, cuánto tiempo debe tomar cada rutina, qué puede y qué no puede obtener golpeado, etc. - una teoría de la operación, diagramas de flujo, cualquier cosa es mejor que nada.

  2. Comenta tu código: el hecho de que algo sea obvio para ti no significa que sea obvio (o correcto) para otra persona. Los comentarios en lenguaje sencillo son necesarios tanto para la revisión como para la mantenibilidad del código.

  3. Codifique a la defensiva: no solo incluya código para entradas normales. Maneje las entradas que faltan, las entradas que están fuera de rango, los desbordamientos matemáticos, etc. Cuantas más esquinas cubra con el diseño de su código, menos grados de libertad tendrá el código cuando se implemente.

  4. Use herramientas de análisis estático: puede ser abrumador la cantidad de errores que las herramientas como PC-lint pueden encontrar en su código. Considere un análisis estático limpio como un buen punto de partida para realizar pruebas serias.

  5. Las revisiones por pares son esenciales: su código debe estar lo suficientemente limpio y bien documentado para que una parte independiente pueda revisarlo de manera eficiente. Deja tu ego en la puerta y considera seriamente cualquier crítica o sugerencia que te haga.

  6. La prueba es esencial: debe hacer su propia validación, así como tener una validación independiente del código. Otros pueden descifrar su código de maneras que no puede imaginar. Pruebe todas las condiciones válidas y todas las condiciones no válidas que se le ocurran. Use PRNG y alimente datos basura. Haga todo lo que pueda para romper las cosas, luego repárelas e inténtelo de nuevo. Si tiene suerte, podrá ejecutar su código en modo de depuración y echar un vistazo a los registros y variables; si no, deberá ser astuto y alternar LED/señales digitales para tener una idea del estado de su dispositivo. Haz lo que sea necesario para obtener la retroalimentación que necesitas.

  7. Mire debajo del capó: no tenga miedo de mirar el código de máquina generado por su compilador de C. Es posible que encuentre (¿encontrará?) lugares donde su hermoso código C se ha disparado en decenas, si no cientos, de operaciones, donde algo que debería ser seguro (ya que es solo una línea de código, ¿verdad?) tarda tanto en ejecutarse que múltiples interrupciones han despedido e invalidado las condiciones. Si algo se vuelve terriblemente ineficiente, refactorícelo y vuelva a intentarlo.

+1 Todos los buenos consejos. Esperaría que cualquier desarrollador de firmware profesional sonriera y asintiera al leer esto.
Un aspecto importante de las revisiones por pares es que la revisión es sobre el código, no sobre el programador. Si analiza su código con términos como "primero hago esto, luego hago aquello", probablemente esté en problemas. "Primero el código hace esto, luego hace aquello" es la forma correcta de pensarlo. Y lo mismo se aplica a los revisores: no "¿por qué hiciste esto?", sino "¿por qué el código hace esto?".
También podría considerar agregar: 1. Usar la verificación de Complejidad Ciclomática 2. Software de control de versiones

La mayoría de las mismas técnicas para crear software confiable en una PC también son aplicables al desarrollo integrado. Es útil separar sus algoritmos del código específico del hardware y probarlos por separado con pruebas unitarias, simulaciones, análisis estático y herramientas como Valgrind. De esa manera, hay mucho menos código que solo se prueba en el hardware.

No abandonaría C. Si bien los lenguajes como Ada pueden ofrecer algunas garantías menores, es fácil caer en la trampa de pensar que el lenguaje promete más de lo que realmente hace.

Sin embargo, Valgrid podría ser un poco más relevante para PC que para una MCU de 8 bits :)
Desafortunadamente , algunas técnicas para crear un buen software a nivel de PC son muy inadecuadas para micros pequeños, y algunas prácticas consideradas malas e incorrectas en PC son perfectamente aceptables en un entorno integrado.

MISRA-C es de hecho muy útil para mejorar la calidad general del código y minimizar errores. Solo asegúrese de leer y comprender todas las reglas, la mayoría de ellas son buenas, pero algunas no tienen ningún sentido.

Una advertencia aquí. El documento MISRA asume que el lector es alguien con un amplio conocimiento del lenguaje C. Si no tiene un veterano de C tan endurecido en su equipo, pero decide obtener un analizador estático y luego seguir ciegamente todas las advertencias dadas, lo más probable es que resulte en un código de menor calidad , ya que podría estar reduciendo la legibilidad e introduciendo errores por accidente. He visto que esto sucede muchas veces, convertir el código al cumplimiento de MISRA no es una tarea trivial.

Hay dos versiones del documento MISRA-C que se pueden aplicar. MISRA-C:2004, que sigue siendo el estándar de facto de la industria integrada actual. O el nuevo MISRA-C:2012 que soporta el estándar C99. Si nunca antes ha usado MISRA-C, le recomiendo que implemente este último.

Sin embargo, tenga en cuenta que los proveedores de herramientas generalmente se refieren a MISRA-C:2004 cuando dicen que tienen verificación de MISRA (a veces incluso se refieren a la versión obsoleta de MISRA-C:1998). Hasta donde yo sé, el soporte de herramientas para MISRA-C:2012 aún es limitado. Creo que solo algunos analizadores estáticos lo han implementado hasta ahora: Klocwork, LDRA, PRQA y Polyspace. Puede ser más, pero definitivamente necesita verificar qué versión de MISRA admite.

Antes de decidir, por supuesto, puede comenzar leyendo el documento MISRA y ver lo que implica. Se puede comprar por £ 10 en misra.org , bastante asequible en comparación con los precios de las normas ISO.

Mathworks (la gente de MATLAB) tiene una herramienta de análisis de código estático llamada Polyspace .

Además del análisis de código estático, lint y similares, sugeriría una definición y un diseño cuidadosos de las interfaces (con un proceso de revisión formal) y un análisis de cobertura de código.

También es posible que desee consultar las pautas para el diseño de códigos críticos para la seguridad, incluido MISRA, pero también los estándares UL1998 e IEC 61508.

No recomiendo acercarse a IEC 61508 a menos que sea necesario. Menciona software, pero carece de fuentes científicas modernas para sus afirmaciones. Ese estándar llegó 30 años demasiado tarde: si se hubiera lanzado en los años 70 como la mayoría de sus llamadas "fuentes", podría haber sido útil.

Para obtener una respuesta completa a esta pregunta, suprimiría la idea de "confiabilidad del código" y, en su lugar, pensaría en la "confiabilidad del diseño", porque el código es solo la expresión final del diseño.

Entonces, comience con los requisitos y escríbalos e inspecciónelos. Si no tiene un documento de requisitos, señale una línea de código aleatoria y pregúntese "¿por qué se necesita esa línea?" La necesidad de cualquier línea de código debe ser finalmente atribuible a un requisito, incluso si es tan simple/obvio como "la fuente de alimentación generará 5 V CC si la entrada está entre 12 y 36 V CC". Una forma de pensar en esto es que si esa línea de código no se puede rastrear hasta un requisito, entonces, ¿cómo sabe que es el código correcto o que es necesario?

A continuación, verifique su diseño. Está bien si está completamente en el código (por ejemplo, en los comentarios), pero eso hace que sea más difícil saber si el código está haciendo lo que realmente se supone. Por ejemplo, el código puede tener una línea que dice output = 3 * setpoint / (4 - (current * 5));¿Es current == 4/5una entrada válida que podría provocar un bloqueo? ¿Qué se debe hacer en este caso para evitar la división por cero? ¿Evita la operación por completo o degrada la salida en su lugar? Tener una nota general en su documento de diseño sobre cómo manejar estos casos extremos hace que sea mucho más fácil verificar el diseño en un nivel superior. Entonces, ahora la inspección del código es más fácil porque se trata de verificar si el código implementa correctamente ese diseño.

Junto con eso, la inspección del código debe verificar si hay errores comunes que su IDE no detecta (usted está usando un IDE, ¿verdad?) ' declaraciones, punto y coma donde no deberían estar, etc.

Mientras escribo esto, se me ocurre que es realmente difícil resumir años de capacitación/experiencia en calidad de software en una sola publicación. Escribo código para dispositivos médicos y lo anterior es un resumen extremadamente simplificado de cómo lo abordamos.

Tengo entendido que la parte del código en un dispositivo médico se prueba casi como si fuera un dispositivo separado. ¿Es eso exacto?
@ScottSeidman Lo más probable es que se pruebe según los requisitos, como se menciona en esta respuesta. Para cada requisito, debe tener un módulo de código, y para cada módulo de código debe tener una prueba. Básicamente, cada requisito tiene una prueba correspondiente y el código es el medio para cumplir con el requisito. Este tipo de seguimiento de requisitos es una práctica común en cualquier sistema de misión crítica, mucho antes de que apareciera la palabra de moda "TDD".
Me refería específicamente a la guía de la FDA, como fda.gov/downloads/RegulatoryInformation/Guidances/ucm126955.pdf El software en realidad requiere más de lo que podría pensar si es parte de un dispositivo médico, comenzando desde la etapa de planificación y los controles de diseño.
Scott, nunca lo había pensado de esa manera, pero tienes razón. Nuestro personal de control de calidad del software verifica el software por separado del resto del sistema (tanto como sea posible) antes de entregarlo a un grupo diferente que es responsable de la verificación y validación del sistema.