Be Software Architecture Wise
Be Wise Apps

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

Sección 3.8 de 3.8 · Intermedio · 25 min de lectura

Simplicidad, reversibilidad y medición de la estructura

Videolección

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

Esta última lección del capítulo trata de mantener los sistemas tan simples como permite el problema, y de medir la estructura sin engañarte a ti mismo.

Algunas cosas son difíciles porque el trabajo es difícil. Una declaración de impuestos es complicada porque la legislación fiscal lo es. Otras cosas son difíciles por cómo decidimos hacerlas, como un sistema de archivo con siete carpetas donde bastarían dos. Los buenos diseñadores aceptan el primer tipo y combaten el segundo. Construyen lo que se necesita ahora, dejan cada dato escrito en un solo lugar, evitan las decisiones que no pueden deshacer y comprueban su trabajo con unas pocas mediciones honestas.

Fred Brooks, en su ensayo No Silver Bullet, separó estos dos tipos. La complejidad esencial pertenece al problema: las reglas reales del IVA, las formas reales en que puede fallar una entrega. Ningún diseño puede eliminarla sin cambiar lo que hace el sistema. La complejidad accidental procede de cómo construimos la solución: herramientas, soluciones provisionales y una estructura más complicada de lo que el problema necesita. Los arquitectos ubican la de tipo esencial detrás de un límite claro, como un servicio de impuestos, y eliminan la de tipo accidental.

Hay tres máximas que ayudan. La primera, conocida como KISS, dice que lo mantengas simple. Un diseño es bueno si las personas que deben repararlo, quizá a las tres de la madrugada, pueden entenderlo. You aren't gonna need it, o YAGNI, advierte contra construir para necesidades que solo supones. Pagas por construirlo, por mantenerlo a lo largo de cada cambio posterior y por repararlo si te equivocaste. Pero YAGNI se aplica a las funcionalidades, no a las prácticas que abaratan el cambio, como las pruebas y la refactorización. Y algunas decisiones, como un modelo de datos o una API pública, son puertas de un solo sentido. Pensar con antelación ahí es el trabajo del arquitecto. Don't repeat yourself, o DRY, trata del conocimiento, no del texto. Cada regla, como el tipo de IVA, necesita un único hogar autorizado. Pero fusionar dos funciones solo porque se parecen acopla conceptos que cambiarán por razones distintas. Por último, la flexibilidad es como una opción financiera. Págala cuando el cambio sea probable, o muy caro sin ella, y la opción sea barata. Para lo demás, confía en una buena modularidad.

Ahora, la medición. La métrica de código más antigua de uso extendido es la complejidad ciclomática, de Thomas McCabe en 1976. Trata una función como un grafo: los bloques de código son nodos, y los posibles saltos entre ellos son aristas. Esta calcula una tarifa de envío. Tiene tres decisiones: ¿el total del carrito es de veinticinco libras o más, la distancia es superior a cinco y se trata de un pedido prioritario? Hay ocho nodos y diez aristas. La complejidad ciclomática es aristas menos nodos más dos: diez menos ocho más dos, que da cuatro. Eso coincide con una comprobación rápida: tres decisiones, más una. Cuatro significa cuatro caminos independientes, así que al menos cuatro casos de prueba para probar cada uno. McCabe sugirió alrededor de diez como límite superior razonable.

Un año después, la función ha incorporado un bucle que añade un recargo por los artículos voluminosos, y la comprobación de prioridad también pregunta si hay un repartidor disponible. Son las tres decisiones originales, más el bucle, la comprobación de artículo voluminoso y el and adicional, si tu herramienta lo cuenta: seis. Así que la complejidad es siete. Está por debajo del límite de McCabe, pero casi se ha duplicado. Probar todos los caminos necesita ahora al menos siete casos, no cuatro. El código mezcla ahora tres inquietudes de precios, que cambian personas distintas. Así que extrae cada recargo a su propia función pequeña. La complejidad total es parecida, pero cada pieza se puede probar por separado.

