Capítulo 9 — Programación II
Mapa de la lección
Lección 09 publicada. Mapa, ejemplo, EX017.
flowchart TD
N0["Resultados de aprendizaje y conexión"]
N1["9.1 Módulos, interfaces e invariantes"]
N2["9.2 Máquinas de estados"]
N3["9.3 Pruebas"]
N4["9.6 Actividad práctica"]
N5["Síntesis del capítulo"]
N0 --> N1 --> N2 --> N3 --> N4 --> N5
N5 --> EX["Ejemplo resuelto"]
EX --> Q["Ejercicio EX017"]
Dibujo lógico
flowchart LR
N0["Resultados de aprendizaje y conexión"]
N1["9.1 Módulos, interfaces e invariantes"]
N2["9.2 Máquinas de estados"]
N3["9.3 Pruebas"]
N4["9.6 Actividad práctica"]
N5["Síntesis del capítulo"]
N0 --> N1 --> N2 --> N3 --> N4 --> N5 --> Q["EX017"]
[Resultados de aprendizaje y conexión]
|
v
[9.1 Módulos, interfaces e invariantes]
|
v
[9.2 Máquinas de estados]
|
v
[9.3 Pruebas]
|
v
[9.6 Actividad práctica]
|
v
[Síntesis del capítulo]
|
v
[Ejemplo resuelto] --> [Ejercicio EX017]
Resultados de aprendizaje y conexión
Al terminar este capítulo, usted podrá (1) estructurar un programa en módulos con interfaces e invariantes claros; (2) implementar máquinas de estados sencillas para la lógica de protocolos y de equipos; (3) escribir pruebas unitarias, incluidas pruebas de límites y de fallas, y medir lo que cubren; y (4) tratar de forma deliberada los errores, el tiempo y la concurrencia. Se apoya en el capítulo 4 y prepara el capítulo 14 (ingeniería de software) y el capítulo 30 (verificación).
9.1 Módulos, interfaces e invariantes
Parnas (1972) sostuvo que un sistema debe descomponerse de modo que cada módulo oculte una decisión de diseño que probablemente cambie. En el software de infraestructura, los cambios probables incluyen el protocolo de campo, el historiador de datos y el esquema de autenticación. Oculte cada uno detrás de una interfaz: un TelemetrySource que entrega lecturas validadas, sea cual sea el protocolo. Cada módulo debe enunciar sus invariantes, propiedades que siempre son ciertas sobre su estado; por ejemplo, «el último contador aceptado de cada estación nunca disminuye». Escriba los invariantes como aserciones en las pruebas y, cuando sea barato, como comprobaciones en tiempo de ejecución que fallen de forma segura.
9.2 Máquinas de estados
Los equipos y los protocolos tienen modos: una bomba está detenida, arrancando, en marcha, deteniéndose o disparada; una sesión está sin autenticar, autenticada o cerrada. Una máquina de estados finitos enumera los estados, los eventos que provocan transiciones, las guardas de las transiciones y las acciones. Escríbala como una tabla e impleméntela a partir de la tabla, rechazando cualquier evento que no figure para el estado actual. Las transiciones no enumeradas son donde viven tanto los errores como los ataques: una orden «stop» que llega mientras la bomba está arrancando, o un «escribir consigna» que llega antes de la autenticación.
El controlador de bombas de LRWA del ejemplo resuelto que sigue está construido exactamente así, de modo que sus pruebas pueden enumerar cada par.
9.3 Pruebas
Una prueba unitaria comprueba un comportamiento de una unidad. Los buenos conjuntos de pruebas incluyen: casos típicos; límites (mínimo, máximo, justo dentro, justo fuera); entradas no válidas; y pruebas de regresión que reproducen errores pasados. La cobertura mide qué sentencias o ramas ejecutaron las pruebas; le dice lo que no se probó, no que el código probado sea correcto. El módulo unittest de Python, incluido en la biblioteca estándar, basta para este libro. Para el controlador de bombas de LRWA, las pruebas de límites incluyen el tiempo de espera de arranque de 30 segundos exactamente a los 30 segundos y justo después, y las pruebas de regresión reproducen cada error de transición encontrado en la puesta en marcha, de modo que una falla corregida no pueda volver discretamente en una versión posterior.
9.4 Errores, tiempo y concurrencia
Decida cómo se trata cada error: rechazado con un motivo, reintentado con un límite o escalado. Nunca silencie excepciones en el código de control o de seguridad. El tiempo es una fuente recurrente de fallas: use relojes monótonos para los intervalos y la hora de reloj sincronizada (con su fuente registrada) para los registros; trate los saltos de reloj. La concurrencia (hilos, tareas asíncronas, varios clientes) crea condiciones de carrera: dos operadores que envían consignas contradictorias, o una comprobación y una acción separadas por una ventana en la que el estado cambia (time-of-check to time-of-use). Prefiera un único propietario para cada elemento de estado, con mensajes para cambiarlo.
9.5 Ejemplo resuelto: una máquina de estados para una bomba (sintético)
El controlador de bombas de LRWA acepta start, stop, trip y reset. La tabla: detenida + start (guarda: permiso verdadero) → arrancando; arrancando + run_confirmed → en marcha; arrancando + tiempo de espera de 30 s → disparada; en marcha + stop → deteniéndose; cualquier estado + trip → disparada; disparada + reset (guarda: operador local) → detenida. La guarda de reset importa: exigir una acción local tras un disparo significa que un atacante remoto que provoca un disparo no puede volver a arrancar de inmediato la bomba en la misma falla. Las pruebas cubren cada transición enumerada y verifican que todo par no enumerado (por ejemplo, disparada + start) se rechaza y se registra.
9.6 Actividad práctica
Implemente la tabla de §9.5 en unas 40 líneas de Python con un diccionario indexado por (state, event). Escriba casos unittest para todas las transiciones enumeradas y un bucle que verifique que cada par (state, event) no enumerado genera un error. Cuente los pares no enumerados. Su bucle debe imprimir el número de pares no enumerados y confirmar que cada uno genera un error y escribe una línea de registro. Después añada una prueba que muestre que se rechaza un reinicio remoto tras un disparo, porque la guarda exige un operador local. Esa prueba protege la propiedad que más importa en el ejemplo resuelto: un atacante que dispara una bomba de forma remota no puede volver a arrancarla directamente en la misma falla. Mantenga la tabla y las pruebas en un solo archivo para que un revisor pueda compararlas.
9.7 Errores comunes y límites
- Implementar la lógica de estados con indicadores dispersos en lugar de una tabla explícita.
- Ramas por defecto que aceptan eventos desconocidos.
- Usar la hora de reloj para los tiempos de espera.
- Pruebas que solo recorren el camino feliz; equiparar cobertura con corrección.
- Tratamiento silencioso de las excepciones.
Cada error anula una propiedad de la máquina de estados de la bomba. Los indicadores dispersos hacen imposible enumerar todas las transiciones. Una rama por defecto dejaría pasar una orden de arranque mientras la bomba está disparada. Un tiempo de espera basado en la hora de reloj podría saltar antes de tiempo o no saltar nunca tras un salto de reloj, y una excepción silenciada en el camino de reinicio dejaría la bomba en un modo desconocido.
Ejercicios — capítulo 9
EX017 — Cuente y pruebe las transiciones no enumeradas. Objetivo: razonar sobre la completitud. Entradas: §9.5 (estados: stopped, starting, running, stopping, tripped; eventos: start, stop, trip, reset, run_confirmed, timeout, stop_confirmed; añada stopping + stop_confirmed → stopped). Pasos: cuente los pares totales, los enumerados y los no enumerados; dé tres pares no enumerados cuyo rechazo tenga una justificación de seguridad funcional o de seguridad. Evidencia esperada: recuentos y justificación. Rúbrica: 3 = recuentos correctos y justificaciones con sentido; 2 = error de recuento; 1 = sin justificación; 0 = nada.
EX018 — Invariante contra repeticiones. Objetivo: expresar y probar un invariante. Entradas: el Receiver del Lab 8. Pasos: escriba el invariante sobre last, una prueba unitaria que envíe los contadores 1, 2, 2, 1, 5 y verifique los resultados de aceptación o rechazo y el estado final, e indique un modo de falla del invariante tras reiniciar el receptor. Evidencia esperada: código de la prueba y enunciado. Rúbrica: 3 = resultados esperados correctos (aceptar, aceptar, rechazar, rechazar, aceptar), problema del reinicio identificado (estado perdido → se aceptan repeticiones salvo que se persista o se roten las claves); 2 = se omite el reinicio; 1 = sin probar; 0 = nada.
Síntesis del capítulo
Descomponga en torno a las decisiones que probablemente cambien y enuncie invariantes. Haga explícitos los modos como máquinas de estados y rechace todo lo que no esté enumerado. Pruebe los límites y las fallas, lea la cobertura como un mapa de lo no probado, trate los errores de forma deliberada, use el reloj adecuado y dé a cada elemento de estado compartido un único propietario.
Siga estudiando — capítulo 09 de 50
← Capítulo 08 · Índice de capítulos del curso · Capítulo 10 →
Continúe con la siguiente lección escrita.