Be Software Architecture Wise
Be Wise Apps

Read in English · Lire en français · Auf Deutsch lesen

Sección 3.3 de 3.8 · Intermedio · 24 min de lectura

Ocultación de información, abstracción e interfaces

Videolección

5 min · narrado en inglés, con subtítulos
Solo escuchar (audio)
Leer la transcripción

Todo buen módulo guarda un secreto. En esta lección aprenderás qué significa de verdad la ocultación de información, cómo funcionan las interfaces como contratos, por qué las abstracciones tienen fugas y cómo mantener estables las interfaces entre componentes.

Cuando envías un paquete por correo, lo entregas en el mostrador y recibes un número de seguimiento. No sabes, ni te importa, si viaja en furgoneta, tren o avión, ni por qué centro de distribución pasa. El servicio postal puede cambiar todo eso mañana, porque el trato en el mostrador sigue siendo el mismo. Los buenos módulos funcionan como ese mostrador. Los problemas empiezan cuando los clientes dependen de detalles que nadie prometió, como que el paquete siempre llegue antes de las diez.

La ocultación de información es el principio que propuso David Parnas en 1972. Cada módulo oculta una decisión de diseño que es difícil o que probablemente cambie, detrás de una interfaz que revela lo menos posible. Buenos candidatos para ocultar son las estructuras de datos, el orden de las operaciones, los formatos de entrada y salida, los algoritmos y, hoy en día, qué proveedor o qué base de datos usas. Lo que está oculto puede cambiar a bajo costo. Lo que está expuesto se vuelve costoso de cambiar, porque otros dependen de ello.

No lo confundas con el encapsulamiento. El encapsulamiento es un mecanismo del lenguaje, como los campos privados. La ocultación de información es la decisión de diseño sobre qué mantener en secreto. Puedes encapsular a la perfección y aun así no ocultar nada. Una clase con campos privados, y un getter y un setter públicos para cada uno, les dice a todos los que la llaman qué campos tiene. Si los cambias, todos los llamantes se rompen. Una interfaz es un contrato, y el diseño por contrato de Bertrand Meyer lo hace preciso. Las precondiciones son lo que quien llama debe hacer verdadero. Las postcondiciones son lo que la operación garantiza a continuación. Los invariantes se cumplen entre cada operación. El contrato también asigna la culpa. Si se incumple una precondición, el error está en quien llama. Si se incumple una postcondición, está en el proveedor.

John Ousterhout ofrece una imagen útil. Imagina un módulo como un rectángulo. El borde superior es la interfaz que todos deben aprender. El área es la funcionalidad que proporciona. Un módulo profundo tiene una interfaz pequeña sobre mucha funcionalidad. El manejo de archivos de Unix es su ejemplo favorito: open, read, write, seek y close ocultan la caché, los dispositivos, los permisos y la disposición en disco. Un módulo superficial tiene una interfaz casi tan compleja como lo que hace, como una clase envoltorio cuyos métodos se limitan a pasar cada uno una llamada. Añade costo sin ocultar gran cosa. También es una prueba valiosa para los servicios. Un servicio cuya API se limita a reflejar las tablas de su base de datos es superficial, y traslada toda la lógica real a sus clientes.

En 2002, Joel Spolsky escribió que todas las abstracciones no triviales tienen fugas en algún grado: sus detalles ocultos te siguen afectando. Una biblioteca de mapeo de bases de datos oculta SQL, hasta que un bucle lanza sin hacer ruido una consulta por fila. Así que sé honesto sobre las fugas. Una llamada remota puede agotar el tiempo de espera, o tener éxito mientras se pierde la respuesta. Una buena interfaz remota acepta un timeout y distingue una solicitud rechazada de un resultado desconocido. La ley de Hyrum añade una advertencia. Con suficientes usuarios de una API, alguien dependerá de cada comportamiento observable: el orden de los resultados, la redacción de un error, incluso cuánto tarda una llamada. Por eso las API estables mantienen una superficie mínima, cambian añadiendo en lugar de modificando, versionan explícitamente los cambios incompatibles y usan pruebas de contrato para detectar accidentes.