Los arquitectos también miden cómo se relacionan los módulos. Las métricas de acoplamiento y estabilidad de la Sección tres punto cuatro revelan la dirección de las dependencias y los ciclos. Las métricas de cohesión estiman si una clase tiene una responsabilidad o varias. Una clase de cliente cuyos métodos se dividen en tres grupos que no comparten campos es, en realidad, tres clases que comparten un archivo. El control de versiones revela el acoplamiento de cambio. Si dos archivos cambian juntos en el setenta por ciento de los commits, están acoplados, tanto si uno importa al otro como si no. Y los hotspots combinan la frecuencia de cambio con la complejidad, para encontrar código que es a la vez difícil de trabajar y que se trabaja a menudo.

Pero cuidado con la ley de Goodhart: cuando una medida se convierte en un objetivo, deja de ser una buena medida. Un límite estricto de complejidad de diez produce funciones divididas en puntos arbitrarios, y una lógica más difícil de seguir. Por eso los profesionales observan las tendencias en lugar de los umbrales, usan las métricas para iniciar conversaciones y no para calificar a las personas, y combinan métricas que tiran en direcciones distintas. Cuando una regla estructural es clara, como no tener ciclos de dependencias, la convierten en una función de aptitud: una comprobación automatizada que hace fallar la compilación y obliga a elegir conscientemente entre arreglar el código o cambiar la regla.

La sección completa termina con una lista de comprobación para revisar el diseño de cualquier módulo. Con esto termina el Capítulo 3. Cuando estés listo, haz la prueba opcional del capítulo y luego continúa con el Capítulo 4, sobre cómo documentar y comunicar la arquitectura. Bien hecho.

Lo que necesitas saber

Dos tipos de complejidad

En "No Silver Bullet: Essence and Accidents of Software Engineering" (presentado en el Congreso Mundial de Computación de la IFIP en 1986 y publicado en IEEE Computer en 1987), Fred Brooks separó las dificultades de construir software en dos tipos. La complejidad esencial pertenece al problema: las reglas reales del IVA, la variedad real de productos de seguros, las formas reales en que puede fallar una entrega. Ningún diseño puede eliminarla sin cambiar lo que hace el sistema. La complejidad accidental procede de cómo construimos la solución: lenguajes, herramientas, frameworks, maquinaria de despliegue, soluciones provisionales y una estructura más complicada de lo que el problema requiere. Brooks sostuvo que los lenguajes de alto nivel y otros avances ya habían eliminado gran parte de la dificultad accidental, por lo que ninguna técnica nueva por sí sola lograría una mejora de diez veces en una década.

Para los arquitectos, la distinción es una herramienta diaria. La complejidad esencial debe ubicarse: ponla donde puedan trabajar en ella las personas que la entienden, detrás de un límite que impida que se filtre a todo lo demás (un módulo de precios, un servicio de impuestos). La complejidad accidental debe eliminarse o prevenirse: cada servicio, cola, caché, capa de abstracción y opción de configuración adicional añade algo, y cada uno debe ganarse su lugar resolviendo un problema real. El ensayo de Ben Moseley y Peter Marks "Out of the Tar Pit" (2006) llevó la idea más lejos, al identificar el estado mutable y el flujo de control como fuentes principales de complejidad accidental, lo que es una de las razones por las que son populares los diseños funcionales y dirigidos por eventos que limitan el estado compartido.

El resto de esta sección trata los principios que mantienen baja la complejidad accidental (KISS, YAGNI, DRY bien entendido, reversibilidad) y las mediciones que ayudan a ver la estructura de forma objetiva, junto con sus límites.

KISS y YAGNI

KISS, que suele desarrollarse como "keep it simple, stupid" ("hazlo simple, estúpido"), es una máxima de diseño registrada en la Marina de los Estados Unidos en 1960 y asociada a Kelly Johnson, ingeniero jefe de Skunk Works de Lockheed, de quien se dice que exigía que los aviones a reacción pudiera repararlos sobre el terreno un mecánico medio con unas pocas herramientas. La lectura para el software es la misma: un diseño es bueno si las personas que deben operarlo y repararlo, a menudo a las 3 de la madrugada y bajo presión, pueden entenderlo. Simple no significa fácil ni ingenuo. Significa pocos conceptos, pocas piezas móviles y pocos casos especiales para el problema que se resuelve.

