SimpleScript (3) El Parser, Expresiones y Comandos

Anterior Post

Veamos hoy parte del Parser, que es un módulo separado. Comienza con una declaración sencilla:

'use strict';

var lexer;

if (typeof lexer == 'undefined')
    lexer = require('./lexer');

var parser = (function () {
    var TokenType = lexer.TokenType;
    var binoperators = [ "+", "-", "*", "/", "==", "!=", "<", ">", "<=", ">=" ];

Usa a lexer, y lo requiere. Luego hay definidos expresiones y comandos. Por ejemplo, esta es la expresión para un simple nombre (por ejemplo, foo):

function NameExpression(name) {
    this.isLeftValue = true;

    this.compile = function () {
        return name;
    };

    this.getName = function () {
        return name;
    }

    this.collectContext = function (context) {
        context.declare(name);
    }
}

Veremos en el próximo post cómo se detecta y construye esta expresión. Una expresión cualquiera tiene que implementar por lo menos dos métodos: compile, que devuelve la expresión compilada a un string de código JavaScript, y collectContext, que permite descubrir las variables declaradas en un programa. En este caso, la NameExpression declara a su nombre (por ejemplo, “foo”), como una variable declarada en el programa (esto es necesario para tener implementado el “hoisting” como en JavaScript).


Veamos una IndexedExpression: compuesta de una expresión y otra expresión para el índice (representaría expresiones como foo[42+1]):

function IndexedExpression(expr, indexpr) {
    this.isLeftValue = true;

    this.compile = function () {
        return expr.compile() + '[' + indexpr.compile() + ']';
    };

    this.collectContext = function (context) {
        expr.collectContext(context);
    }
}

Vean como ahora el collectContext se toma el trabajo de visitar la expresión interna (no vi que fuera necesario visitar y descubrir variables en la expresión de índice, pero puede que lo agregue; esa expresión en general es simple, pero bien podría tener usada una variable).


Pero también hay comandos. Puedo poner como ejemplo el IfCommand:

function IfCommand(cond, thencmd, elsecmd) {
    this.compile = function () {
        var code = 'if (' + cond.compile() + ') { ' + thencmd.compile() + ' }';
        if (elsecmd)
            code += ' else { ' + elsecmd.compile() + ' }';
        return code;
    };

    this.collectContext = function (context) {
        cond.collectContext(context);
        thencmd.collectContext(context);
        if (elsecmd)
            elsecmd.collectContext(context);
    }
}

La distinción entre comandos y expresiones es por ahora puramente formal. Vean que este comando tiene que implementar cómo se compila a JavaScript un if, dado la cond (expresión de la condición), el thencmd (el comando de la rama then), y el elsecmd (el comando de la rama else, que es opcional). Y el collectContext deriva a esas subpartes.


Como siempre, desarrollo paso a paso, usando el flujo de trabajo de TDD (Test-Driven Development). Ejemplo parcial:

exports['Compile string without quotes inside'] = function (test) {
    test.equal(compileExpression("'foo'", test), "'foo'");
    test.equal(compileExpression('"foo"', test), "'foo'");
}

exports['Compile name'] = function (test) {
    test.equal(compileExpression("foo", test), "foo");
}

exports['Qualified name'] = function (test) {
    test.equal(compileExpression("foo.bar", test), "foo.bar");
}

exports['Indexed term'] = function (test) {
    test.equal(compileExpression("foo[bar]", test), "foo[bar]");
}

Ya saben ;-) Sin TDD no hay paraíso!


Próximos temas: cómo se reconocen las expresiones y comandos.


Nos leemos!


Angel “Java” Lopez
http://www.ajlopez.com
http://twitter.com/ajlopez

Posted in JavaScript, NodeJs, Proyectos Open Source, SimpleScript, Test-Driven Development | Comments Off

SimpleLisp (1) Compilando Lisp a JavaScript

Ya varias veces implementé un intérprete Lisp, en C#, en Java, en JavaScript. Ver proyectos

https://github.com/ajlopez/AjLisp
https://github.com/ajlopez/AjLispJava
https://github.com/ajlopez/AjLispJs

Siempre es bueno implementar Lisp. Es un lenguaje simple, sencillo, poderoso, con funciones como ciudadanos de primera clase, y con el “twist” de implementar: algunas funciones que no evalúan de antemano sus argumentos, y macros.

Pero esta vez quiero implementar un compiladr Lisp. Es decir, algo que traslade al lenguaje anfitrión el programa Lisp. He elegido JavaScript como lenguaje anfitrión, y también como lenguaje de compilación: JavaScript mismo es el encargado de leer y traducir código Lisp a JavaScript. Pueden ver mi avance en

https://github.com/ajlopez/SimpleLisp

Como es costumbre, todo armado siguiendo el flujo de trabajo de TDD (Test-Driven Development), y sin tener todo el diseño de antemano, simplemente va surgiendo con los pequeños casos de uso que voy incorporando. Como es mi primer compilador Lisp, estoy aprendiendo nuevas formas de implementar el lenguaje. Algo vi en los últimos años al toparme con Clojure, que compila a Java, a CLR y a JavaScript. Lo que veo que tengo que implementar es:

Símbolos: identificadores por nombre, con valor asociado. Los estoy compilando a variables de JavaScript. En SimpleLisp, un símbolo puede quedar definido para el tope del programa, para un bloque let, o como argumento de una función. Entonces, depende del caso, lo paso a una variable tope de JavaScript (o al menos, tope en el módulo que estoy armando en compilación), a una variable local, o a un argumento con nombre.

Funciones: traducir una función normal de Lisp a una función normal de JavaScript. Lo único diferente es que las funciones de Lisp siempre devuelven en un valor, los “comandos” son siempre expresiones, como en Ruby. Una lista a evaluar en SimpleLisp la compilo a una llamada a función simple en JavaScript.

Special Forms: Su implementación es novedosa para mí. Ahora que tengo que compilar, no interpretar, cada vez que me topo con un una lista cuya cabeza es un if, un do, un let, etc…  voy y la compilo de forma especial a JavaScript. Un (if … ) de SimpleLisp queda transformado en un if de JavaScript (es un poco más complicado, porque el if de SimpleLisp tiene que devolver un valor, es una expresión, mientras que un if de JavaScript es un comando; ya veremos en futuro post la implementación que adopté).

Macros: De nuevo, al tener que compilar, puedo adoptar una implementación nueva: expandir la macro AL MOMENTO DE COMPILAR. Veremos hasta donde puedo llegar con este camino. Una consecuencia que veo: no puedo pasar una macro como valor funcional como argumento de otra función. Justamente, lo que pasa en Clojure: las macros no son valores funcionales, son “trucos” que se expande en tiempo de compilación.

Y como en otras implementaciones, agrego acceso a invocar JavaScript nativo desde SimpleLisp. Mi idea es poder escribir un sitio Node.js/Express con SimpleLisp.

Nos leemos!

Angel “Java” Lopez
http://www.ajlopez.com
http://twitter.com/ajlopez

Posted in JavaScript, Lenguajes de Programación, Lisp, NodeJs, Proyectos Open Source, SimpleLisp, Test-Driven Development | Comments Off

SimpleScript (2) El Lexer

Anterior Post
Siguiente Post

Sigo en estos días mejorando mi compilador de SimpleScript a JavaScript. Hoy quería comentar por encima la existencia e implementación de un Lexer. Ver el repo en

http://github.com/ajlopez/SimpleScript

El Lexer está ahora separado en un archivo lib/lexer.js, que establece un módulo, consumible desde Node.js y desde el browser cuando alguna vez lo necesite. Comienza definiendo los tipos de token que va a ir entregando:
var lexer = (function () {
    var TokenType = { 
        Name: 1, 
        Integer: 2, 
        Real: 3, 
        String: 4, 
        NewLine: 5, 
        Separator: 6, 
        Assignment: 7 };
        

Luego define algunos operadores, separadores y lo que sería un Token, con dos elementos: tipo y valor.
var separators = ".,()[]";
var assignments = ["=", "+=", "-=", "*=", "/="];
var operators = ["+", "-", "*", "/", "==", "!=", "<", ">", "<=", ">="];

function Token(value, type) {
    this.value = value;
    this.type = type;
}

Y luego el gran trabajo lo realiza una “clase” interna Lexer, cuyo principal método es nextToken():
function Lexer(text) {
    var length = text ? text.length : 0;
    var position = 0;
    var next = [];

    this.nextToken = function () {
        if (next.length > 0)
            return next.pop();

        skipSpaces();

        var ch = nextChar();

        if (ch === null)
            return null;

        if (ch === '"' || ch === "'")
            return nextString(ch);

        if (ch === '\n')
            return new Token(ch, TokenType.NewLine);

        if (ch === '\r') {
            var ch2 = nextChar();

            if (ch2 === '\n')
                return new Token(ch + ch2, TokenType.NewLine);

            if (ch2)
                pushChar(ch2);

            return new Token(ch, TokenType.NewLine);
        }

        if (isAssignment(ch))
            return new Token(ch, TokenType.Assignment);

        if (isOperator(ch))
            return nextOperator(ch);

        if (isSeparator(ch))
            return new Token(ch, TokenType.Separator);

        if (isFirstCharOfName(ch))
            return nextName(ch);

        if (isDigit(ch))
            return nextInteger(ch);
    }

Finalmente el módulo expone hacia afuera una factoría de lexers, y la enumeración de los tipos de token:
return {
    lexer: function (text) { return new Lexer(text); },
    TokenType: TokenType
}

Finalmente, esto no estaría completo sin notar que hay un test/lexer.js que fue armado de pasos, junto con lib/lexer.js, siguiendo Test-Driven Development (con algunos refactors, hubo un tiempo que lib/lexer.js no estaba como archivo separado), fragmento:
function getToken(text, value, type, test) {
    var lexer = sslexer.lexer(text);
    var token = lexer.nextToken();
    test.ok(token);
    test.equal(token.value, value);
    test.equal(token.type, type);
    test.equal(lexer.nextToken(), null);
};

exports['Get names'] = function (test) {
    getToken('foo', 'foo', TokenType.Name, test);
    getToken('foo123', 'foo123', TokenType.Name, test);
    getToken('foo_123', 'foo_123', TokenType.Name, test);
    getToken('_foo', '_foo', TokenType.Name, test);
}

exports['Get integer'] = function (test) {
    getToken('123', '123', TokenType.Integer, test);
    getToken('1234567890', '1234567890', TokenType.Integer, test);
}

Recuerden: sin TDD no hay paraíso ;-)

Próximos temas: el parser, comandos, expresiones, compilación a JavaScript.

Nos leemos!

Angel “Java” Lopez
http://www.ajlopez.com
http://twitter.com/ajlopez
Posted in JavaScript, NodeJs, Proyectos Open Source, SimpleScript, Test-Driven Development | Comments Off

Arquitectura y Desarrollo Agil

Hace unos días apareció un hilo de discusión interesante en la lista foro-agiles sobre si la arquitectura emerge. Hubo varios aportes y distintas posturas, los invito a leer por ahí todo lo que se escribió y expuso.

Quisiera esta en mi blog, escribir algo sobre el tema. Primero, algo de contexto. Voy a escribir sobre:

- Proyecto profesional o personal
- Ejecutado por un equipo ágil o por uno mismo
- Proyecto donde el principal entregable es software

Al poner lo ágil en el contexto, estoy asumiendo que vamos a abrazar el cambio, uno de los pilares de lo ágil. Se ha visto que en el desarrollo de software es difícil basarse en algo fijo, sino que lo que se va desarrollando se va descubriendo, desplegando, y mejorando a medida que se va construyendo. Es también difícil tener todo definido de antemano. Otro de los puntos que voy a poner en claro, es que lo que necesitamos es basarnos en los casos de uso, por lo menos en gran parte. Escribí sobre el tema en TDD y Casos de Uso. Recuerdo de ahí alguna cita de @unclebobmartin:

The center of your application is not the database. Nor is it one or more of the frameworks you may be using. The center of your application are the use cases of your application.

It makes me crazy when I hear a software developer describe his system as a “Tomcat system using Spring and Hibernate using Oracle”. The very wording puts the frameworks and the database at the center.

What do you think the architecture of that system would look like? Do you think you’d find the use cases at the center of the design? Or would you find the source code arranged to fit nicely into the pattern of the frameworks? Would you find business objects that looked suspiciously like database rows? Would the schema and the frameworks pollute everything?

Here’s what an application should look like. The use cases should be the highest level and most visible architectural entities. The use cases are at the center. Always! Databases and frameworks are details! You don’t have to decide upon them up front. You can push them off until later, once you’ve got all the use cases and business rules figured out, written, and tested.

Amen!!! Si no tenemos en claro los casos de uso, no podemos tomar grandes decisiones, y menos de arquitectura. Sé que parece que en muchos sistemas los casos de uso parecen estar dados desde el principio, pero en mi experiencia profesional, no veo que haya sido así. Lo que uno presupone, por ejemplo, en un Sprint 0, no termina siendo el caso a las pocas semanas de desarrollo. Es por eso que la arquitectura no es algo que se pueda plantear convenientemente desde el principio, sino que es algo que tenemos que ir desplegando a medida que tengamos más masticados, digeridos, analizados, incorporados los casos de uso que vamos implementando.

“Arquitectura” es un término amplio. Veamos de poner algún ejemplo, para poner un contexto más definido. Supongo que tenemos que nuestro cliente es una petrolera, y tenemos que desarrollar un sistema que recibe mensajes de uno o dos sistemas, que luego pueden ser más, y necesitamos procesarlos, por ejemplo, dado un mensaje que indique que hay un nuevo pozo de perforación, entregar los nuevos datos a dos o tres sistemas que ya están funcionando, y prever que se pueda entregar a otros sistemas que vayan apareciendo. Uno podría verse tentado desde el comienzo: “Bien, es un caso para implementar con un Service Bus”. O “bien, es un caso para implementar usando Storm de Java”, o “acá hay que poner un BPM”. En mi experiencia, semenjante tipo de decisiones son tempranas, y hasta incorrectas o al menos, nos van a desviar de los casos de uso a implementar. Si tuviera que hacerlo yo solo, o si convenzo a mi equipo de la postura que estoy promoviendo en este post, lo haría: “Primero los casos de uso, simple proceso de mensajes, en un simple programa”. Solamente cuando tenga ya uno o varios casos de uso concretos implementados, podría ver cuál es la carga de mensajes (¿10 por día, 100000 por segundo?), la dificultad o facilidad de acceso a los sistemas finales ya existentes, la importancia (o no) de procesar un mensaje en menos de un minuto o en una semana, descubrir una “ley de Pareto”: el 80% de lo importante viene en el 20% de los mensajes, y se cubren con este y este otro caso de uso, etc… Sólo luego de haberse empapado de los casos de uso, y haber implementados algunos, podrá verse más en claro las necesidades de arquitectura que tenemos.

Pero alguien podrá decir ¿no es más trabajo? Así, parece que primero implementamos un caso de uso, y luego tenemos que reimplementarlo al cambiar la arquitectura. De nuevo, mi experiencia indica: la primera implementación es un “baby step” que sirve para ir aproximándonos al problema, sin “bajada de línea” previa. Y luego, seguimos mejorando la implementación, cambiando decisiones de arquitectura. Si realmente abrazamos lo ágil (por ejemplo, adoptando disciplinas como Test-Driven Development), los cambios no cuestan, y las decisiones diferidas no implican un costo, apenas algún esfuerzo. Y si luego vemos que una decisión de arquitectura no es la mejor, porque aparecen nuevos contextos, nuevos casos de uso, nuevas fuerzas, SI ABRAZAMOS LO AGIL, el cambio de implementación, y el cambio de decisión, tampoco debería tener un gran costo.

Eso es lo que veo que tiene que hacer el rol de arquitecto (hablo de rol, y no de puesto): saber diferir decisiones, y saber tomar decisiones diferidas que se puedan cambiar. Por más seguro que estemos “X es el camino a seguir (sea X BPM, MapReduce, Actor model, procesamiento distribuido usando colas… )” SI ABRAZAMOS LO AGIL debemos tomar decisiones CAMBIABLES y DIFERIBLES. Algunas de esas decisiones involucran lo que se llama (ampliamente, insisto) “arquitectura”.

Ahora bien, SI NO ABRAZAMOS lo ágil, y no adoptamos las disciplinas técnicas, duras y blandas que permiten encarar el cambio y el desarrollo evolutivo, entonces sí, vamos a tener problemas en la decisiones diferidas. Cada cambio nos va a costar, cosa que en el pasado “Waterfall” se quería evitar DEFINIENDO la mayor parte del sistema de antemano.

He visto sistemas, que por adoptar una decisión temprana, terminan perdiendo el rumbo. Todo el equipo queda con inercia, y todo caso de uso de uso lo quiere solucionar usando la decisión temprana, que se tomó como LA SOLUCION. Por ejemplo, si la decisión fue “vamos a usar Hadoop”, por más que los casos de uso que vengan clamen por otro tipo de solución, el equipo sigue mapeando todo problema a un Map Reduce distribuido.

Pero si diferimos las decisiones, tenemos más cintura. Ya el propio diferimiento permite que cuando se tome una decisión, el sistema no esté anquilosado, dirigido, congelado solamente a esa decisión. Aunque al final la decisión diferida sea la misma que nos dictaba nuestro “pequeño arquitecto yomelasetodas” desde el principio, el haber implementado gran parte de la solución sin haber tomado la decisión de arquitectura, permite armar un sistema más claro, más simple, donde elementos de arquitectura aparecen claramente por aparecer nuevas fuerzas que tenemos que tomar en cuenta, más que por sesudas ideas en general en el aire tomadas al comienzo. Y por lo que ví en estos años del tercer milenio, esa adopción diferida le da más calidad de “si surgen nuevas fuerzas o casos, podemos cambiar la decisión”, nos da un sistema más adaptable a cualquier cambio que aparezca en el futuro. Porque también eso es algo bueno para el rol de arquitecto: estar advertido que lo que estamos armando, si tiene éxito, va a evolucionar. En vez de hacerlo flexible desde el vamos, es mejor hacerlo cambiable, algo que veo que se va ejercitando con cada decisión diferida.

Imagen tomada de Swarm Intelligence. Me gustó la idea y la imagen, pero vean que abandono el adjetivo “emergente” en lo que escribí luego del primer párrafo, porque da a entender como que la arquitectura aparece sin intervención del equipo, por simple dinámica compleja.

Nos leemos!

Angel “Java” Lopez
http://www.ajlopez.com
http://twitter.com/ajlopez

Posted in Arquitectura de Software, Desarrollo Agil, Programacion, Test-Driven Development | Comments Off

Resoluciones del Nuevo Mes: Noviembre 2014

Tiempo de revisar las resoluciones del mes pasado:

- Dar una charla introductoria a Aktores [completo] ver repo see presentation
- AjErl distribuido [pendiente]
- Más ejemplos Express en AjTalkJs [completo] ver repo
- Explorar redes neuronales en JavaScript [completo] ver repo
- Explorar algoritmos genéticos en JavaScript [parcial] ver repo
- Explorar otros temas de inteligencia artificial en JavaScript [parcial]
- Más Aktores distribuidos [pendiente]
- Comenzar Smalltalk distribuido en AjTalkJs [completo] ver repo

Las resoluciones del nuevo mes:

- Dar una charla sobre JavaScript e Inteligencia Artificial
- Ejemplo de Red Neuronal en JavaScript
- Ejemplo de Algoritmo Genético en JavaScript
- Más Generación de Código con AjGenesis en NodeJs
- Basic Script Language en C#
- Mejorar SimpleScript para JavaScript

Nos leemos!

Angel “Java” Lopez
http://www.ajlopez.com
http://twitter.com/ajlopez

Posted in .NET, C#, JavaScript, NodeJs, Proyectos Open Source, Resoluciones | Leave a comment

Conferencias en Argentina: JavaConf

En un anterior post no había incluido esta conferencia. Vaya este post para completar la lista que conozco.

La gente del Java User Group Argentina organiza:

JavaConf

http://java.conf4.it/

Amigos y Colegas, 

Estamos organizando la JAVACONF, el evento de JAVA más importante para nuestra comunidad en Argentina. Vayan reservándose el día 14 de Noviembre, ya que estamos armando y consolidando charlas y speakers imperdibles. La JAVACONF propone una jornada que durará toda la tarde, por lo que vayan pidiendo permiso en sus trabajos o familias! 

Ya tenemos algunos speakers confirmados

1) Gavin King

Gavin King lidera el projecto Ceylon en Red Hat. Gavin es el creador de Hibernate, el popular mapeador de Objetos/Relacional de Java, y el creador de Seam Framework, un framework de aplicaciones Enterprise en Java. El contribuyo a la Java Community Process como el representante de JBoss and después de Red Hat para las especificaciones de EJB y JPA y como el lider de la CDI specification.

Gavin ahora trabaja a tiempo completo en  Ceylon, puliendo la especificación del lenguaje, desarrollando el frontend del compilador, trabajando con el IDE, y pensando sobre la SDK y el futuro de la plataforma. El sigue siendo un fan de Java, así como otros lenguajes como Smalltalk, Python y ML. 

2) Fernando Rodriguez Olivera

Fernando Rodríguez es egresado de la carrera de Ingeniería de la Univ. de La Matanza y comenzó su carrera en los laboratorios de Fuego, Inc.,  luego trabajó en BEA Systems y Oracle principalmente en el diseño y desarrollo de software de base para un producto de BPM.

Actualmente se desempeña como consultor independiente y profesor de las cátedras de Sistemas Distribuidos, Diseño de Compiladores y Lenguajes de Programación de la
Universidad Austral.

Fernando es el creador de mvnrepository.com uno de los sitios mas populares de la comunidad de Java, organizador del grupo “Buenos Aires High Scalability” y autor de los cursos nosqlessentials.com

3) Pablo Romanelli junto a Adrián Fernando Fiore
Pablo Romanelli es Ingeniero en Sistemas y docente de Técnicas Avanzadas de Programación en UTN FRBA. Desarrollador Scala en Socialmetrix. Apasionado en el diseño de software.
Adrián Fernando Fiore es Ingeniero en Sistemas y docente de Algoritmos y Estructura de Datos en UTN FRBA. Desarrollador Backend en Socialmetrix especializado en Big Data.

