Be Software Architecture Wise
Be Wise Apps

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

Sección 3.6 de 3.8 · Intermedio · 22 min de lectura

Connascence: una medida más fina del acoplamiento

Videolección

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

El acoplamiento te dice que dos partes están conectadas. La connascence te dice cómo, lo arriesgado que es y qué refactorización lo haría más seguro. En esta lección aprenderás sus formas y las tres maneras de juzgarlas.

Cuando dos amigos acuerdan verse en el sitio de siempre a la hora de siempre, quedan ligados por un acuerdo. Nadie más puede verlo. Y si uno de ellos cambia de plan, el otro tiene que saberlo. El software está lleno de acuerdos así: nombres, el orden de los valores, lo que significa en secreto un número, qué paso debe ocurrir primero. La connascence es una forma de enumerarlos, juzgar el riesgo de cada uno y hacer que los arriesgados sean más débiles, más visibles o estén más cerca entre sí.

La palabra viene del latín y significa nacidos juntos, y Meilir Page-Jones la introdujo en 1992. Dos elementos tienen connascence si un cambio en uno exigiría un cambio en el otro para que el sistema siga siendo correcto. Los elementos pueden ser cualquier cosa: una función y quienes la llaman, un productor y los consumidores de sus mensajes, una columna de una base de datos y cada informe que la lee.

Las formas estáticas pueden encontrarse leyendo el código fuente. La más débil es la connascence de nombre: un llamante y una función coinciden en el nombre de esta. Renombrar es mecánico, y las herramientas encuentran cada uso. Connascence de tipo: ambos lados coinciden en un tipo, como una tarifa de envío en peniques enteros. Los verificadores de tipos y los esquemas la hacen visible. Connascence de significado: ambos coinciden en lo que significa un valor, como que el estado cuatro signifique repartidor asignado. Los valores mágicos como ese son invisibles para las herramientas. Connascence de posición: importa el orden de los valores, como en los argumentos de una función o en las columnas de un archivo. Y connascence de algoritmo: ambos lados deben calcular algo de la misma manera, como la misma suma de comprobación.

Las formas dinámicas solo se manifiestan cuando el sistema se ejecuta, así que son más difíciles de encontrar y suelen ser más fuertes. Connascence de ejecución: importa el orden de los pasos. El pago debe autorizarse antes de avisar al restaurante. Connascence de temporización: importa cuándo ocurren las cosas. Una app de repartidor que desiste a los cinco segundos, mientras que el despacho tarda hasta ocho, fallará. Connascence de valor: varios valores deben cambiar juntos. Si el frontend y el backend tienen cada uno la lista de zonas de reparto, las dos listas deben coincidir siempre. Y connascence de identidad: los elementos deben referirse exactamente a lo mismo, como dos módulos que escriben dentro de una misma transacción de base de datos.

Cada caso puede juzgarse de tres maneras. La fuerza es lo difícil que es encontrar y cambiar la forma. El nombre es débil, el significado es más fuerte, y la temporización y la identidad son las más fuertes. La localidad es lo cerca que están los elementos. Los argumentos posicionales entre dos funciones auxiliares privadas de un mismo archivo son inofensivos. Los campos posicionales en un mensaje entre equipos son peligrosos. El grado es cuántos elementos la comparten. Si el código de estado tres aparece en veintiocho lugares que pertenecen a tres equipos, cambiar su significado implica veintiocho ediciones coordinadas. Eso da tres movimientos. Debilitar la forma, por ejemplo sustituyendo un valor mágico por una constante con nombre. Acercar los elementos fuertemente conectados. O canalizar una convención compartida a través de una sola definición, para reducir su grado.

Esta cuadrícula lo reúne todo. Las formas van de la más débil, a la izquierda, a la más fuerte, a la derecha, y la distancia aumenta de arriba abajo. Dentro de una función, todas las formas son aceptables, porque quien cambia un lado puede ver el otro. Entre servicios, solo el nombre y el tipo son sanos: los acuerdos que un esquema publicado hace explícitos. Todo lo que sea más fuerte está en coral: refactorízalo hacia una forma más débil o acerca los elementos. Y si dos servicios comparten connascence de valor o de identidad, probablemente el límite está en el lugar equivocado. Uno debería ser el dueño de los datos, o los dos deberían ser un solo servicio.

