As 10 regras da NASA para desenvolvimento de software
(cs.otago.ac.nz)- Uma análise crítica das 10 regras da NASA para desenvolvimento de software
- Essas regras foram feitas para sistemas embarcados extremamente críticos (ex.: software de espaçonaves)
- Mas é preciso discutir se essas regras também são adequadas em outros ambientes de desenvolvimento, ou se podem ser aplicadas a outras linguagens (que não sejam C)
1. Manter um fluxo de controle simples (goto, setjmp/longjmp, recursão proibidos)
- Esta regra proíbe tratamento de exceções (
setjmp()/longjmp()) e recursão. - Recursão não é necessariamente ineficiente. Com métodos adequados, também é possível garantir que a recursão termine.
- Forçar a conversão de recursão em loops pode gerar código difícil de manter.
Crítica:
- Garantir terminação é importante, mas restrições extremas podem prejudicar legibilidade e manutenção.
- Uma proibição incondicional de recursão tem grande chance de introduzir complexidade desnecessária.
2. Todo loop deve ter um limite superior claro
- O compilador deve conseguir analisar estaticamente a contagem de iterações do loop.
- Mas apenas definir um limite superior não garante facilmente o tempo real de execução.
- Impor um limite para a profundidade de recursão pode ser tão seguro quanto impor um limite para loops.
Crítica:
- Apenas impor um limite superior não garante um tempo de execução viável na prática.
- Mesmo com um limite definido, se o valor for grande demais, na prática não será muito diferente de um loop infinito.
3. Proibir alocação dinâmica de memória após a inicialização
- Em sistemas embarcados, a memória é limitada, então o objetivo é evitar falhas causadas por falta de memória.
- Mas uma alocação dinâmica previsível pode ser mais segura do que o gerenciamento manual de memória.
- Por exemplo, com uso de um coletor de lixo em tempo real (RTGC), a alocação dinâmica também pode se tornar previsível.
Crítica:
- Em vez de proibir a alocação dinâmica em si, pode ser melhor analisar os padrões de uso de memória para garantir segurança.
- Com ferramentas modernas de análise estática (como SPlint), é possível detectar antecipadamente erros relacionados à memória dinâmica.
4. Limitar o tamanho da função a uma folha A4 (cerca de 60 linhas)
- A lógica é que funções muito longas reduzem a legibilidade.
- Mas em ambientes modernos de desenvolvimento há recurso de code folding, então o tamanho da unidade lógica importa mais do que o comprimento da função.
Crítica:
- O critério deve ser a complexidade lógica, não o tamanho físico (número de linhas).
- Dividir funções em partes menores não deve se tornar um objetivo em si → isso pode até dificultar a manutenção.
5. Usar pelo menos dois assert por função
asserté extremamente útil para depuração e documentação.- Mas uma exigência rígida de quantidade pode ser ineficiente.
Crítica:
- Mais importante do que a quantidade de
asserté deixar claro onde a validação de dados é necessária. - Validar todos os argumentos e entradas externas é mais prático.
6. Minimizar o escopo dos objetos de dados
- É um bom princípio que incentiva o uso de variáveis locais.
- Mas é preciso minimizar não só o escopo de funções, como também o de tipos e das próprias funções.
Crítica:
- Em Ada, Pascal, JavaScript e linguagens funcionais, tipos e funções também podem ser declarados localmente → uma abordagem melhor do que a regra da NASA.
7. Validar obrigatoriamente valores de retorno e parâmetros de função
- Valores de retorno devem sempre ser verificados.
- Mas verificar todos os casos é algo difícil na prática.
Crítica:
- Para evitar erros de execução, o ideal é ter o máximo possível de verificações, mas é preciso considerar limites práticos.
- Especialmente em C, verificar valores de retorno é importante, mas em linguagens modernas (Java, Rust etc.) é possível tratar isso com mais segurança usando o sistema de tipos.
8. Restringir o uso do pré-processador (permitir apenas inclusão de headers e macros simples)
- Macros complexas, concatenação de tokens e macros variádicas (
...) são proibidas. - Mas macros variádicas podem ser úteis como ferramenta de depuração.
Crítica:
- Em vez de apenas restringir o uso do pré-processador, é preferível incentivar um estilo de macros mais legível.
- Bloquear compilação condicional como
#ifdefpode dificultar a escrita de código independente de plataforma.
9. Restringir o uso de ponteiros (proibir ponteiros duplos e ponteiros para função)
- Proibir o uso de ponteiros para função → o objetivo é maximizar a estabilidade.
- Mas ponteiros para função são essenciais para callbacks, strategy pattern e drivers de dispositivo.
Crítica:
- Se, sem ponteiros para função, a escolha de função for forçada com
switch-case, a legibilidade do código cai e a manutenção fica mais difícil. - Em desenvolvimento de sistemas operacionais, stacks de rede e drivers, ponteiros para função são indispensáveis.
- Mais do que restringir ponteiros, uma solução melhor é garantir seu uso seguro (smart pointers em C++, Rust etc.).
10. Para todo o código, configurar os avisos do compilador no nível máximo e usar ferramentas de análise estática
- Esta é uma recomendação muito boa.
- Eliminar avisos do compilador + usar ferramentas de análise estática = maior confiabilidade.
Crítica:
- Outras regras da NASA (ex.: proibição de ponteiros, limite no tamanho de funções) existem, em parte, para contornar limitações das ferramentas de análise estática.
- Mas como as ferramentas modernas de análise estática evoluíram muito, é mais útil empregar técnicas de análise mais sofisticadas do que impor restrições excessivas.
6 comentários
Vendo tudo pela perspectiva de tempo real e sistemas embarcados, são regras que fazem sentido e são necessárias. Será que um analisador estático consegue cumprir o papel dessas regras?
Por exemplo, se a alocação dinâmica for permitida, é possível garantir que a alocação de memória terá sucesso em todos os cenários de uso?
Quando se estuda teste de software, sempre aparecem proposições mencionadas logo na primeira aula, no primeiro dia. Uma delas é que "teste perfeito é impossível".
Acho que, por eu reparar mais nas discordâncias,
parece ser um conjunto de regras que não combina comigo haha
Parece que não só a NASA, mas também setores como aviação e automotivo, em que a vida está diretamente em jogo, costumam aplicar regras de codificação parecidas haha
https://github.com/kubernetes/kubernetes/…
Isso me fez lembrar do bloco de código em “space shuttle style” no código-fonte do Kubernetes, que dizem ter sido escrito seguindo o método de escrita de código-fonte usado nos aplicativos do ônibus espacial da NASA.
Thread relacionada no HN: https://news.ycombinator.com/item?id=18772873
Comentários no Hacker News
setjmp/longjmpsetjmp/longjmpé tratamento de exceçõessetjmp()elongjmp()são uma forma ruim de lidar com exceções> O título deve indicar que se trata de uma crítica às regras
222