Si quieren conocer mas datos pueden entrar en:

http://java.conf4.it

Nos leemos!

Angel “Java” Lopez
http://www.ajlopez.com
http://twitter.com/ajlopez

Posted in Argentina, Buenos Aires, Conferencia, Java | Comments Off

Conferencias en Argentina: Ruby, Python, Smalltalk, PHP, Uqbar, JSConf

Esta es la época de las conferencias, en la primavera de mi pais, Argentina. Tenemos para todos los gustos. Las que tengo presentes son:

RubyConf 2014
http://rubyconfargentina.org/
Octubre 24, 25, Buenos Aires

PyCon 2014
http://myconference.co/pyconar2014/
del Jueves 13 de Noviembre al Sábado 15, Rafaela, Santa Fé

Smalltalks 2014
http://www.fast.org.ar/smalltalks2014
Noviembre 5 al 7, Córdoba, Córdoba

PHP Conference 2014
http://2014.phpconference.com.ar/
Noviembre 7 y 8, Buenos Aires

Uqbar WISIT 2014
http://www.uqbar-project.org/events/wisit-2014
Noviembre 28 y 29, 2014, Buenos Aires

JSConf 2014
http://www.jsconfar.com/
Noviembre 29, Buenos Aires (parece que es un solo día)

Nos leemos!