Veamos ahora un ejemplo práctico. Una empresa de reparto de comida llama directamente al código del Proveedor de pagos A en once lugares. Finanzas quiere añadir el Proveedor B, y poder cambiar en un día si uno sufre una caída. El diseño introduce una interfaz de pasarela de pagos que pertenece a la tienda, en los términos propios de la tienda: autorizar, capturar y reembolsar. El proceso de pago y los reembolsos dependen solo de ella. Cada proveedor recibe un adaptador que implementa la interfaz y traduce a la API y a los códigos de error de ese proveedor. Solo los adaptadores saben que existen los proveedores. La configuración elige el adaptador de cada país al arrancar. La decisión oculta es qué proveedor, y cómo se le llama.

La interfaz oculta qué proveedor se usa, su versión, sus códigos de error, sus reglas de reintento y sus identificadores. Sus precondiciones son un importe positivo en unidades menores, una divisa admitida y una clave de idempotencia única para cada intento. Promete uno de tres resultados. Aprobado significa que los fondos están reservados. Rechazado significa que no se ha movido dinero. Desconocido significa que no se pudo determinar el resultado, así que la llamada puede reintentarse con la misma clave. La fuga de la red forma parte del contrato, no es una sorpresa. La recompensa: antes, añadir un proveedor suponía tocar once puntos de llamada y la tabla de pedidos. Ahora es un adaptador, sus pruebas y una entrada de configuración.

La sección completa muestra el diseño por contrato en código, y ocho prácticas para API estables. La ocultación de información te dice qué guarda para sí cada módulo, pero no en qué dirección deben apuntar las dependencias. Eso es la Sección tres punto cuatro. Nos vemos allí.

Lo que necesitas saber

El principio: ocultar lo que probablemente cambie

En la Sección 3.1, el ejemplo KWIC de David Parnas mostró que descomponer un programa por pasos de procesamiento repartía cada decisión de diseño entre varios módulos. Su alternativa, la ocultación de información, da a cada módulo una decisión que mantener en secreto: cómo se almacenan las líneas, cómo se calculan los desplazamientos, cuándo se ordena. La interfaz del módulo se elige para revelar lo menos posible sobre esa decisión. Todo lo demás del sistema depende solo de la interfaz, así que la decisión puede cambiar sin que el resto lo note.

La lista de Parnas de lo que merece la pena ocultar sigue siendo una buena lista de comprobación medio siglo después: las estructuras de datos y cómo se almacenan; el orden en que se realizan las operaciones; los detalles del hardware, los dispositivos y los sistemas operativos; los formatos de entrada y salida; y los algoritmos que podrían sustituirse por otros mejores. Los añadidos modernos siguen la misma lógica: qué proveedor externo se usa, qué base de datos, qué intermediario de mensajes (message broker), qué versión de una API externa y qué reglas de negocio están en revisión.

El principio se aplica de forma idéntica a cualquier escala. Una clase oculta sus campos; un paquete oculta sus módulos internos; un servicio oculta su base de datos, su lenguaje y su despliegue; la API pública de una organización oculta qué servicios existen detrás de ella. En todos los casos, lo que está oculto puede cambiar a bajo costo, y lo que está expuesto se vuelve costoso de cambiar porque otros dependen de ello.

El encapsulamiento no es ocultación de información

Los dos términos se usan a menudo como sinónimos, pero significan cosas distintas. El encapsulamiento es un mecanismo: agrupar los datos con las operaciones que actúan sobre ellos y restringir el acceso directo, mediante campos privados, límites de módulo o modificadores de acceso (Sección 0.3). La ocultación de información es un principio de diseño: decidir qué decisiones mantener en secreto y diseñar la interfaz para que no las revele. El encapsulamiento es una de las herramientas que usas para ocultar información; no decide por ti qué ocultar.

