36 pontos por GN⁺ 2025-02-17 | 6 comentários | Compartilhar no WhatsApp
  • 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 #ifdef pode 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

 
regentag 2025-02-18

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".

 
smboy86 2025-02-18

Acho que, por eu reparar mais nas discordâncias,
parece ser um conjunto de regras que não combina comigo haha

 
rtyu1120 2025-02-17

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

 
ssssut 2025-02-17

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

 
GN⁺ 2025-02-17
Comentários no Hacker News
  • Lendo o texto original, ele explica o objetivo de cada item
  • O original é voltado principalmente para a linguagem C e busca otimizar a verificação mais rigorosa da confiabilidade de aplicações importantes escritas em C
  • O autor original claramente entende muito bem o que está fazendo e descreve vários métodos para verificar código em C
  • Toda a lógica presente no original é perfeitamente compreensível
    • Provavelmente porque aprendi C em sistemas pequenos
    • Aprendi C para hardware de dispositivos médicos implantáveis, e no laboratório também seguíamos diretrizes parecidas
  • O último parágrafo é excelente
    • As regras podem parecer rígidas no começo, mas é preciso considerar casos em que vidas podem depender da correção do código
    • Como o cinto de segurança de um carro, pode parecer incômodo no início, mas com o tempo passa a ser usado naturalmente
  • Minha crítica a essas regras seria bem diferente da do OP
    • Foi difícil levar a sério, desde o começo, uma defesa de setjmp/longjmp
    • Esse padrão é obviamente problemático para qualquer um que já tenha trabalhado com ele
    • O texto afirma que setjmp/longjmp é tratamento de exceções
    • Afirma que tratamento de exceções é algo bom
    • Há um problema sério com a segunda premissa
  • Quer dizer que se deve definir um número máximo de iterações para os loops
    • 10^90 é algo absurdo e irrelevante
    • Parei de ler o texto depois desse ponto
  • Se eu fosse criticar as regras, focaria em pontos como estes
    • O tamanho do corpo da função não tem correlação com a simplicidade de compreensão e pode até ocorrer o oposto do que a regra sugere
    • 2 asserções é um número completamente arbitrário, e tudo o que puder ser afirmado deve ser afirmado
    • Quem usa Ada, Pascal (Delphi), JavaScript ou linguagens funcionais deveria declarar tipos e funções o mais localmente possível
  • Minha abordagem pessoal em JavaScript é não definir funções de forma aninhada, exceto quando quero capturar valores explicitamente
    • Pode ser por causa de um modelo mental antigo
    • Em análises de desempenho, isso aparecia como se a função fosse redefinida a cada chamada
    • Não acho que os interpretadores modernos de JavaScript funcionem assim
    • Provavelmente houve otimizações profundas desde a introdução das arrow functions
    • Velhos hábitos não desaparecem facilmente
    • Funções nomeadas que não capturam variáveis locais eu mantenho no escopo do arquivo/módulo
  • Muitas das outras observações são interessantes e muito minuciosas
    • Num estilo de “o tecnicamente correto é o melhor tipo de correto” que engenheiros antigos adoram
    • Acho muito bom o tom geral de cautela que as regras da NASA querem transmitir e concordo com a maior parte disso
  • Pelo contexto, isso se parece mais com práticas sugeridas do que com “regras”
    • As “regras” formais estão em documentos com nomes como “NPR”
    • Os desenvolvedores não têm obrigação de seguir ou ignorar essas “regras”
  • O GCC pode fornecer, após a compilação, o uso de pilha e a relação entre chamador e chamado
    • setjmp() e longjmp() são uma forma ruim de lidar com exceções
    • O código de limpeza não é executado
    • Seguindo o espírito da regra, não deveria haver recursos que exijam limpeza
  • O principal problema aparece de forma diferente em cada aplicação
    • O que fazer quando o limite de iterações é excedido ou quando os recursos fixos alocados na inicialização não são suficientes
  • Hoje em dia, os programadores leem código na tela, então não está claro por que o tamanho do papel ainda seria relevante
    • Havia repetição sobre página padrão e tamanho de fonte
    • Isso se deve não só às limitações do papel, mas também às limitações humanas
  • A regra sobre recursão existe para garantir um limite estaticamente conhecido para o espaço de pilha necessário
    • A crítica de que isso depende do compilador é válida, mas é uma pré-condição para derivar um limite superior em tempo de execução
    • Em sistemas críticos para a segurança, é necessário ter tempo de resposta garantido
  • O título deveria indicar que se trata de uma crítica às regras
  • Recomenda-se o uso de tipagem estrita
    • Uso de tipagem estrita para todos os tipos escalares
    • Não misturar unidades imperiais com unidades métricas
 
roxie 2025-02-21

> O título deve indicar que se trata de uma crítica às regras

222