Las 10 reglas de la NASA para el desarrollo de software
(cs.otago.ac.nz)- Análisis crítico de las 10 reglas de la NASA para el desarrollo de software
- Estas reglas están pensadas para sistemas embebidos extremadamente críticos (por ejemplo, software de naves espaciales)
- Pero hace falta debatir si estas reglas también son adecuadas en otros entornos de desarrollo, o si pueden aplicarse a otros lenguajes (distintos de C)
1. Mantener un flujo de control simple (goto, setjmp/longjmp, recursión prohibidos)
- Esta regla prohíbe el manejo de excepciones (
setjmp()/longjmp()) y la recursión. - La recursión no es necesariamente ineficiente. Si se usa de forma adecuada, también puede garantizarse su terminación.
- Forzar la conversión de la recursión a bucles puede generar código difícil de mantener.
Crítica:
- Garantizar la terminación es importante, pero restricciones extremas pueden perjudicar la legibilidad y el mantenimiento.
- Una prohibición incondicional de la recursión tiene muchas probabilidades de introducir complejidad innecesaria.
2. Todos los bucles deben tener un límite superior claro
- El compilador debe poder analizar estáticamente cuántas veces se repetirá el bucle.
- Sin embargo, establecer un límite superior por sí solo no basta para garantizar el tiempo real de ejecución.
- Imponer un límite a la profundidad de recursión puede ser tan seguro como poner un límite superior a un bucle.
Crítica:
- Tener un límite superior por sí solo no garantiza un tiempo de ejecución realista.
- Aunque se fije un límite, si el valor es demasiado grande, en la práctica no es muy distinto de un bucle infinito.
3. Prohibir la asignación dinámica de memoria después de la inicialización
- En sistemas embebidos, la memoria es limitada, así que el objetivo es evitar fallos por falta de memoria.
- Pero una asignación dinámica predecible puede ser más segura que la gestión manual de memoria.
- Por ejemplo, si se usa un recolector de basura en tiempo real (RTGC), la asignación dinámica también puede volverse predecible.
Crítica:
- En vez de prohibir la asignación dinámica en sí, puede ser mejor analizar los patrones de uso de memoria para garantizar la seguridad.
- Si se aprovechan herramientas modernas de análisis estático (como SPlint), es posible detectar por adelantado errores relacionados con memoria dinámica.
4. Limitar el tamaño de las funciones a una hoja A4 (aprox. 60 líneas)
- La lógica es que, si una función es demasiado larga, la legibilidad empeora.
- Pero en entornos de desarrollo modernos existen funciones de plegado de código, así que el tamaño de la unidad lógica importa más que la longitud de la función.
Crítica:
- Más que el tamaño físico (cantidad de líneas), debería tomarse como criterio la complejidad lógica.
- Dividir funciones en partes más pequeñas no debe convertirse en un objetivo en sí mismo → incluso puede dificultar el mantenimiento.
5. Usar al menos dos sentencias assert por función
assertes muy útil para depuración y documentación.- Pero un requisito incondicional de cantidad puede resultar ineficiente.
Crítica:
- Más importante que la cantidad de
assertes dejar claro en qué puntos hace falta validar los datos. - Validar todos los argumentos y entradas externas es más práctico.
6. Minimizar el alcance de los objetos de datos
- Es un buen principio que recomienda usar variables locales.
- Pero no solo debe minimizarse el alcance de las funciones, sino también el de los tipos y las propias funciones.
Crítica:
- En Ada, Pascal, JavaScript y lenguajes funcionales, los tipos y las funciones también pueden declararse localmente → es un enfoque mejor que las reglas de la NASA.
7. Verificación obligatoria de valores de retorno y validez de parámetros
- Los valores de retorno siempre deben revisarse.
- Pero en la práctica es difícil comprobar absolutamente todos los casos.
Crítica:
- Para evitar errores de ejecución, hacen falta tantas comprobaciones como sea posible, pero también hay que considerar los límites prácticos.
- Especialmente en C es importante revisar los valores de retorno, pero en lenguajes modernos (Java, Rust, etc.) puede manejarse de forma más segura aprovechando el sistema de tipos.
8. Restringir el uso del preprocesador (solo se permiten inclusiones de encabezados y macros simples)
- Se prohíben macros complejas, concatenación de tokens y macros variádicas (
...). - Pero las macros variádicas pueden ser útiles como herramienta de depuración.
Crítica:
- Más que restringir el uso del preprocesador, conviene promover un estilo de macros legible.
- Si se bloquea la compilación condicional como
#ifdef, puede volverse difícil escribir código independiente de la plataforma.
9. Restringir el uso de punteros (prohibidos los punteros dobles y los punteros a función)
- Se prohíbe el uso de punteros a función → el objetivo es lograr una estabilidad alta.
- Pero los punteros a función son indispensables para callbacks, el patrón estrategia y los controladores de dispositivos.
Crítica:
- Si se obliga a seleccionar funciones con
switch-caseen lugar de usar punteros a función, la legibilidad del código empeora y el mantenimiento se vuelve más difícil. - En el desarrollo de sistemas operativos, stacks de red y controladores, los punteros a función son indispensables.
- Más que restringir los punteros, una mejor solución es garantizar su uso seguro mediante métodos como punteros inteligentes de C++ o Rust.
10. Configurar al máximo las advertencias del compilador para todo el código y usar herramientas de análisis estático
- Esta regla es una recomendación muy buena.
- Eliminar advertencias del compilador + usar herramientas de análisis estático = mayor estabilidad.
Crítica:
- Otras reglas de la NASA (por ejemplo, prohibir punteros o limitar el tamaño de las funciones) buscan simplemente compensar las limitaciones de las herramientas de análisis estático.
- Pero las herramientas modernas de análisis estático han avanzado muchísimo, así que en vez de imponer restricciones excesivas, resulta más útil aprovechar técnicas de análisis más sofisticadas.
6 comentarios
Parece que todas son reglas comprensibles y necesarias si se ven desde una perspectiva de tiempo real y sistemas embebidos. ¿Un analizador estático podría reemplazar estas reglas?
Por ejemplo, si se permitiera la asignación dinámica, ¿se podría garantizar que la asignación de memoria tendrá éxito en todos los escenarios de uso?
Cuando estudias pruebas de software, siempre hay proposiciones que se mencionan en la primera hora del primer día. Una de ellas es que "las pruebas perfectas son imposibles".
Como lo que más me llama la atención es lo contrario,
parece que son reglas que no van conmigo jaja
Parece que no solo la NASA, sino también industrias como la aeronáutica y la automotriz, donde la vida está directamente en juego, suelen aplicar reglas de programación similares jaja
https://github.com/kubernetes/kubernetes/…
Me recordó al bloque de código de estilo "space shuttle style" del código fuente de Kubernetes, que supuestamente fue escrito siguiendo la forma de redactar código fuente de la aplicación del transbordador espacial de la NASA.
HN Thread relacionado: https://news.ycombinator.com/item?id=18772873
Opinión de Hacker News
222