◇ OpenCyber Academy
Engenharia · Capítulo 9 · Português

Capítulo 9 — Programação II

Ensino gratuito e aberto para quem deseja proteger pessoas e tecnologias. Não é preciso criar uma conta para estudar ou consultar as respostas.

Capítulo 9 — Programação II

Mapa da aula

Aula 09 publicada. Mapa, exemplo, EX017.

flowchart TD
  N0["9.1 Módulos, interfaces e invariantes"]
  N1["9.2 Máquinas de estados"]
  N2["9.3 Testes"]
  N3["9.6 Atividade prática"]
  N4["Síntese do capítulo"]
  N0 --> N1 --> N2 --> N3 --> N4
  N4 --> EX["Exemplo resolvido"]
  EX --> Q["Exercício EX017"]

Desenho lógico

flowchart LR
  N0["9.1 Módulos, interfaces e invariantes"]
  N1["9.2 Máquinas de estados"]
  N2["9.3 Testes"]
  N3["9.6 Atividade prática"]
  N4["Síntese do capítulo"]
  N0 --> N1 --> N2 --> N3 --> N4 --> Q["EX017"]
[9.1 Módulos, interfaces e invariantes]
    |
    v
[9.2 Máquinas de estados]
    |
    v
[9.3 Testes]
    |
    v
[9.6 Atividade prática]
    |
    v
[Síntese do capítulo]
    |
    v
[Exemplo resolvido] --> [Exercício EX017]

Objetivos de aprendizagem e conexão

Ao fim deste capítulo você será capaz de (1) estruturar um programa em módulos com interfaces e invariantes claros; (2) implementar máquinas de estados simples para a lógica de protocolos e de equipamentos; (3) escrever testes unitários, incluindo testes de limites e de falhas, e medir o que eles cobrem; e (4) tratar erros, tempo e concorrência de forma deliberada. Ele se apoia no Capítulo 4 e prepara o Capítulo 14 (engenharia de software) e o Capítulo 30 (verificação).

9.1 Módulos, interfaces e invariantes

Parnas (1972) defendeu que um sistema deve ser decomposto de modo que cada módulo esconda uma decisão de projeto com probabilidade de mudar. Em software de infraestrutura, as mudanças prováveis incluem o protocolo de campo, o historiador e o esquema de autenticação. Esconda cada uma atrás de uma interface: uma TelemetrySource que entrega leituras validadas, qualquer que seja o protocolo. Cada módulo deve declarar seus invariantes — propriedades sempre verdadeiras sobre seu estado —, por exemplo, "o último contador aceito para cada estação nunca diminui". Escreva os invariantes como asserções nos testes e, onde for barato, como verificações em tempo de execução que falham de modo seguro.

9.2 Máquinas de estados

Equipamentos e protocolos têm modos: uma bomba está parada, partindo, em funcionamento, parando ou desarmada; uma sessão está não autenticada, autenticada ou encerrada. Uma máquina de estados finitos lista os estados, os eventos que causam transições, as guardas das transições e as ações. Escreva-a como uma tabela e implemente-a a partir da tabela, rejeitando qualquer evento não listado para o estado atual. As transições não listadas são onde vivem tanto os bugs quanto os ataques: um comando "stop" chegando durante a partida, ou um "write setpoint" chegando antes da autenticação. O controlador de bombas da LRWA, no exemplo resolvido abaixo, é construído exatamente assim, para que seus testes possam enumerar todos os pares.

9.3 Testes

Um teste unitário verifica um comportamento de uma unidade. Bons conjuntos de testes incluem: casos típicos; limites (mínimo, máximo, logo dentro, logo fora); entradas inválidas; e testes de regressão que reproduzem bugs passados. A cobertura mede quais instruções ou ramos os testes executaram; ela diz o que não foi testado, não que o código testado está correto. O módulo unittest do Python, na biblioteca padrão, basta para este livro. No controlador de bombas da LRWA, os testes de limite incluem o tempo-limite de partida de 30 segundos exatamente em 30 segundos e logo acima disso, e os testes de regressão reproduzem cada bug de transição encontrado no comissionamento, para que uma falha corrigida não possa voltar discretamente numa versão posterior.

9.4 Erros, tempo e concorrência

