La Ley de Demeter, la que casi nadie aplica ¿Es bueno hacer muchas llamadas encadenadas a métodos?

Hace ya unos meses, bajo el proyecto The Smart Software Quality, realizamos un estudio de la mantenibilidad de los 1000 proyectos Java de GitHub mejor valorados por los usuarios (puedes verlo aquí: Hemos estudiado la calidad de 1000 proyectos GitHub bajo el marco de la ISO 25000).
Al analizar los resultados, una de las cosas que más nos sorprendió es que de entre todas las reglas de acoplamiento de PMD que sacamos, la Ley de Demeter fue la más incumplida (con un total de 99.09% de ocurrencias de las ocurrencias totales de reglas de acoplamiento).
¿Por qué ese número tan alto de violaciones de la Ley de Demeter? Veamos qué dice esta regla para entenderlo mejor.

Ley de Demeter: “Habla solo con tus amigos cercanos. No hables con extraños.”

Allá por 1980, un grupo de programadores que trabajaban en un proyecto llamado el sistema Demeter, se dieron cuenta de que ciertos aspectos de su código orientado a objetos hacían que fuera más fácil de mantener y cambiar. Por ejemplo un bajo acoplamiento entre objetos, ocultar la información, entre otras cosas.
Por ello quisieron agrupar todas estas características que veían que mejoraban la mantenibilidad del software en una heurística, en una regla, que los programadores pudieran aplicar en su código para mejorarlo. Así llegaron a la Ley de Demeter.
Formalmente, la Ley de Demeter dice que un método de un objeto solo puede llamar a métodos de:
1 – El propio objeto.
2 – Un argumento del método.
3 – Cualquier objeto creado dentro del método.
4 – Cualquier propiedad/campo directo del propio objeto.
Así un ejemplo de seguir la Ley de Demeter sería el siguiente (nota: el código no es perfecto ni hay que tomarlo como un ejemplo a seguir, solamente quiero ilustrar las cosas que se pueden hacer según la Ley de Demeter. Al lado de cada comentario está indicado a qué número se refiere de lo dicho anteriormente.):
leyDemeter

Incumpliendo la Ley de Demeter

Una de las principales cosas que la Ley de Demeter quiere evitar que hagas es esto:
objetoA.metodoDeA().metodoDeB().metodoDeC();
Puede que pienses que algo así está bien, es mucho más legible y ocupa menos líneas… Pero tiene sus problemas.
Cuando escribes código de este tipo, estás expuesto a los posibles cambios que puedan ocurrir en la clase del objetoA, del objetoB y del objetoC.
– ¿Qué pasa si el objetoA en el futuro cambia, y ya no necesita una referencia al objetoB?
– ¿Y si luego el objetoB ya no necesita una referencia al objetoC?
– Además, esta clase no es reutilizable, porque para usarla necesitas también las clases B y C.

Beneficios de cumplir la Ley de Demeter

– Se reducen las dependencias entre clases y el acoplamiento.
– Se vuelve más sencillo reutilizar las clases.
– El código es más fácil de probar.
– El código es más mantenible, más flexible a los cambios.

Pero nadie la usa…

Resulta curioso, que siendo GitHub un servicio de alojamiento software tan utilizado y donde el código de los proyectos está sujeto a muchas revisiones, la Ley de Demeter sea una de las reglas más violadas en cuanto acoplamiento, con una cifra tan escandalosa.
¿Es que en general no conocemos la Ley de Demeter o no la llevamos a la práctica? ¿Por qué? ¿No la entendemos?
¿Qué opináis?

Javier Garzás

13 comentarios en “La Ley de Demeter, la que casi nadie aplica ¿Es bueno hacer muchas llamadas encadenadas a métodos?”

  1. La ley no la conosco pero me suena simple code de uncle bob. Me párese muy buenas prácticas para que un código sea mantenible y que otros desarrolladores puedan entenderlo fácilmente. Andamos en esa transición de colocar esas cuestiones a nuestros proyectos además de otros cambios que nos llevara mucho esfuerzo implementarlo espero no tener miedo al cambio.
    Saludos y siempre tan apropiaodos tus post en estos tiempos de cambio que estamos pasando.

  2. No la cumplen porque es imposible cumplirla 100%. Por ejemplo, en el código que has puesto ( y que ya avisas que no es perfecto 100% ), a pesar de lo simple que es, incumple la Ley de Demeter con «System.out.println»
    Un saludo

    1. Yo no lo veo del todo así, porque realmente no estamos encadenando llamadas a métodos. System es una clase, out es uno de los campos de esa clase (estático y de tipo PrintStream) y println() es un método de la clase PrintStream.
      Lo que hacemos con ese System.out.println() es llamar a un método de un campo estático de una clase final.

  3. En realidad sí que está incumpliendo… el método println() no está en ninguna de las 4 categorías indicadas en la ley.
    Pero creo que lo importante de esta ley es comprender el porqué, y no seguirla a ciegas. No tengo mucha idea de Java pero creo que la clase System se comporta como un Singleton, y no tendría sentido ir pasándolo como argumento ni guardar una referencia en la instancia de Persona.

  4. Ciertamente no cumple la ley. Podría recibir como parámetro un PrintStream o que dicho PrintStream fuese atributo de clase por ejemplo.
    Es cuestión de ir con cuidado y seguir metodologías como TDD. También he de decir que en el mundo real siempre ocurren circunstancias en las que se deben hacer concesiones. Hay que minimizarlas y ser práctico.

  5. No entiendo algunos aspectos de esta ley, cuando creas el objeto Postre dentro de ese método de esa clase, o cuando pasas por parámetro un objeto de otro tipo como Pizza, estás, de todas formas, generando esa dependencia de una clase con otra, porque en definitiva, para usar la clase Persona vas a precisar de la clase Postre y la clase Pizza.
    Si me puedes aclarar esa duda te agradezco

    1. Postre y Pizza son clases de las que depende directamente, eso no se puede evitar. Sin dependencias los objetos no podrían colaborar. Sin embargo, se incumpliría la ley de Demeter si el código profundizase en la estructura de esos objetos, p.ej si en lugar de llamar al método pizza.esDeJamon() la clase Persona lo comprobase directamente de esta manera:
      boolean esDeJamon = false;
      for (Ingrediente ingrediente : pizza.getIngredientes()) {
      if (ingrediente.getName() == «jamon»)
      esDeJamon = true;
      }

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Ir arriba