El acoplamiento se trata a menudo como una noción vaga: “está demasiado acoplado” no dice nada sobre qué cambiar concretamente. La connascencia proporciona un vocabulario preciso para nombrar las diferentes formas de acoplamiento, compararlas y decidir cuáles reducir primero.
El concepto está documentado en detalle en connascence.io.
Tres ejes para evaluar la connascencia
Cada instancia de connascencia se analiza según tres ejes:
- Fuerza: cuanto más fuerte es la connascencia, más difícil es detectarla y refactorizarla.
- Grado: una entidad acoplada a cientos de otras es más problemática que una acoplada a dos.
- Localidad: dos componentes cercanos (misma clase, mismo módulo) pueden tolerar formas más fuertes. A distancia, esas mismas formas se vuelven peligrosas.
Los 9 tipos de connascencia
Connascencia de Nombre (CoN)
Varios componentes se ponen de acuerdo sobre el nombre de una entidad. Es la forma más débil e inevitable.
class PaymentService:
def charge(self, amount: int) -> None:
...
service = PaymentService()
service.charge(100) # depende del nombre "charge"
Renombrar charge rompe todos los llamadores. Es fácil de corregir, pero el riesgo crece a escala.
Connascencia de Tipo (CoT)
Varios componentes se ponen de acuerdo sobre el tipo de una entidad.
def send_invoice(amount: int) -> None:
...
send_invoice("100") # error en tiempo de ejecución: str en lugar de int
Python no detecta estos errores en tiempo de compilación, pero mypy o Pyright sí lo hacen.
Connascencia de Significado (CoM)
Varios componentes se ponen de acuerdo sobre el significado de valores específicos. Los valores mágicos son el síntoma clásico.
# Frágil: ¿qué significa 1?
def get_card_type(card_number: str) -> int:
if card_number.startswith("4"):
return 1
# Mejor: constantes nombradas
VISA = 1
MASTERCARD = 2
def get_card_type(card_number: str) -> int:
if card_number.startswith("4"):
return VISA
Pasar de valores mágicos a constantes nombradas mejora la forma de acoplamiento (CoM a CoN).
Connascencia de Posición (CoP)
Varios componentes se ponen de acuerdo sobre el orden de los valores.
# Frágil: el orden de los argumentos importa
def create_user(first_name, last_name, year_of_birth, is_admin):
...
create_user("Thomas", "Richards", 1984, True)
# Mejor: dataclass
from dataclasses import dataclass
@dataclass
class UserData:
first_name: str
last_name: str
year_of_birth: int
is_admin: bool = False
Un dataclass elimina el riesgo de confusión por el orden de los argumentos.
Connascencia de Algoritmo (CoA)
Varios componentes se ponen de acuerdo sobre un algoritmo específico. Si ambos lados deben implementar el mismo cálculo, un cambio en uno debe replicarse en el otro.
import hmac
import hashlib
def sign_payload(data: str, secret: str) -> str:
return hmac.new(secret.encode(), data.encode(), hashlib.sha256).hexdigest()
def verify_payload(data: str, secret: str, signature: str) -> bool:
expected = hmac.new(secret.encode(), data.encode(), hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature)
Si cambias el algoritmo de firma en sign_payload, verify_payload debe actualizarse al mismo tiempo.
Connascencia de Ejecución (CoE)
El orden de ejecución entre componentes es importante.
import threading
lock = threading.Lock()
lock.acquire()
try:
process_critical_section()
finally:
lock.release()
Un context manager (with lock:) encapsula este orden y reduce el riesgo de olvidarlo.
Connascencia de Tiempo (CoT)
El momento de la ejecución es importante. Típico en sistemas concurrentes.
import threading
import time
result = None
def fetch_data():
global result
time.sleep(0.5)
result = {"status": "ok"}
t = threading.Thread(target=fetch_data)
t.start()
# Condición de carrera: result puede ser todavía None aquí
print(result)
Connascencia de Valor (CoV)
Varios valores deben cambiar juntos. Frecuente entre código de producción y tests.
class Article:
DRAFT = "draft"
PUBLISHED = "published"
def __init__(self, content: str):
self.content = content
self.status = self.DRAFT
# Test acoplado al valor inicial
def test_initial_status():
article = Article("contenido")
# Referenciar Article.DRAFT en lugar del string reduce este acoplamiento
assert article.status == Article.DRAFT
Connascencia de Identidad (CoI)
Varios componentes deben referenciar la misma instancia de un objeto.
shared_cache = {}
def writer(cache: dict):
cache["key"] = "value"
def reader(cache: dict):
return cache.get("key")
writer(shared_cache)
assert reader(shared_cache) == "value"
Es la forma más fuerte. A distancia, se vuelve muy difícil de razonar.
La regla práctica
La connascencia no exige eliminar todo acoplamiento. Ayuda a arbitrar: una CoA dentro de una misma clase es aceptable, una CoV entre dos servicios distantes es una señal de alerta.
La regla: cuanto mayor es la distancia entre componentes, más débil debe ser la forma de connascencia. Reducir la fuerza o acercar los componentes implicados mejora concretamente la mantenibilidad del código.
Para explorar todos los tipos en detalle: connascence.io.