Angel “Java” Lopez
http://www.ajlopez.com
http://twitter.com/ajlopez

Posted in Argentina, Conferencia, JavaScript, NodeJs, PHP, Python, Ruby, Smalltalk | Comments Off

Resoluciones del Nuevo Mes: Octubre 2014

Hora de escribir las resoluciones del nuevo mes. Pero antes, repasar las de septiembre:

- Aktores con ejemplo distribuido [completo] ver repo
- AjErl con ejemplo distribuido [pendiente]
- Soporte web y ejemplo para RuScript [parcial] ver repo
- Completar Mochy [parcial] ver repo
- Mejorar RSharp [completo] ver repo
- Trabajar con NodeJs en los temas Deep Learning, Internet of Things, Artificial Intelligence [pendiente]
- Trabajar en Code Generation usando AjGenesis para Node [completo] ver repo principal ver repo Express ver repo Sinatra ver repo Laravel
- Trabajar en Liqueed Project [completo] ver repo

También estuve trabajando en:

- Mejorar AjScript [completo] ver repo
- Refactor BasicScript tests para usar SimpleUnit [completo] ver repo
- Crear SimpleColls, simple collection functions in JavaScript, alone or a la Linq [completo] ver repo
- Refactor SimpleStore tests para usar SimpleUnit [completo] ver repo
- Mejorar AjTalkJs, Smalltalk in JavaScript, soporte de primitiva super, primeros ejemplos Express 4 [completo] ver repo
- Refactor MProc tests para usar SimpleUnit [completo] ver repo
- Refactor AjLogoJs tests para usar SimpleUnit [completo] ver repo
- Refactor SimpleKeeper tests para usar SimpleUnit [completo] ver repo
- Refactor SimpleTags tests para usar SimpleUnit [completo] ver repo
- Mejorar SimpleUnit, publicar nueva versión [completo] ver repo
- Refactor SimplePipes tests para usar SimpleUnit, y publicar nueva versón [completo] ver repo

