viernes, 27 de febrero de 2009

Sesiones de Diseño en Mercap

Esta semana retomamos en Mercap algo que llamamos "sesiones de diseño". La idea es juntarnos en grupos de 4 o 5 personas y resolver de manera completa un problema utilizando TDD, en vivo, con una máquina y proyector.
El motivo de hacer "coding" en vivo usando TDD en vez de solo diagramas en un pizarrón tiene que ver con el resultado que se obtiene en ambos casos. Hacer diagramas en un pizarrón está bien para hacer algo rápido y hasta a veces incompleto, pero como ya sabemos, cuando hay que programar la solución, hay que tener en cuenta todos los detalles, hay que formalizar lo que pensamos y es en estos casos donde se aprende más. Esto me hace acordar la frase "el diablo está en los detalles..." bueno, acá no es el diablo pero sí hay temas muy interesantes que surgen al hacerlo
Por cuestiones de responsabilidades no hacemos que estas reuniones duren más de una hora y media y tratamos de que los problemas no tengan que ver directamente con el negocio en el cual trabajamos para despejar un poco la mente de los problemas del día a día.
La verdad que es muy interesante hacerlo y muy enriquecedor. Desde el punto de vista pedagógico es interesante ver la mecánica de cada grupo, las ideas distintas y similares, etc. También vamos a comparar los resultados de cada grupo para aprender de las diferencias.
Hasta ahora la conclusión más interesante que saqué salió de un error que cometieron todos los grupos y fue que el primer test que hicieron fue muy complejo, no siguió el espíritu de TDD, de hecho llevó casi una hora escribirlo y en dos grupos no logramos hacerlo funcionar en el tiempo de la sesión. Pero también es verdad a que se debió por el enunciado del problema, en donde el primer caso planteado era complejo. 
Otra diferencia interesante fue como un grupo generalizó mucho más de entrada que el resto y fue justamente este grupo el que menos logró hacer finalmente, como era de esperarse.
Es bueno recordar que TDD nos pide hacer iteraciones chiquitas, tratar de tener un "verde" lo más rápido posible, y esto tiene que ver más que nada por una cuestión psicológica... ver que avanzamos nos mantiene contentos e inspirados.
La semana que viene veremos como sigue la cosa. Voy a tratar de seguir posteando las conclusiones que me resulten interesantes.

miércoles, 25 de febrero de 2009

Mock objects considered harmful (sometimes) [1]

Estuve leyendo un poco más sobre BDD y caí en estos artículos que explican como hacer BDD con RSpec en Ruby:

http://www.oreillynet.com/pub/a/ruby/2007/08/09/behavior-driven-development-using-ruby-part-1.html
http://www.oreillynet.com/pub/a/ruby/2007/08/30/behavior-driven-development-using-ruby-part-2.html

El segundo es más interesante que el primero. Si tienen un tiempo léanlo antes de seguir, por lo menos la parte final a partir de la cual crea la clase Game para representar el juego (Sección "Indroducing Mock Objects vis the Game Class"). Por favor, analicen críticamente el diseño que propone y los test que hace antes de seguir (por supuesto que pueden seguir leyendo si quieren, pero seguramente van a entender mejor lo que digo si lo leen).
Bueno, si lo leyeron y analizaron, deberían tener algunas críticas, ¿no?. Yo tengo un par que me parecen muy importantes, es más una hasta me hizo (y hace) dudar sobre la capacidad y experiencia de la persona que escribió pero voy a tratar de no ser muy duro puesto que es muy fácil criticar y muy difícil hacer. (Si llegaron hasta acá y se están preguntando que tiene que ver todo esto con el título, no desesperen, ya llega).
La primer critica que tengo es el acoplamiento que tienen los objetos instancias de Game[2] con la UI. Ver este acoplamiento me hace creer que la persona que escribió el artículo no conoce MCV (Model View Controller), el primer framework para desarrollar aplicaciones visuales (me atrevería a decir el primer framework) y del que se han derivado muchos patrones (si leyeron el libro de Design Patterns saben a que me refiero). 
Una de las reglas de diseño que  MVC "impone" es que el modelo nunca debe conocer la UI. La UI es un consumidor del modelo y el modelo únicamente provee comportamiento que luego las ventanas, views, etc. usarán para mostrarlo. En la solución planteada por G.Brown, objetos instancias de Game (que son objetos del modelo) conocen una UI, de la cual obtiene información como quienes son los jugadores o de cuantos puntos es el tablero. Este acoplamiento desde mi punto de vista es innecesario y puede ser resuelto fácilmente pasándole esa información a Game cada vez que se crea una instancia. O sea, la UI una vez que tiene toda la info y el usuario presionan el botón "Jugar" (o como se llame), crearía una instancia de Game indicándole quienes son los jugadores y cuantos puntos tiene el tablero, algo así:

