Mejora tu código usando tell don’t ask

tell dont ask

Tell don’t ask es un estilo de programación en el cual los objetos solo toman decisiones sobre sus datos internos o sobre los que reciben como parámetros y no deben de hacerlo sobre los datos internos de otros objetos.

Es decir que en lugar de pedirle a los objetos datos, debemos decirles que hacer y esperar el resultado de esa acción, con esto evitamos usar la información interna de los objetos para tomar decisiones fuera de ellos y luego pedirles que hacer o afectar su comportamiento interno.

Por lo cual en lugar de tener algo así.

nosotros podemos escribir algo como esto.

¿Sí notas la diferencia?, seguro estarás de acuerdo que se entiende mas el segundo ejemplo ya que es sencillo de leer.

Es muy posible que estés pensando, sí muy bien pero ese es un ejemplo muy sencillo, ¿cómo lo voy a emplear en un controlador lleno de condiciones, ciclos y consultas?

Afortunadamente uno de los síntomas que debes de observar es cuando comienzas a tener código espagueti orientado a objetos, es ese tipo de código que puede estar en un controlador y tiene muchas condiciones y ciclos operando los resultados de un objeto sin mencionar que el código se vuelve poco legible a simple vista. Así que, es muy probable que una buena parte de ese código realmente deba de estar en otro lugar, como por ejemplo dentro de un Modelo.

¿Cómo puedo evitar violar tell don’t ask?

el-codigo-estructurado

 

Para evitar caer en la trampa del código espagueti orientado a objetos no debes de perder de vista que tus clases deben de tener responsabilidades claras, es decir que cualquier lógica que afecte el estado de un objeto debe de estar dentro de él.

Qué te parece si dejamos la teoría y vemos un ejemplo de la vida real, de esos que quieres ver y que se acerca más a lo que necesitas, posiblemente en algún oscuro rincón de alguno de tus proyectos y que no quieres mencionar que existe.

No hagas lo que puede hacer el objeto por si mismo

 

Vamos a suponer que tenemos un sistema para inscripción a talleres que se realiza mediante un código que se te proporciona cuando te inscribes, para darte de alta en un taller requieres tener un código válido y además no haberte inscrito en otro taller previamente. El código relacionado con esta parte tiene el siguiente aspecto.

¿Puedes ver donde aplicar tell don’t ask? Si no es así no te preocupes, que te parece si organizamos un poco y quitamos el código repetido que se encuentra en las condiciones.

Así, podemos crear un método donde moveremos ese código duplicado como te muestro.

¿Qué te parece?, creo que ahora se ve un poco mejor y resalta el problema, ¿no lo crees?.

Si observas las dos condiciones vas a notar que tienen algo en común y es que ambas siguen el patrón de; si pasa esto, realiza esto otro y si te das cuenta esas acciones se hacen sobre los datos que regresa el modelo Participant y no se tú, pero esto tiene pinta de que el controlador está tomando decisiones que pueden estar dentro del modelo Participant.

La forma de resolver esto es trasladar esas decisiones al modelo y que éste se encargue por si mismo de responder de forma adecuada.

Que te parece si te muestro como podemos hacer eso.

Con esto, ahora solo vamos a obtener un participante si el código es válido y no está inscrito en ningún otro taller, para todo lo demás el modelo Participante lanzará excepciones que el controlador puede manejar fácilmente sin tener que hacerle preguntas al modelo para saber qué hacer.

Quitando las condiciones del controlador y reemplazando por try/catch; nuestro código finalmente debe de quedar como te muestro a continuación.

Adiós condiciones!, Ahora el código es más claro y a simple vista podemos darnos una idea de lo que se pretende en este controlador, además ya no es necesario especificar los mensajes de error ya que los trasladamos a las excepciones y con eso, podemos aprovecharlas para mandar el mensaje correspondiente a nuestra vista si algo sucede.

¿Qué sigue?

 

Practicar y experimentar en tu código, comienza mirado tus controladores y decide que partes de código corresponden a tus modelos o  a otras clases que estas usando. No va ser algo que vas a hacer en un día, te va tomar un tiempo aprender a aplicar el principio, pero si lo haces un paso a la vez muy pronto lo dominaras.

Debes de recordar que no existe nada de malo en tener objetos que nos pueden compartir  algo de su estado siempre que en el código no tomes decisiones que pueden realizar tus objetos.

Si aún tienes dudas de como distinguir en qué momento aplicar tell don’t ask solo recuerda lo siguiente.

  • No hagas lo que el objeto puede hacer por sí mismo.
  • No le pidas datos al objeto para tomar decisiones por él.

Si te ha servido este articulo por favor compartelo en tus redes sociales.

No olvides poner en práctica lo que acabas de leer y déjame saber como te va aplicando el principio en los comentarios o en slack.

Finalmente recuerda hacer un cambio a la vez.

Otros artículos que te pueden interesar:

