C# - A instrução switch é um code smell ?
 Hoje vamos discutir a questão : A utilização da instrução switch da linguagem C# seria considerado um má prática de programação ?

A instrução switch/case é usada para operações condicionais e às vezes o seu uso pode ser considerado um má prática fazendo com que o código 'não cheire bem' (code smell).

Na verdade não podemos ser tão rigorosos a ponto de dizer que nunca devemos usar a instrução switch/case, mas seu uso deve ser feito com muito cuidado principalmente na programação orientada a objetos.

Dependendo do cenário, quando usada na programação orientada a objetos a instrução switch/case pode causar os seguintes problemas :

  1. Violação do princípio Open/Closed ou Aberto/Fechado
    Isso ocorre quando cada vez que é necessário adicionar um novo tipo, e isso deve ser feito em uma nova instrução switch/case, promovendo a modificação do código já existente.
     
  2. Violação do princípio da Responsabilidade Única ou SRP
    Pois cada switch/case pode ser considerado uma nova responsabilidade, e assim existe mais de um motivo para realizar modificações;
     
  3. Promover a redundância de código
    Isso ocorre quando existe repetição de código e de lógica na utilização da instrução switch/case, conforme mostrado no trecho de código abaixo:
     
       switch(input) 
        { 
           case "a": 
             CriarValor("Bom dia"); 
             data+= "Bom dia";       

           case "b": 
             CriarValor("Good Morning"); 
             data+= "Good Morning";       

           case "C": 
             CriarValor("Schönen Tag"); 
             data+= "Schönen Tag"; 

             default: 
             data = string.empty; 
        }

     

  4. Dificulta a manutenção do código
    Cada instrução incluída na instrução switch inclui novos requisitos o que faz o código crescer aumentando a complexidade e a manutenção do código;

Assim de forma geral usar a instrução switch com valores primitivos não causa tanto problemas mas usá-la com tipos e objetos não é uma boa prática.

Como refatorar um código que esta usando switch/case

Na maioria dos cenários a instrução switch/case pode ser refatorada usando polimorfismo e/ou o padrão Strategy.

Para exemplificar vamos analisar o trecho de código a seguir:

List<string> GetDadosOrdenados(string tipo, List<string> dados)
{
    List<string> resultado = null;

    switch (tipo)
    {
        case "BubbleSort":
            //Ordenação Bubble
            resultado = dados;
            break;

        case "HeapSort":
            //Ordenação Heap Sort 
            resultado = dados;
            break;

        case "MergeSort":
            //Ordenação Merge Sort 
            resultado = dados;
            break;

        case "InsertionSort":
            //Ordenação Insertion Sort
            resultado = dados;
            break;
    }

    return resultado;
}

Neste código estamos realizando a ordenação dos dados recebidos com base no tipo de ordenação selecionado. Temos aqui os seguintes problemas:

  1. Violação do princípio open/close;
  2. Violação do princípio da responsabilidade única;
  3. Redundância de código

Assim se for preciso um novo tipo de ordenação, teremos que modificar o método, incluindo mais responsabilidades e lógica e estaremos alterando o código existente violando o princípio Open Closed.

Para eliminar este 'mau cheiro' deste código podemos usar polimorfismo, o padrão Stratey e a correspondência de padrões.

Primeiro vamos definir uma interface IOrdenacao que vai definir um contrato para realizar a ordenação e retornar os dados ordenados:

public interface IOrdenacao
{
    List<string> GetDadosOrdenados(List<string> dados);
}

A seguir vamos implementar cada tipo de ordenação usando esta interface. Assim iremos criar as seguintes classes concretas que implementa a interface IOrdenacao :

  1. BubbleSort
  2. HeapSort
  3. MergeSort
  4. InsertionSort
public class HeapSort : IOrdenacao
{
    public List<string> GetDadosOrdenados(List<string> dados)
    {
        Console.WriteLine("HeapSort");
        return dados;
    }
}

public class MergeSort : IOrdenacao
{
    public List<string> GetDadosOrdenados(List<string> dados)
    {
        Console.WriteLine("MergeSort");
        return dados;
    }
}

public class InsertionSort : IOrdenacao
{
    public List<string> GetDadosOrdenados(List<string> dados)
    {
        Console.WriteLine("InsertSort");
        return dados;
    }
}

Nota:  A implementação de cada método não foi feita para simplificar o exemplo.

A seguir vamos criar a classe OrdenacaoContext onde vamos usar um Dictionary para definir a lógica de instanciar a classe de ordenação conforme o tipo selecionado :

public class OrdenacaoContext
{
    private Dictionary<string, IOrdenacao> ordenacaoStrategy = new Dictionary<string, IOrdenacao>();

    public OrdenacaoContext()
    {
        ordenacaoStrategy.Add("BubbleSort", new BubbleSort());
        ordenacaoStrategy.Add("HeapSort", new HeapSort());
        ordenacaoStrategy.Add("MergeSort", new MergeSort());
        ordenacaoStrategy.Add("InsertionSort", new InsertionSort());
    }

    public List<string> GetDadosOrdenados(string tipo, List<string> dados)
    {
        return ordenacaoStrategy[tipo].GetDadosOrdenados(dados);
    }
}

Podemos testar a refatoração feita usando o código abaixo:


List<string> data = new List<string> { "Banana", "Abacaxi", "Caju", "Laranja", "Abacate", "Manga" };

OrdenacaoContext
ordenacaoContext = new OrdenacaoContext();

List<string> result =
ordenacaoContext.GetDadosOrdenados("BubbleSort", data);

Console.ReadKey();

Com isso temos a refatoração do código.

Desta forma a instrução switch/case é uma declaração condicional que pode ser usada em avaliações numéricas mas quando aplicada a objetos e tipos vai ferir alguns princípios SOLID e causar um mal cheiro no código, e, isso pode ser resolvido usando polimorfismo e o padrão strategy.

Código usado: switch_refatora.txt

E estamos conversados.

"Porque foi do agrado do Pai que toda a plenitude nele habitasse,
E que, havendo por ele feito a paz pelo sangue da sua cruz, por meio dele reconciliasse consigo mesmo todas  as  coisas, tanto as que estão na terra, como as que estão nos céus."
Colossenses 1:19,20

Referências:


José Carlos Macoratti