Decida como cada erro é tratado: rejeitado com um motivo, repetido com um limite ou escalado. Nunca engula exceções em silêncio em código de controle ou de segurança. O tempo é uma fonte recorrente de falhas: use relógios monotônicos para intervalos e a hora de relógio sincronizada (com sua fonte registrada) para os logs; trate os saltos de relógio. A concorrência — threads, tarefas assíncronas, múltiplos clientes — cria condições de corrida: dois operadores emitindo setpoints conflitantes, ou uma verificação e uma ação separadas por uma janela em que o estado muda (time-of-check to time-of-use). Prefira um único dono para cada parte do estado, com mensagens para alterá-la.

9.5 Exemplo resolvido: uma máquina de estados de bomba (sintético)

O controlador de bombas da LRWA aceita start, stop, trip e reset. A tabela: parada + start (guarda: permissão verdadeira) → partindo; partindo + run_confirmed → em funcionamento; partindo + tempo-limite de 30 s → desarmada; em funcionamento + stop → parando; qualquer estado + trip → desarmada; desarmada + reset (guarda: operador local) → parada. A guarda do reset importa: exigir uma ação local após um desarme significa que um atacante remoto que provoque um desarme não consegue religar imediatamente a bomba dentro da mesma falha. Os testes cobrem cada transição listada e afirmam que todo par não listado (por exemplo, desarmada + start) é rejeitado e registrado em log.

9.6 Atividade prática

Implemente a tabela da §9.5 em cerca de 40 linhas de Python, com um dicionário indexado por (state, event). Escreva casos de unittest para todas as transições listadas e um laço que afirme que todo par (state, event) não listado levanta um erro. Conte os pares não listados. Seu laço deve imprimir o número de pares não listados e confirmar que cada um levanta um erro e escreve uma linha de log. Depois acrescente um teste mostrando que um reset remoto após um desarme é recusado, porque a guarda exige um operador local. Esse teste protege a propriedade com que o exemplo resolvido mais se importa: um atacante que desarma uma bomba remotamente não consegue religá-la diretamente dentro da mesma falha. Mantenha a tabela e os testes num único arquivo, para que um revisor possa compará-los.

9.7 Erros comuns e limites

Cada erro derrota uma propriedade da máquina de estados da bomba. Flags espalhadas tornam impossível listar todas as transições. Um ramo padrão deixaria passar um comando de partida com a bomba desarmada. Um tempo-limite baseado na hora de relógio poderia disparar cedo demais, ou nunca, após um salto de relógio, e uma exceção engolida no caminho do reset deixaria a bomba num modo desconhecido.

Exercícios — capítulo 9

EX017 — Conte e teste as transições não listadas. Objetivo: raciocinar sobre completude. Entradas: §9.5 (estados: stopped, starting, running, stopping, tripped; eventos: start, stop, trip, reset, run_confirmed, timeout, stop_confirmed — acrescente stopping + stop_confirmed → stopped). Passos: conte o total de pares, os pares listados e os não listados; dê três pares não listados cuja rejeição tenha uma justificativa de segurança operacional ou de segurança da informação. Evidência esperada: contagens e justificativa. Rubrica: 3 = contagens corretas e justificativas significativas; 2 = erro de contagem; 1 = sem justificativa; 0 = nada.

EX018 — Invariante contra repetição. Objetivo: expressar e testar um invariante. Entradas: o Receiver do Laboratório 8. Passos: escreva o invariante sobre last, um teste unitário que envie os contadores 1, 2, 2, 1, 5 e afirme os resultados de aceitação/rejeição e o estado final, e diga um modo de falha do invariante após um reinício do receptor. Evidência esperada: código do teste e afirmação. Rubrica: 3 = resultados esperados corretos (aceita, aceita, rejeita, rejeita, aceita), problema do reinício identificado (estado perdido → repetições aceitas, a menos que seja persistido ou que as chaves sejam trocadas); 2 = reinício não percebido; 1 = sem teste; 0 = nada.

Síntese do capítulo

Decomponha em torno das decisões com probabilidade de mudar e declare invariantes. Torne os modos explícitos como máquinas de estados e rejeite tudo o que não estiver listado. Teste limites e falhas, leia a cobertura como um mapa do que não foi testado, trate os erros de forma deliberada, use o relógio certo e dê a cada parte do estado compartilhado um único dono.

Continue estudando — capítulo 09 de 50

← Capítulo 08 · Índice de capítulos do curso · Capítulo 10 →

Siga para a próxima lição escrita.