Es fácil encapsular a la perfección y aun así no ocultar nada. Una clase con campos privados y un getter y un setter públicos para cada uno de ellos está totalmente encapsulada, pero todos los que la llaman saben exactamente qué campos existen y de qué tipo son. Si cambias la representación interna, todos los llamantes se rompen. También ocurre lo contrario: Python no impone ninguna privacidad, pero un módulo de Python bien diseñado puede ocultar sus decisiones con mucha eficacia mediante un conjunto de funciones elegido con cuidado.

# Encapsulated, but the representation leaks through the interface
class Price:
    def __init__(self, pounds: float):
        self._pounds = pounds
    def get_pounds(self) -> float:          # callers now depend on 'float pounds'
        return self._pounds
    def set_pounds(self, value: float):
        self._pounds = value

# Information hiding: callers ask for what they need, not for the field
class Money:
    def __init__(self, minor_units: int, currency: str):
        self._minor, self._currency = minor_units, currency
    def add(self, other: "Money") -> "Money": ...
    def allocate(self, ratios: list[int]) -> list["Money"]: ...
    def format(self, locale: str) -> str: ...
# Switching from pence to a decimal type later changes only this class.
Ambas clases están encapsuladas; solo la segunda oculta la decisión sobre cómo se almacena un precio.

La abstracción y las interfaces como contratos

Una abstracción es una vista simplificada de algo que conserva lo que importa para un propósito y deja fuera el resto. Un archivo es una abstracción sobre bloques de disco, cachés y controladores; un pago es una abstracción sobre una red de tarjetas, la API de un proveedor y el proceso de liquidación de un banco. La abstracción y la ocultación de información son dos caras del mismo acto: elegir una abstracción decide lo que muestra la interfaz; ocultar el resto hace que sea seguro confiar en la abstracción.

Por tanto, una interfaz es un contrato entre un módulo y sus usuarios. Su parte formal es lo que puede ver un comprobador de tipos o un esquema de API: nombres de operaciones, tipos de parámetros y de retorno, tipos de error, nombres de campos en un cuerpo JSON. Su parte informal es igual de vinculante: qué hace la operación, sus efectos secundarios, si puede reintentarse con seguridad, cuánto tarda, qué errores son posibles y qué significan. John Ousterhout insiste en que la parte informal solo puede recogerse en la documentación, y en que, si los usuarios tienen que leer el código de un módulo para aprender a usarlo, el módulo no ofrece una abstracción real.

Diseño por contrato

El diseño por contrato, desarrollado por Bertrand Meyer en la década de 1980 con el lenguaje Eiffel y descrito en su libro Object-Oriented Software Construction y en su artículo de 1992 Applying 'Design by Contract', hace preciso el contrato. Cada operación tiene una precondición, que quien llama debe hacer verdadera antes de llamar; una postcondición, que la operación garantiza al terminar si se cumplía la precondición; y cada clase o módulo tiene invariantes, condiciones que se cumplen entre cada par de operaciones. El contrato asigna la responsabilidad: si se incumple una precondición, el error está en quien llama; si se incumple una postcondición, está en el proveedor.

Meyer argumentó en contra del hábito opuesto, la programación defensiva, en la que cada rutina lo comprueba todo por si alguien la llama mal. Las comprobaciones redundantes añaden código, complejidad y nuevas vías de fallo. En cambio, cada condición debe ser responsabilidad clara de una de las partes. Una rutina con una precondición exigente es más sencilla; una rutina con una precondición tolerante es más fácil de llamar; la elección es una decisión de diseño que se toma de forma explícita. En los límites de un sistema, donde la entrada procede de usuarios no fiables o de otras organizaciones, la validación no es opcional: ahí el contrato dice que el propio módulo del límite lo comprueba todo (Capítulo 8).