Las resoluciones de este nuevo mes:

- Dar una charla de introducción a Aktores, actor model distribuido en C#
- Armar AjErl ejemplo distribuido
- Más ejemplos Express en AjTalkJs
- Explorar redes neuronales en JavaScript
- Explorar algoritmos genéticos en JavaScript
- Explorar otros temas de Inteligencia Artifical en JavaScript
- Más Aktores distribuidos
- Comenzar Distributed Smalltalk en AjTalkJs

Nos leemos!

Angel “Java” Lopez
http://www.ajlopez.com
http://twitter.com/ajlopez

Posted in .NET, C#, JavaScript, Liqueed, NodeJs, Proyectos Open Source, Resoluciones | Leave a comment

Code Coverage y Calidad de Código

Voy a usar directamente la expresión en inglés “code coverage” para lo que en español es cubrimiento de código. La pregunta de hoy es ¿hay relación entre un gran nivel de code coverage y calidad de código?

Primero, el code coverage es una métrica: simplemente eso, ni más ni menos. Si conseguimos el porcentaje de code coverage luego, digamos, de ejecutar todos los tests del sistema, un alto porcentaje con todos los tests en verde sólo indica que al ejecutar los tests pasamos por una gran cantidad de líneas de nuestro código, sin provocar un error.

