La complejidad no está donde crees: por qué el código complejo no cuesta nada hasta que alguien lo lee
Dos ingenieros miran la misma pantalla. En ella, un diff recién abierto: una función que ahora recibe un objeto de configuración en vez de cinco argumentos independientes, un caché que pasó a compartirse entre tres módulos que antes no se comunicaban, un flag booleano que decide si el descuento se aplica antes o después del impuesto según en qué país corre el proceso. El primero sostiene que está bien, que es simple, que cualquiera lo entiende con dos minutos. El segundo objeta que es un desastre, que dentro de seis meses nadie va a poder tocar esa función sin romper algo en otro lado.
Los dos están mirando exactamente el mismo código. Ninguno mintió, ninguno vio algo que el otro no vio. Las mismas líneas, la misma pantalla, el mismo commit. Entonces la pregunta que queda no es quién tiene razón, sino algo más incómodo. ¿La complejidad está en el código, esperando a ser descubierta por cualquiera que sepa leerlo? ¿O está en la cabeza de quien lo lee, y lo que cambia de un ingeniero a otro es la cabeza, no el código?
La palabra ya viene dividida
Antes de discutir quién de los dos ingenieros tiene razón, mira la palabra que ambos están usando. El diccionario de la Real Academia da dos acepciones de “complejo” que no apuntan al mismo lugar. La primera es estructural, casi geométrica: “que se compone de elementos diversos”. La segunda es de otra naturaleza, “complicado, enmarañado, difícil”. Una describe una propiedad del objeto, cuántas partes distintas lo forman; la otra describe una relación con quien lo enfrenta, cuánto cuesta desenredarlo. La palabra misma no se decide entre las dos. Y esa indecisión no es un descuido lexicográfico. Es, en germen, la misma tensión que este ensayo va a recorrer entera. ¿La complejidad es algo que el objeto tiene, o algo que al sujeto le pasa?
La raíz latina de la palabra insinúa todavía otra cosa, la idea de enlazar o entrelazar algo con algo, pero eso lo retomaremos más adelante.
Que una sola palabra cargue dos significados que no coinciden es un ejemplo perfecto de la ambigüedad del lenguaje natural, y esa ambigüedad es en sí misma una fuente de complejidad. Dos personas pronuncian “complejo”, creen estar diciendo lo mismo, y sin embargo una habla de estructura y la otra de dificultad. Nunca chocan de frente, porque la palabra las cubre a las dos. Eso mismo les pasa a los dos ingenieros del diff. En un texto anterior escribí sobre el lenguaje ubicuo en el diseño de dominio, donde el problema tenía la misma raíz con otro disfraz. El código y el negocio usaban las mismas palabras con sentidos distintos, y la deuda que se acumulaba no era técnica sino de significado. Antes de decidir dónde vive la complejidad, hay que aceptar que la palabra con la que la nombramos ya está partida por la mitad.
A cada mitad de la palabra le responde una escuela de pensamiento. Y son opuestas.
El código como objeto
Para Rich Hickey, quien planteó esta postura en su charla «Simple Made Easy», la respuesta es la primera. La complejidad no depende de quién mira, es una propiedad del artefacto, tan verificable como el peso de una piedra. Viene de la etimología misma de la palabra. “Complex” desciende del latín complecto, entrelazar o plegar hilos juntos, mientras que “simple” viene de sim-plex, un solo pliegue (esta derivación es un aporte de este ensayo, no de la charla que la inspira). Trenza y pliegue nombran el mismo gesto, doblar hilos. Lo simple es un único pliegue; lo complejo son varios plegados unos sobre otros. Hickey mismo lo explica en términos más directos: “It means to interleave or entwine or braid… complecting.” Complejidad es entrelazamiento. Simplicidad, en cambio, es tener un solo rol, una sola responsabilidad, un solo concepto atado a la vez.
Lo interesante de esta postura no es la metáfora textil, sino la afirmación epistemológica que la acompaña. Para Hickey, si dos conceptos están entrelazados en el código, puedes ir, mirar y comprobarlo, sin necesidad de preguntarle a nadie cómo se siente al leerlo. El estado que guarda a la vez un valor y su historial de cambios está entrelazado, y esa trenza vive en la estructura misma del artefacto. La carga que produce ese entrelazamiento es, según esta tesis, combinatoria. Cada concepto que se suma a la trenza multiplica las formas en que hay que pensar el conjunto, no las suma.
Esto coloca la complejidad del lado del objeto. El diff de los dos ingenieros, bajo esta lente, tiene una respuesta única e independiente de quién lo lea. Si el flag de país entrelaza la lógica de descuento con la del impuesto, es complejo, lo diga quien lo diga. Pero esa afirmación deja una pregunta pendiente. Si la complejidad es del objeto y no del sujeto, ¿por qué el segundo ingeniero necesita mirar más tiempo, y el primero no?
El código como límite
La escuela opuesta responde esa pregunta. Edsger Dijkstra, en su ensayo «On the role of scientific thought», décadas antes de que existiera la ingeniería de software tal como la conocemos, ya había identificado el cuello de botella real. No era la máquina, sino la cabeza de quien programa. “Compared with what could be known, we have very, very small heads.” Separar las preocupaciones, para Dijkstra, no es una técnica de diseño entre otras, sino la única forma que conocía de ordenar el pensamiento con eficacia. La complejidad, en esta lectura, no vive en el código. Vive en la distancia entre lo que un cráneo finito puede sostener a la vez y lo que el problema exige sostener.
John Ousterhout retomó esa idea en A Philosophy of Software Design y la volvió operativa. Define la complejidad directamente en función del lector, como “anything related to the structure of a software system that makes it hard to understand and modify the system”. No hay ninguna propiedad objetiva independiente de ese “hard to”; la dificultad es la definición. Ousterhout identifica dos causas concretas de esa dificultad. Una es la dependencia entre partes que deberían poder cambiarse por separado. La otra es lo que llama “information leakage”, cuando una decisión de diseño se filtra y termina reflejada en varios módulos a la vez, obligando a quien modifica uno a recordar los demás. Y advierte algo que combina mal con la intuición de “lo arreglamos después”. La complejidad es incremental, se acumula de a decisiones pequeñas, ninguna de las cuales parece, por sí sola, gran cosa.
Leído así, el segundo ingeniero del diff no está viendo algo distinto al primero. Está viendo lo mismo, pero paga un costo cognitivo que el primero, por experiencia previa con ese módulo o por simple suerte, no paga todavía. La complejidad, aquí, es un límite del sujeto, no una marca en el objeto.
La bisagra de Parnas
David Parnas, mucho antes que ambos, ya había encontrado el movimiento que conecta las dos posturas sin resolver la disputa entre ellas. En 1972, en «On the Criteria to Be Used in Decomposing Systems into Modules», propuso abandonar la costumbre de descomponer un sistema según un diagrama de flujo. En su lugar, la descomposición “begins with a list of difficult design decisions or design decisions which are likely to change. Each module is then designed to hide such a decision from the others.” A esto Parnas lo llamó information hiding, y es la cura exacta del leakage de Ousterhout. Una decisión que permanece oculta no puede filtrarse a otros módulos. Ocultarla cumple, a la vez, dos funciones que hasta acá parecían pertenecer a bandos distintos.
Del lado del objeto, ocultar la decisión evita que se entrelace con las demás. Es el gesto de Hickey, sin nombrarlo. Una decisión escondida no puede trenzarse con otra que nunca la toca. Del lado del sujeto, ocultar la decisión reduce lo que hace falta tener en la cabeza para trabajar con el resto del sistema. Es la misma operación de Dijkstra y Ousterhout. Hay menos que recordar, menos oportunidad de que la cabeza finita se desborde.
Parnas muestra que ambas escuelas convergen en el mismo remedio, sin explicar por qué algo puede ser, a la vez, una propiedad del código y un límite de quien lo lee. Ese porqué queda abierto.
Lo que el pliegue no cuesta hasta que alguien lo desata
Aquí conviene conceder algo primero. Hickey tiene razón. El entrelazamiento existe, se puede señalar. Pero hay una segunda observación, tan simple que resulta fácil de pasar por alto. Un artefacto entrelazado que nadie lee no cuesta nada. El pliegue está ahí, es real, pero está inerte. Nadie paga por él hasta que alguien intenta desatarlo.
El costo de la complejidad no vive en el código que está quieto. Vive en el acto de comprenderlo.
Ese costo se activa en los tres síntomas que Ousterhout describe. Aparece cuando hay que modificar el sistema en varios lugares a la vez, cuando hace falta cargar más contexto en la cabeza del que cabe cómodamente, cuando no está claro qué otra pieza también hay que tocar. Ninguno de esos tres fenómenos existe en el texto estático del programa. Existen cuando alguien lo usa para pensar, para cambiar, para extender.
Dos ideas de la filosofía del lenguaje ayudan a precisar ese punto, leídas aquí con libertad. Gottlob Frege distinguió entre el referente de una expresión (aquello a lo que apunta) y su sentido (el modo en que se lo presenta). En software, esto significa que dos versiones de una misma función pueden tener idéntica conducta observable, el mismo referente, y sin embargo presentarse de formas radicalmente distintas para quien las lee. Un refactor que preserva el comportamiento cambia el modo de presentación, no lo que la función hace. Por eso el argumento “da igual, hace lo mismo” no alcanza para defender código entrelazado. Puede ser cierto en el referente y falso en el sentido, y el sentido es justamente lo que determina cuánto cuesta entenderlo.
Ludwig Wittgenstein, por su parte, sostuvo que el significado de una expresión está en su uso, no en alguna propiedad fija que posea de antemano. Trasladado al código, no se puede juzgar cuánto cuesta un módulo con solo leerlo en reposo. Se revela al modificarlo, cuando la amplificación de cambios y los “unknown unknowns” de Ousterhout aparecen o no aparecen. Comprender un sistema no es un estado mental privado que ocurre dentro del cráneo finito de Dijkstra. Es una práctica pública, algo que se hace con el código, entre personas, en el tiempo.
Ahí está la tesis. El entrelazamiento es objetivo —Hickey gana esa parte de la disputa—, pero su costo no existe hasta que se mide en comprensión, y comprender es uso.
El costo pertenece al uso
Volvamos al diff de los dos ingenieros. Ninguno estaba equivocado, y no hacía falta que estuvieran de acuerdo mirando la pantalla en silencio. El flag que entrelaza el descuento con el impuesto según el país es, objetivamente, una trenza en el código, exista o no exista alguien mirándola en este momento. Pero esa trenza no le cuesta nada a nadie hasta que alguien necesita extenderla, corregirla o explicarle a otra persona por qué existe. Ahí, y solo ahí, el pliegue se convierte en factura.
Alan Kay, en su charla de OOPSLA 1997 «The Computer Revolution Hasn’t Happened Yet», describió los sistemas construidos sin ninguna práctica compartida de comprensión con la imagen de una pirámide egipcia levantada a fuerza bruta, millones de bloques apilados uno sobre otro sin integridad estructural. Fuerza bruta es lo que queda cuando nadie más puede leer lo que uno entrelazó.
Frente a esa pirámide, Kay proponía organizar el software como la vida organiza la materia, en células que ocultan su interior tras una membrana y se comunican solo por mensajes. Las células escalan por factores de un billón (un millón de millones) mientras una pila de bloques colapsa, por organización y no por acumulación. Su regla es una sola.
You must not let the interior of any one of these things be a factor in the computation of the whole.
No dejar que el interior de un objeto pese en el cómputo del todo se enseña como una técnica y es mucho más que una técnica. Se llama encapsular, y no es esconder los atributos tras un getter que igual los expone. Es contener la complejidad tras una frontera, para que el interior de una parte no se derrame sobre el resto. Es el único modo que conocemos de hacer tratable lo complejo, y es lo que encontraron por caminos distintos el information hiding de Parnas, la membrana de la célula de Kay, el no entrelazar de Hickey y el separar de Dijkstra. Kay no inventó ninguna de esas ideas. Parnas y Dijkstra le son contemporáneos, Hickey llegó mucho después. Lo suyo fue reunirlas y volverlas arquitectura, aunque no la de clases y herencia que casi todos imaginan cuando escuchan OOP. Para Kay lo central nunca fue el objeto sino el mensaje, tanto que terminó lamentando la palabra que él mismo había acuñado por empujar a todos hacia la idea menor. Ninguno de ellos hablaba de código, en el fondo. Hablaban de dónde poner los cortes.
Y donde caigan esos cortes decide quién paga. El costo de vivir con ellos nunca lo carga una sola cabeza. Se reparte en el equipo, se hereda de quien escribió antes, se salda con cada pull request. Un pliegue que su autor entendía sin esfuerzo puede volverse una factura impagable para quien lo hereda seis meses después. Eso es lo que de verdad cuesta la complejidad. No lo que el código entrelaza, sino lo que ese entrelazamiento le exige a la práctica compartida que tiene que seguir leyéndolo y cambiándolo.