class Account:
    """Invariant: balance_pence >= -overdraft_limit_pence."""

    def withdraw(self, amount_pence: int) -> None:
        # Preconditions (the caller's obligations)
        if amount_pence <= 0:
            raise ValueError("amount must be positive")
        if self.balance_pence - amount_pence < -self.overdraft_limit_pence:
            raise InsufficientFunds(self.id)
        old = self.balance_pence
        self.balance_pence -= amount_pence
        # Postcondition (the supplier's guarantee)
        assert self.balance_pence == old - amount_pence
        assert self.balance_pence >= -self.overdraft_limit_pence  # invariant
Un contrato por escrito: precondiciones comprobadas a la entrada, y la postcondición y el invariante enunciados y verificados con aserciones.

Las mismas tres ideas se aplican a escala de servicios. Una especificación de API, como un documento OpenAPI, enuncia las precondiciones (campos obligatorios, formatos, autenticación), las postcondiciones (la respuesta y el cambio de estado, como existe un pedido con estado PLACED) y los invariantes en los que puede confiar un cliente (el total de un pedido nunca cambia después del pago). Las pruebas de contrato dirigidas por el consumidor, en las que cada cliente publica las expectativas de las que depende y el proveedor las ejecuta en su compilación, comprueban esos contratos automáticamente.

Abstracciones con fugas

En noviembre de 2002, Joel Spolsky publicó The Law of Leaky Abstractions, que afirma que Todas las abstracciones no triviales tienen, en algún grado, fugas. Una abstracción con fugas (leaky abstraction) es aquella cuyos detalles ocultos siguen afectando a sus usuarios. TCP presenta un flujo fiable sobre una red no fiable, hasta que se corta un cable y ya no puede. SQL te permite decir lo que quieres, no cómo conseguirlo, hasta que dos consultas lógicamente equivalentes difieren en velocidad por un factor de mil y tienes que leer el plan de consulta. Un mapeador objeto-relacional oculta SQL hasta que un bucle lanza sin hacer ruido una consulta por fila.

La consecuencia para los arquitectos no es que las abstracciones sean malas, sino que una interfaz debe ser honesta sobre los detalles que se van a filtrar. El ejemplo clásico es la llamada remota que parece exactamente una llamada a una función local. Las llamadas de red son órdenes de magnitud más lentas, pueden agotar el tiempo de espera, pueden tener éxito en el servidor mientras se pierde la respuesta y pueden fallar con independencia de quien llama; la Sección 6.1 enumera estas cuestiones como las falacias de la computación distribuida. Una buena interfaz remota lo deja ver: acepta un timeout, devuelve errores que distinguen rechazado de resultado desconocido y acepta una clave de idempotencia para que los reintentos sean seguros. Ocultar la red por completo es la fuga que está esperando a ocurrir.

Módulos profundos y superficiales

A Philosophy of Software Design, de John Ousterhout (publicado por primera vez en 2018; segunda edición en 2021), ofrece una forma útil de juzgar una interfaz. Imagina cada módulo como un rectángulo: el borde superior es su interfaz, que cada usuario tiene que aprender, y el área es la funcionalidad que proporciona. Un módulo profundo tiene una interfaz pequeña sobre mucha funcionalidad, así que compensa muchas veces el costo de aprenderla. Un módulo superficial tiene una interfaz casi tan compleja como lo que hace, así que añade costo sin ocultar gran cosa.

Su ejemplo favorito de profundidad es la entrada y salida de archivos de Unix: unas pocas llamadas (open, read, write, seek, close) ocultan los sistemas de archivos, la disposición en disco, la caché, los permisos, los dispositivos y el acceso concurrente. Su ejemplo de superficialidad es una clase cuyos métodos se limitan a reenviar cada uno una llamada a otra clase, o una clase de lista enlazada cuya interfaz expone casi todo lo que hace. Ousterhout sostiene que el consejo habitual de mantener las clases y los métodos muy pequeños puede producir lo que él llama clasitis: muchas clases superficiales cuyas interfaces suman en conjunto más complejidad de la que tendría un módulo más profundo.

Módulos profundos y superficiales