Pero cualquiera que se ponga a meditar sobre eso, no puede concluir así por que sí, que eso indique una buena calidad de código. Bien podemos imaginarnos código que cumpla con todo eso (buen métrica, pruebas que pasan) y que NO TENGA UNA BUENA CALIDAD de código. Por ejemplo, bien podemos refactorizar todo el sistema para que el código quepa en una clase, con cientos de métodos, y el code coverage seguirá alto y las pruebas pasarán. Y así se nos pueden seguir ocurriendo refactorizaciones, que bajen la calidad de código, sin que la métrica de code coverage se vea afectada en gran medida.

Tenemos que ponernos de acuerdo en qué es calidad de código, pero para este post, podemos tomar algunas cualidades:

- Que el código ejecute lo que se espera
‘- Que sea modificable/extensible de una forma accesible

Vean que no pongo que sea “entendible” porque me imagino que esa cualidad es un requisito de la segunda que puse arriba. La primera cualidad se puede dividir en:

- Que el código de el resultado esperado ante las entradas correctas
- Que el código reaccione de la manera esperada ante las entradas incorrectas

y así podríamos seguir refinando. Igual es tema para discutir. Pero aún no podemos esperar, con las cualidades de arriba u otras que se nos ocurran, que ante un gran nivel de code coverage obtengamos, de forma casi mágica, una buena calidad de código.

