2. “Refactoring is the process of changing a
software system in such a way that it does not
alter the external behavior of the code yet
improves its internal structure.”
Martin Fowler
3. Principi del Refactoring
• Migliorare il design del codice
• Eliminare le duplicazioni
• Rendere il codice più leggibile e facile da modificare
Single Responsibility Principle (SRP)
Una classe dovrebbe avere una sola ragione per
cambiare
4. Regole
• Tempo a disposizione 25 minuti
• I test devono restare verdi
• I test non possono essere modificati
6. Classe CaloriesCalculator
namespace Kata
{
public class CaloriesCalculator {
private readonly List<Food> foods;
private readonly Person person;
public CaloriesCalculator(Person person, List<Food> foods) {
this.person = person;
this.foods = foods;
}
public string Result() {
string str = "Diet Report for " + person.Name +":n";
double cal = 0.0;
for (int i = 0; i < foods.Count; i++) {
Food food = foods[i];
str += food.Name + " - " + food.Kg + "n";
if ("".Equals(food.Name)) throw new FirstException();
if ("apple".Equals(food.Name)) cal += food.Kg * 15 * 10;
if ("magicPill".Equals(food.Name)) cal -= 10;
if ("candy".Equals(food.Name)) cal += food.Kg;
}
str += "Total: " + (double)cal + " kcaln";
str += "kilometers to be run: " + 90 /* calories in one kilometer */ * person.Size / cal;
foreach (Food type in foods) {
if ("".Equals(type.Name)) throw new FirstException();
if (person.Kg > 1000) throw new SecondException();
}
return str;
}
}
}
7. Classe CaloriesCalculator
Responsabilità Collaborazioni
Validazione delle componenti Classe Food
Costruzione del report di stampa Classe Person
Calcolo delle calorie di una lista di cibi Classe FirstException
Calcolo dei Km da percorrere Classe SecondException
CRC Card Classe CaloriesCalculator
Il refactoring verterà sulla:
suddivisione di responsabilità (aumentare la coesione)
diminuzione delle collaborazioni con le classi applicative (diminuire l'accoppiamento)
Favorendo in questo modo un:
maggiore riuso del codice
maggiore robustezza del programma
8. Magic Number
public string Result() {
string str = "Diet Report for " + person.Name +":n";
double cal = 0.0;
for (int i = 0; i < foods.Count; i++)
{
Food food = foods[i];
str += food.Name + " - " + food.Kg + "n";
if ("".Equals(food.Name)) throw new FirstException();
if ("apple".Equals(food.Name)) cal += food.Kg * 15 * 10;
if ("magicPill".Equals(food.Name)) cal -= 10;
if ("candy".Equals(food.Name)) cal += food.Kg;
}
str += "Total: " + (double)cal + " kcaln";
str += "kilometers to be run: " + 90 * person.Size / cal;
foreach (Food type in foods) {
if ("".Equals(type.Name)) throw new FirstException();
if (person.Kg > 1000) throw new SecondException();
}
return str;
}
9. Uso parziale di costanti
public class Person
{
public const int MEDIUM_SIZE = 2;
public const int FAT = 3;
public const int SLIM = 1;
public Person(string name, int kg) {
this._name = name;
this.kg = kg;
}
private string _name;
public string Name {
get { return _name; }
set { _name = value; }
}
private int kg;
public int Kg {
get { return kg; }
set { kg = value; }
}
public int Size {
get
{
if (kg > 130) return 3;
if (kg < 50) return 1;
return MEDIUM_SIZE;
}
}
}
10. Cicli
public string Result() {
string str = "Diet Report for " + person.Name +":n";
double cal = 0.0;
for (int i = 0; i < foods.Count; i++)
{
Food food = foods[i];
str += food.Name + " - " + food.Kg + "n";
if ("".Equals(food.Name)) throw new FirstException();
if ("apple".Equals(food.Name)) cal += food.Kg * 15 * 10;
if ("magicPill".Equals(food.Name)) cal -= 10;
if ("candy".Equals(food.Name)) cal += food.Kg;
}
str += "Total: " + (double)cal + " kcaln";
str += "kilometers to be run: " + 90 * person.Size / cal;
foreach (Food type in foods) {
if ("".Equals(type.Name)) throw new FirstException();
if (person.Kg > 1000) throw new SecondException();
}
return str;
}
11. Nomi di variabili metodi e classi
public string Result() {
string str = "Diet Report for " + person.Name +":n";
double cal = 0.0;
for (int i = 0; i < foods.Count; i++)
{
Food food = foods[i];
str += food.Name + " - " + food.Kg + "n";
if ("".Equals(food.Name)) throw new FirstException();
if ("apple".Equals(food.Name)) cal += food.Kg * 15 * 10;
if ("magicPill".Equals(food.Name)) cal -= 10;
if ("candy".Equals(food.Name)) cal += food.Kg;
}
str += "Total: " + (double)cal + " kcaln";
str += "kilometers to be run: " + 90 * person.Size / cal;
foreach (Food type in foods) {
if ("".Equals(type.Name)) throw new FirstException();
if (person.Kg > 1000) throw new SecondException();
}
return str;
}
12. Information Hiding
public class Food
{
public string Name;
public double Kg;
public Food(string name, double kg)
{
this.Name = name;
this.Kg = kg;
}
}
13. Responsabilità:
Validazione
public string Result() {
string str = "Diet Report for " + person.Name +":n";
double cal = 0.0;
for (int i = 0; i < foods.Count; i++)
{
Food food = foods[i];
str += food.Name + " - " + food.Kg + "n";
if ("".Equals(food.Name)) throw new FirstException();
if ("apple".Equals(food.Name)) cal += food.Kg * 15 * 10;
if ("magicPill".Equals(food.Name)) cal -= 10;
if ("candy".Equals(food.Name)) cal += food.Kg;
}
str += "Total: " + (double)cal + " kcaln";
str += "kilometers to be run: " + 90 * person.Size / cal;
foreach (Food type in foods) {
if ("".Equals(type.Name)) throw new FirstException();
if (person.Kg > 1000) throw new SecondException();
}
return str;
}
14. Responsabilità:
Calcolo delle calorie e dei Km
public string Result() {
string str = "Diet Report for " + person.Name +":n";
double cal = 0.0;
for (int i = 0; i < foods.Count; i++)
{
Food food = foods[i];
str += food.Name + " - " + food.Kg + "n";
if ("".Equals(food.Name)) throw new FirstException();
if ("apple".Equals(food.Name)) cal += food.Kg * 15 * 10;
if ("magicPill".Equals(food.Name)) cal -= 10;
if ("candy".Equals(food.Name)) cal += food.Kg;
}
str += "Total: " + (double)cal + " kcaln";
str += "kilometers to be run: " + 90 * person.Size / cal;
foreach (Food type in foods) {
if ("".Equals(type.Name)) throw new FirstException();
if (person.Kg > 1000) throw new SecondException();
}
return str;
}
15. Responsabilità:
Costruzione del report
public string Result() {
string str = "Diet Report for " + person.Name +":n";
double cal = 0.0;
for (int i = 0; i < foods.Count; i++)
{
Food food = foods[i];
str += food.Name + " - " + food.Kg + "n";
if ("".Equals(food.Name)) throw new FirstException();
if ("apple".Equals(food.Name)) cal += food.Kg * 15 * 10;
if ("magicPill".Equals(food.Name)) cal -= 10;
if ("candy".Equals(food.Name)) cal += food.Kg;
}
str += "Total: " + (double)cal + " kcaln";
str += "kilometers to be run: " + 90 * person.Size / cal;
foreach (Food type in foods) {
if ("".Equals(type.Name)) throw new FirstException();
if (person.Kg > 1000) throw new SecondException();
}
return str;
}
18. Primi Passi
• Effettuate le operazioni individuate in precedenza
(Naming, Information Hiding….)
• Creata la classe Meal
• Responsabile della validazione di una lista di Food
• Aumenta la coesione tra le classi
• Creata la classe Dietist
• Funge da “Mediator”
• Lascia aperti scenari ad estensioni future (DI, etc.)
19. Validazione
Assegnata alla classe Person la responsabilità
della propria validazione
Adottato l'utilizzo del pattern Visitor per la
validazione dei Food:
Permette di aggiungere nuovi
comportamenti indipendenti dalla struttura
dati
Diminuisce la complessità della struttura
dati
20. Calcolo delle Calorie
Utilizzo del pattern strategy per il calcolo delle
calorie:
Maggior grado di disaccoppiamento tra
l'implementazione dell'algoritmo e la sua
esecuzione
Possibilità di incapsulare più strategie o
algoritmi in classi separate semplicemente
implementando le apposite interfacce.
21. Costruzione dei Report
Utilizzo del pattern template per modellare le
logiche di reporting:
Permette di separe le parti invarianti da
quelle varianti di un algoritmo.
Rende facile l'aggiunta di un nuovo report.
Basta implementare il template di base e
definire le parti varianti
Per favorire l'information hiding è stata
aggiunta l'interfaccia IDietReport