playPushed
   ....
   game := Game playedBy: (Array with: 'Juan' with: 'Pepe') withSizeOf: 10.
   ....

De esta manera no existiría ningún acoplamiento entre Game y la UI, de hecho cualquier objeto podría crear un Game, como por ejemplo un test. Lo bueno de esta solución es que tampoco es necesario usar un mock para simular la UI (por no estar esta acoplada con Game), permite que nos concentrarnos en el modelo y que no tengamos que "adelantarnos" y "suponer" cual será el protocolo de la UI. So far, la primer crítica. Primera conclusión, seguir los principios de MVC! Segunda conclusión: El mock de UI no fue necesario
Paso a la segunda crítica: En el ejemplo del paso 15, se puede ver que se redefine el mensaje de construcción de instancia de Grid para devolver un mock que la simule. Según lo que comenta G. Brown, hacerlo evita que se tenga que usar Grid o crear instancias de esta clase "innecesariamente". No quiero sonar rudo, pero me parece un pésimo ejemplo y una muy mala conclusión. En primer lugar porque está rompiendo el encapsulamiento de Game al redefinir el comportamiento del mensaje de construcción de instancias de Grid, y lo está rompiendo porque sabe que Game "usa" Grid para representar el tablero. ¿Qué pasaría si Game decide utilizar otro objeto para representar el tablero? El test dejaría de funcionar. Claramente el hecho de que Game colabore con Grid es una decisión de Game que a nadie debería importarle y menos a un test!. Haberlo hecho hizo que el test se convirtiera en uno de "caja blanca", con todas las desventajas que sabemos que eso tiene. 
Lo mismo sucede con el mock que llama box_set y que utiliza para realizar movimientos por medio del mensaje #stub_move. Lo que está haciendo ahí en definitiva es simular lo que debería suceder en los objetos del modelo del juego, ¿pero para qué? ¿no se supone que estamos testeando el modelo? Además, si el mismo ya está hecho, ¿para qué lo simulamos? (además de romper nuevamente el encapsulamiento...). Devuelta, criticar es fácil, por lo tanto para no ser uno más del montón hay que ofrecer una solución, y para mi la solución acá es simple: ¡utilizar Game sin ningún mock! ¿Cuál es el problema de decirle a una instancia de Game que se unieron dos puntos tantas veces como sea necesario para crear el estado del juego sobre el cual se quiere testear (o empezar a testear) su comportamiento?
Bien, ahora si voy al título del post. Yo me pregunto, ¿G. Brown realmente cree que hacer esto está bueno o simplemente lo hace para utilizar (y mostrar como utilizar) mocks? Si el motivo fuese mostrar como utilizar los mocks, no me parece un buen ejemplo y hasta diría que es destructivo porque como ya sabemos, los seres humanos aprendemos de ejemplos y no quiero ni pensar todos los pobres programadores que están haciendo tests utilizando este ejemplo como guía. Si por el contrario lo hace así porque cree que es un buen diseño, mi pregunta es: ¿por qué cree que es un buen diseño? Una posible explicación es, como puse más arriba, que no conozca de MVC o de encapsulamiento, lo cual sería una lástima por todos los que lo leen; pero otra posible explicación, y es la que me motivó a poner el título del post y que veo muy a menudo últimamente, tiene que ver con la famosa frase que dice: "cuando lo único que conoces es un martillo, todos los problemas se parecen a un clavo". En este caso el martillo son los mock objects y que todo se parezca a un clavo son las soluciones que ofrece en su diseño usándolos. Lamentablemente en nuestra profesión es muy común ver como cuando aparece algo nuevo (o que parece nuevo) se lo empieza utilizar sin realizar el más mínimo análisis crítico de si se lo está usando correctamente; es nuevo, es cool, ¡usémoslo!
Eso es lo que veo muchas veces con los mocks, se los sobre-utiliza generando ejemplos perjudiciales y problemas de mantenimiento en los tests. En el ejemplo del mock para Grid: ¿qué pasaría con el test si se decide modificar el nombre del mensaje de creación de instancia de Grid? Claramente dejaría de funcionar y simplemente por el hecho de haber roto el encapsulamiento.
¿Cuándo utilizar un mock object entonces? Por lo menos para mi en este post me quedó claro que existen dos casos donde no hay que usarlos:
1) Para resolver problemas intrínsecos de diseño (como el acoplamiento de Game y UI)
2) Cuando se rompe el encapsulamiento (como el caso de Game y Grid)
En los tests, según mi experiencia, hay que usar mock objects para representar objetos que están por "afuera" del objeto que se está testeando y que este debe colaborar con para poder llevar adelante una responsabilidad. Pero mi consejo es, antes de usar un mock asegúrense que no pueden usar un objeto "de verdad"...

