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();
}
}