El principio No te repitas ( DRY )

Compartir esta publicación

El Principio No Te Repitas (Don’t Repeat Yourself) es uno de los principios en el desarrollo de software cuyo principal objetivo es evitar la duplicación de código.

«Every piece of knowledge must have a single, unambiguous, authoritative representation within a system»
Andrew Hunt y David Thomas: The Pragmatic Programmer: From Journeyman to Master

Básicamente, cuando descubres que estás escribiendo el mismo código una y otra vez, todo indica que seguramente habrá una mejor manera de hacerlo.

 

Caso Práctico: Principio No Te Repitas

Vamos a escribir un ejemplo, buscamos si hay alguna manera de mejorarlo y lo refactorizamos.
Tenemos un simple class Report que recibe información y la imprime vía consola en un formato determinado.

class Report
{
   public function show(array $data)
   {
       echo "Report: " . ucwords(strtolower($data["name"])) . "\n";
       echo "Product: " . ucwords(strtolower($data["product"])) . "\n";
       echo "Start date: " . date("Y/m/d", $data["startDate"]) . "\n";
       echo "End date: " . date("Y/m/d", $data["endDate"]) . "\n";
       echo "Total: " . $data["total"] . "\n";
       echo "Average x day: " . floor($data["total"] / 365) . "\n";
       echo "Average x week: " . floor($data["total"] / 52) . "\n";
   }
}

Este código podría mejorarse pero por ahora nos es suficiente.
Tenemos una nueva petición para el report: poder guardarlo en un fichero. ¿Fácil no? Hagamos un copiar / pegar, unos pequeños cambios y en un par de minutos ya hemos terminado:

class Report
{
   public function show(array $data)
   {
       echo "Report: " . ucwords(strtolower($data["name"])) . "\n";
       echo "Product: " . ucwords(strtolower($data["product"])) . "\n";
       echo "Start date: " . date("Y/m/d", $data["startDate"]) . "\n";
       echo "End date: " . date("Y/m/d", $data["endDate"]) . "\n";
       echo "Total: " . $data["total"] . "\n";
       echo "Average x day: " . floor($data["total"] / 365) . "\n";
       echo "Average x week: " . floor($data["total"] / 52) . "\n";
       echo "Average x month: " . floor($data["total"] / 12) . "\n";
   }
   public function saveToFile(array $data)
   {
       $report = '';
       $report .= "Report: " . ucwords(strtolower($data["name"])) . "\n";
       $report .= "Product: " . ucwords(strtolower($data["product"])) . "\n";
       $report .= "Start date: " . date("Y/m/d", $data["startDate"]) . "\n";
       $report .= "End date: " . date("Y/m/d", $data["endDate"]) . "\n";
       $report .= "Total: " . $data["total"] . "\n";
       $report .= "Average x day: " . floor($data["total"] / 365) . "\n";
       $report .= "Average x week: " . floor($data["total"] / 52) . "\n";
       $report .= "Average x month: " . floor($data["total"] / 12) . "\n";
       file_put_contents("./report.txt", $report);
   }
}

Espera, espera!! ¿De verdad hemos acabado aquí? Hemos hecho lo que nos han pedido, pero no acaba de estar del todo bien desde la parte técnica. Está lleno de repeticiones por todas partes ( aunque el código solo sean pocas líneas ). Encontramos WET por todas partes (Write Everything Twice – Escribe todo dos veces : contrario a lo que buscamos en DRY).

  Scala Generics III: Generalized type constraints

Hagamos un poco de refactor. Estos dos métodos prácticamente hacen lo mismo y la única diferencia está en el resultado final. Un buen inicio sería extraer el cuerpo en un método nuevo.
De esta forma tendremos una única fuente de verdad: el report solo se crea desde un único punto. Los demás métodos tendrán la responsabilidad única de decidir qué hacer con el report.

class Report
{
   public function show(array $data)
   {
       echo $this->createReport($data);
   }
   public function saveToFile(array $data)
   {
       file_put_contents("./report.txt", $this->createReport($data));
   }
   private function createReport(array $data): string
   {
       $report = '';
       $report .= "Report: " . ucwords(strtolower($data["name"])) . "\n";
       $report .= "Product: " . ucwords(strtolower($data["product"])) . "\n";
       $report .= "Start date: " . date("Y/m/d", $data["startDate"]) . "\n";
       $report .= "End date: " . date("Y/m/d", $data["endDate"]) . "\n";
       $report .= "Total: " . $data["total"] . "\n";
       $report .= "Average x day: " . floor($data["total"] / 365) . "\n";
       $report .= "Average x week: " . floor($data["total"] / 52) . "\n";
       $report .= "Average x month: " . floor($data["total"] / 12) . "\n";
       return $report;
   }
}

Bastante mejor ahora, ¿verdad? ¿Algo más? Hay unas pequeñas repeticiones que todavía podemos mejorar. Por ejemplo, hay las mismas transformaciones en el nombre del report y el producto:

$report .= "Report: " . ucwords(strtolower($data["name"])) . "\n";
$report .= "Product: " . ucwords(strtolower($data["product"])) . "\n";

Podemos extraer esta transformación en un nuevo método (o incluso mejor, a una librería con sus pruebas unitarias)

