Salvador Llaberia · Software Architect ← Todos los artículos

La ley de Demeter (no hables con extraños)

Portada del artículo La ley de Demeter (no hables con extraños)

Imagina una aplicación que gestiona pedidos. Para pintar la etiqueta de un envío, alguien escribe esto y a primera vista no le parece nada mal.

public function shippingLabel(Order $order): string
{
    return sprintf(
        '%s, %s',
        $order->customer()->name(),
        $order->customer()->address()->city(),
    );
}

Tres flechas y dos llamadas de más. Se lee como una frase porque el código está diciendo en voz alta lo que piensa: ve a por el pedido, sácale el cliente, del cliente sácale la dirección y de la dirección sácale la ciudad. Y funciona, el test pasa y el asunto se archiva.

El problema es todo lo que esa frase promete. Para escribirla has tenido que saber que un pedido tiene un cliente, que el cliente tiene una dirección y que la dirección tiene una ciudad. Nada de eso lo has decidido tú, pero acabas de firmarlo. Y lo has firmado en el sitio equivocado, porque quien pinta una etiqueta no debería tener ni idea de cómo se organiza la información de contacto.

La ley de Demeter tiene que ver exactamente con eso.

Cadenas de llamadas

La línea de arriba es un tren de llamadas y, como los trenes, cuanto más largo más cuesta frenarlo y más caro sale cambiar de vía. El mismo vicio aparece en cualquier esquina del proyecto.

$total = $order->lines()->first()->product()->price();
$postcode = $order->customer()->address()->postcode();
$isVip = $order->customer()->segment()->isVip();
$cost = $this->rates->for($order->customer()->address()->postcode());

Ninguna de esas cuatro líneas tiene nada de raro y todas dicen lo mismo. Un extremo del programa pide un dato y, de paso, se declara conocedor de la forma entera del grafo de objetos del dominio. El pedido sabe cómo llegar al cliente, tú sabes cómo llegar del cliente a la dirección, de la dirección a la ciudad y del segmento al VIP. Es decir, repartes tu conocimiento por media aplicación.

Imagina ahora el cambio de negocio que llega siempre. Resulta que un pedido se puede enviar a otra dirección distinta de la del cliente, porque es un regalo, porque es una recogida en tienda o porque el almacén trabaja con direcciones de entrega propias. En cuanto eso existe, todas las líneas que dicen $order->customer()->address() se quedan cortas y hay que salir a buscarlas una por una. Las encontrarás en el caso de uso, en la plantilla del correo, en el informe de logística y en un script que escribió un compañero para exportar pedidos. Y siempre habrá una que se te escape.

La ley: no hables con extraños

La ley de Demeter, en su versión estricta, dice que un método de un objeto solo puede llamar a los métodos de sus amigos directos, y de nadie más.

El objeto mismo, sus propios componentes, los parámetros que recibe y los objetos que él construye. Nadie más. Todo lo que no esté en esa lista es un extraño, y a un extraño no se le habla.

Por eso también se la conoce como el principio del menor conocimiento. La idea de fondo no es prohibir los getters, es acotar qué puede saber una clase. Una clase que solo habla con sus amigos no puede depender de la estructura interna de sus amigos, porque en el fondo ni la conoce. Y si no la conoce, no puede romperse el día que esa estructura cambie.

En el ejemplo del principio, el método shippingLabel recibe un Order como parámetro, así que el pedido es su amigo. Pero $order->customer() le devuelve un Customer que ya no es amigo suyo, es amigo del pedido. Y $order->customer()->address() es el amigo de un amigo, es decir, un extraño. La ley no prohíbe los getters, prohíbe usarlos como pasillo hacia lo que hay detrás.

Por qué nos importa

Cuando hablas con extraños te acoplas a ellos. Es una obviedad que se ve mejor con la ley que casi nunca falla: si para hacer una cosa necesitas conocer la estructura de tres objetos encadenados, cualquier cambio en cualquiera de esos tres objetos es un cambio que te afecta. El acoplamiento no es una impresión, es la cantidad de código que puede obligarte a cambiar mañana, y con cada flecha acabas de multiplicarla.

También se escapa el conocimiento del negocio. Que el envío use la dirección del cliente es una decisión, no un accidente. Si el día de mañana negocio dice que la dirección de entrega es la del pedido y no la del cliente, ¿dónde está escrita esa regla? En ningún sitio. Está repartida dentro de cada cadena de llamadas y, por tanto, no está en ningún sitio de verdad. Es el mismo síntoma que contaba en el artículo de Tell Don't Ask: una regla del dominio viviendo en la capa que no le corresponde, repetida en cada sitio que la necesita y olvidada en uno de ellos.

Y hay un coste que se paga en los tests, aunque este se ve antes de que llegue el fallo. Una cadena como $order->customer()->address()->city() obliga a montar tres objetos para probar la composición una etiqueta. En cuanto el doble de Order tiene que devolver un doble de Customer que a su vez devuelve un doble de Address, el test deja de hablar del negocio y empieza a hablar de cómo está cableado el grafo.

Hablar con los amigos

Arreglar el ejemplo no es poner un getter más corto, es decidir quién tiene que saber qué. La dirección de entrega de un pedido es asunto del pedido, no del que pinta la etiqueta. Así que la conversación se mueve hacia dentro.

final class Order
{
    private function __construct(
        private readonly Customer $customer,
        private readonly Address $shippingAddress,
    ) {
    }

    public function shippingLabel(): string
    {
        return sprintf(
            '%s, %s',
            $this->shippingAddress->name(),
            $this->shippingAddress->city(),
        );
    }
}

El que llama ahora solo tiene un amigo y una llamada.

