Avete mai provato la sensazione di essere immersi in una melma di codice putrido e maleodorante e di non riuscire a venirne fuori nonostante tutti i vostri sforzi?
In questo workshop si cercherà di simulare proprio questo scenario proponendovi un esempio di progetto mal scritto e che sarete chiamati in prima persona a rifattorizzare.
Nel nostro ruolo di coach vi faremo capire come riconoscere il codice che ha bisogno di essere migliorato, ed applicando i solid principles verrete guidati verso un buon design in grado di portarvi in acque più sicure e pulite.
3. Il Gioco
• Simulatore di Bird Watching
• Una sorta di “battaglia navale”...
ma qui gli assi sono 3 (gli uccelli
volano)
4.
5. Classe GameField
Responsabilità Collaborazioni
Gestisce la collezione di Birds
Dispone il campo da gioco
Valida il campo da gioco Classe Bird
Inizializza il campo (start)
Gestisce le logiche degli shot
14. Eliminiamola!
@Before
public void Setup() {
field = new GameField(new FieldSize(10,5,3));
}
public GameField(FieldSize fieldSize) {
this.fieldSize = fieldSize;
birds = new ArrayList<Bird>();
}
15. Eliminiamola!
@Before
public void Setup() {
field = new GameField(new FieldSize(10,5,3));
}
public GameField(FieldSize fieldSize) {
this.fieldSize = fieldSize;
birds = new ArrayList<Bird>();
}
//Place the birds on the fields
private void placeBirds(PlacingMode type) throws Exception {
...
Location location = new Location(new Random().nextInt(fieldSize.width()),
new Random().nextInt(fieldSize.height()));
bird.setLocation(location);
if (!(bird instanceof Chicken))
bird.setHeight(new Random().nextInt(fieldSize.depth()));
...
}
21. bird.setLocation(location);
if (!(bird instanceof Chicken))
bird.setHeight(new Random().nextInt(this.depth));
Bird duck = new Duck();
duck.setLocation(new Location(10,5));
duck.setHeight(3);
int h = bird.getHeight();
Location location = bird.getLocation();
int x = location.x;
int y = location.y;
isValid = fieldSize.isWithinField(h, x, y);
22.
23. Location location = new Location(new Random().nextInt(fieldSize.width()),
new Random().nextInt(fieldSize.height()),
new Random().nextInt(fieldSize.depth()));
24. private boolean isGameFieldValid()
{
boolean isValid = true;
for(Bird bird : birds) {
int h = bird.getHeight();
Location location = bird.getLocation();
int x = location.x;
int y = location.y;
isValid = fieldSize.isWithinField(h, x, y);
if (!isValid)
break;
}
return isValid;
}
25. private boolean isGameFieldValid()
{
boolean isValid = true;
for(Bird bird : birds) {
Location location = bird.getLocation();
int h = location.h;
int x = location.x;
int y = location.y;
isValid = fieldSize.isWithinField(h, x, y);
if (!isValid)
break;
}
return isValid;
}
26. public boolean shot(int x, int y, int h) {
boolean hit = false;
if (gameStarted)
{
for(Bird bird : birds) {
int height = bird.getHeight();
Location location = bird.getLocation();
hit = location.x == x && location.y == y && height == h;
if (hit)
{
bird.sing();
break;
}
}
}
return hit;
}
27. public boolean shot(int x, int y, int h) {
boolean hit = false;
if (gameStarted)
{
for(Bird bird : birds) {
Location location = bird.getLocation();
hit = location.x == x && location.y == y && location.h == h;
if (hit)
{
bird.sing();
break;
}
}
}
return hit;
}
28. public abstract class Bird {
Location location;
int height;
public void setHeight(int height) throws Exception{
this.height = height;
}
public int getHeight() {
return height;
}
public void setLocation(Location location) {
this.location = location;
}
public Location getLocation() {
return location;
}
public abstract void sing();
}
29. Serve ancora?
private void placeBirds(PlacingMode type) throws Exception {
//Random Distribution
if (type == PlacingMode.Random) {
for(Bird bird : birds) {
Location location = new Location(new Random().nextInt(fieldSize.width()),
new Random().nextInt(fieldSize.eighth()),
new Random().nextInt(fieldSize.depth()));
bird.setLocation(location);
if (!(bird instanceof Chicken))
bird.setHeight(new Random().nextInt(this.depth));
}
}
//Custom Distribution
else if (type == PlacingMode.Custom) {
}
}
30. Tell, don’t Ask
public boolean shot(int x, int y, int h) {
boolean hit = false;
if (gameStarted)
{
for(Bird bird : birds) {
Location location = bird.getLocation();
hit = location.x == x && location.y == y && location.h == h;
if (hit)
{
bird.sing();
break;
}
}
}
return hit;
}
31. Applied
public boolean shot(Location shotLocation) {
boolean hit = false;
if (gameStarted)
{
for(Bird bird : birds) {
hit = shotLocation.equals(bird.getLocation());
if (hit)
{
bird.sing();
break;
}
}
}
return hit;
}
32. Let’s apply it another time
public boolean shot(Location shotLocation) {
boolean hit = false;
if (gameStarted)
{
for(Bird bird : birds) {
hit = bird.wasHit(shotLocation)
if (hit)
{
bird.sing();
break;
}
}
}
return hit;
}
33. Another time too
public boolean shot(Location shotLocation) {
boolean hit = false;
if (gameStarted)
{
for(Bird bird : birds) {
hit = bird.wasHit(shotLocation)
if (hit)
{
break;
}
}
}
return hit;
}
34. Programmiamo ad oggetti o
a tipi primitivi...?
private boolean isGameFieldValid()
{
boolean isValid = true;
for(Bird bird : birds) {
Location location = bird.getLocation();
int h = location.h;
int x = location.x;
int y = location.y;
isValid = fieldSize.isWithinField(h, x, y);
if (!isValid)
break;
}
return isValid;
}
37. Come procediamo?
private void placeBirds(PlacingMode type) throws Exception {
//Random Distribution
if (type == PlacingMode.Random) {
for(Bird bird : birds) {
Location location = new Location(new Random().nextInt(fieldSize.width()),
new Random().nextInt(fieldSize.eighth()),
new Random().nextInt(fieldSize.depth()));
bird.setLocation(location);
}
}
//Custom Distribution
else if (type == PlacingMode.Custom) {
}
}
38. I Passi di refactoring
• Estratta responsabilità di “Random placing strategy” in
una classe
• estratta interfaccia IPlacingStrategy
• creata class NullPlacingStrategy
• creata Factory per Placing strategy
• inline metodo placeBirds
• trasformata la factory in un field ed estratto come
parametro del costruttore
39. Il risultato
public class PlacingStrategyFactory { public interface IPlacingStrategy {
public IPlacingStrategy create(PlacingMode type, void place(List<Bird> birds);
FieldSize fieldSize) {
if (type == PlacingMode.Random) { }
return new RandomPlacingStrategy(fieldSize);
}
public class NullPlacingStrategy implements IPlacingStrategy {
return new NullPlacingStrategy();
} @Override
public void place(List<Bird> birds) {
} // Do nothing
}
}
public class RandomPlacingStrategy implements IPlacingStrategy {
private FieldSize fieldSize;
public RandomPlacingStrategy(FieldSize fieldSize) {
this.fieldSize = fieldSize;
}
@Override
public void place(List<Bird> birds) {
for(Bird bird : birds) {
Location location = new Location(new Random().nextInt(fieldSize.width()),
new Random().nextInt(fieldSize.height()),
new Random().nextInt(fieldSize.depth()));
bird.setLocation(location);
}
}
}
40. Il risultato
public class GameField {
...
public boolean startGame(PlacingMode pm) {
placingStrategyFactory.create(pm, fieldSize).place(birds);
gameStarted = isGameStarted();
return gameStarted;
}
...
}
41. Altri IF che non ci
piacciono
private boolean isGameFieldValid()
{
boolean isValid = true;
for(Bird bird : birds) {
isValid = fieldSize.isWithinField(bird.getLocation());
if (!isValid)
break;
}
return isValid;
}
52. Conclusioni
Test funzionali attorno al codice da refattorizzare
Refactoring ad ogni step e’ l’ideale
Se si parte con codice legacy usate le techniche viste