private function normalizeName($name): string
{
   return ucwords(strtolower($name));
}

Otra duplicación: el formato de la fecha.

$report .= "Start date: " . date("Y/m/d", $data["startDate"]) . "\n";
$report .= "End date: " . date("Y/m/d", $data["endDate"]) . "\n";

Hagamos extracción de eso también:


private function formatDate($date): string
{
   return date("Y/m/d", $date);
}

Y por último: el cálculo de la media.


$report .= "Average x day: " . floor($data["total"] / 365) . "\n";
$report .= "Average x week: " . floor($data["total"] / 52) . "\n";
$report .= "Average x month: " . floor($data["total"] / 12) . "\n";

Aunque los cálculos no sean exactamanete los mismos, podemos hacer algo como esto:


private function calculateAverage(array $data, $period): string
{
   return floor($data["total"] / $period);
}

Por lo que el Report final tendrá la siguiente forma


class Report
{
   public function show(array $data)
   {
       echo $this->createReport($data);
   }
   public function saveToFile(array $data)
   {
       file_put_contents("./report.txt", $this->createReport($data));
   }
   private function createReport(array $data)
   {
       $report = '';
       $report .= "Report: " . $this->normalizeName($data["name"]) . "\n";
       $report .= "Product: " . $this->normalizeName($data["product"]) . "\n";
       $report .= "Start date: " . $this->formatDate($data["startDate"]) . "\n";
       $report .= "End date: " . $this->formatDate($data["endDate"]) . "\n";
       $report .= "Total: " . $data["total"] . "\n";
       $report .= "Average x day: " . $this->calculateAverage($data, 365) . "\n";
       $report .= "Average x week: " . $this->calculateAverage($data, 52) . "\n";
       $report .= "Average x month: " . $this->calculateAverage($data, 12) . "\n";
       return $report;
   }
   private function formatDate($date): string
   {
       return date("Y/m/d", $date);
   }
   private function calculateAverage(array $data, $period): string
   {
       return floor($data["total"] / $period);
   }
   private function normalizeName($name): string
   {
       return ucwords(strtolower($name));
   }
}

 

  Bard: el nuevo competidor de ChatGPT

Regla de tres

Este ha sido un ejemplo básico, donde he intentado mostrar la importancia de evitar los duplicados y como mejorarlos. Obviamente, los duplicados no siempre van a ser fácil de reconocer. O para poder eliminarlos puede que sea más complejo y tener que aplicar algunos patrones de diseño, por ejemplo.
Una regla de oro en el refactoring es la Regla de tres. Repetir una vez el mismo código puede ser aceptable. Pero la tercera vez que utilizamos el mismo código, es señal inequívoca de que hay que refactorizar y solucionar la duplicación.

 

Conclusión: Principio No Te Repitas

  • Con todos estos pasos, hemos eliminado el WET de nuestro código
  • Si el report necesita un cambio, solamente se hará en un único punto. No nos hace falta aplicarlo en distintos lugares como hasta ahora.
  • Las pequeñas duplicaciones también las hemos eliminado. Los métodos contienen nombres significativos que explican lo que hacen. Mejor código.
  • Estos métodos los podemos extraer hacia alguna librería de ayuda con sus test unitarios.
  • Tengamos el principio DRY en mente la próxima vez que escribamos código.
  • No te olvides de la REGLA DE TRES

 

Si estás interesado en recibir más artículos sobre principio No te repitas, no te olvides de suscribirse a nuestro boletín mensual aquí.

Si este artículo sobre principio No te repitas te gustó, te puede interesar:

PHP funcional 

Tendencias en aplicaciónes móviles

Patrón MVP en iOS

Debugging con Charles Proxy en Android emulator

Por qué Kotlin?   

Integración Continua en iOS usando Fastlane y Jenkins  

Meetups de arquitectura de software  

  Innovaciones y tendencias de la computación en la nube

MVPP en Android

Author

  • Oriol Saludes

    Experienced Full Stack Engineer with a demonstrated history of working in the information technology and services industry. Skilled in PHP, Spring Boot, Java, Kotlin, Domain-Driven Design (DDD), TDD and Front-end Development. Strong engineering professional with a Engineer's degree focused in Computer Engineering from Universitat Oberta de Catalunya (UOC).

2 Comments

  1. Marco Castilla

    Hola, y como aplicarias DRY en tu ejemplo, si te dicen qu requieren ahora un PDF por ejemplo, pero no toda la información. por ejemplo que no quieren las fechas en este nuevo reporte.
    Se le agregaría parametros a las funciones, o se vería una nueva forma de refactorizar?

    • Oriol Saludes

      Hola Marco,
      gracias por tu comentario.
      El uso de parámetros en los métodos para que por ejemplo imprima unos textos u otros está desaconsejado, de hecho es un code smell.
      Seguramente habría otro método que creara el report sin fechas y extraer el código común entre estos métodos que crean los reports.
      Aquí tienes un buen artículo de Martin Fowler: https://martinfowler.com/bliki/FlagArgument.html
      Saludos
      Oriol

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

Suscríbete a nuestro boletín de noticias

Recibe actualizaciones de los últimos descubrimientos tecnológicos

¿Tienes un proyecto desafiante?

Podemos trabajar juntos

apiumhub software development projects barcelona
Secured By miniOrange