Ir para o conteúdo do rodapé
Iron Academy Logo
Problemas comuns em C#

Refatorando o condicional `if` em C#: Evitando a complexidade desnecessária do código condicional com Derek Comartin

Em C#, instruções condicionais como a instrução if, a instrução if else e a instrução switch são ferramentas essenciais. Mas o que acontece quando essas construções são usadas em excesso — especialmente quando associadas a enums? Derek Comartin, em seu vídeo "[Enums não são maus. O vídeo "Conditionals Everywhere Are" (https://www.youtube.com/watch?v=QoK7jSZ-viw) nos apresenta uma refatoração detalhada que substitui a lógica condicional generalizada por padrões mais limpos e fáceis de manter.

Neste artigo, vamos analisar o raciocínio de Derek passo a passo, usando seus registros de tempo como pontos de referência. Também exploraremos como suas ideias se aplicam a padrões condicionais comuns em C#, como o operador ternário, a instrução else e as estruturas switch-case, destacando onde esses padrões podem causar problemas em grandes bases de código e como você pode refatorar para obter um design melhor.

A explosão do condicional "se": o verdadeiro problema

Derek começa mostrando uma instrução condicional (if) que verifica o tipo de produto:

if (productType == ProductType.Template || productType == ProductType.Ebook)
if (productType == ProductType.Template || productType == ProductType.Ebook)

À primeira vista, a condição "se" acima parece simples. Mas Derek alerta que esse tipo de instrução avalia uma determinada condição e, em seguida, executa um bloco de código somente se a condição for verdadeira — o que se torna problemático quando essa lógica é repetida em todos os lugares.

Você pode encontrar esse bloco novamente em outro método ou classe:

if (offeringType == ProductType.Template || offeringType == ProductType.Ebook)
if (offeringType == ProductType.Template || offeringType == ProductType.Ebook)

Derek explica que esse padrão se espalha rapidamente por um sistema grande. A mesma lógica condicional (if/else) aparece em vários serviços, causando inconsistências e erros ao adicionar um novo valor de enumeração. Por exemplo, o que acontece quando você introduz um novo tipo de produto como Vídeo? Você precisará se lembrar de atualizar cada bloco onde essa expressão condicional existe.

A repetição amplifica a complexidade.

No exemplo a seguir, Derek explora condicionais aninhadas. Dentro de um método, uma instrução if/else verifica a mesma enumeração, e o resultado é passado para outro método, que também contém uma verificação semelhante.

A instrução verifica se é um modelo (Template) ou um e-book (Ebook) e retorna algum valor; caso contrário, retorna nulo. Derek observa que essa redundância não apenas aumenta o comprimento do código, como também introduz riscos de manutenção. A mesma lógica é copiada em vários arquivos, o que leva ao caos no fluxo de controle.

Se o seu sistema exige que você adicione um caso padrão toda vez que acessa uma enumeração, você sabe que algo está errado.

Mudando a forma como pensamos sobre condicionais

Em vez de verificar constantemente os tipos com if else, Derek propõe fazer uma pergunta melhor:

O produto possui funcionalidades para download?

Esta é uma melhor expressão de intenção. Isso torna seu código mais legível e reduz completamente a dependência do enum. Em vez de escrever uma instrução if com duas condições como:

if (product.Type == ProductType.Template || product.Type == ProductType.Ebook)
if (product.Type == ProductType.Template || product.Type == ProductType.Ebook)

Você pode simplesmente encapsular essa lógica em um modelo e escrever:

if (product.HasDownloadableResource())
if (product.HasDownloadableResource())

Isso retorna verdadeiro somente quando um recurso para download está presente, reduzindo a necessidade de expressões condicionais complexas.

De instruções "if" a comportamento encapsulado

Para resolver o problema central, Derek introduz um tipo de recurso DownloadableResource. Este tipo inclui um URL de download e um nome de arquivo padrão. Isso se torna parte integrante do seu domínio, em vez de depender de instruções condicionais para derivá-lo.

Agora, em vez de repetir isso:

if (product.Type == ProductType.Template)
{
    // Generate file name
}
else if (product.Type == ProductType.Ebook)
{
    // Generate file name
}
if (product.Type == ProductType.Template)
{
    // Generate file name
}
else if (product.Type == ProductType.Ebook)
{
    // Generate file name
}

Você escreve isto:

var downloadable = product.GetDownloadableResource();
if (downloadable != null)
{
    Console.WriteLine(downloadable.FileName);
}
var downloadable = product.GetDownloadableResource();
if (downloadable != null)
{
    Console.WriteLine(downloadable.FileName);
}

Isso simplifica drasticamente a lógica e elimina a necessidade de ramificações com instruções else ou até mesmo com uma instrução switch.

Tempo de execução em vez de tempo de compilação: uma mudança estratégica