YAGNI ("you aren't gonna need it", "no lo vas a necesitar") procede de Extreme Programming a finales de la década de 1990: no construyas capacidades para necesidades que solo supones. El análisis de Martin Fowler es útil porque nombra los costos de ignorarlo. Está el costo de construir la funcionalidad supuesta; el costo del retraso, porque no se construyó en su lugar algo valioso; el costo de mantenerla, porque cada cambio posterior debe sortear el código adicional; y, si la suposición era errónea, el costo de repararla. Incluso cuando la suposición resulta acertada, la funcionalidad se construyó con menos conocimiento del que habrías tenido más adelante.

Dos matices impiden que YAGNI se convierta en una excusa. Primero, se aplica a las funcionalidades y a la flexibilidad especulativa, no a las prácticas que abaratan el cambio. Las pruebas automatizadas, la refactorización, la entrega continua y unos límites de módulo claros son lo que hace seguro aplazar funcionalidades; abandonarlos en nombre de YAGNI convierte el aplazamiento en deuda. Segundo, algunas decisiones son puertas de un solo sentido (Sección 1.6). Elegir un modelo de datos, la forma de una API pública, un modelo de tenencia o un límite de seguridad no es una funcionalidad que puedas añadir después al mismo costo. Para esas decisiones, pensar con antelación no es especulación; es el trabajo del arquitecto.

DRY significa conocimiento, no texto

Andy Hunt y Dave Thomas introdujeron DRY en The Pragmatic Programmer (1999; edición del 20.º aniversario en 2019) con esta definición: Cada pieza de conocimiento debe tener una representación única, inequívoca y autorizada dentro de un sistema. La palabra clave es conocimiento. Si el tipo de IVA, la regla de un código postal válido o el significado de un estado de pedido están escritos en varios lugares, cambiarlos exige encontrar cada copia, y las copias irán divergiendo. La edición de 2019 subraya que DRY trata de la duplicación de conocimiento y de intención, no de líneas de código que casualmente se parecen.

El mal uso habitual va en sentido contrario. Dos funciones que se parecen se fusionan porque se parecen, aunque representan conocimientos distintos que cambiarán por razones distintas: la regla de la tarifa de envío y la regla de la comisión del restaurante multiplican hoy ambas un total por un porcentaje, pero pertenecen a actores distintos (finanzas y alianzas) y divergirán. Fusionarlas acopla dos conceptos que el dominio mantiene separados: ambas dependen ahora de una única función compartida, así que un cambio solicitado para una cambia la otra sin que nadie lo note. En los términos de la Sección 3.6, crea connascence donde el problema no la tiene (compartir una función es el remedio correcto solo cuando ambos lados deben calcular realmente lo mismo); también es una violación de la responsabilidad única (Sección 3.5). El conocido consejo de Sandi Metz es preferir la duplicación a la abstracción equivocada, porque una abstracción compartida equivocada acumula parámetros y condicionales hasta que nadie se atreve a cambiarla. Muchos equipos usan la regla de tres: tolerar una segunda copia y extraer una abstracción cuando aparece la tercera y el conocimiento compartido está claro.

A escala de arquitectura, el compromiso se vuelve explícito. Una biblioteca compartida que todos los servicios deben usar elimina el código duplicado, pero acopla los ciclos de lanzamiento de los servicios. Muchas organizaciones de microservicios aceptan deliberadamente el código duplicado (pequeñas utilidades copiadas en cada servicio) mientras exigen un único propietario para el conocimiento duplicado (el registro canónico del cliente, las reglas fiscales). Pregúntate ante cualquier duplicación: si esta regla cambia, ¿deben cambiar todas las copias a la vez? Si la respuesta es sí, es conocimiento duplicado y debe tener un único propietario. Si es no, es una similitud casual y puede quedarse.

