Visibilidad y acoplamiento#
Una API es el protocolo que permite a dos módulos colaborar. Su contrato reúne la información mínima que un consumidor necesita para usarla sin conocer su implementación.
Una firma puede mostrar nombres y tipos:
Set.Add(Value) → AddResultPero no explica por sí sola si se permiten duplicados, cómo se compara un valor, qué cambia después de la llamada ni qué ocurrirá al repetirla. Esas garantías también pertenecen al contrato.
Cada elemento visible crea una dependencia: el consumidor puede utilizarlo y el proveedor tendrá que conservarlo o migrarlo. Reducir visibilidad disminuye acoplamiento solo cuando la API sigue expresando toda la capacidad necesaria.
Lo que puede observar el consumidor#
| Parte | Pregunta | |---|---| | Consumidores | ¿Quién utiliza la API? | | Mensajes | ¿Qué puede pedir o comunicar? | | Vocabulario | ¿Qué significan entradas, resultados, errores y eventos? | | Validez | ¿Qué valores y secuencias se aceptan? | | Garantías | ¿Qué resultado, estado o efecto se conserva? | | Visibilidad | ¿Qué consumidores pueden acceder al contrato? | | Compatibilidad | ¿Qué usos anteriores deben seguir funcionando? |
No todas las APIs necesitan documentar cada dimensión con el mismo detalle. La profundidad depende del riesgo y del número de consumidores. Un módulo local con una operación pura puede quedar claro con una firma y dos ejemplos; un protocolo compartido necesitará más precisión.
Ejemplo: un conjunto sin duplicados#
Consideremos esta API independiente:
Set.Add(Value) → Added | AlreadyPresent
Set.Contains(Value) → bool
Set.Size() → integerSu contrato establece que añadir un valor ausente devuelve Added y hace que Contains sea verdadero. Repetirlo devuelve AlreadyPresent sin aumentar Size.
El contrato aún necesita una decisión sobre igualdad: ¿cuándo representan dos valores lo mismo? No necesita decidir si la implementación usa una tabla hash, un árbol o una lista. Cualquiera de ellas es válida si conserva las garantías.
Este ejemplo también muestra la diferencia entre API y vocabulario. Value, Added y AlreadyPresent forman parte de los mensajes. No son tres APIs nuevas. Solo necesitarían contratos independientes si adquirieran comportamiento, consumidores o evolución propios.
Ejemplo en Go#
Esta implementación protege el contrato y deja la estructura interna fuera de la API.
type AddResult uint8
const (
Added AddResult = iota
AlreadyPresent
)
type UniqueSet[T comparable] struct {
values map[T]struct{}
}
func (s *UniqueSet[T]) Add(value T) AddResult {
if s.values == nil {
s.values = make(map[T]struct{})
}
if _, exists := s.values[value]; exists {
return AlreadyPresent
}
s.values[value] = struct{}{}
return Added
}
func (s *UniqueSet[T]) Contains(value T) bool {
_, exists := s.values[value]
return exists
}
func (s *UniqueSet[T]) Size() int {
return len(s.values)
}API pública e implementación#
Una decisión pertenece a la API cuando un consumidor legítimo necesita utilizarla o distinguirla y el proveedor está dispuesto a conservarla.
| Pertenece al contrato | Permanece en la implementación | |---|---| | Intenciones que el consumidor puede expresar | Secuencia interna de llamadas | | Valores y diferencias que cambian su conducta | Estructuras intermedias | | Errores ante los que puede actuar | Fallos técnicos ya traducidos | | Estado y efectos observables | Algoritmos y mecanismos de coordinación | | Orden o cantidad cuando alteran el resultado externo | Optimización y distribución del trabajo |
Una superficie pequeña es útil si permite expresar la capacidad completa. Ocultar una diferencia necesaria no reduce acoplamiento: obliga al consumidor a deducirla o a buscarla en la implementación.
Estado y efectos#
El resultado inmediato es solo una forma de observación. Un mensaje puede modificar estado que luego se consulta mediante la API o producir un efecto dirigido a otro módulo.
El contrato debe nombrar esos efectos cuando el consumidor depende de ellos. No debe publicar la coordinación utilizada para conseguirlos.
En el ejemplo del conjunto, Size y Contains permiten observar el estado sin exponer su representación. El consumidor puede comprobar la garantía sin recibir la colección interna ni modificarla por referencia.
Cuando la API devuelve colecciones o estructuras mutables, hay que decidir quién conserva su propiedad. Si siguen perteneciendo al proveedor, una copia o una vista inmutable puede evitar cambios accidentales. Si la propiedad se transfiere, conviene decirlo de forma explícita.
Repetición e idempotencia#
Una operación es idempotente cuando repetir la misma intención produce el mismo efecto observable que ejecutarla una vez. No significa que el proveedor ejecute una sola instrucción ni que devuelva la misma instancia.
Set.Add es idempotente respecto del contenido: repetir el mismo valor no crea otra entrada. La respuesta puede cambiar de Added a AlreadyPresent y la garantía seguir siendo válida, porque el estado final no cambia.
La idempotencia solo merece entrar en el contrato cuando existen reintentos, repeticiones o entregas duplicadas que el consumidor deba poder manejar. Añadirla sin necesidad introduce identidad, estado y costes de retención.
Compatibilidad#
Una API publicada acumula consumidores. Cambiar un nombre, una regla de validez, un error o el significado de un campo puede romperlos aunque el código siga compilando.
Antes de modificar el contrato hay que conocer sus consumidores y el comportamiento que observan. Si el cambio no puede ser aditivo, se necesita una convivencia temporal y una condición explícita para retirar la versión anterior.
La compatibilidad no exige conservar para siempre una mala decisión. Exige tratar su corrección como una migración y no como un refactor interno.
Revisión práctica#
Antes de implementar una API, comprueba tres cosas:
- El consumidor puede expresar su intención y distinguir los resultados relevantes.
- Estado, efectos y propiedad de los valores están claros.
- El mecanismo sigue siendo reemplazable y la compatibilidad se trata solo donde importa.
Un buen contrato permite dos cosas a la vez: que el consumidor use el módulo con confianza y que el proveedor cambie su interior con libertad.