[1] Me imagino que todos deben conocer el famoso paper de Dijstra, "Goto considered harmful" que para algunos marcó el hito del inicio de la programación estructurada. De la misma manera que ya el "... driven development" está siendo sobre utilizado, me tomé el atrevimiento de sobre-utilizar el "... considered harmful" para llamar la atención. Espero por lo menos haber sido fiel al espíritu del artículo de Dijstra...
[2] Dicho sea de paso, Game no me parece un buen nombre para esta clase. Debería indicar por lo menos qué juego es.

viernes, 20 de febrero de 2009

Qi4j and state modeling

Un amigo me pasó este link sobre algo que están haciendo en Java: http://www.jroller.com/rickard/entry/qi4j_and_state_modeling
Es interesante, pero es algo que nuevamente refleja las complicaciones que tienen en Java para poder hacer cosas sencillas. 
Primero: el problema que tienen por usar POJOs (Plain Java Objects, lo que comúnmente hacemos en Smalltalk) puesto que lo escrito es muy distinto a lo que se "habla" en el dominio. Me alegro de escuchar ese argumento!, quiere decir que la gente está empezando a apreciar lo que venimos diciendo por años y es la importancia de hablar "el mismo idioma" con el experto del dominio, el problema es que no se dan cuenta que el "problema" está en la sintaxis y diseño de Java y que hay otros lenguajes, como Smalltalk, donde eso no sucede. O sea, el problema no está en usar Plain Objects, el problema esta en usar Plain Objects con la sintaxis de Java. Justamente debido a la sintaxis de Smalltalk es que a nadie se le ocurriría hacer algo como lo que está haciendo esta gente. 
Segundo: Habla de que quieren tener un lenguaje similar al del dominio del problema, pero sin embargo usar un keyword que seguro nadie del dominio entiende como @UseDefault. ¿Qué significa eso para un experto del dominio?, seguro nada
Tercero: Me vuelven loco los ejemplo pelotudos de uso de interface como este en donde extrae la propiedades de ser "nombrable" y crea la interface "Nameable". Y perdón por la palabra pelotudo, pero la verdad este tipo de ejemplos lo único que hacen es complicarle la vida al que aún está aprendiendo. Uno de los motivos es porque esto sucede al final de un desarrollo, no mientras se está desarrollado y entendiendo el dominio. De este ejemplo se puede sacar la conclusión de que cualquier cosa que tenga sentido se puede poner en una interface por que si, sin analizar que hacerlo puede agregar complejidad al diseño! ¿qué otra cosa que no sea un ser humano puede tener nombre y apellido? Nada! Entonce, ¿para que factorizarlo así?. Por otro lado, el motivo por el cual dice que es bueno hacerlo es porque ahora podrán preguntar si "x instanceOf Nameable"... parece que de polimorfismo poco esta persona. Justamente preguntar por el tipo de un objeto es el claro ejemplo de que aún están pensado en "estructurado" y no en objetos. No más palabras sobre esto porque me está subiendo la temperatura
Tercero: Dice que cuando modelan le gusta hablar de propiedades, relaciones, etc., pero en ningún momento veo que hable de comportamiento! lo más importante! Si solo le interesa propiedades y relaciones, ¿por qué no hace un DER?. Nuevamente, un ejemplo de que no lograron entender el paradigma de objetos y están usando técnicas de otros paradigmas. 
Cuarto: No entiendo por qué separan Entity de Value. Es algo que ya lo vi varias veces y el motivo es que los Values son inmutables, las entities no. Nunca escuche esta separación en filosofía ni en epistemología ni en nada similar, solo en nuestra profesión y por lo tanto me parece que es un problema del dominio computable... si es así, ¿por qué confundirlo a experto del dominio con esto?.
Quinto: El ejemplo del método listName() en la sección Privitanzing State me mató! jaja. Más me mató la frase siguiente: "Heat huh?". ¿Neat?, yo diría "a mess man!" a mess que rompe el encapsulamiento y que para perdir un nombre tengo que envier el mensaje "name()" y luego "get()"! o sea, primero tengo algo que no es lo que quiero (seguramente será un Value o Entity) y luego le pido su "valor" que es lo que realmente quiero... ¿no debería ser al revés en todo caso para que sea más simple?... en fin.