Un módulo profundo tiene una interfaz estrecha sobre una implementación grande; uno superficial, una interfaz ancha sobre poca implementaciónMódulo profundointerfaz pequeñagran implementación ocultabúfer, caché, reintentos,controladores, permisos,disco, concurrenciap. ej. open, read, write, seek, closegran beneficio por el costo de aprenderlaMód. superficialinterfaz grandepoca funcionalidadp. ej. clase envoltorio cuyosmétodos pasan una llamadadirectamentecuesta casi tanto aprenderla como ahorra
El esquema de Ousterhout: la anchura del borde superior es la interfaz que deben aprender los usuarios; el área inferior es la funcionalidad que oculta.

No todo el mundo está de acuerdo con lo lejos que lleva Ousterhout esta idea. Los defensores de las clases y funciones pequeñas, como Robert C. Martin en Clean Code, sostienen que las unidades diminutas son más fáciles de nombrar, probar y reutilizar; Ousterhout y Martin debatieron la cuestión en un intercambio por escrito publicado en 2025. Ambas partes coinciden en el objetivo de fondo, que es reducir la complejidad que un lector tiene que tener en mente. A escala de arquitectura, la prueba de la profundidad es especialmente valiosa: un servicio cuya API refleja una a una las tablas de su base de datos, con crear, leer, actualizar y eliminar para cada una, es superficial y traslada toda la lógica real a sus clientes.

La ley de Hyrum: todo lo observable se convierte en el contrato

La ley de Hyrum, que lleva el nombre de Hyrum Wright, entonces ingeniero de software en Google, por iniciativa de su colega Titus Winters, y que se popularizó en Software Engineering at Google (2020), observa que, con un número suficiente de usuarios de una API, no importa lo que prometas en el contrato: alguien dependerá de todos los comportamientos observables de tu sistema. El orden en que una función devuelve los elementos de un conjunto no ordenado, la redacción exacta de un mensaje de error, un campo que siempre está presente aunque esté documentado como opcional, el hecho de que una llamada tarde 50 milisegundos: si los usuarios pueden verlo, algunos dependerán de ello.

La ley explica por qué la ocultación de información tiene que diseñarse, no esperarse. Cada comportamiento que expones por accidente pasa a formar parte del contrato de facto, y cambiarlo romperá algo a alguien, por muy claro que la documentación dijera que no estaba especificado. Algunos equipos aleatorizan a propósito el comportamiento no especificado, como el orden de iteración, para que nadie llegue a depender de él; Go aleatoriza el orden de iteración de los mapas por este motivo. Otros mantienen ligeras las cargas útiles de las respuestas, para que haya menos de lo que depender.

Diseñar API estables entre componentes

Entre componentes y servicios, las interfaces son lo más costoso de cambiar, porque cambiarlas exige coordinarse con cada consumidor. Las prácticas siguientes se derivan de todo lo anterior; la Sección 0.6 presentó las API y la Sección 7.7 trata en profundidad la compatibilidad de esquemas.

Prácticas para API estables de componentes y servicios

PrácticaQué significaPor qué funciona
Superficie mínimaExpón solo las operaciones y los campos que necesita un consumidor conocido; añade más cuando te lo pidan.Menos de lo que depender, así que menos que romper (ley de Hyrum).
Modelar el dominio, no el almacenamientoOfrece operaciones como place_order y cancel_order en lugar de crear, actualizar y eliminar con forma de tabla.El almacenamiento puede cambiar sin cambiar el contrato; el módulo sigue siendo profundo.
Cambio aditivoAñade campos opcionales y operaciones nuevas; nunca renombres, elimines ni cambies el significado de los existentes dentro de una versión.Los clientes antiguos siguen funcionando; los nuevos usan las partes nuevas.
Lector tolerante (tolerant reader)Los clientes ignoran los campos que no entienden y no dependen del orden de los campos.Los proveedores pueden añadir campos sin romper nada a nadie.
Versionado explícitoIntroduce una nueva versión principal (en la URL, en una cabecera o en el esquema del evento) para los cambios incompatibles, y ejecuta la antigua y la nueva en paralelo durante un periodo anunciado.Los consumidores migran a su propio ritmo; se evita el acoplamiento de despliegue.
Expandir y contraer (expand and contract)Añade la forma nueva, migra a los consumidores, confirma mediante la monitorización que nadie sigue usando la forma antigua y luego elimínala, en lanzamientos separados.Los cambios incompatibles se hacen sin un lanzamiento conjunto.
Pruebas de contratoLos consumidores publican sus expectativas; la compilación del proveedor las ejecuta.Los cambios incompatibles accidentales se detectan antes del despliegue.
Semántica de fallos honestaDocumenta los timeouts, la posibilidad de reintento, la idempotencia y el significado de los errores como parte del contrato.Evita las peores fugas de la abstracción de red.