Entonces, si alguien espera que subiendo el code coverage de una aplicación/sistema/librería, escribiendo pruebas para subir esa métrica, de esa forma vamos a obtener/mejorar la calidad de código, les digo una cosa: es loable lo suyo, pero es tan inútil como alinear las sillas en la cubierta del Titanic. Están haciendo algo que parece bueno, pero no es lo que hay que hacer.

Alguien podría decir: ¿pero acaso las pruebas que acompañan al aumento de code coverage, no nos aseguran que el código inicial tenga la calidad esperada? Si hacen las pruebas DESPUES de escribir el código, muchas veces eso no se cumple. Porque se van a ver motivados a elevar la métrica, no la calidad del código. Pongamos un ejemplo en concreto. Sea un método que recibe los argumentos a y b, y revuelve a/b (su división). Al principio no está cubierta, agregamos una prueba que invoca a ese método pasando a=1, b=2 y vemos que devuelve 0.5. Ahora el code coverage subió, de 0 a 100%. Las pruebas pasan. Pero no tenemos idea de haber cubierto los (MINI)CASOS DE USO de ese método. Por ejemplo, nunca probamos que pasa cuando b=cero.

Ese es uno de los problemas de los “pruebas después”. No se cubren los casos de uso de nuestra aplicación. Y si encima nos dejamos guiar por aumentar el code coverage, menos vamos a concentrarnos en los casos de uso. Y si siguen el camino “pruebas después” también se encontrarán, más frecuentemente de lo necesario, que el código ya armado es difícil de probar, o lo van a modificar de formas extrañas para que podamos tener la recompensa del día: uy! aumentamos el code coverage! ¡Qué buenos programadores que somos, qué buena calidad de código!