Algo que me gusto, es que como vine escribiendo últimamente, evitan usar nil (o null en java). Parece que esta idea está empezando a tomar forma simultáneamente en otras comunidades lo cual es bueno. Otra cosa interesante es que reifican las relaciones (algo esperable también por la orientación al paradigma relacional que tienen), algo que también veo como importante pero que lamentablemente nunca tuve la oportunidad de probarlo seriamente.
Conclusión, que lástima ver el tiempo que se pierde en tratar de resolver problemas no esenciales (reinventar la rueda pinchada como dice Alan Kay) creados a partir de decisiones de diseño incorrectas (en este caso decisiones del diseño del lenguaje Java).

miércoles, 18 de febrero de 2009

Tip 3: Objects must be valid since its creation time

Este tip de diseño del que hablé en mi presentación de Smalltalks 2008 es muy importante de mi punto de vista. La idea que trata de transmitir este tip es que cuando se crea un objeto, el mismo debe ser válido. Hay motivos teóricos y prácticos para demostrar por qué es un tip importante. Voy a empezar por el teórico.
Si están de acuerdo con la visión de que un objeto es una representación de un ente de la realidad, es muy importante preguntarse a partir de qué momento ese objeto representa el ente en cuestión. Y la respuesta sería (rápidamente) ¡cuanto antes mejor!, sino mientras tanto tendremos un objeto que no representa nada, un objeto que no puede cumplir con sus responsabilidad, un objeto que puede traer problemas. Este tip se relaciona bastante con el anterior en el cual comentaba la importancia de identificar claramente cuál es la identidad del ente representada en el objeto puesto que el objeto puede representar el ente una vez que la identidad del mismo es modelada por el objeto. Esto ya parece un trabalenguas. Voy a dar un ejemplo para tratar de transmitir la idea más claramente por lo que ahora nos iremos metiendo en los motivos prácticos. 
Supongamos que tenemos que modelar una tasa de interés. En cualquier libro financiero verán que una tasa de interés es representada por un porcentaje durante cierto tiempo. Es común por lo tanto encontrarse con tasas de interés del 10% anual, o del 5% mensual. Una tasa de interés del 10% anual significa que si invertimos nuestro capital por un año, al finalizar el año recibiremos un 10% de nuestro capital como "premio" por haber hecho la inversión (estoy suponiendo que no hay pago intermedios, etc). La tasa de interés es presentada algebraicamente como la división del porcentaje por el tiempo. Esto se puede ver claramente cuando escribimos la fórmula de interés:

interés = capitalInvertido * tasaDeInterés * tiempoDeInversión  donde tasaDeInterés = porcentaje / tiempo 
por lo tanto:
interés = capitalInvertido * porcentaje / tiempo * tiempoDeInversión  

Por lo tanto si capitalInvertido es 100 $, la tasa es 10% anual e invertimos por 2 años, tendríamos:

interés = 100 $ * 0.1 / 1 año * 2 años --> años con años se simplifican y tenemos = 100 $ * 0.1 * 2 = 20 $

Volviendo a nuestro modelo, podemos usar la clase "InterestRate" para modelar tasas de interés, la cual sabrá responder mensajes de creación de instancia como #of: aPercentage every: aTimeMeasure, o mejor aún #yearlyOf: aPercentage. Veamos un ejemplo en código:

InterestRate of: 10 % every: 1 year --> Devuelve una tasa de interés del 10% anual
InterestRate yearlyOf: 10% --> Hace lo mismo que la colaboración anterior

Ahora bien, este tip dice que el objeto que los objetos deben ser válidos desde el momento que se crean, esto significa para este ejemplo que cuando se crea una tasa de interés la misma debe ser válida. ¿Qué tasa de interés no sería válida? (una pregunta medio retorcida puesto que en realidad si no es válida no existe como tasa de interés, pero creo que se entiende no?). Claramente una tasa de interés no válida es aquella cuyo lapso de tiempo sea 0 (0 años, 0 meses, 0 días, etc. ¿recuerdan mi comentario sobre la igualdad del 0 con las medidas?) o cuyo porcentaje o tiempo sea negativo. El primer caso se puede deducir fácilmente puesto que de lo contrario el valor representado por una tasa de interés (que es la división del porcentaje por el lapso de tiempo como vimos más arriba) no se podría calcular por estar dividiendo por 0. El segundo tiene que ver más que nada con reglas de negocio, no existen tasas de interés negativas o con tiempos de inversión negativo! no tiene sentido. 
Esto significa que en el mensaje de construcción de instancia de "InterestRate" tenemos que asegurar estas tres pre-condiciones. La manera en que nosotros lo implementamos es la siguiente:

InteresRate class>>of: aPercentage every: aTimeMeasure

self assertPercentageIsPositive: aPercentage.
self assertTermNotZeroOrNegative: aTimeMeasure.

