sábado, 7 de junio de 2014

Javascript OOP, Visual Method - Part II

Previous post: Javascript OOP, Visual Method




Refactoring to preserve Liskov's Substitution Principle.
   Note: Due "super" is a reserved word, "soper" is its replacement.




/* IShape -- abstract class */
function IShape(obj) {
  if(obj.area && obj.perimeter) return obj;
  else return undefined;
};

/* Circle constructor */
function circle(radius) {
  return IShape( {
    "area": function() {
      return Math.PI * radius * radius;
    },
    "perimeter": function() {
      return 2 * Math.PI * radius;
    }
  });
};

/* Rectangle constructor */
function rectangle(length, width) {
  return IShape( {
    "area": function() { 
      return length * width;
    },
    "perimeter": function() {
      return (2 * length) + (2 * width);
    }
  });
};

/* Triangle constructor */
function triangle(base, height) {
  return IShape( {
    "area": function() {
      return base * height;
    },
    "perimeter": function() {
      return undefined;
    }
  });
};

/* Square -- child class -- constructor */
function square(side) {
  return IShape( {
    "soper": rectangle(side, side),
    "area": function() {
      return this.soper.area();
    },
    "perimeter": function() {
      return this.soper.perimeter(side, side);
    }
  });
};

/* Shapes factory */
function shapesFactory(shapeId) {
  switch (shapeId.toLowerCase()) {
    case "circle":
      return circle;
    case "rectangle":
      return rectangle;
    case "triangle":
      return triangle;
    case "square":
      return square;
    default:
      return undefined;
  }
};

// TESTS
var shape1 = shapesFactory("circle")(1);

console.log("Circle(radius=1)."+
   " Area: " + shape1.area() +
   " Perimeter: " + shape1.perimeter());
// output:
// Circle(radius=1). Area: 3.141592653589793 Perimeter: 6.283185307179586 

var shape2 = shapesFactory("rectangle")(2,3);

console.log("Rectangle(length=2, width=3)."+
   " Area: " + shape2.area() +
   " Perimeter: " + shape2.perimeter());
// output:
// Rectangle(length=2, width=3). Area: 6 Perimeter: 10

var shape3 = shapesFactory("triangle")(2,3);

console.log("Triangle(base=2, height=3)."+
   " Area: " + shape3.area() +
   " Perimeter: " + shape3.perimeter());
// output:
// Triangle(base=2, height=3). Area: 6 Perimeter: undefined

var shape4 = shapesFactory("square")(1);

console.log("Square(radius=1)."+
   " Area: " + shape4.area() +
   " Perimeter: " + shape4.perimeter());
// output:
// Square(radius=1). Area: 1 Perimeter: 4

viernes, 6 de junio de 2014

Javascript OOP, Visual Method

Let's do this inheritance example with Javascript:



All objects must calculate area and perimeter through a common interface and must be generated through a factory.



/* IShape -- abstract class */
function IShape(area, perimeter) {
  return { "area":area, "perimeter":perimeter };
};

/* Circle constructor */
function circle() {
  var area = function(radius) {
    return Math.PI * radius * radius;
  };
  var perimeter = function(radius) {
    return 2 * Math.PI * radius;
  };
  return IShape(area, perimeter);
};

/* Rectangle constructor */
function rectangle() {
  var area = function(length, width) {
    return length * width;
  };
  var perimeter = function(length, width) {
    return (2 * length) + (2 * width);
  };
  return IShape(area, perimeter);
};

/* Triangle constructor */
function triangle() {
  var area = function(base, height) {
    return base * height;
  };
  var perimeter = function() {
    return undefined;
  };
  return IShape(area, perimeter);
};

/* Square -- child class -- constructor */
function square() {
  var extendedObject = rectangle();
  var area = function(side) {
    return extendedObject.area(side, side);
  };
  var perimeter = function(side) {
    return extendedObject.perimeter(side, side);
  };
  return IShape(area, perimeter);
};

/* Shapes factory */
function shapesFactory(shapeId) {
  switch (shapeId.toLowerCase()) {
    case "circle":
      return circle();
    case "rectangle":
      return rectangle();
    case "triangle":
      return triangle();
    case "square":
      return square();
    default:
      return undefined;
  }
};