public function shippingLabel(Order $order): string
{
    return $order->shippingLabel();
}

Fíjate en que dentro de Order también hay flechas, y ahora sí están bien puestas. $this->shippingAddress es un componente del propio objeto, es decir, un amigo directo, y pedirle la ciudad a un amigo es exactamente lo que permite la ley. La diferencia no está en la forma de la línea, está en quién tiene el conocimiento. Antes el grafo lo conocía el que pinta la etiqueta. Ahora lo conoce el pedido, que es el único con algo que decir sobre dónde se envía.

Cuando el negocio cambie y la dirección de entrega dependa de si es un regalo o una recogida en tienda, el cambio se escribe dentro de shippingLabel y no hay ni una llamada más que tocar. Esa es toda la ganancia, y es exactamente la misma que persigue Tell Don't Ask.

El patrón se repite con los otros trenes. El total de un pedido deja de calcularse sumando precios desde fuera.

// antes
$total = $order->lines()->first()->product()->price();

// después
$total = $order->total();

Y el coste del envío deja de obligar a la aplicación a saberse el código postal por dentro del cliente.

// antes
$cost = $this->rates->for($order->customer()->address()->postcode());

// después
$cost = $this->rates->for($order->shippingPostcode());

En los dos casos el que llama acaba pidiendo un dato que el pedido sabe darse porque es suyo. Nadie de fuera ha tenido que abrir el cliente, ni la dirección, ni la línea, ni el producto.

La relación con Tell Don't Ask

Los dos principios se pisan y conviene tener claro dónde termina uno y dónde empieza el otro, porque se citan como si fueran lo mismo y no lo son.

Tell Don't Ask dice quién decide: la regla vive dentro del objeto y desde fuera solo se da la orden. La ley de Demeter dice a quién puedes hablar: solo a tus vecinos directos. Si cumples Tell Don't Ask suele cumplirse Demeter sola, y de hecho es lo que contaba al final del artículo anterior, porque en cuanto le dices al objeto lo que quieres dejas de navegar dentro de él para buscar con qué decidir. La orden sustituye a la cadena.

Pero no funciona al revés, y aquí está lo importante: puedes cumplir Demeter y no Tell Don't Ask.

if ($order->shippingPostcode() === '46001') {
    $cost = $this->rates->sameDayCost();
}

Una sola llamada, un amigo, cero extraños. Y sin embargo es un caso de manual de Tell Don't Ask al revés: preguntas un dato para decidir tú y actuar después. El conocimiento de que Valencia capital admite entrega en el día no vive en ningún objeto del dominio, vive en el if. Demeter no tiene nada que decir sobre eso, porque no es un problema de a quién hablas, es un problema de quién decide.

Entendido así, los dos principios miran cosas distintas. Demeter acota el conocimiento estructural, qué forma tienen tus colaboradores. Tell Don't Ask acota el conocimiento de comportamiento, quién toma las decisiones. Cuando los aplicas juntos, la clase que llama no sabe ni cómo está hecho el objeto ni qué reglas lo gobiernan. Solo sabe pedirle cosas.

Cuándo sí se puede encadenar

Como toda regla de diseño, esta es una heurística y se rompe con criterio. Hay sitios donde encadenar no solo es correcto, es lo natural.

Las interfaces fluidas (fluent interface) y los constructores lo hacen por definición. $query->where('active', true)->orderBy('created_at') no navega por un grafo, mantiene una conversación con el mismo objeto y con el mismo propósito. Lo mismo vale para un valor encadenado consigo mismo, como Money::of(1000)->in('EUR')->format(). Aquí no hay extraños, hay un diálogo con un amigo que sigue siendo el mismo.

Los datos también son una excepción. Un DTO, un array o un objeto de solo lectura que representa una fila no son colaboradores con comportamiento, son estructuras. Acceder a $dto->address->city no es hablar con un extraño, es leer un dato compuesto, y la ley se pensó para mensajes entre objetos, no para esto.

Y luego está la frontera del sistema. Un adaptador que serializa un pedido a JSON, un repositorio que reconstruye una entidad desde la base de datos o una plantilla que pinta una pantalla necesitan leer medio grafo, y ahí es correcto leerlo. La regla que sí conviene mantener es que ese sea el único sitio. Si el mapeo vive en el adaptador, el modelo puede quedarse limpio y la estructura solo se conoce donde toca.

El caso que más cuesta ver es el de las colecciones. $order->lines() que devuelve una colección sobre la que filtras y sumas no es una violación si la colección es un objeto de primera clase con comportamiento propio. Se vuelve violación cuando la usas de pasillo para llegar a los elementos y operar sobre ellos desde fuera. La pregunta útil no es cuántas flechas hay, es si el objeto que hay al final de la flecha te pertenece o pertenece a otro.

La pregunta útil

No hace falta convertir la ley en una religión ni envolver cada acceso en un método que delegue para cumplirla. De hecho, delegar a lo tonto acaba creando una clase que sabe todo de todos, un dios que conoce a todo el mundo para poder contestar por todos. Eso es peor que las cadenas.

La pregunta que de verdad ayuda delante de un tren de llamadas es esta: este eslabón, ¿me pertenece o me lo estoy saltando? Si el objeto al final de la flecha es un componente con el que tengo una relación directa, adelante. Si es el vecino del vecino, lo que en realidad falta es preguntarse por qué datos de un extraño están decidiendo aquí. Casi siempre la respuesta es que esa decisión pertenecía al objeto al que sí le hablabas.

La ley de Demeter no prohíbe encadenar llamadas, va de tener claro quién es tu amigo y quién un extraño. Habla con tus amigos, que para eso lo son, y deja que sean ellos quienes hablen con los suyos.