El Contrato de la Librería
Una aplicación es juzgada por sus usuarios; una librería es juzgada por programadores, y en un momento específico — cuando quieren algo que el autor no imaginó. Una buena librería no es aquella que es conveniente cuando se usa como se pretendía. Es aquella que no tiene que ser descartada cuando se usa de otra manera.
Cada regla a continuación se deriva de una asimetría. Dentro de la librería puedes cambiar cualquier cosa, para siempre. Fuera de ella, todo lo que un llamante puede observar se congela la primera vez que alguien depende de ello — y con suficientes llamantes, alguien depende de todo.
Qué se congela, y cuándo
La Ley de Hyrum es la condición operativa, no una curiosidad: dados suficientes usuarios, cada comportamiento observable se convierte en una carga — redacción de excepciones, orden de enumeración, salida de logs, temporización, incluso el comportamiento que documentaste como indefinido. "Nunca estuvo en el contrato" no deshace el daño del llamante que envió su código basado en ello.
Así que la línea de congelación está en la observabilidad, no en la palabra clave public,
y se siguen tres consecuencias:
- La superficie comienza pequeña. Un símbolo, una vez público, es una promesa que puedes ampliar pero nunca retirar silenciosamente. "Público por ahora" es una deuda contraída sin beneficio; expón bajo demanda.
- Los formatos sobreviven al código. Un formato de archivo, un protocolo de red, un esquema persistido sobrevive a cada reescritura de la librería que lo envuelve. Serializar la estructura interna tal cual suelda la capa congelada a la editable.
- Un módulo interno cruza esta línea en su segundo consumidor, no en el empaquetado. En el momento en que existen dos llamantes, su API tiene la misma economía de congelación que un paquete publicado — solo que el radio de explosión es menor.
El Versionado Semántico es la forma pública de la misma línea, y comienza negándose a funcionar sin una: "El software que utiliza Versionado Semántico DEBE declarar una API pública" (SemVer 2.0.0, cláusula 1). Una vez declarada, los incrementos se deciden en lugar de sentirse — un cambio incompatible con versiones anteriores a esa API DEBE aumentar la versión MAYOR (cláusula 8), una nueva funcionalidad compatible con versiones anteriores DEBE aumentar la versión MENOR (cláusula 7), una corrección compatible con versiones anteriores DEBE aumentar la versión PATCH (cláusula 6). La Ley de Hyrum es lo que hace difícil la declaración: el especifica congela lo que declaraste, y los llamantes congelan lo que observaron.
El espec también tiene un nombre para el módulo que aún no ha cruzado la línea. "La versión mayor cero (0.y.z) es para desarrollo inicial. CUALQUIER COSA puede cambiar en cualquier momento" (cláusula 4). Esa es la etiqueta honesta para un módulo con un consumidor, y el día que gana un segundo es el día en que se debe 1.0.0, no el día en que se empaqueta.
No tomes el control
Separa el mecanismo de la política. Realizar una solicitud HTTP es mecanismo; reintentarla tres veces con retroceso exponencial es política, y la política integrada en el núcleo reduce la librería a los llamantes que la comparten. Ofrece valores predeterminados convenientes para registro, hilos, reintentos, tiempos de espera, caché, serialización — y haz que cada uno de ellos sea reemplazable.
El rol de la aplicación es singular, y no es tuyo. Salir del proceso, escribir en stdout, atrapar SIGINT, instalar manejadores de excepciones de nivel superior, redimensionar el pool de hilos: exactamente un componente por proceso puede hacer esto, y una librería que lo hace lucha contra el componente que tiene derecho a hacerlo. Leer las entradas del proceso pertenece al mismo rol: las variables de entorno, los archivos de configuración y los argumentos de línea de comandos se leen en la raíz de la aplicación, que entrega cada valor a la librería como un parámetro o un objeto de opciones. El llamante entonces elige de dónde proviene un valor, y una prueba lo pasa en lugar de configurar la máquina.
La magia — auto-detección, auto-conversión, auto-reconexión, auto-caché — es admisible solo mientras el llamante pueda reconstruir por qué hizo lo que hizo. La conveniencia que el usuario no puede depurar es control tomado, no trabajo ahorrado.
No agregues costos ocultos
Cuándo suceden las cosas pertenece al llamante. Una importación que inicia un hilo, un constructor que abre una conexión, un inicializador estático que lee variables de entorno — cada uno mueve trabajo real a un momento que el llamante nunca eligió y no puede devolver.
Una dependencia es parte de tu API. Su tiempo de ejecución, su cadencia de actualización, su estabilidad ABI, su licencia, sus efectos secundarios de inicialización se transfieren a cada llamante; estás eligiendo en su nombre.
Las características de rendimiento son contrato. Convertir una búsqueda O(1) en O(n) — o duplicar asignaciones y copias — es un cambio incompatible que lleva una firma compatible, y los llamantes lo descubren en producción.
Donde la entrada llega más rápido de lo que sale — flujos, eventos, registros, colas — un búfer interno ilimitado es una fuga de memoria con retraso. La contrapresión es parte del diseño, no un detalle operativo.
El estándar para la abstracción en sí misma es costo cero, en ambas cláusulas de Stroustrup. Lo que el llamante no usa, no lo paga: la capa de conveniencia no debe gravar a la capa primitiva con sus asignaciones, su despacho o su configuración. Y lo que usan, no podrían escribirlo mejor a mano de manera razonable: una abstracción superada por código hecho a mano se elude, y una librería eludida es una librería descartada que lleva un número de versión.
No mantengas estado oculto
Singletons, cachés estáticas, bucles de eventos globales, configuración a nivel de proceso: cada uno funciona mientras tu librería es la única en el proceso, y choca en el momento en que una segunda librería, un host de complementos, o un conjunto de pruebas paralelo lo comparten. El estado vive en una instancia que el llamante crea, posee, y puede crear dos veces.
No ocultes fallos
La forma de la API declara qué puede suceder y quién es responsable. Errores, cancelación, tiempo de espera, éxito parcial, ausencia — modelados en los tipos, no encaminados a través de "esto nunca ocurre normalmente" y una excepción. La misma forma lleva las respuestas de propiedad que un comentario no puede hacer cumplir: quié n cierra el flujo, si el búfer puede reutilizarse, en qué hilo llega el callback.
Conserva la causa.
Colapsar un fallo en false o null elimina exactamente la información que la capa superior necesitaba para decidir;
registrarlo tú mismo y continuar pierde la misma información y se apropia de la política de registro del llamante.
La calidad se separa en el camino anormal. En el camino feliz las librerías son intercambiables; tiempo de espera, reconexión, desconexión en medio del flujo, lecturas parciales, datos corruptos y reintentos son donde residen las diferencias, así que diseña para su composición primero.
La cancelación se adapta mal — un token de cancelación atraviesa cada firma que toca. Las operaciones de E/S y de larga duración lo aceptan desde la primera versión.
No bloquees la salida
Construye dos pisos.
Una API de alto nivel que sirve el caso común en una línea,
descansando sobre primitivas públicas — ReadAsync() sobre ReadAsync(Memory<byte>) — para que el llamante cuyo requisito queda fuera del piso superior baje en lugar de irse.
Una librería con solo el piso superior es descartada ante el primer requisito que no anticipó.
La observabilidad se inyecta, no se posee: registradores, métricas, trazas se conectan a la pila del llamante. Un formato de diagnóstico que solo tu librería lee es telemetría tomada como rehén.
Un impuesto, dos monedas
Esta habilidad y Clean Architecture son la misma negativa en dos monedas. La abstracción de costo cero rechaza el impuesto sobre el rendimiento en tiempo de ejecución; la Regla de Dependencia rechaza el impuesto sobre la capacidad de cambio. En ambas, una abstracción se juzga por lo que hace que la parte dependiente renuncie.
| Deber aquí | Su nombre arquitectónico |
|---|---|
| Mecanismo separado de política | Caso de uso separado del framework y del controlador |
| Valores predeterminados reemplazables | Inversión de dependencias — el lado estable elige la implementación |
| Primitivas debajo de la capa de conveniencia | Detalles externos intercambiables |
| Sin estado global | Dependencias explícitas, para que se pueda dibujar un límite |
| Una dependencia es parte de tu API | La Regla de Dependencia |
La forma saludable del llamante coloca tu librería en la parte inferior: un detalle reemplazable debajo de su política y dominio. La forma invertida — la aplicación viviendo dentro de un framework, arrastrando su ciclo de vida, sus tipos, su DI, su modelo de error, su modelo asíncrono — es la librería actuando como la aplicación, escrita a escala arquitectónica.
La forma general: una buena arquitectura minimiza no el número de dependencias sino la libertad que cada dependencia quita.
La sentencia
Una abstracción en una librería existe para ampliar las opciones del llamante. En el momento en que quita rendimiento, control o información a cambio, ha dejado de abstraer y ha comenzado a decidir.
Cuándo no aplicar
Un módulo con un consumidor, un repositorio, un equipo no ha cruzado la línea de congelación — está en 0.y.z en términos de SemVer, lleva o no un número — y construir cada punto de enganche de política reemplazable para ello es especulación con una factura de mantenimiento. Conserva las disciplinas que no cuestan nada desde el primer día — sin estado global, fallos que conservan su causa, sin trabajo antes de que el llamante lo pida — y agrega los puntos de enganche cuando llegue el segundo consumidor, que es el momento en que dejan de ser especulativos.
Fuentes
- Versionado Semántico 2.0.0, semver.org: cláusula 1 (la API pública DEBE declararse), cláusulas 6–8 (incrementos PATCH, MENOR, MAYOR), cláusula 4 (0.y.z), y la entrada de FAQ sobre deprecación (documentarlo, luego publicar una versión menor con la deprecación en su lugar). Redacción verificada contra la página.
- La Ley de Hyrum es una observación, no un estándar, y se cita como tal.