// TESTS
var shape1 = shapesFactory("circle");

console.log("Circle(radius=1)."+
   " Area: " + shape1.area(1) +
   " Perimeter: " + shape1.perimeter(1));
// output:
// Circle(radius=1). Area: 3.141592653589793 Perimeter: 6.283185307179586 

var shape2 = shapesFactory("rectangle");

console.log("Rectangle(length=2, width=3)."+
   " Area: " + shape2.area(2,3) +
   " Perimeter: " + shape2.perimeter(2,3));
// output:
// Rectangle(length=2, width=3). Area: 6 Perimeter: 10

var shape3 = shapesFactory("triangle");

console.log("Triangle(base=2, height=3)."+
   " Area: " + shape3.area(2,3) +
   " Perimeter: " + shape3.perimeter(2,3));
// output:
// Triangle(base=2, height=3). Area: 6 Perimeter: undefined

var shape4 = shapesFactory("square");

console.log("Square(radius=1)."+
   " Area: " + shape4.area(1) +
   " Perimeter: " + shape4.perimeter(1));
// output:
// Square(radius=1). Area: 1 Perimeter: 4




Using JQuery the factory could be an object on another file:


/* Shapes factory */
jQuery.shapesFactory = function(shapeId) {

  /* IShape -- abstract class */
  function IShape(area, perimeter) {
    return { "area":area, "perimeter":perimeter };
  };

  /* Circle constructor */
  function circle() {
    var area = function(radius) {
      return Math.PI * radius * radius;
    };
    var perimeter = function(radius) {
      return 2 * Math.PI * radius;
    };
    return IShape(area, perimeter);
  };

  /* Rectangle constructor */
  function rectangle() {
    var area = function(length, width) {
      return length * width;
    };
    var perimeter = function(length, width) {
      return (2 * length) + (2 * width);
    };
    return IShape(area, perimeter);
  };

  /* Triangle constructor */
  function triangle() {
    var area = function(base, height) {
      return base * height;
    };
    var perimeter = function() {
      return undefined;
    };
    return IShape(area, perimeter);
  };

  /* Square -- child class -- constructor */
  function square() {
    var extendedObject = rectangle();
    var area = function(side) {
      return extendedObject.area(side, side);
    };
    var perimeter = function(side) {
      return extendedObject.perimeter(side, side);
    };
    return IShape(area, perimeter);
  };

  /* Shapes factory */
  switch (shapeId.toLowerCase()) {
    case "circle":
      return circle();
    case "rectangle":
      return rectangle();
    case "triangle":
      return triangle();
    case "square":
      return square();
    default:
      return undefined;
  }
};

// TESTS - Another file -
var shape1 = $.shapesFactory("circle");

console.log("Circle(radius=1)."+
   " Area: " + shape1.area(1) +
   " Perimeter: " + shape1.perimeter(1));
// output:
// Circle(radius=1). Area: 3.141592653589793 Perimeter: 6.283185307179586 

var shape2 = $.shapesFactory("rectangle");

console.log("Rectangle(length=2, width=3)."+
   " Area: " + shape2.area(2,3) +
   " Perimeter: " + shape2.perimeter(2,3));
// output:
// Rectangle(length=2, width=3). Area: 6 Perimeter: 10

var shape3 = $.shapesFactory("triangle");

console.log("Triangle(base=2, height=3)."+
   " Area: " + shape3.area(2,3) +
   " Perimeter: " + shape3.perimeter(2,3));
// output:
// Triangle(base=2, height=3). Area: 6 Perimeter: undefined

var shape4 = $.shapesFactory("square");

console.log("Square(radius=1)."+
   " Area: " + shape4.area(1) +
   " Perimeter: " + shape4.perimeter(1));
// output:
// Square(radius=1). Area: 1 Perimeter: 4



Part II: Managing to preserve Liskov's Substitution Principle (LSP)

Manipular Funciones Javascript


Las funciones en Javascript se pueden manipular tal como se hace con las variables. He aquí unos ejemplos.

 
 var porDos = function(n) { return 2*n; };
 console.log("4 x 2 = "+porDos(4));

// Output:
// 4 x 2 = 8 

La función que multiplica un número N por dos se almacenó en la variable "porDos". Para evaluar la función se usó el nombre de la variable con el argumento definido en la declaración "function(n)".

