Diseño de software

La ley de Demeter (Law of Demeter): cuándo el encadenamiento de métodos se convierte en mal código

Al hacer una revisión de código, es posible que alguna vez recibas este comentario.

4 min de lectura
Imagen de portada de La ley de Demeter (Law of Demeter): cuándo el encadenamiento de métodos se convierte en mal código

Al hacer una revisión de código, es posible que alguna vez recibas este comentario.

«¿No hay demasiados puntos (.) en esta línea?»

Escribirlo todo seguido en una sola línea parece incluso más limpio, pero ¿cuál es el problema?

La clave de esta historia es la ley de Demeter.

Hoy veremos qué es la ley de Demeter y, con ejemplos, por qué algunos encadenamientos de métodos están bien y otros se convierten en mal código.

Empecemos por la conclusión.

La ley de Demeter es la regla de «no hables con objetos desconocidos».

El encadenamiento de métodos no es malo por sí mismo; se convierte en mal código cuando penetra en los detalles internos de otros objetos.

Con recordar esta frase ya te llevarás la mitad de lo importante de este artículo.


Qué es la ley de Demeter

La ley de Demeter (Law of Demeter) también se conoce como el «principio del mínimo conocimiento».

Dicho de forma sencilla:

Un objeto solo debería comunicarse con sus «amigos cercanos», es decir, con los objetos que conoce directamente.

Los problemas empiezan cuando se usan también los amigos de los amigos y los amigos de estos.

Normalmente, los objetos que se pueden invocar dentro de un método se resumen así:

  • Métodos del propio objeto (self)
  • Objetos recibidos como parámetros del método
  • Objetos creados directamente dentro del método
  • Objetos de las propiedades (miembros) que posee el objeto

La idea es comunicarse solo dentro de este ámbito.

De aquí surgió la conocida regla práctica de «un solo punto por línea».

Por supuesto, el número de puntos es solo una pista, no una regla absoluta.


¿Por qué el encadenamiento de métodos puede ser mal código?

Te lo mostraré con código que encontré en la práctica.

El caso consistía en obtener el nombre de la ciudad del cliente de un pedido.

// Se profundiza consecutivamente desde el pedido hasta el cliente, la dirección y la ciudad
let city = order.customer
                .address
                .city
                .name

A primera vista parece limpio, ¿verdad?

Pero esta línea significa que Order conoce todo el interior de Customer, además de Address y City dentro de este.

Aquí aparece el problema.

Si cambia la estructura de Address o desaparece City, este código también se rompe.

Como estamos metiendo la mano hasta los cajones de la casa de otra persona, nuestro código se rompe cuando cambia la estructura de esa casa.

Este tipo de encadenamiento que recorre el grafo de objetos suele llamarse «accidente de tren (train wreck)».

Encadenamiento de accidente de tren, con vagones unidos en fila
Encadenamiento de accidente de tren, con vagones unidos en fila

Porque tiene el aspecto de vagones unidos uno tras otro.

Esta línea, con puntos encadenados uno tras otro, es precisamente un accidente de tren
Esta línea, con puntos encadenados uno tras otro, es precisamente un accidente de tren

Entonces, ¿cómo se distingue un encadenamiento bueno de uno malo?

Esta es la parte más importante del artículo.

No todo encadenamiento de métodos es malo.

El criterio es uno solo.

«¿Mientras encadenas, sigues extrayendo objetos internos de otros objetos?»

Como en el ejemplo anterior, profundizar continuamente en objetos ajenos distintos —customer → address → city— constituye una infracción.

En cambio, está bien encadenar cuando se manejan objetos del mismo tipo y cada operación devuelve el propio objeto, como se muestra abajo.

// filter·map El encadenamiento no infringe la regla porque cada vez devuelve el «mismo contexto»
let names = users
    .filter { $0.isActive }
    .map(\.name)

Este tipo de encadenamiento de funciones de orden superior y el patrón Builder no rebuscan en el interior de otros objetos, por muchos puntos que tengan.

Simplemente devuelven cada vez el propio objeto, es decir, el mismo flujo, para continuar.

La comparación entre ambos códigos queda así en una tabla.

Categoría Mal encadenamiento Buen encadenamiento
Objeto Extrae continuamente objetos internos ajenos Devuelve el mismo flujo/tipo
Ejemplo order.customer.address filter { }.map { }
Acoplamiento Alto (vulnerable a cambios estructurales) Bajo
Evaluación Infracción de Demeter No es una infracción

Cómo corregir el código que infringe la regla

La solución es más sencilla de lo que parece.

Solo tienes que pensar en «Dilo, no lo preguntes (Tell, Don’t Ask)».

En lugar de extraer los detalles internos, pídele al objeto que haga el trabajo.

En el ejemplo anterior de la ciudad, se puede cambiar así.

// OrderSe pregunta solo por el resultado necesario
let city = order.shippingCity
Dilo, no lo preguntes: al parecer, basta con esta línea
Dilo, no lo preguntes: al parecer, basta con esta línea

Dentro de Order, hacemos que se recorran Customer y Address y se devuelva la ciudad automáticamente.

Así, aunque cambie la estructura de Address, el código externo permanece intacto.

El alcance de los cambios queda completamente limitado al interior de Order.

Al refactorizarlo así, comprobé que después se redujo mucho el número de lugares que había que tocar al cambiar la estructura.


En resumen:

La ley de Demeter no dice que haya que contar los puntos, sino que no se debe manipular sin más el interior de otros objetos.

La próxima vez que encuentres un encadenamiento largo, hazte una sola pregunta.

«¿Estoy abriendo los cajones de otra persona?»

Con solo plantear esta pregunta, el código se vuelve mucho más limpio.