La reversibilidad y el costo de las opciones

La simplicidad tiene una dimensión temporal. Un diseño que hoy es simple pero imposible de cambiar no es barato; un diseño lleno de flexibilidad que nadie usa tampoco lo es. El concepto útil es la reversibilidad: ¿cuánto costaría deshacer o cambiar esta decisión más adelante? La Sección 1.6 presentó las puertas de un solo sentido y de doble sentido. Aquí lo importante es que la flexibilidad se comporta como una opción financiera: tiene un precio ahora (código adicional, abstracción adicional, costo operativo adicional) y un valor que depende de lo probable y lo caro que sea el cambio contra el que protege.

Esto da una regla práctica que concilia YAGNI con la previsión. Paga por una opción solo cuando el cambio sea razonablemente probable, o cuando sería muy caro sin la opción, y cuando la propia opción sea barata. Envolver un proveedor de pagos tras una interfaz propia cuesta un día y protege frente a un cambio que es a la vez plausible y caro: una buena opción. Construir una capa de abstracción multinube para una startup con un solo cliente es una opción cara frente a un cambio improbable. A menudo la opción más barata es simplemente una buena modularidad: unos límites bien situados mantienen locales muchos cambios futuros sin que nadie tenga que predecirlos, que es el argumento original de Parnas a favor de la ocultación de información (Sección 3.1).

Medir la estructura: la complejidad ciclomática

El juicio sobre la simplicidad mejora cuando se apoya en la medición. La métrica de código más antigua de uso extendido es la complejidad ciclomática, presentada por Thomas McCabe en "A Complexity Measure" (IEEE Transactions on Software Engineering, 1976). Trata una función como un grafo de flujo de control, con nodos para los bloques de código y aristas para las posibles transferencias de control, y cuenta los caminos linealmente independientes que la atraviesan: M = E − N + 2P, donde E es el número de aristas, N el número de nodos y P el número de componentes conexas (1 para una sola función). En el código estructurado con un único punto de entrada, esto equivale al número de puntos de decisión más uno.

McCabe sugirió 10 como límite superior razonable para un módulo. El posterior informe del NIST sobre pruebas estructuradas de Arthur Watson y McCabe (1996) señala que equipos experimentados han usado con éxito límites de hasta 15, y recomienda una explicación por escrito siempre que un módulo supere el límite elegido. Herramientas de análisis estático como radon o el plugin mccabe para flake8 en Python, la regla complexity de ESLint en JavaScript y SonarQube la calculan automáticamente. Las herramientas difieren en detalles, por ejemplo en si cada and u or de una condición cuenta como una decisión adicional, así que compara números de la misma herramienta.

def delivery_fee(total: int, distance_km: float, priority: bool) -> int:
    if total >= 2500:          # decision 1: free delivery over 25 pounds
        return 0
    fee = 299
    if distance_km > 5:        # decision 2
        fee += 100
    if priority:               # decision 3
        fee += 200
    return fee
Tres decisiones, así que la complejidad ciclomática es 4.

El grafo de flujo de control y el recuento

Grafo de flujo de control de una función de tarifa de envío con tres decisiones, que da una complejidad ciclomática de cuatrototal >= 2500?return 0distance > 5?fee += 100priority?fee += 200return feesalidasínosínosínoRecuentoNodos N = 8Aristas E = 10M = E − N + 2M = 10 − 8 + 2 = 4Verif.: 3 decisiones + 1 = 44 caminos indep.al menos 4 casos de prueba(prueba de camino básico)
Ocho nodos y diez aristas dan M = 10 − 8 + 2 = 4, que coincide con tres decisiones más uno.

Ejemplo práctico

Ejemplo práctico: cuando la función crece

Un año después, delivery_fee ha incorporado un bucle sobre el carrito y una condición compuesta. Calcula su complejidad y decide qué hacer.

