C# - Princípio DRY (revisitado)


  Hoje vamos recordar o princípio DRY - Don´t Repeat Yourself

O princípio DRY (Don't Repeat Yourself) é um princípio de desenvolvimento de software que promove a eliminação de duplicação de código e enfatiza a importância de evitar a repetição desnecessária de informações ou lógica em um sistema, pois a duplicação de código pode levar a problemas de manutenção, aumento da complexidade e inconsistências.

O princípio DRY pode ser resumido pela seguinte frase: "Toda parte do conhecimento deve ter uma representação única, não ambígua e autoritativa no sistema".

 Este princípio foi popularizado pelo livro "The Pragmatic Programmer" de Andrew Hunt e David Thomas.

Aqui estão algumas informações sobre como o princípio DRY atua, como funciona, como pode ser usado e onde pode ser aplicado:

  1. Evitar duplicação: O princípio DRY visa eliminar a duplicação de código, seja em forma de repetição literal de trechos de código, repetição de lógica semelhante ou redundância de informações. Ao evitar a duplicação, o código se torna mais conciso, fácil de manter e menos propenso a erros.
     
  2. Centralização do conhecimento: O DRY incentiva a centralização do conhecimento em um único lugar no sistema. Em vez de ter a mesma lógica espalhada em vários lugares, é preferível ter um único local onde a lógica é definida e referenciada sempre que necessário. Isso facilita a manutenção e evita inconsistências.
     
  3. Reutilização de código: O DRY promove a reutilização de código existente. Em vez de duplicar código, é preferível criar abstrações ou componentes reutilizáveis que possam ser compartilhados em diferentes partes do sistema. Dessa forma, uma única alteração nesses componentes afeta todas as suas instâncias.
     
  4. Simplificação e legibilidade: Ao eliminar a duplicação, o código se torna mais simples, conciso e legível. A lógica é expressa em um único lugar, o que facilita a compreensão e a depuração. Além disso, alterações futuras são mais fáceis de serem feitas, pois só precisam ser aplicadas em um único local.

O princípio DRY pode ser aplicado em diversos níveis no desenvolvimento de software, desde a granularidade do código até a arquitetura do sistema. Alguns exemplos de onde o DRY pode ser usado incluem:

A seguir temos alguns exemplos básicos mostrando o uso deste princípio:

1: Evitar duplicação de código

// Exemplo não DRY
public void CalcularAreaCirculo(double raio)
{
  
double area = Math.PI * raio * raio;
   Console.WriteLine(
"A área do círculo é: " + area);
}

public void CalcularPerimetroCirculo(double raio)
{
  
double perimetro = 2 * Math.PI * raio;
   Console.WriteLine(
"O perímetro do círculo é: " + perimetro);
}

DRY :

// Exemplo DRY
public
void CalcularCirculo(double raio)
{
  
double area = Math.PI * raio * raio;
  
double perimetro = 2 * Math.PI * raio;
   Console.WriteLine(
"A área do círculo é: " + area);
   Console.WriteLine(
"O perímetro do círculo é: " + perimetro);
}

No exemplo acima, inicialmente temos duas funções separadas para calcular a área e o perímetro de um círculo. Isso resulta em duplicação de código.

No exemplo DRY, a duplicação é eliminada, criando uma única função CalcularCirculo que calcula tanto a área quanto o perímetro. Isso reduz a repetição de código e facilita a manutenção, já que qualquer alteração na fórmula do círculo precisa ser feita em um único local.

2: Centralizar informações comuns

// Exemplo não DRY
public
void ExibirErro(string mensagem)
{
  Console.WriteLine(
"Erro: " + mensagem);
}

public void ExibirAviso(string mensagem)
{
  Console.WriteLine(
"Aviso: " + mensagem);
}

public void ExibirInformacao(string mensagem)
{
  Console.WriteLine(
"Informação: " + mensagem);
}

Usando DRY:

// Exemplo DRY
public
void ExibirMensagem(string tipo, string mensagem)
{
  Console.WriteLine(tipo +
": " + mensagem);
}

Neste exemplo, inicialmente temos três funções separadas para exibir diferentes tipos de mensagens (erro, aviso e informação). Isso resulta em duplicação de código.

No exemplo DRY, a duplicação é evitada, criando uma única função ExibirMensagem que recebe um parâmetro adicional indicando o tipo de mensagem a ser exibida. Isso centraliza a lógica comum e reduz a repetição de código.

3: Reutilização de código com herança

// Exemplo não DRY
public
class Retangulo
{
 
public double Largura { get; set; }
 
public double Altura { get; set; }
 
public virtual double CalcularArea()
  {
    
return Largura * Altura;
  }
}

public class Quadrado : Retangulo
{
  
public override double CalcularArea()
   {
    
return Largura * Largura;
   }
}

Usando DRY:

public class Figura
{
 
public double Largura { get; set; }
 
public double Altura { get; set; }
 
public virtual double CalcularArea()
  {
   
return Largura * Altura;
  }
}

public class Quadrado : Figura
{
 
public override double CalcularArea()
  {
   
return Largura * Largura;
  }
}

Neste exemplo, inicialmente temos duas classes, Retangulo e Quadrado, onde Quadrado herda de Retangulo. No entanto, essa hierarquia não segue o princípio DRY, pois há duplicação de código na implementação de CalcularArea.

No exemplo DRY, a duplicação é evitada, criando uma classe base Figura que contém as propriedades Largura
e Altura, bem como a implementação comum de CalcularArea. A classe Quadrado herda de Figura e substitui apenas o método necessário, mantendo o código mais limpo e reutilizando a lógica da classe base.

4- Utilizar constantes em vez de valores literais repetidos

// Exemplo não DRY
public
double CalcularCircunferencia(double raio)
{
  
return 2 * Math.PI * raio;
}

public double CalcularAreaCirculo(double raio)
{
  
return Math.PI * raio * raio;
}

usando DRY:

private const double Pi = Math.PI;

public double CalcularCircunferencia(double raio)
{
  
return 2 * Pi * raio;
}

public double CalcularAreaCirculo(double raio)
{

   return
Pi * raio * raio;
}

Neste exemplo, inicialmente temos duas funções que calculam a circunferência e a área de um círculo. Ambas as funções repetem o valor de Math.PI. No exemplo DRY, o valor de Math.PI é armazenado em uma constante Pi, evitando a repetição desnecessária.

5- Utilizar herança para compartilhar comportamento comum

// Exemplo não DRY
public
class Animal
{
 
public virtual void EmitirSom()
  {
    Console.WriteLine(
"O animal emite um som.");
  }
}

public class Gato : Animal
{
  
public override void EmitirSom()
   {
     Console.WriteLine(
"O gato mia.");
   }
}

public class Cachorro : Animal
{
 
public override void EmitirSom()
  {
    Console.WriteLine(
"O cachorro late.");
  }
}

usando DRY:

public abstract class Animal
{
 
public abstract void EmitirSom();
}

public class Gato : Animal
{
 
public override void EmitirSom()
  {
    Console.WriteLine(
"O gato mia.");
  }
}

public
class Cachorro : Animal
{
 
public override void EmitirSom()
  {
    Console.WriteLine(
"O cachorro late.");
  }
}

Aqui, inicialmente temos uma classe base Animal e subclasses Gato e Cachorro, onde cada animal emite um som específico. No exemplo DRY, a classe base Animal é definida como abstrata, com um método abstrato EmitirSom().

As subclasses Gato e Cachorro herdam de Animal e fornecem suas próprias implementações do método EmitirSom(). Dessa forma, o comportamento comum é compartilhado através da herança, evitando duplicação desnecessária de código.

6- Utilizar métodos genéricos para evitar repetição de código similar

// Exemplo não DRY
public
void ImprimirInteiros(List<int> numeros)
{
 
foreach (int numero in numeros)
  {
    Console.WriteLine(numero);
  }
}

public void ImprimirStrings(List<string> strings)
{
 
foreach (string str in strings)
  {
    Console.WriteLine(str);
  }
}

usando DRY:

public void ImprimirElementos<T>(List<T> elementos)
{
 
foreach (T elemento in elementos)
  {
    Console.WriteLine(elemento);
  }
}

Neste exemplo, inicialmente temos duas funções que imprimem elementos de listas específicas: uma para inteiros e outra para strings.

No exemplo DRY, a duplicação é evitada usando um método genérico ImprimirElementos<T>() que aceita uma lista de qualquer tipo T e itera sobre os elementos para imprimi-los. Isso elimina a necessidade de escrever métodos separados para cada tipo específico.

Esses exemplos demonstram diferentes maneiras de aplicar o princípio DRY em código C#. O objetivo é evitar a duplicação desnecessária de código, centralizar informações comuns, compartilhar comportamentos similares e utilizar abstrações adequadas para promover a reutilização de código. Isso resulta em um código mais conciso, legível e fácil de manter.

E estamos conversados.

"Seja, porém, o vosso falar: Sim, sim; não, não; porque o que passa disto é procedente do mal."
Mateus 5:37

Referências:


José Carlos Macoratti