Este es un ejemplo práctico de una app de transporte con conductor. Su función de reserva ha crecido hasta tener ocho argumentos posicionales: un pasajero, cuatro coordenadas, un número, true y cero. Si intercambias las coordenadas de recogida y de destino, sigue pasando la comprobación de tipos y envía en silencio al conductor al lugar equivocado. Eso es connascence de posición. Dos significa un vehículo grande, true significa pagar con tarjeta y cero significa reservar ahora. Eso es connascence de significado. Y hay treinta y un puntos de llamada repartidos en tres componentes. La refactorización se hace por pasos. Introduce un tipo de ubicación y argumentos con nombre. Sustituye los valores mágicos por valores con nombre. Después agrupa todo en un único objeto de solicitud de reserva. El comportamiento no ha cambiado. Pero los acuerdos fuertes e invisibles se han convertido en acuerdos débiles de nombre y de tipo, que el verificador de tipos y el editor pueden comprobar.

La sección completa incluye una tabla con las nueve formas y las maneras de debilitar cada una, y un análisis más detallado de la connascence entre servicios. A continuación, en la Sección tres punto siete, veremos la ley de Conway. Nos vemos allí.

Lo que necesitas saber

Por qué el acoplamiento necesita un vocabulario más fino

La Sección 3.2 describió el acoplamiento con los niveles del diseño estructurado (de contenido, común, externo, de control, de estampado, de datos) y con las formas distribuidas que importan entre servicios. Esos niveles clasifican cómo están conectados los módulos. Dicen menos sobre dos preguntas a las que se enfrentan los ingenieros cada día: ¿cuánto dolerá un cambio? y ¿qué refactorización reduciría ese dolor? La connascence responde a ambas.

La connascence (del latín, "nacidos juntos") la introdujo Meilir Page-Jones en un artículo de 1992 en Communications of the ACM, "Comparing techniques by means of encapsulation and connascence", y la desarrolló en su libro What Every Programmer Should Know About Object-Oriented Design (Dorset House, 1995), reelaborado más tarde como Fundamentals of Object-Oriented Design in UML (1999). Su definición es sencilla: dos elementos tienen connascence si algún cambio en uno exigiría un cambio en el otro para que el sistema siga siendo correcto. Los elementos pueden ser cualquier cosa: dos funciones, una función y quienes la llaman, un productor y los consumidores de sus mensajes, una columna de una base de datos y cada informe que la lee.

Dos características hacen que la idea sea útil para los arquitectos. En primer lugar, la connascence se presenta en formas con nombre que pueden ordenarse por fuerza, de modo que puedes decir no solo "estos están acoplados", sino "estos están acoplados por posición, que es peor que el acoplamiento por nombre, y así es como se convierte uno en el otro". En segundo lugar, tiene dos dimensiones más, la localidad y el grado, que te indican si una forma dada es aceptable allí donde está.

Formas estáticas: visibles en el código

La connascence estática puede encontrarse leyendo el código fuente, a menudo con herramientas, sin ejecutar el programa. De la más débil a la más fuerte, en el orden que se da con más frecuencia:

Connascence de nombre: los elementos deben coincidir en un nombre. Un llamante usa calculate_tax; un consumidor de JSON lee customer_id. Es la forma más débil, porque renombrar es mecánico y las herramientas encuentran cada uso.

Connascence de tipo: los elementos deben coincidir en un tipo. Una función espera un número entero de peniques; pasarle un número de coma flotante compila en Python, pero corrompe el resultado. Los verificadores de tipos y los esquemas la hacen visible.

Connascence de significado (o de convención): los elementos deben coincidir en lo que significa un valor. El estado 3 significa "enviado"; -1 significa "no encontrado"; una cadena vacía significa "usa el valor predeterminado". Esos valores mágicos son invisibles para las herramientas: nada vincula el 3 del código del almacén con el 3 del servicio de correo electrónico.

Connascence de posición: los elementos deben coincidir en el orden de los valores. create_user("Ann", "Smith", "ann@example.com") depende del orden de los parámetros; un archivo CSV depende del orden de las columnas; una tupla (lat, lon) se rompe en silencio si alguien supone (lon, lat), como hace GeoJSON.

Connascence de algoritmo: los elementos deben coincidir en un algoritmo. El emisor y el receptor de un webhook deben calcular la misma firma HMAC; un cliente y un servidor deben aplicar el mismo esquema de hash de contraseñas o la misma suma de comprobación; cada servicio que enruta por cliente debe usar la misma función hash para elegir un shard.

Formas dinámicas: visibles solo en tiempo de ejecución

La connascence dinámica depende de cómo se comporta el sistema cuando se ejecuta, así que es más difícil de encontrar y suele ser más fuerte.

Connascence de ejecución: importa el orden de ejecución. Hay que abrir una conexión antes de enviar datos; begin() debe preceder a commit(); hay que crear un pedido antes de capturar su pago.

