Monday, October 5, 2009

Conditional Complexity: Replace Conditional Logic with Strategy

Continuiamo con lo smell: Conditional Complexity
400px-Control_flow_graph_of_function_with_two_if_else_statements.svg

Problema:

Esiste un metodo di controllo con una logica condizionale composta da diverse varianti i quali valori si conosceranno solamente a runtime.
La stessa logica di controllo viene riprodotta in varie classi affinchè si ottenga il valore desiderato.

Un esempio di logica errata:
Di seguito un esempio di logica errata, sul quale lavoreremo:
public double Capital()
{
if (expiry == null && maturity != null)
return commitment * Duration() * RiskFactor();
if (expiry != null && maturity == null)
{
if (GetUnusedPercentage() != 1.0)
return commitment * GetUnusedPercentage() * Duration() *
RiskFactor();
else
return (OutStandingRiskAmount() * Duration() *
RiskFactor()) +
(UnUsedRiskAmount() * Duration() * UnUsedRiskFactor());
}

return 0.0;
}

Il codice di sopra è raffigurabile nella seguente maniera:
 conditional_complexity

Motivazione:

Nel momento in cui iniziamo a scrivere delle logiche di controllo, solitamente, si parte da un paio di controlli che, se li volessimo togliere, porterebbero solamente a complicare la leggibilità del codice stesso.
Il problema nasce quando i controlli da effettuare continuano a crescere, senza preoccuparsi di migliorare la leggibilità del codice tramite l’applicazione di un Pattern ne, tanto meno, tentando di rimpicciolire le condizioni.

Soluzione:
Tramite il pattern Strategy semplificheremo il mantenimento delle varianti che potranno nascere.
In pratica, utilizzandolo, avremo tutte le varianti in un’unico punto del codice.
Un’altra soluzione applicabile è tramite il pattern Decorator.

Le condizioni vengono solitamente scritte per capire quale algoritmo utilizzare; tramite:


  • Decompose Conditional
  • Compose Method
possiamo semplificarne la leggibilità ma avremo tanti piccoli metodi che verranno utilizzati dall’algoritmo.
Questo è il codice che otteniamo applicando il pattern Strategy:

class Program
{
static void Main(string[] args)
{
ILoan loan1 = new StandardLoan();
loan1.Capital();
}
}

public class CapitalStrategy
{
private readonly ILoan _loan;
private object RiskFactor;
private object UnusedRiskFactor;

public CapitalStrategy(ILoan loan, object riskFactor, object unusedRiskFactor)
{
_loan = loan;
RiskFactor = riskFactor;
UnusedRiskFactor = unusedRiskFactor;
}

public double Capital()
{
switch (_loan.LoanType)
{
case LoanType.Standard:
return _loan.Commitment * _loan.Duration() * RiskFactorFor();
case LoanType.NonStandardUnUsedPercentage:
return _loan.Commitment * _loan.UnusedPercentage() * _loan.Duration() * RiskFactorFor();
case LoanType.NonStandardUnUsedPercentageDotOne:
return (_loan.OutstandingRiskAmount() * _loan.Duration() * RiskFactorFor())
+ (_loan.UnusedRiskAmount() * _loan.Duration() * UnusedRiskFactorFor());
default:
return 0.0;
}
}

private double UnusedRiskFactorFor()
{
return UnusedRiskFactor.GetFactor(_load).GetUnusedRiskFactor();
}

private double RiskFactorFor()
{
return RiskFactor.GetFactor(_load).GetRiskFactor();
}
}

public enum LoanType
{
Standard,
NonStandardUnUsedPercentage,
NonStandardUnUsedPercentageDotOne
}

public interface ILoan
{
double Capital();
LoanType LoanType { get; }
double Commitment { get; }
double Duration();
double UnusedPercentage();
double OutstandingRiskAmount();
double UnusedRiskAmount();
}

public class StandardLoan : ILoan
{
readonly CapitalStrategy _capitalStrategy;
private object myRiskFactor = new object();
private object myUnusedRiskFactor = new object();

public StandardLoan()
{
_capitalStrategy = new CapitalStrategy(this, myRiskFactor, myUnusedRiskFactor);
}

#region ILoan Members

public double Capital()
{
return _capitalStrategy.Capital();
}

public LoanType LoanType
{
get { return LoanType.Standard; }
}

public double Commitment
{
get { throw new NotImplementedException(); }
}

public double Duration()
{
throw new NotImplementedException();
}

public double UnusedPercentage()
{
throw new NotImplementedException();
}

public double OutstandingRiskAmount()
{
throw new NotImplementedException();
}

public double UnusedRiskAmount()
{
throw new NotImplementedException();
}

#endregion
}

public class NonStandardUnUsedPercentage : ILoan
{
readonly CapitalStrategy _capitalStrategy;
private object myRiskFactor = new object();
private object myUnusedRiskFactor = new object();

public NonStandardUnUsedPercentage()
{
_capitalStrategy = new CapitalStrategy(this, myRiskFactor, myUnusedRiskFactor);
}

#region ILoan Members

public double Capital()
{
return _capitalStrategy.Capital();
}

public LoanType LoanType
{
get { return LoanType.NonStandardUnUsedPercentage; }
}

public double Commitment { get; private set; }
public double Duration()
{
throw new System.NotImplementedException();
}

public double UnusedPercentage()
{
throw new System.NotImplementedException();
}

public double OutstandingRiskAmount()
{
throw new System.NotImplementedException();
}

public double UnusedRiskAmount()
{
throw new System.NotImplementedException();
}

#endregion
}


 refactored_conditional_complexity
 




Benefici e non
+ Rende gli algoritmi più chiari diminuendo o eliminando le condizioni logiche.
+ Semplifica le classi muovendo le variazioni dell’algoritmo nella gerarchia.
+ Abilita la possibilità di swappare tra un algoritmo e un’altro a run-time.

- Complica il design.
- Complica la visione di come l’algoritmo ottiene/riceve i dati.
E non prendete come scusa: “il mio sistema ormai è troppo evoluto per poterne apportare queste migliorie. E’ troppo tardi.”
Se fosse realmente così non esisterebbe il Refactoring :)
Per questo post ho preso, molto spunto da libro Refactoring To Patterns di Joshua Kerievsky.

No comments:

Post a Comment