Capítulo 8 — Programação II
Mapa da aula
Aula 08 publicada. Mapa, exemplo, EX015.
flowchart TD
N0["8.1 A entrada é uma fronteira de segurança"]
N1["8.2 Regras de validação"]
N2["8.4 Testes"]
N3["8.6 Atividade prática"]
N4["Síntese do capítulo"]
N0 --> N1 --> N2 --> N3 --> N4
N4 --> EX["Exemplo resolvido"]
EX --> Q["Exercício EX015"]
Desenho lógico
flowchart LR
N0["8.1 A entrada é uma fronteira de segurança"]
N1["8.2 Regras de validação"]
N2["8.4 Testes"]
N3["8.6 Atividade prática"]
N4["Síntese do capítulo"]
N0 --> N1 --> N2 --> N3 --> N4 --> Q["EX015"]
[8.1 A entrada é uma fronteira de segurança]
|
v
[8.2 Regras de validação]
|
v
[8.4 Testes]
|
v
[8.6 Atividade prática]
|
v
[Síntese do capítulo]
|
v
[Exemplo resolvido] --> [Exercício EX015]
Objetivos de aprendizagem e conexão
Depois deste capítulo, você consegue (1) validar entradas não confiáveis com regras explícitas e rejeitar o que não está conforme; (2) tratar erros de modo que um programa falhe de forma segura e visível; (3) escrever testes de unidade, inclusive para casos de limite e malformados; e (4) explicar por que a análise sintática (parsing) é uma fronteira de segurança. O capítulo se apoia nos Capítulos 3 e 7 e prepara o Capítulo 9 (dados) e o Capítulo 27 (verificação).
8.1 A entrada é uma fronteira de segurança
Todo programa que lê dados de fora — um arquivo, uma mensagem de rede, a digitação de um operador — faz premissas sobre eles. Quando as premissas estão erradas, os programas travam, calculam errado ou, pior, fazem algo que um atacante escolheu. Os catálogos de fraquezas de software são dominados por erros de tratamento de entrada: injeção, leituras e escritas fora dos limites, validação de entrada inadequada (CWE Top 25). O padrão defensivo é analisar, não adivinhar: defina exatamente como é uma entrada válida, converta o texto bruto em valores tipados num só lugar e rejeite todo o resto com um erro claro.
8.2 Regras de validação
Para um setpoint enviado a uma estação de bombeamento, uma entrada válida poderia ser: um número; finito (não "NaN" nem infinito); dentro dos limites de engenharia (0,5–4,5 m); mudando não mais que 0,5 m em relação ao valor atual; de uma origem autorizada. Cada regra deve ser explícita, testada e registrada em log quando rejeita. A allowlist (aceitar só as formas sabidamente boas) é mais forte que a denylist (rejeitar as formas sabidamente ruins), porque os atacantes encontram formas em que você não pensou. O float("nan") do Python funciona, e nan < 0.5 e nan > 4.5 são ambos falsos — então uma verificação de faixa escrita como "rejeitar se estiver abaixo ou acima" aceita NaN silenciosamente. A verificação robusta é "aceitar só se math.isfinite(x) e 0.5 <= x <= 4.5".
8.3 Erros e falha segura
Quando a validação falha, o programa precisa fazer algo definido: rejeitar o comando e manter o último valor bom, disparar um alarme, registrar o motivo. Ele não pode continuar com um valor padrão que por acaso seja perigoso (um setpoint de zero) nem engolir o erro em silêncio (except: pass). Em contextos de controle, "seguro" é uma decisão do processo — uma válvula pode precisar falhar fechada para o cloro, mas aberta para a água de resfriamento —, então o programador implementa um estado de falha que o dono do processo especificou, em vez de inventar um.
8.4 Testes
Um teste de unidade confere uma função contra casos conhecidos. Bons conjuntos de testes incluem valores típicos, limites (exatamente 0,5 e 4,5), valores logo fora dos limites (0,49, 4,51), entradas malformadas (string vazia, "abc", "1e309", "nan") e casos dependentes de estado (um valor válido que muda rápido demais). O módulo embutido unittest do Python ou simples instruções assert bastam neste nível. Um teste que nunca falhou nunca mostrou que funciona: rode cada teste novo uma vez contra uma versão deliberadamente quebrada.
Para o validador de setpoint da CW, a versão contra a qual falhar é a v1: o seu teste de "nan" retorna True e o seu teste de "abc" levanta uma exceção, então dois dos sete testes falham antes da correção e nenhum depois dela.
8.5 Exemplo resolvido: o validador de setpoint da CW (sintético)
Este caso é sintético. A interface de operador da Calder Water envia setpoints de nível de reservatório como texto. Versão 1 do validador:
def accept_v1(text, current):
x = float(text)
if x < 0.5 or x > 4.5: return False
if abs(x - current) > 0.5: return False
return True
accept_v1("nan", 3.0) retorna True: as duas comparações com NaN são falsas, e abs(nan - 3.0) > 0.5 também é falso. accept_v1("abc", 3.0) levanta uma exceção que, dependendo de quem chama, pode derrubar a interface. Versão 2:
import math
def accept_v2(text, current):
try:
x = float(text)
except ValueError:
return False
return math.isfinite(x) and 0.5 <= x <= 4.5 and abs(x - current) <= 0.5
A versão 2 declara o que é aceitável, e não o que não é. Testes: ("3.2", 3.0) → True; ("4.5", 4.2) → True; ("4.51", 4.2) → False; ("nan", 3.0) → False; ("inf", 3.0) → False; ("abc", 3.0) → False; ("2.0", 3.0) → False (passo grande demais).
O bloco try transforma uma trava numa rejeição, e math.isfinite remove NaN e infinito antes de qualquer comparação. O que a v2 ainda não faz é dizer por que rejeitou: a interface de operador deveria registrar qual regra falhou.
8.6 Atividade prática
Implemente as duas versões e os sete testes acima. Rode os testes contra a v1 e registre quais falham; depois, contra a v2. Acrescente dois testes seus que você acha que a v2 ainda pode errar (dica: espaços em branco, strings muito longas, vírgula como separador decimal) e decida se o comportamento é aceitável.
Contra a v1, espere que o teste de "nan" falhe (ele retorna True) e que "abc" levante uma exceção em vez de retornar False. Contra a v2, os sete devem passar. Nos seus próprios testes, " 3.2 " com espaços é aceito pelo float do Python, o que provavelmente está bem; "3,2" é rejeitado, o que pode surpreender um operador que usa a vírgula decimal. Decida se essa rejeição deveria produzir uma mensagem clara, em vez de uma recusa silenciosa.
8.7 Erros comuns e limites
- Verificações de faixa que deixam passar NaN.
- Capturar toda exceção e continuar.
- Validação só na interface do usuário, e não onde o comando é executado.
- Testes que só cobrem valores típicos.
- Acreditar que a validação torna um canal autêntico: um comando forjado com aparência válida passa mesmo assim (o Capítulo 17 acrescenta a autenticação).
Na CW, o terceiro erro é o perigoso: se só a interface de operador confere os setpoints, qualquer outro cliente que alcance o PLC por Modbus contorna inteiramente a validação, como mostra o conduíte da zona de campo do Laboratório 4. O quinto erro se liga ao Laboratório 8: um setpoint bem formado de 2,0 m passa pela v2 quer tenha sido enviado por um operador, quer por um intruso.
Exercícios — capítulo 8
EX015 — Quebre a v1. Objetivo: encontrar entradas que derrotam uma verificação por denylist. Entradas: accept_v1. Etapas: encontre três entradas distintas para as quais a v1 dá um resultado inseguro ou indefinido e explique cada uma. Evidência esperada: entradas e explicações. Rubrica: 3 = NaN aceito; texto não numérico (ou uma string vazia) levanta uma exceção não tratada; e um terceiro caso distinto, como um valor atual NaN, que faz a verificação de passo passar para qualquer setpoint dentro da faixa; 2 = dois; 1 = um; 0 = nada.
EX016 — Especifique o estado de falha. Objetivo: ligar o código à segurança do processo. Entradas: um setpoint rejeitado numa bomba de dosagem de cloro e numa válvula de entrada de água bruta. Etapas: para cada um, declare o que o programa deve fazer na rejeição, quem deve decidir isso e o que precisa ser registrado. Evidência esperada: duas especificações curtas. Rubrica: 3 = manter o último valor bom mais alarme, o dono do processo decide, campos de log nomeados (hora, origem, entrada bruta, regra que falhou); 2 = parcial; 1 = genérico; 0 = nada.
Síntese do capítulo
A análise sintática é uma fronteira de segurança. Defina com precisão a entrada válida, aceite só ela, falhe para um estado que o dono do processo escolheu, registre toda rejeição e teste limites e casos malformados. A validação pega entradas malformadas; ela não prova que uma entrada bem formada veio de alguém autorizado.
Continue estudando — capítulo 08 de 40
← Capítulo 07 · Índice de capítulos do curso · Capítulo 09 →
Siga para a próxima lição escrita.