Connascence de temporización: importa el momento de la ejecución. El timeout de un cliente debe ser más largo que el tiempo de procesamiento normal del servidor; una prueba que espera dos segundos confía en que un trabajo asíncrono haya terminado; dos hilos compiten por actualizar un saldo.

Connascence de valor: varios valores deben cambiar juntos, normalmente debido a un invariante. La lista de códigos de país válidos del frontend web y la del backend deben coincidir; el total de un pedido debe ser igual a la suma de sus líneas; los cargos y los abonos de un asiento contable deben cuadrar.

Connascence de identidad: los elementos deben referirse a la misma entidad, no solo a una igual. Dos repositorios deben compartir la misma transacción de base de datos para que sus escrituras sean atómicas; dos componentes deben publicar en la misma instancia de cola, no en dos colas con la misma configuración.

Page-Jones también dio nombre a la relación opuesta, la contranascence: elementos que deben diferir, como dos clases de un mismo espacio de nombres que no deben compartir nombre, o dos servicios que no deben usar el mismo puerto. Obedece las mismas reglas.

Las nueve formas, con un ejemplo de cada una

FormaClaseEjemplo en una app de reparto de comidaForma habitual de debilitarla
NombreEstáticaEl servicio de pedidos llama a restaurant.get_menu()Ya es la forma más débil; mantener nombres significativos
TipoEstáticaLa tarifa de envío se pasa como un entero de peniques, no como un número de coma flotante en librasAnotaciones de tipo, esquemas, un tipo Money
SignificadoEstáticastatus == 4 significa "repartidor asignado"Constantes con nombre o una enumeración, compartidas mediante una sola definición
PosiciónEstáticaquote(2.5, 51.5, -0.12, True)Argumentos con nombre, un objeto de parámetros, campos JSON con nombre
AlgoritmoEstáticaLa app y el servidor calculan la misma suma de comprobación del carritoUna sola biblioteca compartida, o dejar que un lado calcule y envíe el resultado
EjecuciónDinámicaEl pago debe autorizarse antes de avisar al restauranteEncapsular la secuencia en un solo lugar (un flujo de trabajo o una saga)
TemporizaciónDinámicaLa app del repartidor desiste a los 5 s, mientras que el despacho tarda hasta 8 sHacer asíncrona la interacción; derivar los timeouts de los SLO
ValorDinámicaEl frontend y el backend tienen cada uno la lista de zonas de repartoUn solo propietario de los datos; los demás los consultan o se suscriben a ellos
IdentidadDinámicaDos módulos deben escribir dentro de la misma transacción de base de datosDar a un solo módulo la propiedad de la unidad de trabajo

Fuerza, localidad y grado

Cada caso de connascence puede juzgarse según tres propiedades. La fuerza es lo difícil que es descubrir y cambiar la forma: el nombre es débil porque las herramientas lo encuentran y lo renombran; el significado es más fuerte porque nada señala los valores mágicos; la temporización y la identidad son las más fuertes porque puede que solo aparezcan bajo carga o en producción. El orden de la tabla es la clasificación convencional, aunque los autores difieren ligeramente en las formas estáticas (algunos sitúan la posición por debajo del significado).

La localidad es lo cerca que están los elementos con connascence. Los parámetros posicionales entre dos funciones auxiliares privadas del mismo archivo son inofensivos: quien cambia una ve la otra. Los campos posicionales en un mensaje entre servicios que pertenecen a equipos distintos son peligrosos, porque las personas que deben cambiar a la vez no se sientan juntas, no lanzan juntas y puede que ni siquiera sepan unas de otras.

El grado es cuántos elementos intervienen. Un número mágico compartido por dos funciones es una molestia; el mismo número usado por cuarenta archivos en seis servicios es un proyecto de migración. El grado puede contarse: si el código de estado 3 aparece en 14 lugares del servicio del almacén, en 9 del servicio de notificaciones y en 5 de las vistas de la base de datos de informes, el grado es 28, y cambiar su significado implica coordinar 28 ediciones entre tres equipos y al menos dos despliegues, con una migración de datos para las vistas.

La fuerza debe disminuir a medida que aumenta la distancia

Cuadrícula de formas de connascence ordenadas por fuerza frente a localidad, que muestra que las formas más fuertes solo son aceptables si los elementos están cercaMisma funciónMismo móduloMismo componenteEntre serviciosNombreTipoSignificadoPosiciónAlgoritmoEjecuciónTemporizaciónValorIdentidadEstáticasDinámicasFuerza creciente: más difícil de descubrir y de cambiarDistancia crecienteClaro: suele ser aceptable. Coral: refactoriza hacia una forma más débil o acércalos.
Las formas fuertes de connascence son tolerables dentro de una función o una clase; a través de los límites de componentes y servicios, busca las formas más débiles.