Query scopes como buena práctica en Laravel

¿Como optimizar el consumo de memoria en Laravel?

Comparte este artículo

Entra en la discusión y deja tu comentario

  • Freddy Johanes Vargas Ramirez

    Excelente.

    • Herminio Heredia S

      Muchas gracias por el comentario !

  • Juan Contreras

    Muy buena idea me llamo mucho la atención1

    • Herminio Heredia S

      Espero que puedas ponerlo en practica y me comentes que tal te va, y muchas gracias por el comentario, me ayuda para ir planeando futuros artículos!.

  • sald19

    asi queria mejor la funcion

    public function isRegisteredAtWorkshop()
    {
    return $this->workshops->isNotEmpty();
    }

    • Herminio Heredia S

      Excelente bien aporte, esa es la idea ir haciendo cambios pequeños para ir mejorando la lógica y la legibilidad muchas gracias

    • hosmelq

      Buen refactor, cuando tienen este tipo de if es mejor solo el return, buen trabajo (Y).

  • str

    El primer ejemplo yo lo cambiaria por:

    class store {
    public function sellBeerTo(Person $sebastian, $beer)
    {
    if (!$sebastian->isAdult()) {
    throw new Exception(“{$sebastian->name} is underage.”);
    }
    $sebastian->getProduct($beer);
    }

    }

    Moviendo la validación al modelo, para no estar haciendo la validación cada vez que mando a llamar el método

    • Herminio Heredia S

      Totalmente de acuerdo ese sería el siguiente paso, muchas gracias por el aporte creo que complementa muy bien el ejemplo inicial.

    • Hola.
      De hecho, yo movería la consulta al propio modelo de Person, no en Store, no creéis?

      class Store {
      function sellBeerTo(Person $sebastian, Product $beer)
      {
      $sebastian->getProduct($beer);
      }
      }

      /***/

      class Person {
      function getProduct(Product $beer){
      if ( ! $this->isAdult()) {
      throw new Exception(“{$this->name} is underage.”);
      }
      $this->consume($beer); // will throw new Exception(“{$this->name} drank too much beer!!.”);
      }
      }

      • str

        No creo que sea buena idea meter la lógica en el modelo de persona, primero porque comenzaría a crecer mucho tu modelo si metés todo en la clase persona. Segundo, porque entonces tu clase Persona ya no puede ser reusada en otro proyecto ya que depende de la clase Tienda. Tercero, porque es regla de negocio de la tienda y no de la persona la validación, al dejárselo a la persona se puede saltar la validación a su conveniencia

        • Gracias por tu comentario
          No sabía si se trataba de un ejemplo didáctico o un caso real, pero está claro que dónde hacer la pregunta dependerá del Dominio, y por lo tanto influye a quien Tell, but dont ask 😉
          Un saludo!
          PD: Ya que estamos en el tema, traslado una pregunta a https://laraveles.com/conociendo-la-inyeccion-dependencias/

      • str

        Además, el método Person->getProduct recibe un AbstractProduct para que pueda comprar cualquier cosa, por lo que la validación de mayoría de edad no aplicaría.

  • freireP

    Muy bien explicado.

    • Herminio Heredia S

      Gracias freire, tome en cuenta tu consejo de la vez pasada

  • Modelos gordos!, es recomendable tener modelos anémicos o en forma xD. Yo pondría esa lógica en un servicio.

    • Otra cosa que gano al tener modelos anémicos es que la lógica de negocio puede ser exportada a otros frameworks o proyectos sin dolor.

  • hosmelq

    Este condición esta mal, no deberia de ser negada.

    Si el usuario no esta registrado en algún workshop retorna false, así que entrara al if cuando el usuario no este en algún workshop.

    • Herminio Heredia S

      El error es intencional para que se pueda opinar y nutrir el articulo, tu solución muy adecuada y con el plus de que sirve para versiones menores a la 5.4. También es muy bueno el aporte de @sald19:disqus.

      Muchas gracias por el aporte de verdad esto nutre el articulo mas que la explicación

      • hosmelq
        • Totalmente de acuerdo, pero solo funciona para esas dos versiones, en cambio isEmpty funciona desde la 5.1 y anterior hasta la versión 5.4 si yo tengo un paquete que mantener puedo tranquilamente dejar isEmpty y mis pruebas, van a seguir pasando. Cuando salga la siguiente versión LTS el cambio es mínimo.

          Pero ese es mi caso, si estás desarrollando en este momento para 5.3 o 5.4 es mejor utilizar lo que sugieres ya que visualmente es más claro ver un isNotEmpty

      • Si el método $this->workshops->isEmpty() ya retorna un bool, veo completamente innecesario el uso del condicional, es como obviar demasiado. Por lo que solo bastaría hacer esto:
        public function isRegisteredAtWorkshop()
        {
        return $this->workshops->isEmpty();
        }

        • hosmelq

          Si usas isEmpty tienes que negarlo.

Veces