Código nuevofor item in items: con if item.bulky: fee += 50 dentro, y if priority and rider_available: fee += 200 en lugar de if priority.
DecisionesLas 3 originales, más el bucle for (1), más el if item.bulky (1), más el and (1, si la herramienta cuenta los operadores booleanos): 6.
Complejidad ciclomática6 + 1 = 7. Por debajo del límite de 10 de McCabe, pero casi se ha duplicado.
Implicación para las pruebasLa prueba de camino básico necesita al menos 7 casos de prueba para recorrer una vez cada camino independiente; el antiguo conjunto de 4 ya no cubre todos los caminos.
Lectura de diseñoLa función mezcla ahora tres inquietudes de precios: el umbral según el importe del carrito, los recargos por distancia y por artículo, y la prioridad. Cambian por solicitudes de áreas distintas (marketing, operaciones, producto).
RefactorizaciónExtrae distance_surcharge, bulky_surcharge y priority_surcharge, cada una con complejidad 2 o 3, y deja que delivery_fee las sume. La complejidad total es parecida, pero cada pieza es pequeña, tiene nombre y se puede probar por separado.

La refactorización rara vez reduce la complejidad total; la redistribuye en piezas que son más fáciles de entender, probar y cambiar de forma independiente. La complejidad esencial de las reglas de precios se mantiene.

La métrica señaló una tendencia que merecía una conversación; la decisión de dividir se tomó leyendo el código y preguntando quién lo cambia.

Medir la estructura más allá de las funciones

La complejidad ciclomática ve una función cada vez. Los arquitectos necesitan medidas de cómo se relacionan los módulos.

Métricas de acoplamiento y estabilidad. La Sección 3.4 (gestionar las dependencias) presentó el acoplamiento aferente y eferente, la inestabilidad I = Ce / (Ca + Ce), el grado de abstracción y la distancia a la secuencia principal. Son baratas de calcular a partir de los grafos de importaciones y sirven bien para revelar componentes de los que dependen muchos y que, a su vez, dependen de código volátil, y para detectar ciclos de dependencias.

Métricas de cohesión. La familia LCOM (falta de cohesión de los métodos) estima si una clase tiene una responsabilidad o varias. La versión original de Shyam Chidamber y Chris Kemerer (1994) compara pares de métodos según si usan alguno de los mismos campos. Una variante posterior muy usada, LCOM4, de Martin Hitz y Behzad Montazeri, construye un grafo en el que los métodos están conectados si comparten un campo o si uno llama al otro, y cuenta los grupos conectados. Una clase con LCOM4 = 1 es coherente; una clase con LCOM4 = 3 es, en realidad, tres clases que comparten un archivo. Por ejemplo, una clase Customer cuyos update_address y format_address usan los campos de dirección, cuyos add_points y points_balance usan los campos de fidelización, y cuyo send_newsletter usa solo el campo de correo electrónico tiene LCOM4 = 3, lo que sugiere responsabilidades de dirección, fidelización y marketing.

Métricas basadas en el historial. El control de versiones registra cómo cambia realmente el sistema, algo que el análisis estático no puede ver. El acoplamiento de cambio encuentra archivos o servicios que se confirman juntos una y otra vez: si pricing.py e invoice_template.html cambian juntos en el 70% de los commits, están acoplados tanto si uno importa al otro como si no, y el acoplamiento puede cruzar un límite de servicio que debía separarlos. Los hotspots (puntos calientes) combinan la frecuencia de cambio con la complejidad para encontrar código que es a la vez difícil de trabajar y que se trabaja con frecuencia; Your Code as a Crime Scene (2015; segunda edición en 2024), de Adam Tornhill, popularizó la técnica. La Sección 1.7, sobre deuda técnica, mostró cómo los hotspots dirigen el pago hacia la deuda que genera más intereses.

Métricas estructurales de un vistazo