Las pautas

El consejo de Page-Jones puede resumirse en tres afirmaciones: minimizar la connascence global dividiendo un sistema en elementos encapsulados; minimizar la connascence que cruza los límites de encapsulamiento; y maximizar la connascence dentro de ellos. La última sorprende a la gente, pero es simplemente la cohesión expresada de otro modo: lo que debe cambiar a la vez debe estar junto.

Jim Weirich, el desarrollador de Ruby conocido sobre todo como autor de la herramienta de compilación Rake, recuperó la idea para una nueva generación en charlas a partir de 2009, entre ellas "The Building Blocks of Modularity" (impartida en varias conferencias de Ruby en 2009, entre ellas MountainWest RubyConf) (que él llamaba en broma una gran teoría unificada del desarrollo de software) y "Connascence Examined". Condensó la práctica en dos reglas. La regla de la localidad: cuanto más alejados estén dos elementos, más débil debe ser la connascence entre ellos. La regla del grado: convierte las formas fuertes de connascence en otras más débiles, sobre todo cuando intervienen muchos elementos.

En la práctica, esto da tres movimientos, aproximadamente en orden de preferencia:

Debilitar la forma. Sustituir el significado por el nombre (una constante con nombre), la posición por el nombre (argumentos con nombre, campos con nombre), el algoritmo por el nombre (una única función compartida a la que llaman ambos lados) y la ejecución por el encapsulamiento (un único método que realiza los pasos en el orden correcto).

Aumentar la localidad. Si dos elementos deben compartir una forma fuerte, acércalos: a la misma clase, módulo, componente o servicio, propiedad del mismo equipo.

Reducir el grado. Si muchos elementos comparten una convención, canalízalos a través de una sola definición, un solo propietario o un solo adaptador, para que solo unos pocos lugares conozcan el detalle.

Ejemplo práctico

Ejemplo práctico: sustituir parámetros posicionales y valores mágicos

Una app de transporte con conductor tiene una función que usan la API de reservas, el planificador y una herramienta de administración. Su firma ha crecido durante dos años: book(rider_id, 51.501, -0.142, 51.515, -0.072, 2, True, 0). Lee la connascence y refactorízala.

PosiciónOcho argumentos posicionales. Intercambiar las coordenadas de recogida y de destino sigue pasando la comprobación de tipos y envía en silencio al conductor al lugar equivocado.
Significado2 es la clase de vehículo (1 estándar, 2 grande, 3 accesible); True significa "pagar con tarjeta"; 0 significa "reservar ahora", mientras que cualquier otro número es una hora programada en segundos desde la época Unix.
GradoUna búsqueda encuentra 31 puntos de llamada en tres componentes, así que cada parámetro nuevo implica 31 ediciones en una posición fija.
Paso 1: nombre en lugar de posiciónIntroducir un tipo Location y argumentos solo por nombre, para que los llamantes escriban pickup=Location(lat=51.501, lon=-0.142).
Paso 2: nombre en lugar de significadoSustituir 2 por VehicleClass.LARGE, True por Payment.CARD y 0 por when=None, que significa ahora.
Paso 3: reducir el gradoAgrupar los argumentos en un objeto BookingRequest; añadir más adelante un campo opcional solo afecta al código que lo necesita.
ResultadoLa connascence de posición y de significado (fuerte, de grado 31) se convierte en connascence de nombre y de tipo (débil), que el verificador de tipos y el IDE pueden comprobar y renombrar automáticamente.

El comportamiento no cambió en nada; la refactorización solo cambió qué acuerdos deben respetar los llamantes y lo visibles que son esos acuerdos.

Haz el cambio en pasos que mantengan funcionando a los llamantes antiguos, por ejemplo aceptando ambas formas durante un lanzamiento y registrando el uso de la antigua.

from dataclasses import dataclass
from datetime import datetime
from enum import Enum

class VehicleClass(Enum):
    STANDARD = "standard"
    LARGE = "large"
    ACCESSIBLE = "accessible"

class Payment(Enum):
    CARD = "card"
    CASH = "cash"

@dataclass(frozen=True, kw_only=True)
class Location:
    lat: float
    lon: float

@dataclass(frozen=True, kw_only=True)
class BookingRequest:
    rider_id: str
    pickup: Location
    dropoff: Location
    vehicle: VehicleClass = VehicleClass.STANDARD
    payment: Payment = Payment.CARD
    when: datetime | None = None          # None means now

