Las 10 reglas esenciales de codificación de NASA para software crítico de seguridad
Proyectos de software grandes y complejos usan varios estándares y guías de codificación. Estas guías establecen las reglas básicas que deben seguirse al escribir software. Usualmente, determinan:
a) ¿Cómo debería estructurarse el código?
b) ¿Qué características del lenguaje deberían o no usarse?
Para ser efectivas, el conjunto de reglas tiene que ser pequeño y lo suficientemente específico para que se pueda entender y recordar fácilmente.
Los mejores programadores del mundo trabajando en NASA siguen un conjunto de guías para desarrollar código crítico de seguridad. De hecho, muchas agencias, incluyendo el Laboratorio de Propulsión a Chorro (JPL) de NASA, se centran en código escrito en lenguaje de programación C. Esto es porque hay amplio soporte de herramientas para este lenguaje, como extractores de modelos lógicos, depuradores, compilador estable, analizadores de código fuente fuertes y herramientas de métricas.
En casos críticos, se vuelve necesario aplicar estas reglas, especialmente donde la vida humana puede depender de su corrección y eficiencia. Por ejemplo, programas de software usados para controlar aviones, naves espaciales o plantas de energía nuclear.
¿Pero sabes qué estándares usan las agencias espaciales para operar sus máquinas? A continuación, listamos las 10 reglas de codificación de NASA establecidas por el científico principal de JPL, Gerard J. Holzmann. Todas se centran principalmente en parámetros de seguridad, y podrías aplicarlas a otros lenguajes de programación también.
Regla No. 1 – Flujo de control simple
Escribe programas con constructos de flujo de control muy simples – No uses constructos setjmp o longjmp, declaraciones goto, y recursión directa o indirecta.
Razón: Un flujo de control simple resulta en claridad de código mejorada y capacidades más fuertes para verificación. Sin recursión, no habrá gráfico de llamadas de funciones cíclico. Así, todas las ejecuciones que se supone que son acotadas permanecen realmente acotadas.
Regla No. 2 – Límite superior fijo para bucles
Todos los bucles deben tener un límite superior fijo. Debe ser posible para una herramienta de verificación probar estáticamente que un límite superior preestablecido en el número de iteraciones de un bucle no puede ser excedido.
La regla se considera violada si el límite del bucle no puede probarse estáticamente.
Razón: La presencia de límites de bucle y la ausencia de recursión previenen código descontrolado. Sin embargo, la regla no aplica a iteraciones que están destinadas a ser no terminantes (por ejemplo, planificador de procesos). En tales casos, se aplica la regla inversa – Debe ser probables estáticamente que la iteración no puede terminar.
Regla No. 3 – Sin asignación dinámica de memoria
No uses asignación dinámica de memoria después de la inicialización.
Razón: Asignadores de memoria como malloc, y recolectores de basura a menudo tienen comportamiento impredecible que puede impactar excepcionalmente el rendimiento. Además, errores de memoria también pueden ocurrir por error del programador, que incluye:
- Intentar asignar más memoria que la físicamente disponible
- Olvidar liberar memoria
- Continuar usando memoria después de que fue liberada
- Exceder límites en memoria asignada
Forzar a todos los módulos a vivir dentro de un área de almacenamiento fija, pre-asignada puede eliminar estos problemas y hacer más fácil verificar el uso de memoria.
Una forma de reclamar memoria dinámicamente en ausencia de asignación de memoria del heap es usar memoria de pila (stack).
Regla No. 4 – Sin funciones grandes
Ninguna función debería ser más larga de lo que podría imprimirse en una sola hoja de papel en formato de referencia estándar con una línea por declaración y una línea por sentencia. Esto significa que una función no debería tener más de 60 líneas de código.
Razón: Funciones excesivamente largas a menudo son señal de mala estructura. Cada función debería ser una unidad lógica que sea entendible así como verificable. Es bastante más difícil entender una unidad lógica que abarca múltiples pantallas en una pantalla de computadora.
Regla No. 5 – Baja densidad de aserciones
La densidad de aserciones del programa debería promediar un mínimo de dos aserciones por función. Las aserciones se usan para verificar condiciones anormales que nunca deberían ocurrir en ejecuciones reales. Deberían definirse como tests booleanos. Cuando una aserción falla, se debe tomar una acción de recuperación explícita.
Si una herramienta de verificación estática prueba que una aserción nunca puede fallar o nunca mantenerse, la regla se considera violada.
Razón: Según estadísticas de esfuerzo de codificación de la industria, pruebas unitarias capturan al menos un defecto por 10 a 100 líneas de código. Las posibilidades de interceptar defectos aumentan con la densidad de aserciones.
El uso de aserciones también es importante ya que son parte de una fuerte estrategia de codificación defensiva. Se usan para verificar pre y post condiciones de una función, parámetro, y valor de retorno de una función e invariantes de bucle. Las aserciones pueden desactivarse selectivamente después de probar el código crítico de rendimiento.
Regla No. 6 – Declarar objetos de datos en el nivel más pequeño de alcance
Esta apoya el principio básico de ocultación de datos. Todos los objetos de datos deben declararse en el nivel más pequeño posible de alcance.
Razón: Si un objeto no está en alcance, su valor no puede ser referenciado ni corrompido. Esta regla desalienta la reutilización de variables para múltiples propósitos incompatibles que pueden complicar el diagnóstico de fallos.
Regla No. 7 – Verificar parámetros y valor de retorno
El valor(es) de retorno de funciones no-void deberían ser verificados por cada función que llama, y la validez de parámetros debería verificarse dentro de cada función.
En su forma más estricta, esta regla significa que incluso el valor de retorno de declaraciones printf y archivo close deberían verificarse.
Razón: Si la respuesta a un error es correctamente no diferente de la respuesta al éxito, se debería verificar explícitamente un valor de retorno. Este es usualmente el caso con llamadas a close y printf. Es aceptable castear explícitamente el valor de retorno de la función a void – indicando que el programador decide explícitamente (no accidentalmente) ignorar un valor de retorno.
Regla No. 8 – Uso limitado del preprocesador
El uso del preprocesador debería limitarse a la inclusión de archivos de cabecera y definiciones de macros. Llamadas recursivas de macro, pegado de tokens, y listas de argumentos variables no están permitidas.
Debería haber justificación para más de una o dos directivas de compilación condicional incluso en grandes esfuerzos de desarrollo de aplicaciones, más allá del boilerplate estándar, que evita inclusión múltiple del mismo archivo de cabecera. Cada tal uso debe ser marcado por un verificador basado en herramientas y justificado en el código.
Razón: El preprocesador de C es una herramienta poderosa y ambigua que puede destruir la claridad del código y confundir a muchos verificadores basados en texto. El efecto de constructos en código de preprocesador sin límites podría ser excepcionalmente difícil de descifrar, incluso con una definición de lenguaje formal en mano.
La precaución contra compilación condicional es igualmente importante – con solo 10 directivas de compilación condicional, podría haber 1024 versiones posibles (2^10) del código, lo que aumentaría el esfuerzo de prueba requerido.
Regla No. 9 – Uso limitado de punteros
El uso de punteros debe restringirse. No más de un nivel de desreferenciación está permitido. Operaciones de desreferencia de punteros no deberían ocultarse dentro de declaraciones typedef o definiciones de macro.
Punteros a funciones tampoco están permitidos.
Razón: Los punteros se usan mal fácilmente, incluso por expertos. Hacen difícil seguir o analizar el flujo de datos en un programa, especialmente por analizadores estáticos basados en herramientas.
Los punteros a funciones también restringen el tipo de verificaciones realizadas por analizadores estáticos. Así, solo deberían usarse si hay una justificación fuerte para su implementación. Si se usan punteros a funciones, se vuelve casi imposible para una herramienta probar la ausencia de recursión, así que métodos alternativos deberían proveerse para compensar esta pérdida en capacidades analíticas.
Regla No. 10 – Compilar todo el código
Todo el código debe compilarse desde el primer día de desarrollo. Las advertencias del compilador deben habilitarse en la configuración más meticulosa del compilador. El código debe compilar con estos ajustes sin ninguna advertencia.
Todo el código debería verificarse diariamente con al menos una (preferiblemente más de una) analizador estático de código fuente de vanguardia y debería pasar el proceso de análisis con cero advertencias.
Razón: Hay muchos analizadores de código fuente efectivos disponibles en el mercado; unos pocos son herramientas freeware. No hay absolutamente ninguna excusa para que ningún programador no haga uso de esta tecnología fácilmente disponible. Si el compilador o analizador estático se confunde, el código causando la confusión/error debería reescribirse para que se vuelva trivialmente válido.
¿Qué dice NASA sobre estas reglas?
Tecnología Industrial
- Temperaturas Críticas de Superconductores Elementales y de Alta Temperatura (Kelvin)
- Reglas esenciales de derivadas: Referencia rápida
- Reglas fundamentales de antiderivadas: Constantes, sumas y diferencias
- BigStitcher: Imagen 3D de tejidos de precisión simplificada
- Construyendo un Futuro Más Seguro: Avanzando Soluciones Inteligentes de Seguridad Alimentaria
- Por qué modernizar su flota reduce costos e impulsa la sostenibilidad
- Más allá de la programación: Habilidades clave para proyectos de automatización exitosos
- Domine el Cumplimiento UID: 3 Reglas Esenciales que Todo Proveedor del DoD Debe Seguir
- 4 estrategias probadas para crear listas de verificación de mantenimiento eficaces
- Desarrolle protocolos de seguridad integrales para trabajadores y técnicos
- Las 5 Mejores Prácticas de Seguridad Comprobadas para Operar Maquinaria Industrial