MétricaAlcanceLo que revelaPrincipal punto ciego
Complejidad ciclomáticaFunciónLógica de ramificación difícil de probar y de razonarNo dice nada de los nombres, los datos ni cómo se relacionan las funciones
Ca, Ce, inestabilidad, distanciaPaquete o componenteDirección de las dependencias, estabilidad, ciclosCuenta las dependencias, no su fuerza ni su significado
LCOM (por ejemplo, LCOM4)ClaseClases que tienen varias responsabilidades sin relación entre síSensible a los métodos de acceso y a las características del lenguaje; ruidosa en clases pequeñas
Acoplamiento de cambioArchivo, módulo o servicioAcoplamiento oculto que solo se ve en cómo cambia el sistemaNecesita suficiente historial; los commits masivos generan ruido
HotspotsArchivo o móduloDónde la mejora se amortiza más rápidoPrioriza; no dice qué está mal
Connascence (evaluada mediante revisión)CualquieraLa forma y la fuerza de cada dependenciaEn gran parte manual; pocas herramientas miden las formas dinámicas

La ley de Goodhart y los límites de las métricas

Cada una de las métricas anteriores es un indicador indirecto de algo que nos importa pero que no podemos medir directamente: lo fácil que es entender y cambiar el sistema. La ley de Goodhart, que lleva el nombre del economista Charles Goodhart (1975) y es más conocida en la formulación de 1997 de la antropóloga Marilyn Strathern, advierte que cuando una medida se convierte en un objetivo, deja de ser una buena medida. Las personas optimizan el número, no la propiedad.

Los equipos de software lo ven constantemente. Un límite estricto de complejidad ciclomática de 10 produce funciones divididas en puntos arbitrarios, cada una de las cuales supera la comprobación mientras la lógica se vuelve más difícil de seguir. Un objetivo de cobertura del 80% produce pruebas que ejecutan código sin comprobar nada. Una regla según la cual ninguna clase puede superar las 200 líneas produce clases divididas en OrderHelper y OrderHelper2. Un objetivo de "cero ciclos" puede cumplirse moviendo código a un paquete common convertido en cajón de sastre del que todo depende.

Los profesionales usan las métricas de tres formas que resisten la manipulación. Miran las tendencias en lugar de umbrales absolutos (la complejidad del módulo de pagos se ha duplicado en seis meses: ¿por qué?). Usan las métricas para iniciar conversaciones y priorizar revisiones, no para calificar a las personas. Y combinan métricas que tiran en direcciones distintas, como la complejidad con la frecuencia de cambio, o el acoplamiento con la propiedad de los equipos, de modo que ningún número por sí solo pueda optimizarse a costa de los demás.

Funciones de aptitud: hacer que la estructura se pueda comprobar

Algunas reglas estructurales son lo bastante claras para automatizarlas, y entonces dejan de depender de la vigilancia. Building Evolutionary Architectures (2017; segunda edición en 2022 con Pramod Sadalage), de Neal Ford, Rebecca Parsons y Patrick Kua, llama a estas comprobaciones funciones de aptitud (fitness functions): pruebas objetivas y automatizadas de una característica arquitectónica que se ejecutan en el pipeline de compilación o contra el sistema en ejecución. Algunos ejemplos para la estructura: ningún ciclo de dependencias entre paquetes de nivel superior; el paquete del dominio no importa nada de los paquetes web ni de base de datos; ninguna función por encima de una complejidad de 15 sin una excepción explícita; ningún servicio lee las tablas de la base de datos de otro servicio.

Herramientas como ArchUnit (Java), import-linter (Python) y dependency-cruiser (JavaScript y TypeScript) expresan estas reglas como código; la Sección 3.4 dio ejemplos. La diferencia con una métrica de un panel es que una función de aptitud codifica una decisión que el equipo ya ha tomado, así que incumplirla hace fallar la compilación y obliga a una elección consciente: arreglar el código o cambiar la regla y dejar constancia del motivo. El tratamiento completo, incluidas las funciones de aptitud para el rendimiento, la seguridad y otras cualidades, está en Funciones de aptitud y arquitectura evolutiva.

[importlinter]
root_package = shop

[importlinter:contract:domain-is-pure]
name = Domain must not depend on infrastructure
type = forbidden
source_modules =
    shop.domain
