Como vimos no post anterior existem várias estrategias de refatorar o codigo quando ele tem muitos If-Else, só para relembrar:
1. Early return
2. Tabelas de decisão
3. Polimorfismo
4. Delegação de funções
Early Return
Early Return: é a abordagem que elimina as outras ramificações e concluído as decisões com retornos imediatos, mesmo se tais condições forem falsas. Como podemos ver:
public class CalculadoraDesconto
{
public decimal CalculoDesconto(Cliente cliente, Pedido pedido)
{
if (cliente.IsPremium)
{
return pedido.QuantiaTotal > 100 ? pedido.QuantiaTotal * 0.1m : pedido.QuantiaTotal * 0.05m;
}
else
{
return pedido.QuantiaTotal > 200 ? pedido.QuantiaTotal * 0.15m : pedido.QuantiaTotal * 0.1m;
}
}
}
Acima temos uma função com a capacidade de fazer um retorno rápido usando operador ternário, agora veremos o cálculo da Complexidade Ciclomática:
Analise: temos 3 pontos de decisão:
-
if (cliente.IsPremium)
(1 decisão), -
pedido.QuantiaTotal > 100
(dentro do primeiro if), -
pedido.QuantiaTotal > 200
(dentro do else).
Cálculo:
N = 3
E = 6
P = 1
M = E - N + 2P
M = 6 - (3) + 2(1) = 5
Tabelas de Decisão
Tabelas de decisão é uma técnica usada para simplificar a lógica condicional em código, especialmente quando se tem muitos if-else. Elas ajudam a mapear entradas para saídas, eliminando a necessidade de escrever várias condições aninhadas.
public class CalculadoraDesconto
{
public decimal CalculoDesconto(Cliente cliente, Pedido pedido)
{
decimal descontoPercentual = ObterDescontoPercentual(cliente.IsPremium, pedido.QuantiaTotal);
return pedido.QuantiaTotal * descontoPercentual;
}
private decimal ObterDescontoPercentual(bool isPremium, decimal quantiaTotal)
{
if (isPremium)
{
return quantiaTotal > 100 ? 0.1m : 0.05m;
}
else
{
return quantiaTotal > 200 ? 0.15m : 0.1m;
}
}
}
Como vimos no código acima, repartiu-se as funções isso para que só depois se fizesse o calculo e não sou acabou por usar a abordagem Early Return na segunda função, o que esteticamente melhorou mas a sua complexidade ciclomática continua a mesma como podemos ver:
Analise: temos 3 pontos de decisão:
-
if (cliente.IsPremium)
(1 decisão), -
pedido.QuantiaTotal > 100
(dentro do primeiro if), -
pedido.QuantiaTotal > 200
(dentro do else).
Cálculo:
N = 3
E = 6
P = 1
M = E - N + 2P
M = 6 - (3) + 2(1) = 5
Polimorfismo
O polimorfismo é um dos pilares da programação orientada a objetos que permite que diferentes classes respondam de maneiras distintas a uma mesma mensagem (método). Ele possibilita que métodos de mesmo nome, em diferentes classes, tenham comportamentos variados, sem que o código cliente precise saber das diferenças.
Para termos mais detalhes de como funciona veja o exemplo abaixo:
public abstract class Cliente
{
public abstract decimal CalcularDesconto(Pedido pedido);
}
public class ClientePremium : Cliente
{
public override decimal CalcularDesconto(Pedido pedido)
{
return pedido.QuantiaTotal > 100 ? pedido.QuantiaTotal * 0.1m : pedido.QuantiaTotal * 0.05m;
}
}
public class ClienteNormal : Cliente
{
public override decimal CalcularDesconto(Pedido pedido)
{
return pedido.QuantiaTotal > 200 ? pedido.QuantiaTotal * 0.15m : pedido.QuantiaTotal * 0.1m;
}
}
Como vimos criou-se uma classe abstrata chamada Cliente
que tem um método chamado CalcularDesconto
este método é subscrito nas outras duas funções ClientePremium
e ClienteNormal
. Esta abordagem permitem fazer cálculos de forma independente e sem condições aninhadas.
Analise: temos 3 pontos de decisão:
- Na classe
ClientePremium: pedido.QuantiaTotal > 100
(1 decisão), - Na classe
ClienteNormal: pedido.QuantiaTotal > 200
(1 decisão).
Cálculo:
N = 1
E = 2
P = 1
M = E - N + 2P
M = 2 - (1) + 2(1) = 3
Esta abordagem requer mais organização por outro lado esta abordagem é mais propensa a escalabilidade. Sem falar que a Complexidade ciclomática é muito inferior que as duas anteriores.
Funções Delegadas
Funções Delegadas (ou simplesmente Delegates) é um conceito de programação que permite tratar métodos como objetos. Em linguagens como C#, um delegate é uma referência a um método e pode ser passado como argumento para outras funções, armazenado em variáveis ou retornado de funções, tornando o código mais flexível e modular.
public class CalculadoraDesconto
{
private Func<Pedido, decimal> ObterCalculoDesconto(Cliente cliente)
{
if (cliente.IsPremium)
{
return pedido => pedido.QuantiaTotal > 100 ? pedido.QuantiaTotal * 0.1m : pedido.QuantiaTotal * 0.05m;
}
else
{
return pedido => pedido.QuantiaTotal > 200 ? pedido.QuantiaTotal * 0.15m : pedido.QuantiaTotal * 0.1m;
}
}
public decimal CalculoDesconto(Cliente cliente, Pedido pedido)
{
var calcularDesconto = ObterCalculoDesconto(cliente);
return calcularDesconto(pedido);
}
}
Analise: temos 3 pontos de decisão:
-
if (cliente.IsPremium)
(1 decisão), - Dentro do retorno da função delegada, há outra decisão para
pedido.QuantiaTotal
.
Cálculo:
N = 2
E = 4
P = 1
M = E - N + 2P
M = 4 - (2) + 2(1) = 4
Conclusão
Algumas das complexidades ciclomáticas tiveram valores diferentes, umas iguais ao if-else aninhado e outras muito inferiores, não é regra mais isso dependerá muito do exercício que estas criar e levarás em conta a melhor abordagem.
Podemos ver que a complexidade ciclomatíca ajuda muito analisar as abordagens e hoje já existem softwares que verificam esta complexidade. Como programador será difícil as vezes de verificar se este é a melhor forma de fazer, mas convenhamos de que códigos com muitos If-elses não são agradáveis de se ver por mais que esta a funcionar e que quando iremos refatorar será como pisar em ovos. Treine até o ponto de teres olho clinico de analisar a melhor via para ti e para os que poderão mexer nesse código.
Top comments (0)