^self new initializeOf: aPercentage every: aTimeMeasurement
Se puede ver claramente que lo primero que se hace es validar que las pre-condiciones de existencia (y fíjense que ya no dije validez) de una tasa de interés se cumplan. Si la validación no es exitósa se generará una excepción, que en nuestro caso será una subclase de InstanceCreationException, indicando que pre-condición no se cumplió.
Fíjense que por haber hecho esto al momento de crear una tasa de interés, es imposible tener tasas de interés inválidas. El concepto de tener objetos inválidos desaparece y de repente solo tengo o no tengo dichos objetos, lo cual podríamos decir que se condice con la realidad puesto que en la misma no existen tasas de interés inválidas, nunca un banco daría una tasa de interés con tiempo 0 o negativo, es impensable. Uno podría decir que -10% anual es una tasa de interés, lo podría escribir en un pizarrón o aún en este blog, pero llamarla tasa de interés es un abuso y hacerlo es simplemente una ilusión que podemos tener puesto que realmente no es una tasa de interés y un experto del dominio nos lo diría de entrada.
El problema de tener objetos válidos o inválidos tiene que ver más que nada con un problema computacional debido a que alguien (persona o computadora) en algún momento debe ingresar los "datos" para crear la tasa de interés. Esto implica que existe un tiempo en el cual se ingresan "datos" y que por lo tanto deben estar en "algún lado" en nuestro modelo durante ese tiempo y también implica que esos datos pueden estar incompletos. Pero entonces ese problema no corresponde ser resuelto por el objeto del modelo, en este caso "InteresRate", sino por aquel que se encarga del ingreso de datos. 
Al haber hecho que una tasa de interés no se pueda crear si no es válida, ya no solo podemos asegurar que el usuario nunca creará tasas inválidas sino también que ningún programador lo hará!, si lo hace enseguida nuestro modelo le indicará que se equivocó, lo cual es buenísimo y tiene que ver con otro tip que veremos más adelante que se llama "Fail fast". Otra ventaja es que tenemos las famosas validaciones de negocio, o mal llamadas reglas de negocio en un solo lugar y no dispersas por cualquier lado (la UI, una interfaz batch, etc). Siempre que se cree una tasa de interés, venga de donde venga, la UI, un programador, otra computadora, etc, se realizará este chequeo que nos asegura que nuestros objetos siempre serán.... válidos (pensé en terminar la frase en serán, puesto que si no lo son, no existen que es lo mismo según mi punto de vista a que no son válidos; es como pensar que la palabra "válido" desaparece de mi idioma, deja de tener sentido).
Ustedes podrán preguntar cómo representamos entonces aquellos casos para los cuales la información ingresada es incompleta o no correcta, bueno, por medio de un objeto para tal fin! Por ejemplo, si aplicamos el mismo concepto para un "Deal" podríamos tener una intención de deal (DealIntention) para representar aquel que aún no se sabe si es válido. Esto puede parecer como que generará una explosión de clases pero la realidad no es así. En nuestro caso en lo que respecta a la UI, son los mismos widget o controllers los que se encargan de contener esa información inválida (Este blog ya es muy largo, pero cuando pueda voy a comentar como funciona la UI que hicimos para XTrade porque me parece muy interesante)
Esta característica de tener objetos solo válidos se potencia aún más cuando dichos objetos son inmutables, como el caso de la tasa de interés, o fechas, o medidas, etc. puesto que no habrá manera de que dicho objeto pase a ser inválido con el transcurso del tiempo. Si el objeto es mutable, podemos aplicar lo que mencione y detallaré más adelante como sincronización por copias. 
Analizando aún más este tip, veremos que para objetos inmutables estas pre-condiciones se fusionan con el concepto de invariante. Hemos logrado poner en un solo lugar las pre-condiciones de varios métodos y la invariante de un objeto, lo cual me parece muy interesante por haber simplificado esta representación.
Algunos detalles de implementación adcionales:
1) Todos los mensajes de creación de instancia deben en definitiva terminar cayendo en la evaluación de un único método de creación de instancia que es además es que realiza todas las aserciones. Para nuestro ejemplo, #yearlyOf: debería estar implementado así:

InterestRate class>>yearlyOf: aPercentage

   ^self of: aPercentage every: 1 year

2) Debería haber un solo mensaje de inicialización que es enviado desde un único método de creación de instancias que es el que realiza las aserciones. Esto implica que el mensaje #new enviado a self se realiza en un solo lugar.
3) Se puede reificar aún mejor el concepto de pre-condiciones para luego hacer meta-programación con las mismas y por lo tanto generar documentación automática (para interfaces por ejemplo) o test automáticos como presentó Meyer la semana pasada
Bueno, suficiente por ahora, no tengo ni tiempo de releer lo que escribí así que espero que se pueda leer bien! 

