domingo, 4 de septiembre de 2011

KATA REFACTORING

Last Koding dojo in BecodeMyFriend, was about the kata
https://github.com/wouterla/GildedRose/
and all of us where jumping between solutions as with deep in the code.
When i go back to home i decided to did that kata with an stricted procedure , and this is the results, i had expend 5 pomodoros of 30 minutes

The flow was :
You could follow all my keystrokes with the file of MoonEdit soft , KataGildedRose
  1. prepare test over Item Class

  2. prepare test for GildeRose Class.

  3. replace magic number with symbolic constant

  4. write code to complete the conjured item test

  5. consolidate conditional expresions

  6. extract method for increments , decrements of values, and expired()
  7. Split conditionals, substitute else, split nested if clauses, change sucesive if with && operator.

  8. noTopQuality() and hasQuality() conditions, send to increaseQuality and decreaseQuality method couse they always are checked before those methods

  9. solve double negations

  10. erase inclusive at sentences with exclusive clauses ( if (isA) && (!isB) ==> so isA

  11. try to extract common clauses, (ad !sufuras if posible) then extract it

  12. extract method of each item behaviour

  13. join the code of conjured item to the new behaviour sistem

  14. replace conditional with polimorphism




    1. prepare test over Item Class
      download the file

      import junit.framework.TestCase;


      public class ItemTest extends TestCase {
      //All items have a SellIn value
      public void testHasSellInValue()
      {
      int expectedResult =23;
      int myItemSellIn=23;
      int myItemQuality = 55;
      Item myItem= new Item("myName",myItemSellIn,myItemQuality);
      assertEquals(expectedResult, myItem.getSellIn());
      }
      //All items have a Quality value
      public void testHasQualityValue()
      {
      int expectedResult =55;
      int myItemSellIn=23;
      int myItemQuality = 55;
      Item myItem= new Item("myName",myItemSellIn,myItemQuality);
      assertEquals(expectedResult, myItem.getQuality());
      }
      //At the end of each day our system lowers both values for every item
      public void testCouldBeDecreasedSellInValue()
      {
      int expectedResult =22;
      int myItemSellIn=23;
      int myItemQuality = 55;
      Item myItem= new Item("myName",myItemSellIn,myItemQuality);
      myItem.setSellIn(myItem.getSellIn()-1);
      assertEquals(expectedResult, myItem.getSellIn());
      }
      public void testCouldBeDecreasedQuality()
      {
      int expectedResult =54;
      int myItemSellIn=23;
      int myItemQuality = 55;
      Item myItem= new Item("myName",myItemSellIn,myItemQuality);
      myItem.setQuality(myItem.getQuality()-1);
      assertEquals(expectedResult, myItem.getQuality());
      }


      }


    2. prepare test for GildeRose Class. here i make a little change to avoid use of reflection for test private fields. i 'd change items from private to public scope
      download the testClass file of GildedRose

      import junit.framework.TestCase;


      public class GildedRoseTest extends TestCase {
      private static final int DEXTERY_VEST = 0;
      private static final int AGED_BRIE = 1;
      private static final int ELIXIR_MONGOOSE = 2;
      private static final int SULFURAS = 3;
      private static final int BACKSTAGE_PASSES = 4;
      private static final int CONJURED = 5;

      // Once the sell by date has passed, Quality degrades twice as fast
      public void testDegradeTwiceWhenSellInDatePassed (){
      initializeGildedRose();
      int valueBeforeLastNormalDegradation =0;
      int valueAfterLastNormalDegradation =0;

      while (GildedRose.items.get(DEXTERY_VEST).getSellIn()>0) {
      valueBeforeLastNormalDegradation = GildedRose.items.get(DEXTERY_VEST).getQuality();
      GildedRose.updateQuality();
      valueAfterLastNormalDegradation = GildedRose.items.get(DEXTERY_VEST).getQuality();
      }
      int qualityNormalDegradationRate = valueBeforeLastNormalDegradation-valueAfterLastNormalDegradation;
      int qualityBeforeDegradeAfterSellInPassed = GildedRose.items.get(DEXTERY_VEST).getQuality();

      GildedRose.updateQuality();

      assertEquals(qualityBeforeDegradeAfterSellInPassed - (qualityNormalDegradationRate*2),GildedRose.items.get(DEXTERY_VEST).getQuality());
      }
      // The Quality of an item is never negative
      public void testNeverNegativeQuality(){
      initializeGildedRose();
      updateTillReachQuality(ELIXIR_MONGOOSE, 0);

      GildedRose.updateQuality();

      assertTrue(GildedRose.items.get(ELIXIR_MONGOOSE).getQuality()>=0);
      }
      // "Aged Brie" actually increases in Quality the older it gets
      public void testAgedBrieIncrementsQualiyAtUpdate(){
      initializeGildedRose();
      int beforeQuality = GildedRose.items.get(AGED_BRIE).getQuality();
      GildedRose.updateQuality();
      int afterQuality = GildedRose.items.get(AGED_BRIE).getQuality();

      assertTrue(afterQuality>beforeQuality);
      }
      // The Quality of an item is never more than 50
      public void testqualityNeverMoreThanFifty(){
      initializeGildedRose();
      updateTillReachQuality(AGED_BRIE, 45);
      for (int timesToGuaranteeAsMuchAsCanFifty = 0; timesToGuaranteeAsMuchAsCanFifty < 5; timesToGuaranteeAsMuchAsCanFifty++) { GildedRose.updateQuality(); } GildedRose.updateQuality(); assertTrue(GildedRose.items.get(AGED_BRIE).getQuality()<=50); } // "Sulfuras", being a legendary item, never has to be sold or decreases in Quality public void testSulfurasNoSellInAndNoLooseQuality(){ initializeGildedRose(); int beforeQuality = GildedRose.items.get(SULFURAS).getQuality(); int beforeSellIn = GildedRose.items.get(SULFURAS).getSellIn(); GildedRose.updateQuality(); int afterQuality = GildedRose.items.get(SULFURAS).getQuality(); int afterSellIn = GildedRose.items.get(SULFURAS).getSellIn(); assertTrue(beforeSellIn == afterSellIn); assertTrue(beforeSellIn==0); assertTrue(afterQuality==beforeQuality); } // "Backstage passes", like aged brie, increases in Quality as it's SellIn value approaches; Quality increases by 2 when there are 10 days or less and by 3 when there are 5 days or less but Quality drops to 0 after the concert public void testBackStageBehaviour(){ initializeGildedRose(); updateTillSellIn(BACKSTAGE_PASSES,11); int qualityBeforeAnUpdateToTenDays = GildedRose.items.get(BACKSTAGE_PASSES).getQuality(); GildedRose.updateQuality(); int qualityAfterAnUpdateToTenDays = GildedRose.items.get(BACKSTAGE_PASSES).getQuality(); updateTillSellIn(BACKSTAGE_PASSES,6); int qualityBeforeAnUpdateToFiveDays = GildedRose.items.get(BACKSTAGE_PASSES).getQuality(); GildedRose.updateQuality(); int qualityAfterAnUpdateToFiveDays = GildedRose.items.get(BACKSTAGE_PASSES).getQuality(); updateTillSellIn(BACKSTAGE_PASSES,1); GildedRose.updateQuality(); int qualityCeroDaysLeft = GildedRose.items.get(BACKSTAGE_PASSES).getQuality(); int qualityGapAtTen = qualityAfterAnUpdateToTenDays-qualityBeforeAnUpdateToTenDays; int qualityGapAtFive = qualityAfterAnUpdateToFiveDays - qualityBeforeAnUpdateToFiveDays; assertEquals(qualityGapAtTen, 2); assertEquals(qualityGapAtFive, 3); assertTrue(qualityCeroDaysLeft==0); } // "Conjured" items degrade in Quality twice as fast as normal items public void testConjuredItemDegradesTwiceAsNormal (){ initializeGildedRose(); int valueBeforeLastNormalDegradation = GildedRose.items.get(DEXTERY_VEST).getQuality(); int valueBeforeDegradation = GildedRose.items.get(CONJURED).getQuality(); GildedRose.updateQuality(); int valueAfterLastNormalDegradation = GildedRose.items.get(DEXTERY_VEST).getQuality(); int valueAfterDegradation = GildedRose.items.get(CONJURED).getQuality(); int qualityNormalDegradationRate = valueBeforeLastNormalDegradation - valueAfterLastNormalDegradation; int qualityConjuredDegradationRate = valueBeforeDegradation - valueAfterDegradation; assertEquals(qualityNormalDegradationRate*2 , qualityConjuredDegradationRate); } public void testConjuredDegradeTwiceWhenSellInDatePassed (){ initializeGildedRose(); int valueBeforeLastNormalDegradation =0; int valueAfterLastNormalDegradation =0; while (GildedRose.items.get(CONJURED).getSellIn()>=0) {
      valueBeforeLastNormalDegradation = GildedRose.items.get(CONJURED).getQuality();
      GildedRose.updateQuality();
      valueAfterLastNormalDegradation = GildedRose.items.get(CONJURED).getQuality();
      }
      int qualityNormalDegradationRate = valueBeforeLastNormalDegradation-valueAfterLastNormalDegradation;
      int qualityBeforeDegradeAfterSellInPassed = GildedRose.items.get(CONJURED).getQuality();

      GildedRose.updateQuality();

      assertEquals(qualityBeforeDegradeAfterSellInPassed - (qualityNormalDegradationRate*2),GildedRose.items.get(CONJURED).getQuality());
      }


      private void updateTillSellIn(int itemNumber, int sellInBottom) {
      while (GildedRose.items.get(itemNumber).getSellIn() >= sellInBottom) {
      GildedRose.updateQuality();
      }
      }
      private void initializeGildedRose() {
      GildedRose.main(null);
      }

      private void updateTillReachQuality(int itemNumber, int qualityToReach) {
      while (GildedRose.items.get(itemNumber).getQuality() <= qualityToReach) { GildedRose.updateQuality(); } } }


    3. replace magic number with symbolic constant

    4. write code to complete the conjured item test

    5. consolidate conditional expresions

    6. extract method for increments , decrements of values, and expired()
      after this we gat the code

      import java.util.ArrayList;
      import java.util.List;


      public class GildedRose {

      private static List items = null;

      /**
      * @param args
      */
      private final static String DEXTERY_VEST = "+5 Dexterity Vest";
      private final static String AGED_BRIE = "Aged Brie";
      private final static String ELIXIR_MONGOOSE = "Elixir of the Mongoose";
      private final static String SULFURAS = "Sulfuras, Hand of Ragnaros";
      private final static String BACKSTAGE_PASSES = "Backstage passes to a TAFKAL80ETC concert";
      private final static String CONJURED = "Conjured Mana Cake";
      private final static int MAX_QUALITY = 50;
      private final static int MIN_QUALITY = 0;

      public static void main(String[] args) {

      System.out.println("OMGHAI!");

      items = new ArrayList();
      items.add(new Item(DEXTERY_VEST, 10, 20));
      items.add(new Item(AGED_BRIE, 2, 0));
      items.add(new Item(ELIXIR_MONGOOSE, 5, 7));
      items.add(new Item(SULFURAS, 0, 80));
      items.add(new Item(BACKSTAGE_PASSES, 15, 20));
      items.add(new Item("Conjured Mana Cake", 3, 6));

      updateQuality();
      }


      public static void updateQuality()
      {
      int conjuredQualityBeforeUpdate = getConjuredQuality();
      for (int i = 0; i < items.size(); i++) { if ((!isAged(i) && !isBackStage(i)) { if (hasQuality(i)) { if (!isSulfuras(i)) { decrementQuality(i); } } } else { if (noTopQuality(i)) { incrementQuality(i); if (isBackStage(i)) { if (items.get(i).getSellIn() < 11) { if (notTopQuality(i)) { incrementQuality(i); } } if (items.get(i).getSellIn() < 6) { if (noTopQuality(i)) { incrementQuality(i); } } } } } if (!isSulfuras(i)) { decrementSellIn(i); } if (expired(i)) { if (!isAged(i)) { if (!isBackStage(i)) { if (hasQuality(i)) { if (!isSulfuras(i)) { decrementQuality(i); } } } else { looseAllQuality(i); } } else { if (noTopQuality(i)) { incrementQuality(i); } } } } int conjuredQualityAfterUpdate = getConjuredQuality(); int normalItemQualityChange = conjuredQualityAfterUpdate - conjuredQualityBeforeUpdate); setConjuredQuality(getConjuredQuality() + normalItemQualityChange); } private static int getConjuredQuality(){ return getQualityByName(CONJURED); } private static void setConjuredQuality(int amount){ setConjuredQualityByName(CONJURED,amount); } private static void setConjuredQualityByName(String name,int amount){ for (int i = 0; i < items.size(); i++) { if (name.equals(items.get(i).getName())) items.get(i).setQuality(amount); } } private static int getQualityByName(String name){ for (int i = 0; i < items.size(); i++) { if (name.equals(items.get(i).getName())) return items.get(i).getQuality(); } return 0; } private static boolean isAged(int i) { return AGED_BRIE.equals(getNameByIndex(i)); } private static boolean isBackStage(int i) { return BACKSTAGE_PASSES.equals(getNameByIndex(i)); } private static boolean isSulfuras(int i) { return SULFURAS.equals(getNameByIndex(i)); } private static String getNameByIndex(int i) { return items.get(i).getName(); } private static void decrementQuality(int i) { changeQuality (i,- 1); } private static void incrementQuality(int i) { changeQuality(i,1); } private static void changeQuality(int i,int amount) { items.get(i).setQuality(items.get(i).getQuality() + amount); } private static boolean hasQuality(int i) { return (items.get(i).getQuality() > MIN_QUALITY);
      }
      private static boolean noTopQuality(int i)
      {
      return (items.get(i).getQuality() < MAX_QUALITY); } private static void looseAllQuality(int i) { items.get(i).setQuality(MIN_QUALITY); } private static void decrementSellIn(int i) { items.get(i).setSellIn(items.get(i).getSellIn() - 1); } private static boolean expired(int i) { return (items.get(i).getSellIn() < 0); } }

    7. Split conditionals, substitute else, split nested if clauses, change sucesive if with && operator.

    8. this only the code changed

      public static void updateQuality()
      {
      int conjuredQualityBeforeUpdate = getConjuredQuality();
      for (int i = 0; i < items.size(); i++) { //hasquality() and no topQuality are quality pro if ((!isAged(i) && !isBackStage(i))&&(hasQuality(i))&&(!isSulfuras(i))) { decrementQuality(i); } if ((!((!isAged(i) && !isBackStage(i)))&&(noTopQuality(i)))) { incrementQuality(i); } if ((!((!isAged(i) && !isBackStage(i)))&&(noTopQuality(i))&&(isBackStage(i))&&(items.get(i).getSellIn() < 11)&&(noTopQuality(i)))) { incrementQuality(i); } if ((isBackStage(i))&&(items.get(i).getSellIn() < 6)&& (noTopQuality(i))) { incrementQuality(i); } if (!isSulfuras(i)) { decrementSellIn(i); } if ((expired(i))&&(!isAged(i))&&(!isBackStage(i))&&if (hasQuality(i))&&if (!isSulfuras(i))) { decrementQuality(i); } if (((expired(i)&&(!isAged(i)) &&(isBackStage(i)))) { looseAllQuality(i); } if ((isAged(i))&&(noTopQuality(i))) { incrementQuality(i); } } int conjuredQualityAfterUpdate = getConjuredQuality(); int normalItemQualityChange = conjuredQualityAfterUpdate - conjuredQualityBeforeUpdate; setConjuredQuality(getConjuredQuality() + normalItemQualityChange); }

    9. noTopQuality() and hasQuality() conditions, send to increaseQuality and decreaseQuality method couse they always are checked before those methods

    10. solve double negations

    11. erase inclusive at sentences with exclusive clauses ( if (isA) && (!isB) ==> so isA

    12. try to extract common clauses, (ad !sufuras if posible) then extract it


    13. public static void updateQuality()
      {
      int conjuredQualityBeforeUpdate = getConjuredQuality();
      for (int i = 0; i < items.size(); i++) { //extrac condition common if (!isSulfuras(i))) { decrementSellIn(i); if (isAged(i) || isBackStage(i)) { incrementQuality(i); } else { decrementQuality(i); } if (isBackStage(i)) { if (expired(i) { looseAllQuality(i); } if ((items.get(i).getSellIn() < 11)) { incrementQuality(i); } if ((items.get(i).getSellIn() < 6)) { incrementQuality(i); } } if ((expired(i))&&(!isAged(i))&&(!isBackStage(i)) { decrementQuality(i); } if ((isAged(i))) { incrementQuality(i); } } } int conjuredQualityAfterUpdate = getConjuredQuality(); int normalItemQualityChange = conjuredQualityAfterUpdate - conjuredQualityBeforeUpdate; setConjuredQuality(getConjuredQuality() + normalItemQualityChange); }

    14. extract method of each item behaviour

    15. join the code of conjured item to the new behaviour sistem



    16. public static void updateQuality()
      {
      for (int i = 0; i < items.size(); i++) { if (!isSulfuras(i)) { decrementSellIn(i); updateNormalItem(i); updateConjuredItem(i); if (isAged(i)) updateQualityAged(i); if (isBackStage(i)) updateQualityBackStage(i); } } } private static void updateConjuredItem(int i) { updateNormalItem(i); updateNormalItem(i); } private static void updateNormalItem (int i) { updateQualityNormalItem(i); if (expired(i)) updateQualityExpiredNormalItem(i); } private static void updateQualityExpiredNormalItem(int i) { if ((!isAged(i))&&(!isBackStage(i))) { decrementQuality(i); } } private static void updateQualityNormalItem(int i) { if (!isBackStage(i) && !isAged(i)) { decrementQuality(i); } } private static void updateQualityAged(int i) { incrementQuality(i); incrementQuality(i); } private static void updateQualityBackStage(int i) { incrementQuality(i); if (expired(i)) { looseAllQuality(i); } if ((items.get(i).getSellIn() < 11)) { incrementQuality(i); } if ((items.get(i).getSellIn() < 6)) { incrementQuality(i); } }

    17. replace conditional with polimorphism



    18. import java.util.ArrayList;
      import java.util.List;


      public class GildedRose {

      private static List items = null;

      /**
      * @param args
      */
      private final static String DEXTERY_VEST = "+5 Dexterity Vest";
      private final static String AGED_BRIE = "Aged Brie";
      private final static String ELIXIR_MONGOOSE = "Elixir of the Mongoose";
      private final static String SULFURAS = "Sulfuras, Hand of Ragnaros";
      private final static String BACKSTAGE_PASSES = "Backstage passes to a TAFKAL80ETC concert";
      private final static String CONJURED = "Conjured Mana Cake";
      //
      public static void main(String[] args) {

      System.out.println("OMGHAI!");

      items = new ArrayList();
      items.add(new Item(DEXTERY_VEST, 10, 20));
      items.add(new Item(AGED_BRIE, 2, 0));
      items.add(new Item(ELIXIR_MONGOOSE, 5, 7));
      items.add(new Item(SULFURAS, 0, 80));
      items.add(new Item(BACKSTAGE_PASSES, 15, 20));
      items.add(new Item("Conjured Mana Cake", 3, 6));

      updateQuality();
      }

      public static void updateQuality()
      {
      for (int i = 0; i < items.size(); i++) { if (!isSulfuras) { Article itemToUpdate = factoriaDeItems(i); itemToUpdate.decrementSellIn(); itemToUpdate.updateQuality(); copyValues(items.get(i),itemToUpdate); } } } private static void copyValues(Item item, Article itemToUpdate) { item.setQuality(itemToUpdate.getQuality()); item.setSellIn(itemToUpdate.getSellIn()); } private static Article factoriaDeItems(int i) { if (isAged(i)) return new AgedBrie(items.get(i)); if (isBackStage(i)) return new BackStage(items.get(i)); if (isConjured(i)) return new Conjured(items.get(i)); return new GenericArticle(items.get(i)); } private static boolean isAged(int i) { return AGED_BRIE.equals(getNameByIndex(i)); } private static boolean isBackStage(int i) { return BACKSTAGE_PASSES.equals(getNameByIndex(i)); } private static boolean isSulfuras(int i) { return SULFURAS.equals(getNameByIndex(i)); } private static boolean isConjured(int i) { return CONJURED.equals(getNameByIndex(i)); } private static String getNameByIndex(int i) { return items.get(i).getName(); } } public abstract class Article extends Item { private final static int MAX_QUALITY = 50; private final static int MIN_QUALITY = 0; public Article(String name, int sellIn, int quality) { super(name, sellIn, quality); } public Article (Item item) { super (item.getName(),item.getSellIn(), item.getQuality()); } public abstract void updateQuality(); protected void decrementSellIn() { setSellIn(getSellIn() - 1); } protected void decrementQuality() { if (hasQuality()) changeQuality (- 1); } protected void incrementQuality() { if (noTopQuality()) changeQuality(1); } protected void changeQuality(int amount) { setQuality(getQuality() + amount); } protected boolean hasQuality() { return (getQuality() > MIN_QUALITY);
      }
      protected boolean noTopQuality()
      {
      return (getQuality() < MAX_QUALITY); } protected void looseAllQuality() { setQuality(MIN_QUALITY); } protected boolean expired() { return (getSellIn() < 0); } } public class GenericArticle extends Article { public GenericArticle (Item item) { super (item); } public void updateQuality() { //extracted of method updateNormalItem() decrementQuality(); if (expired()) decrementQuality(); } } public class AgedBrie extends Article { public AgedBrie (Item item) { super (item); } public void updateQuality() { incrementQuality(); incrementQuality(); } } public class BackStage extends Article { public BackStage (Item item) { super (item); } public void updateQuality() { incrementQuality(); if ((getSellIn() < 11)) { incrementQuality(); } if ((getSellIn() < 6)) { incrementQuality(); } if (expired()) { looseAllQuality(); } } } public class Conjured extends Article { public Conjured (Item item) { super(item); } public void updateQuality() { decrementQuality(); decrementQuality(); if (expired()) { decrementQuality(); decrementQuality(); } } }


      read next post to see
      from Is-a to Has-a and from extends to implements

viernes, 26 de agosto de 2011

lecturas que voy haciendo

http://www.slideshare.net/fungfung/refactoringch7-moving-feature-btw-objects
http://www.source-code.biz/snippets/java/3.htm, just use vector f.e.
http://www.ensta-paristech.fr/~diam/java/online/notes-java/data/expressions/22compareobjects.html
http://pivotallabs.com/users/alex/blog/articles/273-lovely-demeter-meter-maid, LOL , al final se calienta el tema
http://www.purpletech.com/blog/index.php?itemid=25
http://moffdub.wordpress.com/2008/09/27/domain-driven-method-naming-guidelines/#comment-236

Test Driven Develpment SMELL thoughts

Cuando enuncio un test , y luego desarrollo el código para conseguir ponerlo en verde , veo que al poner assertions de tipo TRUE FALSE, el error no me escupe el resultado que comparo, mientras que si pongo un equals si que me lo da.
SMELL -> assertsTrue and assertsFalse use.
GOOD PRACTICE -> no uses assertions tipo True/false mas que como último remedio.

Otro problema que me he encontrado es que a veces para depurar el código y ver la razón por la que no me ha funcionado un test, me veo obligado a poner salidas por el stdout que me muestren algún valor de variables en el código, o en el test.
Si te ves obligado a ello , probablemente debes pensar primero si tu test es lo suficiente unitario, "solo probar una funcionalidad cada vez", o bien si en los métodos de tu código estas necesitando extract type refactoring (class, method, field).
SMELL -> logs, debug intermediate variables, system.out.println, .......
GOOD PRACTICE -> reescribe el código o crea un nuevo test antes de usar logs para depurar el código.

En algunos al escribir un test sobre una funcionalidad complicada me veo perdido pues para poner verde el test debo de programar mucho código con muchos methodos privados. Normalmente esto metodos lo que hacen es calcular datos necesarios o validar variables, hay que ver los métodos y clases involucradas y usar el fallback de ellas para probar cada una de las opciones , y expander el test haciendolo mas vervose y probando cada una de las opciones. Por ejemplo metodos que devuelvan un booleano ,pensar un test que pruebe el resultado de la funcionalidad completa con ese booleano a false, y luego en true, asi con todo.
SMELL -> too many methods or classes creates for acomplish a test. Need to change the scope of a method to public , just to test it.
GOOD PRACTICE -> Split test in each case depending on secundary methods results.

miércoles, 24 de agosto de 2011

Test Driven Develpment Issues in refactoring

I am practicing with the Kata minesWeesper, an in the development of the code i have introduce a Class that parses the file.
When that Class parses and find a pair of numbers valid (see kata doc) it returns that pair.
First i was coding that pair as int[2], in order to make an extract class refactoring when the code was write, so my methods like getNextDimensions() returns a int[2].
I wrote the test in Junit and in order to check the answer i had compared to a new int[2]= [expectedResultLines,expecterResulColumns].
Once all my test pass, i began to refactor code. (this could be a point of my error, but not the clue), when i did the refactor, i created a class MapDimensions , which stores the dimensions of the map in two variables.
And now a find me changing all the test and the clients of my class.

How i could avoid that mistake?

I don't know just now but i can expose some clues.
SMELL.- test comparing primitive results.--> change to a class container.
think also to use encapsulate field
Good practice --> introduce name of the expected type of answer in test name
introduce "expected" word in the declaration of the expected result to mach in the test.

jueves, 18 de agosto de 2011

Test System.out.println() Junit

Durante la realizacion de una kata
minesweeper
Me surgió la implementación de una clase que mantiene los mapas de minas, y que debe tener un método publico el cual imprime el mapa de minas.
al principio lo que hice fué crear un metodo tambien publico llamado expresate() que devolvia la cadena que luego se imprimiria mediante un System.out.println(), sin embargo esto lo que suponia era la exposicion de una metodo en el api que no tenia porque ser publico.
al refactorizar y hacer los metodos privados excepto los necesarios para el api, me encontraba con el problema de comprobar que es lo que sale por el stdOut
esto lo solucioné mirando en una pagina que me indicaba como hacerlo así que ...
antes tenia:

package buscaminas;

import junit.framework.TestCase;

public class MapaMinasTest extends TestCase {
public void testProcesaLineaVacia() {
String lineaAProcesar = "......";
int ordenTablero = lineaAProcesar.length();
MapaMinas testMapa = new MapaMinas(1,ordenTablero);
testMapa.procesaLinea(0, lineaAProcesar);
String resultado=testMapa.expresate(0);
assertEquals("000000", resultado);
}

y ahora:

package buscaminas;

import java.io.ByteArrayOutputStream;
import java.io.PrintStream;

import junit.framework.TestCase;

public class MapaMinasTest extends TestCase {
private final ByteArrayOutputStream outContent = new ByteArrayOutputStream();
private final ByteArrayOutputStream errContent = new ByteArrayOutputStream();


public void testProcesaLineaVacia() {
System.setOut(new PrintStream(outContent));
System.setErr(new PrintStream(errContent));

String lineaAProcesar = "......";
int ordenTablero = lineaAProcesar.length();
MapaMinas testMapa = new MapaMinas(1,ordenTablero);
testMapa.procesaLinea(0, lineaAProcesar);
testMapa.imprime();

assertEquals("000000\r\n", outContent.toString());
System.setOut(null);
System.setErr(null);

}


notese adema de las variables creadas y el método llamado, el comparador:

assertEquals("000000", resultado);
assertEquals("000000\r\n", outContent.toString());

se debe introducir el retorno de carro y el final de linea del println() que hay en el código del método imprimete()

miércoles, 17 de agosto de 2011

File Read good Practice

Muchas veces nos encontramos ante la lectura de un fichero que contiene datos que nuestro programa debe procesar.
Seguro que existen muchas "recomendaciones" al respecto pero yo no encuentro ninguna ahora así que voy a poner lo que me parece hoy por hoy y según se amplíe mi conocimiento y experiencia lo modificaré.
1.- ¿Comprobamos que existe el fichero y su integridad?
los siguientes puntos pueden depender de si una información posterior en el fichero puede alterar el proceso de información anterior. pero se trata de tener una norma genérica.
2.- ¿leemos los datos de golpe o secuencialmente linea tras linea?
3.- ¿procesamos los datos según los estamos leyendo o los almacenamos en memoria?
4.- ¿si los almacenamos , liberamos la memoria al final?


ventajas de guardar el puntero a donde estamos leyendo en un variable global
ventajas de leer todo

lunes, 15 de agosto de 2011

replace conditional with polymorphism

Este metodo de refactorizacion ha ido cambiando , he empezado a trabajar con el ejemplo que se puede ver en Refactorización: Reemplazar un condicional por polimorfismo
y he acabado implementando un factory patter al estilo de la web
el codigo inicial era , completado para que funcione.

double final MAX_VEHICLE_SPEED = 18;
double final TRUKC_LOAD_FACTOR = 28;
double final BUS_SAFETY_FACTOR = 44;
double loadFactor;
double safetyFactor;

double getSpeed(){

_type= "CAR";
_load = 12;
_risk = 3;
setLoadFactor (TRUCK_LOAD_FACTOR);
setSafetyFactor (BUS_SAFETY_FACTOR);

switch (_type) {
case CAR:
return getMaxSpeed();
case TRUCK:
return getMaxSpeed() - getLoadFactor()* _load;
case BUS:
return getMaxSpeed() - getSafetyFactor()*_risk;
}
double getMaxSpeed()
{
return MAX_VEHICLE_SPEED;
}
private setLoadFactor(Double factor)
{
loadFactor = 0;
if (factor) loadFactor = factor;
}
double getLoadFactor()
{
return loadFactor;
}
private setSafetyFactor(Double factor)
{
safetyFactor = 0;
if (factor) safetyFactor = factor;
}
double getSafetyFactor()
{
return safetyFactor;
}

esto pasa de la forma que se puede observar en el archivo de MoonEdit

Class Cliente
{
private Integer _load;
private Integer _risk;
privat String _type;

double getSpeed()
{
_load = 12;
_risk = 3;
return new VehicleFactory(_type,_load,_risk).getVehicle().getSpeed(); //smell
}
}

Class VehicleFactory
{
final String CAR = "car";
final String BUS = "bus";
final String TRUNCK = "truck";

getVehicle(String type, Integer load , Integer risk)
{
switch (type)
{
case CAR:
return new Car();
case TRUCK:
return new Truck(load);
case BUS:
return new Bus(risk);
}
return new Exception ("Vehiclefactory","no vehicle type");
}
}

Class abstract Vehicle
{
private integer _maxSpeed;

private Integer getMaxSpeed()
{
return _maxSpeed;
}
private setMaxSpeed(Integer speed)
{
_maxSpeed = speed;
}
public abstract Double getSpeed()
}

Class Car extends Vehicle
{
private final Integer CAR_MAX_SPEED = 18;

public Car()
{
setMaxSpeed(CAR_MAX_SPEED);
}

public Double getSpeed()
{
return getMaxSpeed();
}
}

Class Truck
{
private final Integer TRUCK_MAX_SPEED = 18;
private final Integer TRUCK_LOAD_FACTOR = 28;
private Integer _loadFactor;
private Integer _load;

public Truck(Integer load)
{
setMaxSpeed(TRUCK_MAX_SPEED);
setLoadFactor(TRUNK_LOAD_FACTOR);
if (load) _load = load else load = 0;
}
public Integer getLoadFactor()
{
return _loadFactor;
}
private setLoadFactor (Integer loadFactor)
{
_loadFactor = loadFactor;
}
private Double getSpeed()
{
return getMaxSpeed() + getLoadFactor() * _load;
}
}

Class Bus
{
private final Integer BUS_MAX_SPEED = 18;
private final Integer BUS_SAFETY_FACTOR = 44;
private Integer _safetyFactor;
private Integer risk;

public Bus(Integer risk)
{
setMaxSpeed(BUS_MAX_SPEED);
setSafetyFactor (BUS_SAFETY_FACTOR);
if (risk) _risk = risk else _risk =0; ahora luego hago los seters para ponerlo bien.
}
public Integer getsafetyFactor()
{
return _safetyFactor;
}
private setSafetyFactor (Integer safetyFactor)
{
_safetyFactor = safetyFactor;
}
private abstract Double getSafetySpeed()
{
return getMaxSpeed() - getSafetyFactor() * risk;
}
private Double getSpeed()
{
return getSafetySpeed();
}
}

domingo, 7 de agosto de 2011

flash cards refactoring

Aquí tengo subidas unas cuantas flashcards para ayudar a prenderme todos los refactorings que hay y las familias a las que pertenecen, así como códigos de ejemplo para aprender a identificar el refactoring que hay que hacer.
flashcards sobre refactoring

sábado, 6 de agosto de 2011

Replace Parameter with Method

el proceso entero con comentarios se puede seguir mediante el archivo de MoonEdit (programa muy chulo , gratis y sin peso apenas) replace parameter with method
CODIGO INICIAL
public double getPrice()
{
int basePrice = _quantity * _itemPrice;
int discountLevel;
if (_quantity > 300) discountLevel = 3;
else if (_quantity > 200) discountLevel = 2;
else discountLevel = 1;
double finalPrice = discountedPrice (basePrice, discountLevel);
return finalPrice;
}

private double discountedPrice (int basePrice, int discountLevel)
{
if (discountLevel == 2) return basePrice * 0.1;
elseif (discountLevel == 3) return basePrice * 0.2;
else return basePrice * 0.05;
}


CODIGO FINAL
public double getPrice()
{
if (Discount.isEnoughQuantityForPlatinum(_quantity))
return getBasePrice() * Discount.PLATINUM.getPercentage();
elseif (Discount.isEnoughQuantityForPlus(_quantity))
return getBasePrice() * Discount.PLUS.getPercentage();
else
return getBasePrice() * Discount.NORMAL.getPercentage();
}
private int getBasePrice()
{
return _quantity * _itemPrice;
}
class Discount
{
static final int MIN_QUANTITY_FOR_PLATINUM = 300;
static final int MIN_QUANTITY_FOR_PLUS = 200;

static final int PLATUNUM_LEVEL = 3;
static final int PLUS_LEVEL = 2;
static final int NORMAL_LEVEL = 1;
static final float PLATINUM__PERCENTAGE = 0.2;
static final float PLUS__PERCENTAGE = 0.1;
static final float NORMAL_PERCENTAGE = 0,05;
public static final Discount NORMAL = new Discount (NORMAL_LEVEL , NORMAL_PERCENTAGE);
public static final Discount PLUS = new Discount (PLUS_LEVEL , PLUS_PERCENTAGE);
public static final Discount PLATINUM = new Discount (PLATINUM_LEVEL , PLATINUM_PERCENTAGE);
private final int _level;
private final float _percentage;

private Discount(int level,float percentage)
{
_level = level;
_percentage = percentage;
}
public getLevel()
{
return _level;
}
public getPercentage()
{
return _percentage;
}
public boolean isEnoughQuantityForPlus(quantity)
{
if (quantity > MIN_QUANTITY_FOR_PLUS) return true;
return false;
}
public boolean isEnoughQuantityForPlatinum(quantity)
{
if (quantity > MIN_QUANTITY_FOR_PLATINUM) return true;
return false;
}


bad Smell: getPrice() -- long method , finalPrice variable is always the discount Calculated Price
refactoring used :
replace parameter with method <- making methods calls simpler
invented name refactor (i don´t know if its a current name for this)
introduce multible return points <- simplifying Conditional Expressions
inline method <- componsing methods
replace Magic Number with Symbolic constant <- organizing data
replace type code with class <- organizing data
move method <- move features between objects
consolidate conditional expression <- simplifying conditional expressions

jueves, 4 de agosto de 2011

Preserve Whole Object exception

He tratado de realizar variaciones sobre la posibilidad que se apunta en "sourcemaking.com":
[motivation] "Passing in the required object causes a dependency between the required object and the called object. If this going to mess up your dependency structure, don't use Preserve Whole Object".
Como excepción a la aplicación de preserve whole object<-making methods caller sympler.
para ello he usado un código inventado por mi a bote pronto, sobre la gestión de una película y la contabilidad de sus gastos quedando el código de la forma siguiente:
se puede descargar el archivo de moonEdit para ver como he llegado a ese código y consideraciones preserver whole object exception
class peliculaDeMiedo
{
main
Camara camara = new Camara(4);
integer Plano = 6;
integer escena = 8;
integer litrosSangre= 44;
String [] actores = [hulbrainer -> datosHulbrainer, kevincosnerjose ->datosKevincosnerjose, avaganer->datosAvaganer];
Vampiro dragucula = new Vampiro(camara,plano,escena,actores[hulbrainer]);
...
...
ElementosPelicula elementosUsados = new ElementosPeliculaMiedo(camara,plano,escena,litrosSangre);
Double pagaDia = new ContableMiedo().dameLaPagaStaff(elementosUsados);
}

class ElementosPelicula
class ElementosPeliculaAmor extends ElementosPelicula
class ElementosPeliculaMiedo extends ElementosPelicula


class Camara
{
Camara(integer camara)
{
un monton de peticiones a otros metodos, consultas a servicios y logaritmos nepericonianos;
this.ordenDeLaCamara=camara;
}
Double calculaCosteAnual()
{
un monton de calculos y de historias que miran el precio de la camara , depreciacion , duracion de la lampara y lo que te de la gana
}
}

class Vampiro
Vampiro (Camara CamaraEnfocada, integer PlanoPelicula, integer escena,Actor actor)
{
sus cosas
}

class Contable
{
Contable()
{
se hacer cositas con los números
}
public Double damePagaStaff (elementosPelicula elementos)
{
Double consteCamara = elementos.camara.calcilaCosteAnual()/365;
double gastoExtraPelicula = calculaGastoProducto(elementos);
etc etc....
return unapasta;
}
private abstract double calculaGastoProducto(ElementosPelicula elementos);
}

class ContableMiedo extends Contable
calculaGastoProducto(elementos)
{
return elementos.litrosSangre*getPrecioMercadoEuros();
}

class ContableAmor extends Contable
calculaGastoProducto(elementos)
{
return elementos.kilosCebolla*getPrecioMercadoEuros();
}


class peliculaDeAmor
{
main
Camara camara = new Camara(4);
integer Plano = 6;
integer escena = 8;
integer kilosCebolla= 4;
String [] actores = [hulbrainer -> datosHulbrainer, kevincosnerjose ->datosKevincosnerjose, avaganer->datosAvaganer];
...
...
ElementosPelicula elementosUsados = new ElementosPeliculaAmor(camara,plano,escena,kilosCebolla)
Double pagaDia = new ContableAmor().dameLaPagaStaff(elementosUsados);
}

extract method

Estas son una serie de consideraciones sobre el código de ejemplo de Extract method de la web de sourcemaking.com

void printOwing(double previousAmount)
{
Enumeration e = _orders.elements();
double outstanding = previousAmount * 1.2;

// print banner
System.out.println ("**************************");
System.out.println ("***** Customer Owes ******");
System.out.println ("**************************");

// calculate outstanding
while (e.hasMoreElements()) {
Order each = (Order) e.nextElement();
outstanding += each.getAmount();
}

//print details
System.out.println ("name:" + _name);
System.out.println ("amount" + outstanding);
}

que pasa de la forma que se puede ver en el archivo de moonEdit extract method archive
a ser el siguiente código


void printOwing(double previousAmount)
{
printBanner();
printDetails(calculateOutstanding(previusAmount*1.2));
}
void printBanner()
{
System.out.println ("**************************");
System.out.println ("***** Customer Owes ******");
System.out.println ("**************************");
}
void printDetails(Double outstandingArg)
{
System.out.println ("name:" + _name);
System.out.println ("amount" + outstandingArg);
}
Double calculateOutstanding(Double previousValue)
{
Double result = previousValue;
Enumeration e = _orders.elements();
while (e.hasMoreElements()) {
Order each = (Order) e.nextElement();
outstanding result += each.getAmount();
}
return result;
}

lunes, 1 de agosto de 2011

Reflexiones aplicación refactoring

A partir del pseudo código de ejemplo de Form Template Method, de la web sourcemaking.com/refactoring se me planteó la duda sobre como hacer que este código funcione, y esto es lo que sale, con ejemplos de uso de :
para ver paso a paso como lo he hecho puedes descargar este archivo de MoonEdit form template method.me
pseudo código Original
en el que expone el problema para usar Form Template Method.

class Site
class Residential extends Site

public Double getBillableAmount()
{
return double base=_units*_rate;
double tax=base * Site.TAX_RATE;
return base+tax;
}

class Lifeline extends Site
public Double getBillableAmount()
{
double base=_units*_rate * 0,5;
double tax=base * Site.TAX_RATE*0,2;
return base+tax;
}
Proponiendo en su portal esta solución:

class Site
function Double getBillableAmount()
{
return getBaseAmount() + getTaxAmount();
}
funtion Double abstract getBaseAmount();
funcion Double abstract getTaxAmount();


class Residential extends Site
function Double getBaseAmount()
{
Double base= _units*_rate;
return base;
}
funtion Double getTaxAmount()
double tax=base * Site.TAX_RATE;
return tax;
}

class Lifeline extends Site
function Double getBaseAmount()
{
Double base= _units*_rate*0,5;
return base;
}
funtion Double getTaxAmount()
double tax=base * Site.TAX_RATE*0,2;
return tax;
}

utilizando las refactorizaciones:

  1. replace temp with query

  2. pull up method

  3. form template method

  4. pull up field

  5. self encapsulate field

  6. lazy initialization


llego al siguiente código (tambien añadida la especificacion de que base requiera un importante cálculo para su obtención)

// Refactoring about Form template method code in sourcemaking.com
class Site
{
Double base;
public Site()
{
setBase(undefined);
}
private Double getBase()
{
return this.base();
}
private setBase (Double base)
{
this.base = getBaseAmount();
}
public Double getBillableAmount()
{
return getBaseAmount()+ getTaxAmount();
}

private Abstract getBaseAmount();
private Abstract getTaxAmount();
}

class Residential extends Site
{
private Double getBaseAmount()
{
Double result = espensive calculus;
setBase(result);
return result;
}
private Double getTaxAmount()
{
if (!defined(getBase()) getBaseAmount();
return getBase() * Site.TAX_RATE;
}
}

class Lifeline extends Site
{
private Double getBaseAmount()
{
Double result = espensive diferent calculus;
setBase(result);
return result;
}
private Double getTaxAmount()
{
if (!defined(getBase()) getBaseAmount();
return getBase() * Site.TAX_RATE*0,2;
}
}

//SMELL : similar code in two subclases
//replace temp with query<- composing methods
//pull up method <- Dealing with Generalizaton
//form template method <- Dealing with Generalization
//pull up field <- Dealing with generalization
//self Encapsulate Fiel <- Organizing data
//lazy initialization



bad Smell, cosas que me huelen mal del código y que aún no tengo claro el refactoring:
  1. la linea if(!defined(getBase())

    • ¿a que igualo this.base en la inicialización? a undefined, a null, a 0, a DOUBLE_NULL_VALUE