Otro ejemplo:


 var porTres = function(n) { return 3*n; };
 console.log("4 x 3 = "+porTres(4));

// Output:
// 4 x 3 = 12

Cumplen las reglas de composición:


 var porCinco = function(n) {
  return porDos(n) + porTres(n);
 };
 console.log("4 x 5 = "+porCinco(4));

// Output:
// 4 x 5 = 20

 var porSeis = function(n) {
  return porDos(porTres(n));
 };
 console.log("4 x 6 = "+porSeis(4));

// Output:
// 4 x 6 = 24

Una función puede retornar otra función:


 var fabricaPorM = function(M) {
  return function(n) {
   return M*n;
  };
 };
 var porOcho = fabricaPorM(8);
 console.log("4 x 8 = "+porOcho(8));

// Output:
// 4 x 8 = 32

Una función se puede pasar como argumento:


  var multiplosHastaDiez = function(fnMultiplicar) {

     var valIni = fnMultiplicar(1);
     console.log("Multiplos de "+valIni+" del 1 al 10");

     for(var i=1;i<=10;i++) {
        console.log(""+i+" x "+valIni+" = "+fnMultiplicar(i));
     }
  }

Probando:


 multiplosHastaDiez(porCinco);

// Output:
// Multiplos de 5 del 1 al 10
// 1 x 5 = 5 
// 2 x 5 = 10 
// 3 x 5 = 15 
// 4 x 5 = 20 
// 5 x 5 = 25 
// 6 x 5 = 30 
// 7 x 5 = 35 
// 8 x 5 = 40 
// 9 x 5 = 45 
// 10 x 5 = 50 

También:


 multiplosHastaDiez(fabricaPorM(9));

// Output:
// Multiplos de 9 del 1 al 10
// 1 x 9 = 9
// 2 x 9 = 18
// 3 x 9 = 27
// 4 x 9 = 36
// 5 x 9 = 45
// 6 x 9 = 54
// 7 x 9 = 63
// 8 x 9 = 72
// 9 x 9 = 81
// 10 x 9 = 90 

miércoles, 2 de abril de 2014

PSP en tres minutos


Se encuentra con un cliente, es la tercera reunión, y éste pregunta: ¿Y qué es PSP?

Hace una pausa y medita para luego concluir que el plan de la reunión es defectuoso y que estrategia de comunicación hasta el momento ha fallado.

Toma nota mental y continúa la junta.

Una vez aclarada la duda del interlocutor, toma nota mental del tiempo transcurrido empleado en solucionarla.

Con estos datos se pueden modificar los planes y diseñar estrategias más efectivas de comunicación con los clientes.

Pero...
La memoria es selectiva. Muchas de esas notas se olvidan o cambian de prioridad según el contexto o el tiempo transcurrido.


La propuesta de PSP radica en emplear métodos formales para registrar esos datos, compararlos y luego tomar decisiones.

Para ello, al detectar el fallo:

Registra el evento de acuerdo a una clasificación estándar, incluye una pequeña descripción, inicia un cronómetro e inicia la reparación.

Una vez aclarado el problema, registra el tiempo efectivo empleado en la solución.

Un análisis estadístico de frecuencias determinará los problemas más comunes y el tiempo promedio que toma arreglarlos. Esto permitirá priorizar el esfuerzo en corregir los errores más costosos; los que en conjunto quitan más tiempo.

Pero...
Al modificar el proceso ¿Cómo determinar el efecto de las mejoras?


Además de estandarizar el registro de fallos, el proceso también debe ser monitoreado. Para ello, PSP divide el proceso de desarrollo de software en etapas, cada una de las cuales tiene sus propios indicadores de calidad.

Así, al hacer un ajuste y luego recopilar nueva información se tendrá una idea clara del impacto, positivo o negativo, sobre el proceso en general.


Para terminar, vale la pena aclarar que el método PSP, propuesto por Watts Humphrey para alcanzar los objetivos anteriores, es sumamente invasivo. Interviene activamente en cada etapa y rige gran parte del proceso, todo con el fin de obtener medidas precisas y confiables, indispensables para el análisis posterior. He ahí la razón de su complejidad.




lunes, 31 de marzo de 2014

Json to Dom with jQuery