Behavior Driven Development

Estuve leyendo sobre una "nueva técnica" (si, otra más!) para desarrollar software. Es una técnica y no una metodología por lo tanto no es algo pesado ni complicado. En realidad es una técnica que según el autor trata de resolver algunos problemas que se encontró mientras enseñaba y usaba TDD. 
El artículo donde describe la técnica como introducción es interesante. La técnica se llama Behavior Driven Development y se puede leer una introducción acá.
No la he probado aún pero hay varias cosas que comenta que comparto y que veo que tan equivocados no estábamos cuando empezamos allá por el 2000 o 2001 a utilizar TDD como técnica de desarrollo para luego darnos cuenta que era más que una técnica de testing. Parece ser que ya varios se dieron cuenta de esto y el cambio propuesto por Dan North y hasta las palabras que usa, como "Behavior" me recuerda mucho a lo que siempre estuvimos enseñando.
Espero que disfruten del artículo y si alguien tiene alguna experiencia usándolo por favor comente que le parece. Según tengo entendido está teniendo bastante actividad en la comunidad de Ruby.

lunes, 16 de febrero de 2009

Bertrand Meyer en Argentina

La semana pasada tuvimos el honor de que nos visitara Bertran Meyer. Dio dos charlas en Buenos Aires, una en la UTN, organizado por los Seminarios Athena y otra en la FCEyN de la UBA organizada por Marcelo Frías. 
La primera (dada en la UTN) se trató sobre su punto de vista sobre como enseñar el curso de introducción a programación en estos días. En la misma comentó por qué los métodos más usados no eran de su agrado (programming in the small, teach API, just formal programming, teach a language, just functional programming) y cuales utiliza él. Uno de los temas que recuerdo como interesante era el hecho de hacer que los alumnos empiecen a programar con un framework ya creado para que aprendan a usar primero para luego aprendan a hacer. Me hizo recordar a lo que hacemos nosotros cuando les damos Smalltalk en POO, primero lo empiezan a usar y luego lo tienen que ver en más detalle y hasta modificar en DAO. De los 26 temas que comentaba como importantes para enseñar solo hay 16, que para aquellas personas que conocemos la bibliografía de Meyer no son nada novedosas, ej. Design by Contract, OO, etc. Me parece que lamentablemente el tema no fue el más apropiado para la audiencia porque la mayoría eran estudiantes y no profesores!, además mostró un ejemplo que usan en la primer clase de la materia donde crean la clase Turist que subclasificaba Preview (el framework que usan es para mostrar ciudades y rutas), en la cual el objeto París sabe responder show, Louvre sabe responder highlight, etc. responsabilidades que no le daría a esos objetos, o nombres de objetos que no usaría en todo caso.
La charla en la UBA se trato sobre testing automatizado. Básicamente lo que hacen es generar objetos random con una heurística determinada para algunos tipos de objetos y luego enviar mensajes a dichos objetos (siempre y cuando los mismos cumplan las pre-condiciones) y ver si hay post-condiciones o invariantes que no se cumplan. Si fuese así, se detectó un error. Mostró una estadística en la cual el testing manual encontró 14 errores y el automático 9. Había errores automáticos no encontrados manualmente y viceversa por supuesto. Le pregunté sobre la cobertura que ofrecía esa herramienta y se armó un gran revuelo puesto que dijo que la cobertura no indicaba nada, cosa que concuerdo a medias. Si hay un 20% no cubierto, uno sabe que debe testear ahí. Si tengo un 100% de cobertura por supuesto que no asegura que haya errores puesto depende de con que conjunto de "datos" se hizo la cobertura. En conclusión, no sacaron estadísticas de cobertura. Le pregunté si habían medido el tiempo que llevó encontrar lo errores manuales y los automáticos, dijo que no. Le pregunté si había pensado en utilizar mutation testing para modificar métodos y validar contratos y viceversa, modificar contratos y ver validar código y respondió que no "entendía mutation testing". Esta charla fue más interesante desde mi punto de vista que la anterior. Lástima que a veces hablaba mucho sobre ciertos temas en los que se perdía el núcleo de lo que quería decir.
Por supuesto, no dejó de publicitar su libro que está por salir "Touch of class ..." y Eiffel.

viernes, 13 de febrero de 2009

Igualdad de Objetos II

