PARTE XLI · Computación, datos e Internet
455
Software y programación: describir comportamientos verificables
Programar no consiste sólo en indicar pasos: consiste en conservar significados y restricciones mientras el sistema cambia
En este capítulo
Un programa puede producir hoy la respuesta esperada y fallar mañana sin que cambie su texto: llega una entrada no prevista, se actualiza una biblioteca, aumenta el volumen o aparece una interacción que nadie había modelado. El código es indispensable, pero no agota el software.
El software comprende instrucciones, datos, configuraciones, interfaces, dependencias y conocimiento operativo que hacen posible un comportamiento en un entorno. Programar es construir una descripción ejecutable de ese comportamiento. La dificultad central no es que la máquina obedezca, sino expresar qué debe conservarse entre casos y cambios.
Este capítulo pregunta: ¿cómo se transforma una necesidad en comportamiento ejecutable y qué evidencias permiten confiar en él mientras evoluciona?
455.1. Lenguajes, abstracciones y traducción#
Un lenguaje de programación define formas válidas y reglas de significado. La sintaxis determina cómo se combinan símbolos; la semántica, qué efecto tienen. Dos fragmentos pueden parecer similares y comportarse distinto por reglas de alcance, evaluación, tipos o errores. El manual de un lenguaje intenta separar esas reglas de detalles accidentales de una implementación.
Los lenguajes ofrecen abstracciones: nombres para valores, tipos, funciones, módulos, objetos o procesos. Una abstracción oculta detalles para razonar con una operación estable. «Ordenar esta colección» evita describir comparaciones y movimientos en cada uso. Ocultar no significa ignorar: cuando rendimiento, seguridad o precisión dependen del detalle, la frontera debe permitir inspeccionarlo.
El código fuente rara vez llega directamente al hardware. Un compilador puede traducirlo a instrucciones; un intérprete puede ejecutar representaciones sucesivas; una máquina virtual puede usar una forma intermedia; un compilador justo a tiempo puede especializar durante la ejecución. Estas categorías se combinan. «Compilado» e «interpretado» no son propiedades absolutas del lenguaje.
La traducción debe preservar el significado especificado, no la forma textual. Una optimización puede eliminar cálculos cuyo resultado no se observa o reordenar operaciones cuando el modelo lo permite. Si el programa depende de un comportamiento no definido por el lenguaje, distintas traducciones pueden divergir legítimamente.
Los tipos clasifican valores y operaciones. Pueden detectarse antes de ejecutar, durante la ejecución o mediante una mezcla. Un sistema de tipos evita ciertas combinaciones, pero no demuestra por sí solo que la política sea correcta, que un cálculo no desborde o que una consulta respete privacidad. La garantía sólo alcanza las propiedades modeladas.
Una abstracción útil reduce estados posibles y hace explícitas las decisiones relevantes. Una mala abstracción esconde costes, mezcla responsabilidades o obliga a romper su interfaz en cada excepción. Su calidad se mide por el razonamiento que permite, no por lo sofisticado del vocabulario.
455.2. Estado, control y funciones#
El estado es la información cuya variación influye en el comportamiento futuro: saldo, posición, sesión, archivo abierto. Una asignación cambia una asociación o un objeto; una entrada externa incorpora información; una salida produce un efecto. Seguir estos cambios es esencial para explicar un programa.
El flujo de control decide qué operación ocurre después. Secuencia, selección, iteración, llamadas, excepciones y concurrencia permiten componer comportamientos. Un bucle expresa repetición bajo una condición; una función agrupa una transformación con entradas y resultados; una excepción separa una ruta anómala de la ordinaria. Ninguna estructura garantiza claridad si sus condiciones son ambiguas.
Una función pura devuelve el mismo resultado para las mismas entradas observables y no altera estado externo. Esto facilita sustitución, pruebas y paralelismo. Los programas reales también necesitan efectos: leer, escribir, comunicar y actuar. La disciplina consiste en localizar esos efectos y declarar sus dependencias, no fingir que no existen.
Los nombres tienen alcance y duración. Una variable local puede desaparecer al terminar una llamada; un objeto compartido persiste; un valor capturado por una función conserva contexto. Confundir identidad con igualdad produce fallos: dos referencias pueden apuntar al mismo objeto, o dos objetos distintos contener valores iguales.
La concurrencia añade varios flujos que progresan sin un orden total predecible. Si comparten estado mutable, una operación que parecía indivisible puede intercalarse. Bloqueos, mensajes, operaciones atómicas e inmutabilidad imponen orden parcial. El objetivo no es «hacerlo simultáneo», sino conservar invariantes bajo todas las intercalaciones permitidas.
Los errores también forman parte del modelo. Una entrada inválida puede rechazarse; un recurso ausente puede reintentarse; una violación interna puede detener el proceso. Capturar toda excepción y continuar oculta estados desconocidos. Una política de error debe distinguir lo esperado, lo recuperable y lo que invalida la operación.
455.3. Modularidad e interfaces#
Un módulo reúne decisiones relacionadas y expone una interfaz: operaciones, datos y garantías que otros componentes pueden usar. La implementación queda detrás. Esta separación permite cambiar estructura interna sin modificar consumidores, siempre que el contrato observable se conserve.
Una interfaz no es sólo una lista de nombres. Incluye precondiciones, resultados, errores, efectos, rendimiento relevante, compatibilidad y reglas de concurrencia. Una función reservar() puede necesitar aclarar si cobra, cuánto dura la reserva y qué ocurre ante repetición. Lo no declarado no desaparece; se convierte en dependencia implícita.
La cohesión busca que un módulo tenga una razón reconocible para cambiar. El acoplamiento describe cuánto sabe de otros. Menor acoplamiento suele facilitar sustitución, pero añadir capas indiscriminadamente crea complejidad propia. Una frontera vale cuando estabiliza una decisión o protege un invariante.
Los invariantes deben pertenecer al componente capaz de imponerlos. Si cualquier consumidor puede alterar directamente una estructura, todos deben recordar sus reglas. Encapsular las operaciones reduce el número de lugares que pueden producir estados inválidos.
La composición introduce propiedades nuevas. Dos módulos correctos pueden fallar juntos si discrepan en unidades, codificación, orden, identidad o reintentos. Una operación individualmente segura puede duplicarse tras una interrupción. Las interfaces necesitan contratos de integración y pruebas en las fronteras.
La compatibilidad tiene dirección. Una versión nueva puede leer datos antiguos sin que la antigua entienda los nuevos; un cambio opcional puede ser seguro para un consumidor y romper otro. Versionar no resuelve por sí solo: hay que declarar qué se mantiene, durante cuánto tiempo y cómo se migra.
455.4. Pruebas, errores y mantenimiento#
Una prueba ejecuta el sistema bajo condiciones elegidas y compara observaciones con un oráculo: resultado esperado, propiedad, referencia o relación entre ejecuciones. Sin oráculo, ejecutar sólo produce actividad. Una prueba que siempre acepta no aporta evidencia.
Las pruebas unitarias aíslan componentes; las de integración examinan contratos entre ellos; las de sistema observan recorridos completos; las de aceptación relacionan el producto con necesidades. Pruebas de propiedades generan casos y comprueban invariantes; pruebas diferenciales comparan implementaciones; análisis estático estudia rutas sin ejecutar. Las categorías responden preguntas distintas.
Una suite verde no prueba ausencia de defectos. Demuestra que, en ese entorno, las observaciones cubiertas coincidieron con sus oráculos. Puede faltar un caso, estar equivocado el oráculo o no reproducirse la producción. La cobertura de líneas indica ejecución, no calidad de afirmaciones.
Un defecto se reproduce con la entrada y estado mínimos posibles; luego se formula la propiedad violada. Corregir sólo el síntoma puede desplazarlo. Una prueba de regresión conserva evidencia del fallo, pero debe comprobar el contrato y no congelar detalles irrelevantes.
El mantenimiento incluye corregir, adaptar, mejorar y prevenir deterioro. Gran parte del coste aparece después de la primera entrega porque cambian usuarios, normas, plataformas y dependencias. El estándar ISO/IEC/IEEE 12207:2026 sitúa desarrollo, operación, soporte y retiro dentro del ciclo de vida, no como fases ajenas al producto.
Observabilidad —registros, métricas y trazas— aporta evidencia durante la operación. Debe permitir relacionar un síntoma con una versión y una solicitud sin exponer secretos o datos personales. Monitorizar no sustituye pruebas; revela condiciones que el laboratorio no anticipó.
455.5. Dependencias, licencias y evolución del software#
Casi todo software incorpora componentes ajenos: bibliotecas, compiladores, servicios, imágenes, datos y herramientas de construcción. Una dependencia reduce trabajo local a cambio de aceptar una interfaz, un ciclo de actualización, fallos y decisiones externas. Incluso una dependencia indirecta puede llegar al producto final.
Fijar versiones favorece reproducibilidad, pero puede retener defectos conocidos. Aceptar siempre la última versión reduce retraso y aumenta variación no revisada. Una política sostenible registra el conjunto resuelto, verifica procedencia, prueba actualizaciones y conserva una ruta de reversión.
Una lista de materiales de software o SBOM identifica componentes y relaciones. Estándares como SPDX permiten intercambiar esa información y expresar licencias de forma legible por personas y máquinas. El inventario ayuda a responder qué está afectado; no demuestra que el componente esté presente en tiempo de ejecución ni que sea vulnerable en ese contexto.
La licencia concede permisos y establece condiciones sobre uso, modificación o distribución. «Código visible» no equivale necesariamente a software libre; «gratuito» no describe derechos. Cuando se combinan componentes, sus obligaciones pueden interactuar. La identificación automática ayuda, pero casos ambiguos requieren revisión competente.
El software evoluciona mediante cambios acumulados. Cada cambio modifica un sistema de contratos: código, datos, configuración, documentación, operación y expectativas. Una migración incompleta puede dejar lectores y escritores en versiones incompatibles. Diseñar expansión y contracción, compatibilidad temporal y retiro evita depender de un instante coordinado imposible.
Retirar también es ingeniería. Hay que localizar consumidores, preservar datos necesarios, eliminar secretos y rutas obsoletas, y observar que el tráfico realmente cesó. El código sin uso aparente puede seguir siendo una dependencia de emergencia; la evidencia precede al borrado.
Síntesis#
Los lenguajes permiten describir comportamientos mediante abstracciones y reglas semánticas. Estado, control y efectos determinan lo que una ejecución puede hacer. Los módulos contienen complejidad mediante contratos, pero la composición crea riesgos propios. Las pruebas aportan evidencia limitada y complementaria; el mantenimiento sostiene el comportamiento ante cambios. Dependencias, licencias y migraciones forman parte del software, no de su periferia.
Preguntas de transferencia#
1. Un programa compila sin advertencias. ¿Qué sabemos?#
Que satisfizo las reglas comprobadas por ese traductor y configuración. No sabemos todavía si resuelve la necesidad, maneja todos los casos o es seguro.
2. ¿Por qué una interfaz con pocos métodos puede seguir siendo compleja?#
Porque puede ocultar estados, efectos, errores, compatibilidad y reglas temporales. La complejidad contractual no se mide contando nombres.
3. Una prueba ejecuta todas las líneas. ¿Demuestra corrección?#
No. Puede no explorar combinaciones relevantes ni comprobar resultados útiles. La cobertura localiza código ejecutado; el oráculo determina qué se afirmó.
4. ¿Qué falta si una SBOM enumera una biblioteca vulnerable?#
Confirmar versión y procedencia, si el componente llega al producto y a la ejecución, si la ruta vulnerable es alcanzable y qué actualización es compatible.

Clave de lectura. El ciclo representa dependencias de razonamiento, no una metodología obligatoria ni fases que deban ejecutarse una sola vez.
Fuentes y lecturas del capítulo#
- ISO, IEC e IEEE. ISO/IEC/IEEE 12207:2026 — Systems and software engineering — Software life cycle processes. 2026.
- IEEE Computer Society. Guide to the Software Engineering Body of Knowledge (SWEBOK Guide). Recurso oficial.
- Python Software Foundation. The Python Language Reference: Execution model. Documentación 3.14, consultada en 2026.
- SPDX Project. SPDX Specifications. SPDX 3.0; ISO/IEC 5962:2021.
- David L. Parnas. On the Criteria To Be Used in Decomposing Systems into Modules. Communications of the ACM, 1972.
- Edsger W. Dijkstra. Notes on Structured Programming. 1970.