jQuery plugin to fill in DOM nodes with JSON. Simpler and more flexible than most templating / binding engines:
https://github.com/gescript/json-to-dom


domingo, 9 de marzo de 2014

Versionar Base de Datos y versionar los datos de la DB


En #AgileOpenBogotá (estuvo buenísimo!) mientras Adrián Moya nos contaba sobre los métodos y herramientas automatizadas para operaciones basadas en las técnicas ágiles tales como TDD, BDD, ATDD, Integración Continua...etc. Surgió una duda sobre los problemas de versionado de la base de datos.

En el caso expuesto como problemático, la base de datos cambiaba con el tiempo, de tal manera que en poco tiempo los datos eran incompatibles entre versiones. El escenario se hacía más complejo al tener, obligatoriamente, que mantener diferentes versiones de la base de datos de manera concurrente.

El caso puntual: cuando es el cliente quién decide (o no) actualizar. Por ejemplo: un juego en línea descargado desde una tienda para dispositivos móviles, tabletas o pc's.

O un caso adicional: cuando la ley exige la integridad de esos datos (como en contabilidad) pero esa misma ley es cambiante en cuanto a métodos, fórmulas o restricciones; como por ejemplo una reforma tributaria. O simplemente la base de datos es muy grande.

Para hacerse una imagen del problema, piense en el software como una fábrica de televisores donde cada rutina es una máquina ensambladora. Cuando un televisor recién ensamblado presenta un defecto se puede encontrar la máquina que está fallando para ser reparada o reemplazada (trazabilidad interna) y el televisor se repara o desecha. Si el televisor está fuera de la fábrica; usando trazabilidad normal o inversa se obtiene el mismo resultado, depende de quién haya encontrado el defecto, ya sea el fabricante o el cliente.

Este ejemplo surge de las vacas locas donde la carne infectada debía ser retirada del comercio y la información de trazabilidad permitía encontrar el rancho de donde provenía.

Versionamos el software según sus partes o remiendos, pero no versionamos los datos.

Ese fenómeno puede bautizarse como las vacas locas del sofware. Tenemos software que consume productos de software. Pensar que en algún momento los datos se integrarán y las máquinas enloquecerán no es ficción, ocurre en las bolsas de valores a velocidades inimaginables y de forma compleja, dejando de paso pérdidas extraordinarias entre otros.

¿Qué hacer?

La explicación de Adrián (según entendí) solucionaba el problema registrando tanto la versión del software como la de la base de datos y migrando los datos al nuevo formato.

Pero este no es el caso. ¿Qué hacer si no es posible migrar los datos? ¿Hay una opción simple, barata, fácil de implementar y mantener?

Primera solución (ingenua y causa overhead.)

Agregar una columna a cada tabla donde se registra la versión del software que la elaboró (tal como se marca el televisor e inviolable como una llave primaria.) Será un valor diferente a la versión de la base de datos (un entero basta.)
Con esto al menos el software podría reaccionar ante registros desactualizados.

Otra solución (engorrosa y absurda.)

Crear una base de datos nueva cada vez que hay un cambio. Pero, un select o un join serían trabajo arduo, incluso imposible.


¿Qué otras soluciones hay?

En verdad hay muy poca información sobre trazabilidad normal e inversa en este campo (o no la supe hallar.)


Mientras tanto, el aviso "Hay una nueva versión del software, descárguela o asuma las consecuencias" es una opción "recomendable".


jueves, 27 de febrero de 2014

Modelo-Vista-Controlador (MVC) con JQuery - Ejemplo Básico

Este ejemplo de "hello world" sale de http://cakebaker.42dh.com/2007/03/17/mvc-with-javascript/ Lo que hice fue actualizarlo para JQuery.

--- HTML ---
  <a id="userEvent" href="#">
 notify user action to controller
  </a>

  <div id="feedBackMessages"> </div>


--- JavaScript ---
  var model = {}, view = {}, controller = {};
 
  model.getText = function () {
 return 'hello world';
  };

  view.showMessage = function (message) {
 $('#feedBackMessages').html(message);
  };

  controller.sayHelloWorld = function () {
 view.showMessage(model.getText());
  };
 
  $("#userEvent").click( function(event){
 event.preventDefault();
 controller.sayHelloWorld();
  });