Algo que me olvidé de comentar en el post sobre Igualdad de Objetos, es que como el mensaje #= debe devolver true si los dos objetos representan el mismo ente su implementación siempre será verificar si aquellos colaboradores internos que representan la identidad del ente en el objetos receptor del mensaje es igual a los colaboradores internos que representan la identidad en el objeto pasado como parámetro. En pocas palabra, asegurar que ambos "identifican" al mismo ente.
En el caso de Date, esto será verificar que el año, mes y día sean iguales (si esa es la representación que se decidió usar para Date).
Una consecuencia interesante en identificar que colaboradores internos representan la identidad del ente es que esto se podría hacer automáticamente lo mismo que una implementación default del mensajes hash, como también asegurar que dichos colaboradores no pueden cambiar.

nil (pongo algo más para que no parezca un error :-)

Cuando escribí el título de este post era solo "nil", pero imaginé que alguien podría pensar que sucedió algún error y que por eso aparece solo "nil" en el título. ¿No les pasó eso miles de veces en los distintos sistemas que hicieron? Yo lo vi mucho, por eso modifiqué el título para no crear esa confusión :-) (por supuesto eso implicaría que Blogger está escrito en Smalltalk, cosa que sabemos que no, pero en fin, me pareció una caso curioso)
Ahora si, lo importante del blog. En las charlas que dio Meyer, comentó que en Eiffel pueden ahora asegurar que nunca se envía un mensaje inválido a nil de manera estática,  que él representó como que "x.f con x en null es detectado estáticamente" (1). Lo que me pareció curioso de ese comentario es que aún existan personas que estén pensando en ese problema cuando la solución es bien sencilla! y es simplemente no usar nil! pero claro, todos lo siguen haciendo por una cuestión histórica y porque la mayoría de los ejemplos que vemos cuando estamos aprendiendo lo hacen. 
Cuando le comenté a Bertrand que nosotros no tenemos el problema del nil (o null para él) puesto que no usamos nil para nada porque tenemos es una regla de programación que nos pide crear todos los objetos correctamente de entrada y representar la ausencia de algún objeto con un null object, me parece que lo sorprendió un poco. Inmediatamente me preguntó "cómo representábamos el fin de una lista encadenada por ejemplo", a lo que respondí que simplemente reificaríamos(2) el concepto de fin de lista y utilizaríamos un objeto especial para tal fin, pero no nil. Mi sensación es que se quedó pensando, por lo menos ahí terminó esta parte de la conversación.
Más allá de esta anécdota reciente con Meyer, les quería transmitir nuestra experiencia respecto de este tema y de que se puede vivir sin el objeto nil y conviene hacerlo por ser la fuente de innumerables dolores de cabeza. No se dan una idea cómo se simplifica la codificación creando objetos correctos de entrada, sincronizando con copias, evitando usar nil... Piensenlo por un minuto y traten de imaginar cuantos errores desaparecerían, cuantos "doesNotUnderstand" o "null pointer exception" dejarían de aparecer si nil o null no existieran... seguramente sientan que son muchos.
Les comento otro detalle anecdótico que me parece al mismo tiempo importante y en sintonía con este consejo. Hoy cuando leía unos blogs me encontré con esta charla que dará Hoare en QCon: "Null References: The Billion Dollar Mistake". Según lo que interpreté de su abstract y título, hablará justamente de que la existencia de null references fue un error gravísimo, y que sólo lo creó en ALGOL W por ser un feature fácil de implementar.... otro feature fácil de implementar que trae dolores de cabeza, y del cual coincido plenamente con Hoare, es realmente un "Billion Dollar Mistake".
(1) Después cuando nos comentó como lo hacían durante la cena, mucho no le entendí pero me pareció muy complejo.
(2) Digo reificaríamos y no reificamos porque nunca tuvimos que crear una lista encadenada en Smalltalk :-)

martes, 10 de febrero de 2009

Igualdad de Objetos