Ejemplo práctico: una interfaz de pago que oculta al proveedor

Una empresa de reparto de comida cobra los pagos a través del Proveedor A. Finanzas quiere añadir el Proveedor B, con comisiones más bajas en un país, y poder cambiar de proveedor en un día si uno sufre una caída. Hoy, el código del proceso de pago llama directamente al SDK del Proveedor A en once lugares, usa sus códigos de error para decidir qué mostrar al cliente y guarda sus objetos de transacción en la tabla de pedidos. Cada una de esas cosas es una decisión que se ha filtrado.

El diseño introduce una interfaz PaymentGateway que pertenece a la tienda, no a ningún proveedor. Se expresa en los términos propios de la tienda: importes como enteros en unidades menores con una divisa, el identificador de pedido de la tienda, una clave de idempotencia y tres resultados. Cada proveedor recibe un adaptador que implementa la interfaz y traduce hacia y desde la API, los códigos de error y las peculiaridades de ese proveedor. La configuración elige el adaptador por país. Es el mismo movimiento que el intermediario de la Sección 2.6, visto ahora como ocultación de información: la decisión oculta es qué proveedor, y cómo se le llama.

from typing import Protocol, Literal
from dataclasses import dataclass

@dataclass(frozen=True)
class PaymentResult:
    outcome: Literal["approved", "declined", "unknown"]  # unknown: safe to retry
    reference: str | None        # our reference, not the provider's object

class PaymentGateway(Protocol):
    def authorise(self, order_id: str, amount_minor: int, currency: str,
                  idempotency_key: str) -> PaymentResult: ...
    def capture(self, reference: str) -> PaymentResult: ...
    def refund(self, reference: str, amount_minor: int) -> PaymentResult: ...

class ProviderBAdapter:            # implements PaymentGateway
    def authorise(self, order_id, amount_minor, currency, idempotency_key):
        resp = self._client.create_charge(value=amount_minor / 100,   # B wants decimals
                                          currency=currency, key=idempotency_key)
        return PaymentResult(_map_status(resp.code), _store_ref(resp.id))
La interfaz pertenece a la tienda y habla su idioma; solo los adaptadores saben que existe un proveedor.

La interfaz de pago y sus adaptadores

Pago depende de una interfaz PaymentGateway propia de la tienda; los adaptadores de dos proveedores la implementan y solo ellos conocen sus APIPagoReembolsopropia de la tiendaPaymentGatewayauthorise, capture,refundConfiguraciónpor país: proveedor BProviderAadaptadorProviderBadaptadorAPI de AAPI de Belige adaptadoral arrancarQuien llama solo ve la interfaz; proveedores, códigos de error, reintentos y reglas de divisa quedan en los adaptadoresFlechas continuas: depende o llama. Discontinuas: implementa o selecciona.
Los llamantes dependen de una interfaz que pertenece a la tienda; los adaptadores la implementan, y la configuración elige cuál se ejecuta.

Ejemplo práctico

Lo que oculta la interfaz, y lo que deliberadamente no oculta

Contrastar el diseño con las ideas de esta sección.