No me malinterpreten: el code coverage sirve, pero NO ASEGURA calidad de código. Ni siquiera la mantenibilidad de un sistema. No es difícil encontrar sistemas con alto code coverage, uno los modifica, las pruebas pasan, pero luego, el sistema web vuela luego de dos clicks en las páginas. ¿Por qué? De nuevo, porque el code coverage alto y las “pruebas después” no aseguran para nada que hayamos contemplado todos los casos de uso (como el simple de más arriba, dividor por b igual a cero, el método tiene 100 por ciento de code coverage, pero no tiene el 100 por ciento de los casos de uso).

Algo de valor agrega el subir el code coverage, porque al final, alguna de las pruebas adicionales algo nuevo cubre. Pero es lo que alguien ha llamado “pica pica bajada de cordón (de vereda)”, trabajo que agrega poco valor en general a lo que programamos, y que lleva tiempo.

Bueno, luego de esta larga diatriba, algo de agua para mi molino:

Si en vez de intentar subir el code coverage con pruebas después, seguimos el FLUJO DE TRABAJO de Test-Driven Development, el code coverage alto es como que viene de la mano de ese flujo de trabajo, sin gran esfuerzo. Si aplicamos ese flujo a cubrir cada caso de uso, vamos construyendo software que va creciendo, como un organismo, paso a paso, de la manera más simple, y asegurándonos que cada prueba que agregamos responde a una necesidad del sistema que estamos armando. El código va fluyendo, naciendo, transformándose, y la etapa de refactor es de importancia para aumentar la calidad, no para el code coverage.

Espero que se haya entendido: sin TDD, no hay paraíso :-)

Nos leemos!

Angel “Java” Lopez
http://www.ajlopez.com
http://twitter.com/ajlopez

Posted in Programacion, Test-Driven Development | Comments Off

Cortando la cola del pavo