forbidden_modules =
    shop.infrastructure
    shop.web
Un contrato de import-linter en setup.cfg o .importlinter que hace fallar la compilación si la capa de dominio importa infraestructura.

Lista de comprobación: revisar el diseño de un módulo

Úsala al revisar un nuevo módulo, componente o servicio, o un cambio importante en uno de ellos. Se basa en todas las secciones de este capítulo.

  • ¿Puedes enunciar su responsabilidad en una frase y nombrar al actor que solicita sus cambios?Si la frase necesita una "y", o varios actores lo cambian, plantéate dividirlo (modularidad, SRP, CCP).
  • ¿Oculta una decisión de diseño que probablemente cambiará?Un módulo debe ser dueño de un secreto, como un formato de datos, un proveedor o un algoritmo (ocultación de información).
  • ¿Es la interfaz pequeña en relación con lo que oculta?Los módulos profundos ocultan mucho detrás de poco; las capas de paso añaden costo sin ocultar nada.
  • ¿Apuntan sus dependencias hacia módulos más estables y más abstractos?Comprueba que no haya ciclos ni módulos estables que dependan de otros volátiles (ADP, SDP, DIP).
  • ¿Qué necesita saber un cliente para usarlo correctamente?Busca parámetros posicionales, valores mágicos, órdenes de llamada obligatorios y supuestos de temporización; debilítalos o mantenlos locales (connascence).
  • ¿Quién es su propietario, y puede ese equipo cambiarlo y desplegarlo sin coordinarse con otros?Un único equipo propietario por módulo; las interfaces entre equipos deben ser explícitas y estar versionadas (ley de Conway).
  • ¿Hay alguna parte de su complejidad que sea accidental?Cuestiona cada capa, opción y mecanismo genérico adicional: ¿a qué requisito real sirve (KISS, YAGNI)?
  • ¿Hay algún conocimiento duplicado, o alguna similitud casual compartida por error?El conocimiento duplicado necesita un único propietario; el código de aspecto similar con propietarios distintos puede seguir separado (DRY).
  • ¿Qué decisiones de él son difíciles de revertir, y son deliberadas?La forma de las API públicas, los modelos de datos y los límites merecen más escrutinio que los detalles internos.
  • ¿Qué dicen las métricas y el historial?Comprueba las tendencias de complejidad, las métricas de acoplamiento, el acoplamiento de cambio y los hotspots, y añade una función de aptitud para cualquier regla que merezca la pena mantener.

Comprueba lo que has entendido

Una función tiene dos sentencias if y un bucle while, y ningún operador booleano. ¿Cuál es su complejidad ciclomática?

Tres decisiones más uno, así que 4.

Dos servicios contienen cada uno una función idéntica de 20 líneas para dar formato a fechas. ¿Es una violación de DRY?

Probablemente no. Es texto duplicado, no conocimiento de negocio duplicado, y compartirlo acoplaría los lanzamientos de los servicios; las reglas de negocio duplicadas, como el cálculo de impuestos, son otra cuestión.

¿Por qué YAGNI no se aplica a escribir pruebas automatizadas?

Las pruebas forman parte de lo que hace que el cambio futuro sea barato y seguro; YAGNI trata de no construir funcionalidades ni flexibilidad especulativas, y depende de esas prácticas para funcionar.

A un equipo se le dice que ninguna función puede superar una complejidad de 10, y la complejidad baja mientras los defectos aumentan. ¿Qué ha ocurrido probablemente?

La ley de Goodhart: las funciones se dividieron mecánicamente para cumplir la métrica, lo que hizo la lógica más difícil de seguir sin hacerla más simple.

Fin del capítulo 3Hacer la prueba del capítuloOpcional: 20 preguntas de opción múltiple y 2 respuestas escritas, en la app.Ir a la prueba →

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: “Nuestro equipo quiere imponer una complejidad ciclomática máxima y una cobertura de pruebas mínima en la CI. ¿Cómo usamos estas métricas sin que la gente las manipule?”