Accidental Complexity
Complejidad de la solución y su contexto
Accidental Complexity describe complejidad introducida por cómo representamos, implementamos, integramos, desplegamos u operamos una solución, en lugar de surgir directamente del problema considerado. La clasificación requiere declarar problema, restricciones y responsabilidad asumida. No equivale solamente a código mal escrito.
En Commerce, una devolución parcial puede ser una distinción requerida. Traducir un OrderId entre tres representaciones internas incompatibles puede agregar trabajo que esa devolución no necesita por sí misma. Hay que comprobar por qué existen las representaciones antes de calificarlas como innecesarias.
Accidental tampoco significa involuntaria, pequeña, aleatoria o un bug. Una compatibilidad deliberadamente mantenida puede exigir mucho esfuerzo y ser razonable durante una migración. El término identifica procedencia de complejidad, sin emitir un juicio moral sobre el equipo.
Framing histórico y adaptación del Atlas
Brooks distingue dificultades esenciales y accidentales de construir software. Entre sus ejemplos históricos de reducción de dificultades accidentales figuran lenguajes de alto nivel y mejores entornos de trabajo. El argumento trata representación e implementación, sin clasificar tecnologías actuales como buenas o malas. No Silver Bullet, informe original UNC de 1986, sección 3.
Aquí usamos el par con essential-complexity como adaptación arquitectónica del framing essence/accident. Los ejemplos sobre distribución, operación y Commerce son aplicaciones del Atlas, no tesis de Brooks sobre microservices, brokers o infraestructura contemporánea.
Moseley y Marks distinguen además complejidad accidental útil en soluciones reales: su modelo ideal no elimina las necesidades prácticas de toda implementación. Esa precisión ayuda a separar accidental de prescindible inmediatamente. Out of the Tar Pit, secciones 6 y 7.2.
Necesaria bajo restricciones actuales
El contrato vigente puede exigir integrar un protocolo XML legacy. La semántica de autorizar un pago quizá no requiera XML; el equipo necesita interpretar mensajes y errores mientras ese contrato siga vigente. El trabajo es necesario en el contexto actual.
constraints ayuda a preguntar quién impone la condición, qué alcance tiene y cuándo puede revisarse. Para un equipo de modernización, ese protocolo es una restricción externa. Para el problema comercial subyacente, es una realización histórica que podría sustituirse.
Estas perspectivas no obligan a etiquetar cada función con una categoría absoluta. Preguntar «esencial para qué» y «necesaria bajo qué condiciones» permite conservar ambos hechos. Un diseño puede reducir exposición al protocolo aun cuando carezca de autoridad para eliminarlo.
Diferencia con Technical Debt y calidad
technical-debt analiza condiciones de diseño o implementación que agregan carga a cambios futuros, sean deliberadas o descubiertas después. El SEI enfatiza esas consecuencias en el tiempo y distingue estrategia de diseño de calidad intrínseca del código. Managing the Consequences of Technical Debt.
Accidental Complexity abarca también desajustes de representación o límites de una plataforma sin requerir una decisión de deuda. Una deuda puede agregarla, pero no toda complejidad accidental demuestra deuda, negligencia ni una elección apresurada identificable.
legacy-systems añade contexto de herencia y evolución: comportamiento, datos y contratos valiosos pueden convivir con complejidad introducida por la solución. Retirar mecanismos requiere distinguir obligaciones vigentes de representaciones que perdieron propósito, sin identificar legacy con toda complejidad accidental.
Código limpio, pruebas y convenciones coherentes tampoco justifican por sí solos el sistema elegido. Una integración perfectamente implementada puede ser innecesaria para el alcance actual. Inversamente, una traducción inevitable bajo un contrato puede estar bien diseñada y seguir introduciendo complejidad respecto del problema comercial.
Arquitectura puede introducirla
Una división muy fina puede agregar contratos, comunicación, discovery y versionado que los drivers no exigían. Una unidad excesivamente amplia puede concentrar conocimiento, propagar cambios o impedir ampliar una capacidad relevante. granularity permite comparar esos costos sin asociar simplicidad a un tamaño universal.
También pueden agregar trabajo abstracciones demasiado generales, reglas duplicadas, wrappers de retry superpuestos, representaciones incompatibles o capas de compatibilidad obsoletas. Ningún mecanismo pertenece siempre a esta categoría: hay que relacionarlo con requisitos y alternativas concretas. Framework e infraestructura tampoco designan complejidad accidental por sí mismos; pueden realizar capacidades necesarias con menos trabajo propio, agregando compromisos que deben evaluarse.
decomposition y boundaries organizan responsabilidades, pero crear fronteras cuesta. Mapping, confirmaciones e integración necesitan alguien que los sostenga. Una representación de arquitectura con pocas cajas puede ocultar procedimientos manuales y sistemas externos detrás de cada flecha.
Complexity puede moverse
Simplificar una parte no prueba reducción del trabajo global. systems-thinking permite observar quién absorbe las obligaciones trasladadas y qué aparece después de cambiar una frontera. El siguiente cuadro compara movimientos posibles, no suma una magnitud universal de complejidad.
| Decisión contextual | Trabajo que puede reducir | Trabajo que aparece o permanece |
|---|---|---|
| Usar una base administrada | Operación propia de ciertas tareas de almacenamiento. | Dependencia del proveedor, contratos y recuperación acordada. |
| Introducir un broker | Necesidad de respuesta inmediata entre ciertos participantes. | Entrega, orden, duplicados y observación del flujo. |
| Separar una capacidad | Alcance de algunos cambios locales. | Coordinación, compatibilidad y operación entre unidades. |
| Automatizar deployment | Secuencia manual y memoria del operador. | Lógica ejecutable, configuración y verificación de la automatización. |
El traslado puede ser una mejora real. Una plataforma especializada puede atender mejor una obligación que cada equipo por separado. La evaluación necesita incluir costos transferidos, responsables y resultados, en vez de equiparar menor código local con menor complejidad total.
Abstraction: ocultar y agregar
Una interface de autorización con significado puede concentrar variación de proveedores. Ousterhout describe abstracciones cuya superficie sencilla oculta implementación más amplia y distingue interfaces formales de información adicional que los consumidores deben conocer. Managing Complexity, Abstraction.
interfaces permite evaluar qué necesita conocer Orders para colaborar con Payments. Un adapter puede aumentar código y contratos mientras reduce exposición a protocolos privados. Esa inversión debe compararse con frecuencia de cambio y consumidores reales.
Una API genérica que exige flags, casts, metadata y conocimiento de secuencias privadas puede conservar la dificultad detrás de otra indirection. Si los consumidores deben conocer internals para usarla correctamente, hay que revisar qué simplificó realmente.
Más abstraction no significa menos complejidad total. Mapping, lifecycle y debugging pueden tener costo. Una abstracción útil concentra variación significativa; otra anticipada sin casos reales puede inmovilizar supuestos y exigir trabajo que ningún driver actual justifica.
Modularity contiene exposición
modularity puede localizar decisiones privadas y limitar quién debe comprenderlas. Un Payments que protege códigos del proveedor permite a Orders depender de resultados acordados. La complejidad de integración sigue existiendo dentro de la responsabilidad que la atiende.
dependency-structure ayuda a observar propagación: si varios consumidores interpretan schemas privados, todos deben cambiar cuando cambia la representación. Un contrato deliberado puede reducir ese alcance y conservar colaboración. Contener, esconder y eliminar son intervenciones diferentes.
Coupling contribuye a complejidad sin definirla por completo. Un módulo puede mantener reglas necesarias difíciles con pocas dependencias externas. Un sistema de reglas sencillas puede exigir cambios coordinados entre muchas partes. Contar flechas tampoco reemplaza analizar su significado.
Deployment, Scaling y operación
deployment distingue unidades instalables de responsabilidades. Si no se necesita entrega independiente, separar numerosos artifacts puede añadir pipelines, orden de actualización y compatibilidad. Si Payments debe continuar durante una entrega de Orders, algunos contratos y coexistencia de versiones pueden estar justificados.
scaling permite identificar capacidad requerida. Particiones y autoscaling pueden atender una carga real o una restricción futura suficientemente respaldada. Aplicarlos sin ese contexto puede agregar estados y diagnóstico, pero la alternativa tampoco debe ignorar crecimiento comprometido.
operability recuerda que la complejidad no termina en el código. Deployment manual en orden secreto, recuperación conocida por una sola persona o configuración distribuida sin trazabilidad pueden convertir una aplicación aparentemente sencilla en un sistema difícil de operar.
Automatizar veinte pasos traslada parte del procedimiento a lógica ejecutable que necesita pruebas y mantenimiento. Puede reducir carga manual y errores; no borra las condiciones, dependencias y fallos que esos pasos atendían. Importa la evidencia de mejora operacional.
Drivers, calidad y profiles
architectural-drivers organiza necesidades, riesgos y restricciones que justifican complejidad. Una replicación multizona puede no pertenecer a la semántica de Order y ser necesaria para disponibilidad acordada. No corresponde identificar todo lo arquitectónico como accidental e injustificado.
portability puede requerir adaptación entre proveedores; esas capas introducen costo y preservan opciones. cost-efficiency incorpora esfuerzo de ingeniería, operación, incidentes y capacitación. Una arquitectura más fácil de comprender tampoco garantiza menor costo de runtime bajo cualquier carga.
microservices puede aportar independencia requerida de entrega, capacidad o autoridad. modular-monolith puede evitar parte del trabajo distribuido y conservar deployment o recursos comunes. Los perfiles son alternativas contextuales; ninguno tiene un grado de simplicidad garantizado por el nombre.
Commerce: semántica y representación
Si el negocio exige reembolsos parciales, Payments debe conservar importe, moneda y devolución realizada. Colapsar esos hechos en Paid no preserva comportamiento. Esa simplificación aparente pierde semántica, en vez de retirar dificultad introducida por la solución.
Otra situación representa importes como decimal con moneda, centavos enteros, cadenas localizadas y un DTO específico. Las traducciones pueden responder a contratos externos reales; duplicarlas internamente sin una necesidad clara agrega decisiones sobre precisión, compatibilidad y validación.
Un adapter puede contener parte de esa variación. El cambio necesita comprobar importes y errores preservados, consumidores afectados y operación durante transición. No basta sustituir cuatro modelos por uno si se pierde una distinción necesaria o se rompe una integración obligatoria.
Tiempo y retirada de mecanismos
Una fachada de compatibilidad puede ser razonable mientras existen clientes antiguos. Años después, si no quedan consumidores y las obligaciones de conservación están resueltas, mantenerla puede introducir costo sin el motivo original. evolvability invita a revisar esa vigencia.
API, facade, servicio legacy, procedimiento y base de integración pueden haberse agregado por decisiones localmente justificadas. Su acumulación necesita análisis del contexto actual, sin atribuir culpa retrospectiva a equipos que enfrentaban otras restricciones.
Retirar puede requerir inversión, migración y evidencia de seguridad operacional. Accidental no significa eliminable gratis ni inmediatamente. Aceptar temporalmente un mecanismo tiene sentido cuando se entiende el beneficio, el costo de retirarlo y la condición que permitiría reconsiderarlo.
Reducir con una intervención explícita
Eliminar una capacidad cambia alcance; consolidar representaciones puede retirar duplicación; mejorar una interface puede ocultar detalles; automatizar traslada procedimientos. Comparar estas intervenciones evita prometer una eliminación global cuando solo cambió exposición o responsable.
modifiability ayuda a evaluar propagación de un cambio y deployability su entrega segura. La pregunta incluye qué compromiso se conserva y cómo reconocer mejora. Más LOC no demuestra más complejidad, y familiaridad o documentación pueden reducir dificultad percibida sin alterar todas las relaciones.
La heurística es aceptar complejidad justificada por resultados y revisar la que perdió propósito. No exige cero mecanismos, cero dependencias ni una puntuación de complejidad. Una solución de apariencia simple que incumple seguridad, disponibilidad o corrección resulta insuficiente.