Había una vez una familia que se reunía una vez por año, para celebrar juntos. La dueña de casa solía cocinar al horno un pavo relleno. Una receta que habia pasado de generación a generación, por vía oral, especificaba qué colocarle, cuánto tiempo cocinarlo, cómo preparar el relleno. Pero había algo que hacía siempre: cortarle la cola al pavo. Alguien preguntó: “¿Por qué hay que cortarle la cola al pavo? ¿Por qué está esa instrucción en la receta?”. Sorprendentemente, la cocinera sólo atinó a responder: “No sé, así lo hacía mi mamá, así me lo enseñó”. Fueron a preguntarle a la madre: “No sé, a mí también me lo enseñó así mi mamá”. Le preguntaron a la abuela: “Yo tampocó sé, así me lo enseñó mi mamá, en mi infancia”. Y al final, llegaron a la bisabuela: “Bueno, cuando vivíamos en Europa, en una casita en el campo, el pavo no entraba en el horno, y teníamos que cortarle la cola”.

¿Cuántas veces nos pasa, en nuestras actividades, profesionales o no, “cortar la cola al pavo”, sin notar que las fuerzas que llevaban a esa conducta ya desaparecieron?

Para poner un poco de contexto, veamos un ejemplo en el desarrollo de software. Cuando aparecieron las primeras microcomputadores, no había discos duros, solamente discos flexibles. Y muchas veces, la computadora sólo tenía disponible una dispositivo de discos flexibles (la “diskettera”). Recuerdo un sistema de sueldos y jornales, a principio de los ochenta. A fin de año había que calcular por empleado cuánto tenía que abonarse de aguinaldo. La ley había cambiado: ya no era un monto asociado al último sueldo, sino lo que había cobrado por cada cada mes. No había forma que un disco flexible tuviera toda esa información. Así que el operador, para calcular el aguinaldo, tenía que introducir y retirar uno por uno los discos de las liquidaciones mensuales. Eran las limitaciones de esos tiempos.

Y en minicomputadores, los discos duros no eran el último lugar de resguardo. Se usaban cintas, para sacar copias de los programas y los datos.

Desde los “mainframes” fue llegando la implementación de bases de datos (relaciones, jerárquicas, en red, aunque sólo sobrevivieron las primeras; la nueva “onda” de NoSQL no es tan nueva :-). Y la próximas implementaciones de los sistemas en computadores personales pasaron a usar bases de datos, en general relacionales.

Y siguió el progreso. Pero algo quedó de resabio, de reliquia, algo como “cortar la cola del pavo”: hoy se piensa en tener todo en la base de datos. Pero hoy tenemos gran cantidad de memoria disponible. Muchas veces, podemos tener nuestro dominio TOTALMENTE en memoria, y muchos casos de uso son de “muchas lecturas, escrituras esporádicas”. Pero en vez de aprovechar directamente la memoria (vean que puse TODO el dominio en memoria, no estoy hablando de “cache” selectivo), todavía se inicia el desarrollo de un sistema viendo el esquema de base de datos.

No digo que sea aplicable a todos los casos, pero en muchos sistemas, la base de datos (o el disco, al final), es la “nueva cinta”: la forma de resolver la persistencia. Pero la operación bien podría hacerse completamente en memoria. Aún en memoria distribuida. Por ejemplo, en Facebook, en su implementación, todo es un objeto (una persona, una relación, un mensaje, una foto, …), y se almacena en un sistema que llaman TAO (de The Abstract Object). Usan MySql (muchas bases) sólo para persistencia. La operación la resuelvan en memoria: TAO corre en multitud de máquinas distribuidas en varios data center, y usa MySQL principalmente para persistencia, y replicación, apenas para el negocio.

No hace falta ser Facebook para aprovecharse de “primero la memoria”, en vez de “hay que cortar la cola del pavo, hay que usar base de datos relacionales”. En un sistema que estuve examinando, con un caso de “muchas lecturas, pocas escrituras”, pude implementar el caso de uso principal en memoria, quedando CUATROCIENTAS VECES más rápido (no digo 400 por ciento, digo 400 VECES). Y otro proceso, que tardaba en el orden de las ocho horas, paso a ejecutarse en minutos, adoptando un modelo en memoria.

Y ése es sólo un ejemplo. Ahora, agua para mi molino:

Test-Driven Development (TDD), al empujarme, como flujo de trabajo, hacia la implementación más simple, casi como que me impide complicarme con soluciones que níngún caso de uso pidió. Sólo se adopta una base de datos relacional, o un NoSQL, cuando el caso de uso lo pide. Y siguiendo TDD he notado que esa decisión, que puede ser diferida, aún cuando se tome, se puede cambiar en el futuro. Por eso soy escéptico cuando alguien quiere plantear cuáles herramientas usar ANTES DE PASAR por los casos de uso.

Con el caso de uso “nuevo horno más grande” desde el principio, nunca se llega a implementar “cortar la cola del pavo”.

Nos leemos!

Angel “Java” Lopez
http://www.ajlopez.com
http://twitter.com/ajlopez

Posted in Programacion, Test-Driven Development | Comments Off