En el post anterior hable sobre el hecho de identificar correctamente la identidad de un objeto y sobre el verdadero significado del mensaje #==, que en definitiva verifica si dos objetos son idénticos, puesto que dos objetos pueden no ser idénticos pero estar representando el mismo ente.
Como bien comenta Gabriel (Gaboto) en un comentario, puede suceder que existan dos objetos representando el mismo ente (su ejemplo me confundió un poco) y debemos tener alguna manera de identificar dicha situación. Un ejemplo claro se da en Smalltalk con la clase Date, donde dos instancias distintas (o sea no idénticas) pueden representar el mismo día (por ejemplo hoy). 
El mensaje que utilizamos para saber si dos objetos están representando el mismo ente es el mensaje #=, más conocido como "igual". Su semántica es devolver verdadero si dos objetos representa el mismo ente, lo que en lenguaje vulgar o natural decimos "si son iguales"... claro está, acá empieza la confusión. Son iguales por estar representando al mismo ente (por ejemplo el día de hoy, que es idéntico a si mismo) pero no son el mismo objeto necesariamente y por no lo tanto no idénticos entre sí.
Por lo tanto el mensaje #= toma una nueva dimensión y para mí es más claro decir que este mensaje "devuelve true si dos objetos representan el mismo ente", puesto que definir qué significa "si son iguales" es bastante problemático como veremos más adelante. Sería menos confuso usar otro mensaje para esta semántica, por ejemplo #representsSameEntityAs:, pero bueno, ya estamos muy acostumbrado al otro.
El motivo por el cual dos objetos pueden representar el mismo ente se debe únicamente a problemas computacionales. En el caso de la clase Date esto se podría solucionar teniendo una cache de fechas creadas y siempre devolver el mismo objeto para la misma fecha, una implementación similar a la de Symbol, sin embargo hacer esto puede no tener sentido por poder ocupar más memoria o ser más lento que la implementación actual al tener que buscar el objeto en esa cache. En definitiva, si existe un solo objeto para representar un ente o si existen varios es una decisión implementativa y es por ello que siempre hay que utilizar el mensaje #= para saber si dos objetos representan el mismo ente (si son iguales) y sólo utilizar el mensaje #== para dominios computaciones, donde realmente nos interese saber si estamos hablando del "mismo objeto", del objeto que ocupa la misma zona de memoria.
Ahora bien, si se decide que puede haber más de un objeto para representar el mismo ente como el caso de Date, hay que asegurar que esos objetos sean inmutables porque sino estaremos en graves problemas puesto que podrían existir representaciones desincronizadas del mismo ente (creo que a esto apuntaba Gaby con su ejemplo). Imagínense dos objetos representando la misma cuenta corriente donde el saldo en uno es distinto al saldo en otro y donde claramente solo uno de ellos representa bien el ente en ese momento por tener el mismo saldo de la cuenta (por supuesto que también podría suceder que ninguno represente el ente por tener los dos el saldo incorrecto...). En definitiva, un grave problema.
Si no es factible que dicho objeto sea inmutable, hay que asegurar que exista una única instancia "master" para representar el ente y el resto sean solo "representaciones temporales" que tendrán un propósito determinado y esporádico. Un ejemplo de este caso es lo que en mi presentación de Smalltalks mostré como "sincronización" de objetos que comentaré en más detalle más adelante, pero que básicamente consiste en sincronizar la copia "master" con la "termporal" de una vez, con el envío de un solo mensaje. Por supuesto que acá nos metemos con problemas transaccionales, pero por ahora también los esquivaré.
Otro tema interesante que se desprende de este es definir exactamente que significa "que sean iguales". Por ejemplo, 0.5 y 1/2 ¿son iguales?. Ambos representan la misma cantidad, la mitad de algo, por lo tanto desde el punto de vista numérico nos interesa que sean iguales, pero también son distintos. Por ejemplo, desde el punto de vista implementativo uno será representado como un número de punto flotante y el otro como fracción. Mismos ejemplos se pueden dar para medidas equivalentes representadas por distintas unidades. Más interesante aún es pensar si 0 es igual a 0 metro, 0 litro, 0 pesos, etc. No voy a ahondar mucho en este tema ahora, pero la verdad es que sí, todos estos objetos representan lo mismo, o sea nada en cantidad y por lo tanto "son iguales". A muchos esta afirmación les parecerá muy loca pero la explicación matemática es clara. Una medida es la representación de la multiplicación de un número por una unidad, por lo tanto 0 metro es lo mismo a decir 0*metro, que algebraicamente es 0 de la misma manera que es 0 la expresión algebraica 0*A. Algunos denominan esta situación como "el cero polimórfico". Ahora bien, todas estas medidas están midiendo cosas distintas y sería interesante poder determinar esa situación, ¿cómo lo hacemos si devuelven true cuando se les envía el mensaje #=? (lo mismo para 1 metro y 1000 milimetros o 100 centímetros, etc). En Mercap estuvimos discutiendo mucho sobre esta situación, discusión en la que también participó Máximo Prieto y que fueron muy interesantes y acalorados (por qué ocultarlo). Llegamos a la conclusión que como en matemática, debería existir la posibilidad de definir distintas categorías de equivalencias, de la cual la igualdad es simplemente una de ellas. Si esto tiene sentido entonces el mensaje #= sería una implementación de una categoría de equivalencia determinada y podrían existir distintos mensajes para implementar las otras... suena interesante no? ¿Cómo se definiría una categoría o cuál sería su significado? Dependerá de lo que queramos representar, es una decisión totalmente arbitraria... aunque quizá suene mejor consensuada, consensuada con una comunidad o grupo...
En fin, si les interesa este tema avisen, da mucho para discutir y charlar.