Derek aprofunda o assunto explicando uma importante escolha de design: transferir a lógica do tempo de compilação para o tempo de execução. Isso significa consultar o sistema em tempo de execução para verificar se existe um DownloadableResource para um produto. Se isso acontecer, aja de acordo. Caso contrário, ignore.

Ele observa que essa mudança transforma a lógica estática do tipo "senão" em consultas em tempo de execução. Pode adicionar uma chamada ao banco de dados, mas reduz a lógica condicional aninhada (if/else) e centraliza o comportamento. Isso melhora a capacidade de manutenção em grande escala.

Utilizando a herança para produtos para download

Outra via que Derek explora é a herança. Você pode criar uma classe base abstrata chamada Product e, em seguida, definir tipos derivados como Ebook, Template ou OfflineCourse.

Cada um deles sobrescreve métodos como:

public virtual string GetDownloadUrl() { ... }
public virtual string GetDownloadUrl() { ... }

Essa abordagem permite que cada produto lide com sua própria lógica. Embora isso evite uma instrução switch ou múltiplas instruções condicionais, Derek destaca que você ainda pode acabar escrevendo expressões condicionais internamente se não tiver cuidado.

Melhor encapsulamento sem herança

Se a herança parecer excessiva, Derek sugere o uso de tipos explícitos como DownloadableProduct, que contém suas próprias propriedades e métodos, sem estar vinculado a uma hierarquia.

No seu programa, isso poderia ser representado da seguinte forma:

var downloader = new DownloadableProduct(product);
Console.WriteLine(downloader.GetDefaultFileName());
var downloader = new DownloadableProduct(product);
Console.WriteLine(downloader.GetDefaultFileName());

Não há necessidade de uma instrução if else ou switch para determinar o comportamento — cada objeto sabe o que fazer.

Correção Leve: Métodos de Extensão em Enums

Se você não está pronto para abandonar os enums, Derek propõe uma solução mais leve: criar um método de extensão:

public static bool IsDownloadable(this ProductType type)
{
    return type == ProductType.Template || type == ProductType.Ebook;
}
public static bool IsDownloadable(this ProductType type)
{
    return type == ProductType.Template || type == ProductType.Ebook;
}

Agora, em vez de escrever:

if (product.Type == ProductType.Template || product.Type == ProductType.Ebook)
if (product.Type == ProductType.Template || product.Type == ProductType.Ebook)

Você pode simplificar para:

if (product.Type.IsDownloadable())
if (product.Type.IsDownloadable())

Isso centraliza sua lógica e evita a repetição constante de chaves e blocos de código.

Evite o uso excessivo de operadores ternários e switches.

Derek também alerta contra o uso excessivo de abreviações como o operador ternário:

string filename = product.Type == ProductType.Template ? "template.pdf" : "default.pdf";
string filename = product.Type == ProductType.Template ? "template.pdf" : "default.pdf";

Embora seja uma sintaxe válida, pode ser propensa a erros e difícil de ler quando a lógica se torna complexa. Principalmente se a sua condição for avaliada como falsa, o valor errado pode ser atribuído de maneiras sutis.

Da mesma forma, o switch com uma instrução break e o default case também caem nessa armadilha. É melhor solicitar o comportamento dos objetos do que usar a lógica switch-case.

Conclusão: Controle mais inteligente com menos configurações condicionais desnecessárias.

Em conclusão, o vídeo de Derek não é um ataque aos enums, mas sim uma crítica à forma como utilizamos as estruturas condicionais "if" em torno deles. Ao espalhar instruções if else e switch por toda a sua base de código, você torna o sistema mais difícil de testar, manter e evoluir.

Independentemente de optar por encapsulamento, pesquisa em tempo de execução, herança ou métodos de extensão simples, o objetivo permanece o mesmo: reduzir as condicionais e mover a lógica para onde ela pertence.

Lembrar:

  • As condicionais não são ruins.

  • A desordem condicional é.

  • Código limpo não depende de múltiplas instruções if/else espalhadas por várias classes.

  • Avalie o contexto e refatore de acordo.

Como diz Derek, "Depende do contexto". Mas uma coisa é certa: um produto nem sempre é apenas um produto — às vezes, é um sinal para repensar o design.

Hero Worlddot related to Refatorando o condicional `if` em C#: Evitando a complexidade desnecessária do código condici...
Hero Affiliate related to Refatorando o condicional `if` em C#: Evitando a complexidade desnecessária do código condic...

Ganhe mais compartilhando o que você ama.

Você cria conteúdo para desenvolvedores que trabalham com .NET, C#, Java, Python ou Node.js? Transforme sua expertise em renda extra!

Equipe de suporte de ferro

Estamos online 24 horas por dia, 5 dias por semana.
Bater papo
E-mail
Liga para mim