workshop su refactoring
TRANSCRIPT
Workshop sulRefactoring
Stefano Leli
XPUG Marche – 2° Meeting
Martin Fowler
“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.”
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
Regole
• Tempo a disposizione 25 minuti
• I test devono restare verdi
• I test non possono essere modificati
Analisi del problema
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 + " kcal\n";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;
}}
}
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
Responsabilità Collaborazioni
Validazione delle componentiCostruzione del report di stampaCalcolo delle calorie di una lista di cibiCalcolo dei Km da percorrere
Classe FoodClasse PersonClasse FirstExceptionClasse SecondException
CRC Card Classe CaloriesCalculator
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 + " kcal\n";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;
}
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;
}}
}
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 + " kcal\n";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;
}
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 + " kcal\n";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;
}
Information Hiding
public class Food{
public string Name;public double Kg;public Food(string name, double kg){
this.Name = name;this.Kg = kg;
}}
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 + " kcal\n";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;
}
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 + " kcal\n";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;
}
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 + " kcal\n";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;
}
Passi di Refactoring
Visione di alto livello
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.)
Validazione
Assegnata alla classe Person la responsabilitàdella propria validazione
Adottato l'utilizzo del pattern Visitor per la validazione dei Food:
� Permette di aggiungere nuovicomportamenti indipendenti dalla strutturadati
� Diminuisce la complessità della strutturadati
Calcolo delle Calorie
Utilizzo del pattern strategy per il calcolo dellecalorie:
� 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.
Costruzione dei Report
Utilizzo del pattern template per modellare le logiche di reporting:
� Permette di separe le parti invarianti daquelle 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 è stataaggiunta l'interfaccia IDietReport
Riferimenti
� Martin Fowler -
Refactoring: Improving the Design of Existing Code
� Michael Feathers -
Working Effectively with Legacy Code
Domande?