As 10 regras da NASA para o desenvolvimento de código crítico à segurança
A NASA tem escrito softwares de missão crítica para exploração espacial há décadas, e agora a organização está transformando essas diretrizes em um padrão de codificação para a indústria de desenvolvimento de software.
O Laboratório de Software Confiável do Laboratório de Propulsão a Jato da NASA (JPL) publicou recentemente um conjunto de diretrizes de código, “The Power of Ten — Rules for Developing Safety Critical Code”. O autor do artigo, o cientista-chefe da JPL Gerard J. Holzmann, explicou que a massa de diretrizes de codificação existentes é inconsistente e cheia de regras arbitrárias, raramente permitindo tarefas agora essenciais, como verificações de conformidade baseadas em ferramentas. As diretrizes existentes, disse ele, inundam codificadores com regras vagas, fazendo com que a qualidade do código até mesmo as aplicações mais críticas sofram.
“Os projetos mais sérios de desenvolvimento de software usam diretrizes de codificação”, escreveu Holzmann. “Essas diretrizes visam afirmar quais são as regras básicas para o software a ser escrito: como ele deve ser estruturado e quais recursos de linguagem devem e não devem ser usados. Curiosamente, há pouco consenso sobre o que é um bom padrão de codificação.”
Holzmann estabeleceu 10 regras rígidas para o desenvolvimento de software com segurança de código em mente. As regras foram especificamente escritas com a linguagem C em mente (uma linguagem que a NASA recomendou para código crítico de segurança devido à sua longa história e amplo suporte a ferramentas), embora as regras possam ser generalizadas para codificação em qualquer linguagem de programação.
1: Restringir todo o código a construções de fluxo de controle muito simples. Não utilize declarações GOTO, construções setjmp ou longjmp ou recursão direta ou indireta.
2: Todos os laços devem ter um limite superior fixo. Deve ser trivialmente possível que uma ferramenta de verificação prove estaticamente que um limite superior predefinido sobre o número de iterações de um loop não pode ser excedido. Se o loop-bound não puder ser provado estaticamente, a regra é considerada violada.
3: Não use a alocação dinâmica de memória após a inicialização.
4: Nenhuma função deve ser maior do que o que pode ser impresso em uma única folha de papel (em um formato de referência padrão com uma linha por declaração e uma linha por declaração.) Normalmente, isso significa não mais do que cerca de 60 linhas de código por função.
5: A densidade de afirmação do código deve ter uma média mínima de duas afirmações por função. As afirmações devem ser sempre livres de efeitos colaterais e devem ser definidas como testes booleanos.
6: Os objetos de dados devem ser declarados no menor nível possível de escopo.
7: Cada função de chamada deve verificar valores de retorno da função não-vazio, e a validade dos parâmetros deve ser verificada dentro de cada função.
8: O uso do pré-processador deve ser limitado à inclusão de arquivos de cabeçalho e definições de macro simples. Colação de tokens, listas de argumentos variáveis (elipses) e chamadas macro recursivas não são permitidas.
9: O uso de ponteiros deve ser restrito. Especificamente, não é permitido mais do que um nível de dereferenciamento. As operações de dereferência do ponteiro não podem estar ocultas em definições macro ou em declarações de digitação interna. Ponteiros de função não são permitidos.
10: Todo o código deve ser compilado, desde o primeiro dia de desenvolvimento, com todos os avisos do compilador ativados na configuração mais pedante do compilador. Todo o código deve ser compilado com esta configuração sem avisos. Todo o código deve ser verificado diariamente com pelo menos um — mas de preferência mais de um — analisador de código-fonte estático de última geração, e deve passar as análises sem avisos.
Holzmann incluiu razões detalhadas para cada uma dessas regras no artigo, mas a essência geral é que, juntas, as regras garantem uma estrutura de fluxo de controle clara e transparente para facilitar a construção, teste e análise de códigos ao longo de padrões amplamente aceitos, mas totalmente desarticulados. A JPL desenvolveu software automatizado para missões espaciais profundas, como o rover Mars Curiosity e a sonda Voyager, e o laboratório já está usando as regras em uma base experimental para escrever softwares de missão crítica.
Holzmann acreditava que o cumprimento das regras da NASA, por mais rigorosas que sejam, pode diminuir a carga sobre os desenvolvedores e levar a uma melhor clareza de código, análise e segurança.
“Se as regras parecem draconianas no início, tenha em mente que elas são destinadas a tornar possível verificar códigos onde muito literalmente sua vida pode depender de sua correção: código que é usado para controlar o avião em que você voa, a usina nuclear a poucos quilômetros de onde você vive, ou a espaçonave que leva astronautas em órbita, “, escreveu.
“As regras agem como o cinto de segurança do seu carro: Inicialmente eles são talvez um pouco desconfortáveis, mas depois de um tempo seu uso se torna de segunda natureza, e não usá-los torna-se inimaginável.”
Aplicando os padrões de codificação da NASA ao JavaScript
As regras da NASA JPL para o desenvolvimento de código crítico de segurança são amplas o suficiente para generalizar a escrita de código em qualquer linguagem de programação, mas um desenvolvedor já conectou os pontos à linguagem de desenvolvimento da Web mais popular lá fora: JavaScript.
O desenvolvedor holandês Denis Radin, também conhecido como Pixels Commander, publicou um post no blog que descreve como cada uma das 10 regras da NASA se relaciona com o JavaScript. Confira um resumo resumido de como os desenvolvedores do JavaScript podem aderir a cada regra e onde o benefício reside em fazê-lo.
1: Restringir todo o código a construções de fluxo de controle muito simples.”
Por que [as] diretrizes da NASA prescrevem para evitar uma técnica simples que estávamos estudando tão cedo quanto na escola? A razão para isso são os analisadores de código estático que a NASA usa para reduzir a chance de erro”, escreveu Radin.
Ele recomendou que os desenvolvedores do JavaScript usassem construções justificadas pela complexidade, usassem analisadores de código para reduzir defeitos, monitorar métricas de codebase e analisar tipos com várias ferramentas.
2: Todos os laços devem ter um limite superior fixo. “
Isso torna a análise estática mais eficaz e ajuda a evitar loops infinitos”, escreveu Radin. “Se [o] limite for excedido, [a] função retorna [um] erro e isso tira [o] sistema do estado de falha. Com certeza, isso é muito valioso para software com 20 anos de atividade!”
3: Não use a alocação dinâmica de memória após a inicialização. “
Vazamentos de memória muitas vezes, desenvolvedores de JavaScript mimados não têm uma cultura de gerenciamento de memória, coletores de lixo diminuem o desempenho quando executados e é difícil de domar”, escreveu Radin.
Com base na regra, ele recomendou que os desenvolvedores do JavaScript gerenciassem variáveis com respeito, observassem vazamentos de memória e trocassem JavaScript para modo de memória estática.
4: Nenhuma função deve ser mais longa do que o que pode ser impresso em uma única folha de papel. “
Isso se encaixa perfeitamente para JavaScript. Código decomposto é melhor entender, verificar e manter”, escreveu Radin.
5: A densidade de afirmação do código deve ter uma média mínima de duas afirmações por função. “
[A] especialidade das afirmações é que elas executam em tempo de execução… [a] prática mais próxima do JavaScript é uma combinação de testes de unidade e verificações de tempo de execução para conformidade do estado do programa com a geração de erros e manipulação de erros”, escreveu Radin.
6: Os objetos de dados devem ser declarados no menor nível de escopo possível.”
Essa regra [tem] intenções simples por trás [dela]: manter os dados em escopo privado e evitar acesso não autorizado. Parece genérico, inteligente e fácil de seguir”, escreveu Radin.
7: Cada função de chamada deve verificar valores de retorno da função não-vazios, e a validade dos parâmetros deve ser verificada dentro de cada função. “
Os autores da diretriz garantem que este é o mais violado”, escreveu Radin. “E isso é fácil de acreditar, porque em sua forma mais rigorosa significa que até mesmo funções incorporadas devem ser verificadas. [Em] minha opinião, faz sentido verificar os resultados das bibliotecas de terceiros sendo devolvidas ao código do aplicativo, e os parâmetros de entrada de função devem ser verificados para a existência e o tipo de conformidade.”
8: O uso do pré-processador deve ser limitado à inclusão de arquivos de cabeçalho e definições de macro simples. “
O uso de pré-processadores deve ser limitado em qualquer idioma”, escreveu Radin. “Eles não são necessários, uma vez que [o JavaScript tem uma] sintaxe padronizada, limpa e confiável para colocar comandos no motor.”
9: O uso de ponteiros deve ser restrito. Especificamente, não é permitido mais do que um nível de dereferenciamento.
Esta é a única regra que Radin admitiu que um desenvolvedor JavaScript não pode tirar nada.
10: Todo o código deve ser compilado, desde o primeiro dia de desenvolvimento, com todos os avisos do compilador ativados na configuração mais pedante do compilador. “
Todos sabemos disso… Não guarde avisos, não adie correções, mantenha o código limpo e [mantenha o] perfeccionista dentro de você vivo”, escreveu Radin.
As explicações completas de Radin sobre como os desenvolvedores do JavaScript podem seguir as dez regras de código críticas de segurança da NASA estão disponíveis no post do Pixels Commander no blog.
Autor: Rob Marvin