def book(request: BookingRequest) -> str: ...
Después de la refactorización: cada acuerdo entre llamante y llamado es un nombre o un tipo (kw_only hace que las llamadas posicionales sean un error).

La connascence a través de los límites de los servicios

Mark Richards y Neal Ford, en Fundamentals of Software Architecture (2020; segunda edición, 2025), presentan la connascence junto con la cohesión y el acoplamiento como una de las medidas fundamentales de la modularidad, y sostienen que es tan útil entre servicios como dentro del código. Lee las formas pensando en un sistema distribuido y cada una se corresponde con un problema conocido:

El nombre y el tipo entre servicios son los acuerdos que un esquema publicado hace explícitos: nombres y tipos de campos en un documento OpenAPI, un esquema Avro o Protobuf, o un catálogo de eventos. Son las formas que quieres, y las pruebas de contrato pueden comprobarlas.

El significado entre servicios es el código de estado compartido o el indicador sin explicar: lo que la Sección 3.2 llamó acoplamiento semántico. Prefiere los valores autodescriptivos ("status": "rider_assigned") a los números, y las unidades en los nombres de los campos (fee_pence, timeout_ms) a las suposiciones. Las unidades merecen especial cuidado: la Mars Climate Orbiter de la NASA se perdió en 1999 porque el software de tierra de un equipo producía cifras de impulso en libras-fuerza-segundo, mientras que el software de navegación que las consumía esperaba newtons-segundo, una connascence de significado entre organizaciones que nadie había hecho explícita.

La posición entre servicios aparece en los intercambios de archivos de ancho fijo o CSV y en los formatos de mensaje posicionales; los campos con nombre la eliminan. El algoritmo aparece cuando dos servicios deben calcular por separado el mismo precio, descuento o hash; el remedio suele ser que un servicio lo calcule y envíe el resultado.

La ejecución y la temporización entre servicios son los problemas difíciles de los sistemas distribuidos, y la temporización es la connascence que hay detrás del acoplamiento temporal de la Sección 3.2: un consumidor que da por hecho que la escritura del productor en la base de datos es visible antes de que llegue el evento, una cadena de llamadas síncronas cuyos timeouts no están bien anidados, un flujo de trabajo que solo funciona si los mensajes llegan en orden. Hacer que las interacciones sean asíncronas, idempotentes y tolerantes al orden debilita estas formas; patrones como las sagas y la bandeja de salida (outbox) (Capítulo 6) existen en gran medida para gestionarlas.

El valor y la identidad entre servicios son señales de un límite en el lugar equivocado. Si dos servicios deben mantener un valor consistente (el total del pedido en el servicio de pedidos y el total de la factura en facturación), o deben operar sobre la misma fila en la misma transacción, entonces o bien un servicio debe ser el dueño de esos datos y el otro debe pedírselos, o bien los dos servicios probablemente deberían ser uno solo. Una connascence fuerte a través de una red es la definición de un monolito distribuido.

Comprueba lo que has entendido

Una función recibe (width, height) y un llamante pasa (height, width). ¿Qué forma de connascence se ha roto y cuál es la manera más sencilla de debilitarla?

La connascence de posición. Usa argumentos con nombre o un objeto de parámetros para que el acuerdo pase a ser de nombre.

¿Por qué la connascence de significado es más fuerte que la connascence de nombre?

Las herramientas y los lectores pueden encontrar cada uso de un nombre, pero nada vincula los usos separados de un valor mágico como 3, así que es fácil pasar por alto algún cambio.

Dos servicios deben actualizar siempre el límite de crédito de un cliente en la misma transacción. ¿Qué sugiere eso?

Una connascence fuerte de identidad o de valor a través de un límite de red: probablemente los datos necesitan un único dueño, o los dos servicios deberían estar juntos.

¿Por qué podría ser perfectamente aceptable una connascence fuerte dentro de una sola función pequeña?

Porque la localidad es alta: quien cambia un elemento ve el otro de inmediato, así que el costo y el riesgo de mantenerlos consistentes son mínimos.

Siguiente · 3.7Ley de ConwayLa observación de Melvin Conway de 1968 según la cual los sistemas copian la estructura de comunicación de las organizaciones que los diseñan, la evidencia de la investigación, la aritmética de las vías de comunicación, la maniobra inversa de Conway y la carga cognitiva del equipo.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: “Nuestros servicios de pedidos y de facturación calculan ambos el total del pedido y a veces difieren en un penique. ¿Cómo describiría este acoplamiento en términos de connascence y qué deberíamos cambiar?”