Decisión ocultaQué proveedor, su SDK y su versión de API, sus códigos de error, sus reglas de reintento, sus importes decimales o enteros, sus identificadores de transacción.
Contrato (precondiciones)Importe positivo en unidades menores, una divisa ISO 4217 admitida y una clave de idempotencia única por cada intento de pago.
Contrato (postcondiciones)approved significa que los fondos están reservados y se devuelve una referencia; declined significa que no se ha movido dinero; unknown significa que no se pudo determinar el resultado y que la misma llamada puede reintentarse con la misma clave.
No ocultación deliberadaLa interfaz expone unknown en lugar de fingir que los pagos siempre tienen éxito o fallan: la fuga de la red forma parte del contrato, no es una sorpresa.
Costo de un nuevo proveedorAntes: cambios en 11 puntos de llamada más la tabla de pedidos. Después: una nueva clase adaptadora, sus pruebas y una entrada de configuración.
ProfundidadTres operaciones ocultan una cantidad considerable de lógica específica de cada proveedor: profundo, no superficial.

Cuidado con la trampa del mínimo común denominador: si el Proveedor B ofrece una función útil de la que A carece, como el pago a plazos, o la interfaz incorpora una capacidad opcional o la función no estará disponible. Las abstracciones sobre proveedores siempre sacrifican parte de los puntos fuertes de cada proveedor a cambio de la libertad de cambiar.

La ley de Hyrum sigue aplicándose: si el proceso de pago empieza a depender de la latencia habitual del Proveedor A, un Proveedor B más lento lo pondrá en evidencia. Incluye los timeouts en el contrato.

Cómo lo aplican los profesionales

Los diseñadores con experiencia empiezan una interfaz a partir de las necesidades de quienes la llaman, escrita en el lenguaje de quien llama, y solo después diseñan la implementación. Mantienen una lista de las decisiones que oculta cada módulo importante y revisan las nuevas interfaces contrastándolas con ella. Tratan cada API pública como un producto con un ciclo de vida (propuesta, revisión, versionado, retirada), porque, según la ley de Hyrum, todo lo que se publica acabará usándose. Y aceptan que las abstracciones tienen fugas, así que hacen explícitas esas fugas: los presupuestos de latencia, la semántica de los errores y las reglas de reintento figuran en el contrato.

La ocultación de información te dice qué debe guardarse para sí cada módulo. No te dice en qué dirección deben apuntar las dependencias entre módulos, ni qué ocurre cuando forman bucles. Ese es el tema de la Sección 3.4.

Comprueba lo que has entendido

¿Por qué una clase con campos privados y getters y setters públicos para todos ellos puede fallar igualmente en la ocultación de información?

Porque la interfaz revela la representación interna exacta, así que quienes la llaman dependen de ella y cualquier cambio en la representación los rompe; hay encapsulamiento, pero no se oculta ninguna decisión.

En el diseño por contrato, ¿de quién es la culpa si se viola una precondición?

De quien llama, porque cumplir la precondición es su obligación; una postcondición violada indica un error en el proveedor.

¿Qué hace que un módulo sea profundo en el sentido de Ousterhout?

Ofrece mucha funcionalidad detrás de una interfaz pequeña y sencilla, así que el costo de aprender la interfaz es bajo en comparación con lo que oculta.

¿Por qué la ley de Hyrum aboga por respuestas de API pequeñas?

Porque algún cliente acabará dependiendo de cada campo o comportamiento observable, así que todo lo que se expone se vuelve difícil de cambiar; exponer menos deja más libertad.

Siguiente · 3.4DependenciasHacia dónde deben apuntar las dependencias y por qué: los principios de dependencias acíclicas, de dependencias estables y de abstracciones estables de Robert C. Martin, las métricas de inestabilidad, grado de abstracción y distancia, cómo romper ciclos, la organización en capas y las comprobaciones automáticas de dependencias.Continuar →

Fuentes y lecturas recomendadas

¿Tienes una pregunta sobre esta sección? ArchitectBot, el asistente de estudio con IA del curso, responde primero a partir del curso.

Pregunta: “¿Cómo debo diseñar una API interna entre los servicios de dos equipos para que podamos cambiar la implementación sin rompernos nada mutuamente?”