martes, 18 de diciembre de 2007
Smalltalks 2007 finalizó... Retornando a la normalidad
Hemos recibido muy buenos comentarios de personas que han asistido como así también de integrantes del Departamento de Computación de Exactas.
El evento fue todo un éxito, con 200 personas asistiendo de las 300 registradas (era de esperar que no fueran todos porque era un evento gratuito).
Las charlas tuvieron un nivel técnico estupendo. Se ha hablado de todo tipo de cosas, desde temas relacionados con desarrollar sistemas grandes con Smalltalk, pasando por temas de User Interface, WebDevelopment hasta herramientas útiles para el ambiente. En fin, muy completo.
En el sitio web ya están disponibles la mayoría de las presentaciones, fotos y el link a la encuesta.
Ya estamos planeando el Smalltalks 2008, espero que este no nos lleve tanto tiempo organizarlo!!
Por otro lado, voy a ver si puedo retomar el blog con temas de diseño nuevamente aunque a esta altura del año y a pocos días de irme de vacaciones difícilmente me venga algo interesante a la mente... veremos.
jueves, 29 de noviembre de 2007
Más sobre Smalltalks 2007
Hay gente que viene de distintos lugares de Argentina y también del mundo, lo cual nos asombra gratamente.
No dejen de anotarse y venir... hay sorpresas para aquellos que vengan.
jueves, 22 de noviembre de 2007
Smalltalks 2007 - Update
la verdad que seguimos impresionados por la respuesta que estamos teniendo con esta conferencias.
Además de haber tenido que extenderla un día más, se agregó un tercer día con un Workshop sobre GLASS, la nueva plataforma de GemStone que utiliza SeaSide para facilitar la generación de páginas web.
Hemos llegado ya a los 220 inscriptos! todo un logro y algunos sitios de internet llaman a esta "la
conferencia de Smalltalk más Importante del mundo" . ¡¡¡Increíble no!!! y es acá, en Argentina!!
Hay inscriptos de muchos lugares del país y del exterior!
El programa ya fue publicado y lo pueden ver en http://www.dc.uba.ar/events/smalltalks/2007/programme/ . Algunas charlas tienen comentarios muy interesantes que agregó Leandro Caniglia.
Nos vemos el 10 de Diciembre!
viernes, 9 de noviembre de 2007
Smalltalks 2007 - Sigue creciendo
Ya lo pueden ver publicado en la página del evento (www.dc.uba.ar/smalltalks2007): La cantidad de abstracts que hemos recibido llegaron a los 28!!! y la cantidad de suscripciones a la fecha llegan a 130 más o menos.
Esto nos ha obligado a hacer un evento de dos días en vez de uno y a pedir el aula magna de pabellón I!!! Una bestialidad, nunca pensamos llegar a tener tanta repercusión... vamos a tener qué pensar que significa, cuál es la necesidad que estamos cubriendo que hasta ahora no lo estaba, cómo podemos ofrecer aún más, etc.
Ideas bienvenidas!
viernes, 26 de octubre de 2007
Videos de Squeak BOF
Es interesante ver la OLPC en acción (o por lo menos como luce)
lunes, 22 de octubre de 2007
Sobre bloques
hace un par semanas que no puedo escribir porque la organización de Smalltalks 2007 me está llevando más tiempo del pensado, pero lo estamos haciendo con mucho entusiasmo así que es tiempo bien invertido.
Lo que quería hacer hoy era reflexionar sobre los bloques de Smalltalk. Como todos saben, los bloques son objetos que representan colaboraciones, o sea, en un lenguaje más básico, código.
Idealmente, los bloques deberían ser polimórficos con los métodos compilados, que son los otros objetos que modelan colaboraciones entre objetos, sin embargo en la realidad no lo son y además poseen algunas pequeñas diferencias que no tiene sentido nombrar ahora porque están fuera del objetivo de este post.
En realidad lo que me interesa es conversar un poco sobre las desventajas de los bloques puesto que las ventajas saltan a la vista (siendo la más importante, desde el punto de vista de "computer science" que representan funciones lambda).
Uno de los problemas surge al momento de enseñar qué son los bloques. Es muy difícil para los que recién empiezan a utilizar Smalltalk (y más si no conocen Lisp o algo similar) entender que las colaboraciones también se modelan con objetos. No entiendan que los bloques son objetos y los ven como construcciones sintácticas. En particular resulta complicado que entiendan que el contexto de evaluación del bloque no es el bloque mismo sino el contexto en el cual fue creado el bloque.
Otra desventaja es la tentación de poner colaboraciones muy complejas o muchas colaboraciones en un bloque. En realidad podemos decir que esto sucede con cualquier método, no solo bloques, pero como supuestamente los bloques existen para poder "escribir código" rápidamente es que parece que nos olvidamos que también deben seguir ciertas reglas básicas. Una cosa es "escribir código" rápidamente cuando se está trabajando en inspectores, workspace, etc., y otra es "escribir código" en un browser, puesto que este perdura.
Es muy común ver este caso cuando se usa el mensaje #select:, donde el motivo de la selección de objetos implica verificar muchas cosas sobre el objeto en cuestión y entonces se empiezan a componer condiciones lógicas con #and: y #or: que no permiten entender a primera vista la intención del #select:. Por ejemplo:
....
trades select: [ :aTrade |
(aTrade isPurchase or: [aTrade isSale])
and: [Date today bettween: aTrade aggrementDate and: aTrade settlementDate]].
....
Este ejemplo adolece de varios males. El primero, como comenté, no se entiende qué se está seleccionando a simple vista, hay que analizarlo detenidamente. El segundo un poco más conceptual, es que seguramente hay alguna característica del objeto en cuestión que debería saber responder él, debería reificarla él y no aquellos que lo usan.
Para resolver el primer caso, siempre mi consejo es no haya más de un envío de mensaje dentro de un bloque, y que dicho mensaje "revele la intención" del bloque. Por ejemplo:
....
trades select: [ :aTrade | self isActive: aTrade ]
....
Un comentario al margen; fijensé que el mensaje se llama #isActive: y no #isTradeActive: puesto que se lee mucho mejor "self isActive: aTrade" que "self isTradeActive: aTrade". El segundo caso comete el error de poner el tipo del colaborador en el nombre del mensaje, un error muy común realizado por aquellos que vienen de los lenguajes "parentisados" puesto que en dichos lenguajes deben poner en el nombre si o si alguna referencia a los colaboradores puesto que no se los lee de manera tan similar al lenguaje natural como Smalltalk.
Volviendo al ejemplo, ahora se puede leer mucho más claro qué objetos se están seleccionando y además en caso de querer modificar el motivo de selección, no habrá problema en hacerlo. Mientras se está iterando la colección, se puede modificar el método #isActive: sin problema, modificando el comportamiento on the fly y sin necesidad de reiniciar la ejecución de select.
Si por el contrario, dejamos el bloque como el primer ejemplo, cada vez que se lo modifique, el debugger reiniciará la ejecución desde el principio del método donde está definido el bloque. Esto es necesario porque el contexto de evaluación dejó de ser válido y es necesario reiniciarlo, aunque se podría argumentar que se modificó el bloque y no el método per-se.
Peor aún si tenemos el siguiente código:
....
trades select: self activeTradeBlock
....
activeTradeBlock
^[ :aTrade | (aTrade isPurchase or: [aTrade isSale])
and: [Date today bettween: aTrade aggrementDate and: aTrade settlementDate]]
....
En este caso, si queremos modificar el bloque desde el debugger, no podremos. En VisualWorks saldrá un error que dice que no se puede modificar el bloque porque su contexto no está en el stack de ejecución. En el caso de VisualAge, no chistará pero tampoco modificará el bloque.
Más confusión trae a los principiantes si van y modifican el método #activeTradeBlock desde un browser mientras están iterando los trades con el debugger puesto que el cambio no surtirá efecto!! y claro, no surtirá efecto porque el objeto instancia de Block que contiene las colaboraciones que se están evaluando ya existe y no se creará uno nuevo hasta que no se evalúe el método #activeTradeBlock nuevamente.
Es por este motivo que también aconsejo poner siempre un solo mensaje en el bloque puesto que si el ejemplo dijera:
....
trades select: self activeTradeBlock
....
activeTradeBlock
^[ :aTrade | self isActive: aTrade ]
....
modificar el comportamiento de #isActive: es factible y se logra la flexibilidad y dinamismo esperado de un ambiente de objetos.
Por último, y yendo al plano conceptual, el encargado de tener la responsabilidad de decidir si está activo o no es el mismo trade, por lo tanto el ejemplo debería quedar:
....
trades select: [ :aTrade | aTrade isActive ]
....
De esta manera no se pierde flexibilidad y además la responsabilidad está bien otorgada. (No siempre se puede delegar la responsabilidad de selección en el objeto que se está analizando, por eso dejé el ejemplo donde se envía el mensaje #isActive:, pero está claro que lo mejor es utilizar este último caso)
Otro problema que traen los bloques se observa trabajando con objetos distribuidos. ¿Qué significa distribuir un bloque es está bindeado a un stack de ejecución particular? En particular, utilizando GemStone se encuentra esta limitación rápidamente si se intenta pasar un bloque como objeto remoto (GemStone provee soluciones, como utilizar un decompilador para recrear el bloque en la imagen remota, pero no siempre dicho decompilador está disponible como por ejemplo en VisualAge). Para estos casos, la mejor solución es crear un objeto que sea polimórfico con el bloque que se utilizaría. Por ejemplo:
....
trades select: IsActiveTradeCondition new
....
IsActiveCondition>>value: aTrade
^aTrade isActive
....
La desventaja que tiene esta opción es que se pierde justamente la facilidad que ofrecen los bloques de no tener que crear un clase cada vez que se quiere modelar colaboraciones o que los mismos sean expresiones lamda, pero se gana en la posibilidad de modificar el comportamiento dinámicamente y en poder distribuir el objeto sin problema.
Un caso concreto de abuso de bloques es el framework de SmallLint. En esta herramienta las reglas pueden ser modeladas con bloques, sin embargo el código de las reglas que provee está escrita en el bloque mismo, produciendo los problemas ya comentados. Me acuerdo haber realizado varios extract method para poder modificar las reglas existentes de manera dinámica puesto que sino, me sucedía lo comentado más arriba.
En conclusión, los bloques son muy útiles, pero al momento de no estar trabajando únicamente con inspectores o el workspace, hay que usarlos con más cuidado y sin olvidar, como siempre, que el código que uno escribe debe poder ser modificado dinámicamente y que seguramente será leído muchas más veces que escrito y por lo tanto cuanto menos esfuerzo de lectura implique, mejor.
martes, 9 de octubre de 2007
Smalltalks 2007 - Primer Congreso Argentino de Smalltalk
La primera vez de este tipo. Seguro ya recibieron el anuncio por otro lugar, pero bueno, no podía dejar de anunciarlo aca!!
------------------------------
Smalltalks 2007
------------------------------
El Lunes 10 de Diciembre de 2007, desde las 9:30 hasta las 18:00 horas
se llevará a cabo en la Facultad de Ciencias Exactas y Naturales (FCEN) de
la Universidad de Buenos Aires, el Primer Congreso Argentino de Smalltalk,
denominado "Smalltalks 2007"
Todas aquellas personas que estén relacionadas con Smalltalk, sean
docentes, alumnos, investigadores, desarrolladores o empresarios, están
invitados a participar como oyentes o expositores de manera gratuita
inscribiéndose en http://www.dc.uba.ar/smalltalks
El objetivo de la conferencia es reunir a la comunidad Smalltalk Argentina
para estrechar vínculos compartiendo un día de exposiciones de trabajos,
experiencias y vivencias relacionadas con esta tecnología o temas afines.
La jornada estará dividida en dos secciones:
1) Sección de Investigación/Educación
2) Sección de Industria
En la "Sección de Investigación/Educación" se expondrán aquellos trabajos de
investigación y educación que se estén realizando o se hayan realizado con
Smalltalk, en ámbitos de universidades y establecimientos públicos o
privados.
En la "Sección de Industria" se expondrán trabajos relacionados al
Desarrollo de Software en Argentina utilizando Smalltalk.
Algunos temas sugeridos para las presentaciones, de manera no excluyente son:
* Prácticas Ágiles (XP, TDD, etc)
* Herramientas de Desarrollo
* Desarrollo con Prototipos
* Model Driven Development
* Desarrollos de Aplicaciones Web
* Meta-Modeling
* Nuevos Modelos o Frameworks implementados
* Nuevos Frameworks de UI
* Material Educativo
* Sistemas Embebidos y Robótica
* SOA y Web services
* Reportes de Experiencias de Desarrollo o Investigación
* Sistemas comerciales
Aquellos interesados en exponer, deben tener en cuenta los siguientes
hitos y tareas:
1) Deben presentar un Abstract de la presentación a más tardar para el
Lunes 5 de Noviembre de 2007. Dicho Abstract no debe superar dos carillas
y debe incluir mínimamente una descripción del trabajo a presentar sin
omitir el objetivo del mismo y la sección a la cuál se apunta la presentación.
Debe detallarse también si la presentación incluirá una demostración en vivo
del modelo Smalltalk relacionado. El Abstract se puede presentar enviando un
email a smalltalks2007@gmail.com indicando como subject "Presentación"
2) El Comité de Programa de la Conferencia definirá para el Lunes 19 de
Noviembre aquellos trabajos que serán elegidos para ser presentados durante
la jornada a partir de los Abstracts recibidos según lo especificado
en el punto anterior. Se dará mayor importancia a aquellos trabajos con
capacidad de mostrar ejemplos dinámicos y no únicamente presentaciones
estáticas.
3) Aquellos que sean seleccionados para realizar las exposiciones, deberán
como mínimo generar un presentación con Power Point o herramienta similar.
(Por restricciones de tiempo, no se exigirá presentación de paper, pero se
agradecerá a aquellos que puedan hacerlo). Todo material utilizado para
la exposición (presentación, paper si corresponde, etc.) deberá ser
entregado en su totalidad a más tardar el Miércoles 5 de Diciembre para la
realización de los Proceedings de la Conferencia.
4) La definición del Cronograma de la Conferencia se publicará a través
de la página web el Lunes 26 de Noviembre.
Las actividades se llevarán a cabo en el aula Magna del Pabellón de
Industrias de Ciudad Universitaria. Para más información sobre cómo llegar,
dudas, fechas, programa, etc., pueden acceder a
http://www.dc.uba.ar/smalltalks
Los esperamos a todos!
Comité Organizador de Smalltalks 2007
viernes, 5 de octubre de 2007
Morph en JavaScript!
(Copio el mail que mando a la lista de Squeak)
---------------
As many of you know, I and a couple of friends at Sun have been
playing around with JavaScript, and we've finally figured out out how
to have fun with a browser.
You can find out all about it at...
http://research.sun.com
...and if you have a very recent build of Safari (preferably Webkit
nightly builds) or FireFox (preferably latest Gran Paradiso build),
then you can probably have fun with it too!
It's not at all polished, but it should look strangely familiar ;-)
Enjoy
lunes, 1 de octubre de 2007
¿Cómo asegurar la evolución de un sistema?" - Parte II - Visitor
algo que estuve haciendo últimamente tiene que ver con cómo asegurar en un ambiente dinámicamente tipado la implementación correcta del patrón Visitor.
Los problemas que este patrón puede generar son:
1) La clase root de la jerarquía de clases que se visita no implementa el mensaje #accept: como subclassResponsibility
2) Los visitors no implementan todos los mensajes que envían lo objetos que se pueden visitar
3) Lo contrario de la 2), los visitors implementan mensajes que no se envían desde los objetos que se visitan
Aquellos programadores defensores del tipado estático arremeterían contra los lenguajes dinámicos argumentando que estos errores no sucederían en dichas herramientas. La verdad es que a simple vista pareciera que no sucederían, sin embargo no es tan así.
En el caso del punto 1) , este error se vería evidenciado solo al momento de utilizar un Visitor sobre dicha jerarquía y únicamente si la variable del objeto que se quiere visitar está "claseada" (tipada) con la clase root que acepta visitors. Este error puede por lo tanto, pasar inadvertido por bastante tiempo (a menos que se esté utilizando TDD)
El punto 2) es donde se hace efectivo el chequeo de tipos puesto que no sería factible que un objeto visitado pueda enviar un mensaje no definido en el visitor que recibe como colaborador. Sin embargo el punto 3), que es el caso que típicamente se da en la evolución de un sistema, no es evidenciado como error por un sistema de tipos. Este caso se da al borrar por ejemplo, una clase de la jerarquía que es visitada.
Como podemos ver, hay ciertos aspectos del patrón Visitor que un sistema de tipos no puede asegurar. Es por ello que muchas veces se ha hablado de reificar los patrones como "first class objects", hacerlos de alguna manera, parte del metamodelo.
Sin embargo, cómo puse más arriba, hay 3 condiciones que se deben cumplir para que un visitor esté correctamente implementado, es por ello que implementé 3 test utilizando SUnit que verifican dichas condiciones: (Estos test los llamamos test de "Estándares de Código" y solamente se corren antes de cerrar una "unidad de trabajo")
1) Toda clase que defina el mensaje #accept: debe tener una superclase que implemente el #accept: como subclassResponsibility o indicar cual es la clase root de la jerarquía que acepta el visitor (esta última parte se debe a aquellas clases que quedan mal clasificadas por diversos motivos, pero que aún así deben poder ser visitadas por el mismo visitor que las clases que se encuentran bien clasificadas). Es importante que este test no falle puesto que de hacerlo puede haber algún objeto instancia de una clase de la jerarquía visitada que no implemente el mensaje #accept:. Este test hace uso de otro test que verifica que todo mensaje definido como subclassResponsibility es implementado en las subclasses concretas.
2) Todo objeto que sepa responder a un mensaje #visitXXXX:, debe saber responder a todos los mensajes #visitXXX: que son enviados por los objetos que pertenecen a la jerarquía que define la clase root que acepta el visitor
3) Los mensajes #visitXXX: que sabe responder un objeto deben ser igual al protocolo que define el Visitor. Si hay algún otro mensaje #visitXXX: implementado por un objeto que no corresponde a los visitors que dicho objeto implementa, quiere decir que el método que implementa ese mensaje es candidato a ser eliminado.
Para implementar estos test reifique algunos conceptos como VisitorProtocol y VisitorImplementation, de tal manera que se puede crear el VisitorProtocol de esta manera:
visitorProtocol := VisitorProtocol for: aClassRootAcceptingVisitor
y luego colaborar con visitorProtocol de la siguiente manera:
visitorProtocol isValid <-- Indica si el protocolo es válido, i.e., no tiene mensajes #visitXXX: repetidos
visitorProtocol implementors <-- Devuelve todas las clases que implementan el protocolo
y con un VisitorImplementor se puede:
visitorImplementor isValid <-- Indica si este implementor implementa correctamente el visitor
visitorImplementor missingImplementors <-- Devuelve los mensajes que no están implementados por este visitor
visitorImplementor implementMissingImplementors <-- Crea un implementación default para todos aquellos mensajes que no están implementados
visitorImplementor extraImplementors <-- Devuelve los mensajes que sobran en la implementación
visitorImplementor deleteExtraImplementors <-- Borra los mensajes que sobran en la implementación
Lo interesante de esta implementación es que mientras se está desarrollando y experimentando con un modelo, se pueden romper estas reglas, de hecho se hace constantemente y solo son verificadas en el momento oportuno. Y como toda regla, puede tener sus excepciones, algo imposible de hacer con un sistema de tipos cerrado.
Conclusiones:
1) Reificar las reglas de desarrollo que define el grupo de trabajo en el que se encuentran
2) Implementar test que verifiquen dichas reglas
3) Hacer estos test de tal manera que solo corran en el momento oportuno, que no sean una molestia
En definitiva, hacer que los mismos objetos se responsabilicen de su evolución (en este caso a través de 1, 2 y 3).
lunes, 24 de septiembre de 2007
La historia de Self
Realmente les aconsejo este paper, todos aquellos amantes de los objetos lo encontraran fasinante. Las comparaciones con Smalltalk, las restrospectivas históricas, los por qué del diseño de Self, etc.
Si no lo pueden bajar, ya conocen mi email...
viernes, 21 de septiembre de 2007
HOPL III
Este año se hizo la HOPL III, en San Diego y la verdad que hay artículos que parecen ser muy interesantes, entre ellos la historia de Self contada por David Ungar y Randall Smith, la de AppleScript, Modula-2 (contada por Niklaus Wirth"), la de Erlang y Haskell.
Una lástima haber perdido esta conferencia!!!
Igual me estoy bajando los papers... después les cuento.
IEEE Software - Dynamic Languages Edicion
Falta de tiempo
Nuevo sitio de GNU Smalltalk: http://smalltalk.gnu.org/
Explicación de Monads en videos: http://lambda-the-ultimate.org/node/2455
Web Server en Erlang, que dicen ser más rápido que apache: http://yaws.hyber.org/
Saludos!
jueves, 6 de septiembre de 2007
Demo de Croquet
http://jlombardi.blogspot.com/2007/09/croquet-demo-movie.html
Salvando Dolphin
El mail lo pueden ver en http://www.nnseek.com/e/comp.lang.smalltalk/ ("Future of Dolphin Survey") o ir a la página de la encuesta en http://www.surveymonkey.com/s.aspx?sm=F0w4GqMqUCFazkRUXaAL9A_3d_3d
martes, 4 de septiembre de 2007
Squeak by Example
El link a lo que tienen hecho hasta ahora es: http://web.cecs.pdx.edu/~black
El link a la página del libro es: http://www.iam.unibe.ch/~scg
viernes, 31 de agosto de 2007
¿Cuándo verificar si un objeto es válido?
Lo primero que debemos determinar es de quién es la responsabilidad de verificar si es válido el objeto. Se cae de maduro que los que no pueden realizar esa verificación son los objetos que lo usan, por lo tanto quedan dos opciones: 1) La clase de la cual será instancia el nuevo objeto 2) El objeto mismo a ser validado.
Veamos un ejemplo práctico. Un precio es un ente de la realidad que dice cuanto cuesta algo[1]. Por ejemplo el precio de 1 Kilo de Bananas es 10$. Para representar "precios" podemos crear una clase, Precio, la cual sabrá responder al mensaje #para:es:. Por lo tanto, una colaboración para crear precios sería:
Precio para: 1*Kilo*Banana es: 10*Peso
(Este ejemplo supone que están usando Aconcagua o algún paquete de medidas similar y por lo tanto puede crear medidas multiplicando cantidades por unidades, por eso 10*Peso es equivalente a lo que comúnmente denominamos "diez pesos". Por supuesto este ejemplo también asume que Kilo, Banana y Peso son objetos accesible en el contexto de evaluación y representan unidades).
Lo que tiene de particular los precios es que los mismos existen solo para cantidades distintas de 0. O sea, no tiene sentido hablar de el precio de 0 kilos de banana o de 0 autos. El motivo por el cual no tiene sentido se puede ver si representamos el precio como una cuenta matemática (en definitiva lo es). Un precio es la división del valor por la medida a la cual se le asigna el precio. Por lo tanto, si el precio para un kilo de bananas es 10 pesos, matemáticamente (y en Smalltalk) se representa como:
(10*Peso) / (1*Kilo*Banana)
Como pueden ver, no tiene sentido que el divisor de esta división sea 0. Es esta condición por lo tanto, la que se debe verificar cada vez que se crea un precio.
Veamos como se implementaría la opción 2). Esta implica que primero se crea el objeto y luego se valida. A nivel código la implementación sería:
Precio class>>para: unaMedida es: unValor
^self new initializePara: unaMedida es: unValor
Precio>>initializePara: unaMedida es: unValor
unaMedida isZero ifTrue: [ self error: 'No se puede crear un precio con cantidades en 0' ].
medida := unaMedida.
valor := unValor.
La implementación de la opción 1) sería:
Precio class>>para: unaMedida es: unValor
unaMedida isZero ifTrue: [ self error: 'No se puede crear un precio con cantidades en 0' ].
^self new initializePara: unaMedida es: unValor
Precio>>initializePara: unaMedida es: unValor
medida := unaMedida.
valor := unValor.
Dadas estas dos implementaciones, ¿cuál les parece más correcta?.
¿Lo pensaron?.... tomensé unos minutos... no lean la solución que propongo directamente!
[... pensando ... ]
Bien, asumo que ya lo pensaron. La opción correcta es la 1. ¿Por qué? Simplemente porque en la opción 2) el objeto ya fue creado, el precio ya existe pero es inválido! por lo tanto esta rompiendo la premisa que comenté en el post anterior, que solo pueden existir objetos válidos!. Es este el motivo principal por el cual la opción correcta el la 1. En este caso el objeto solo se crea si la validación es correcta.
Otra ventaja que tiene la opción 1 es que se crearan menos objetos en el caso de tener muchos casos inválidos... dependiendo del comportamiento de la aplicación esto puede llegar a impactar en performance puesto que la creación de objetos está directamente relacionada con la del garbage collector.
Bueno, espero que les haya gustado este tipo de heurísticas de las que vine hablando. Estaría bueno que me comenten que les parece... siempre es bueno un poco de feedback para repensar la cosas y seguir escribiendo.
[1] Un precio en realidad es algo más complejo que únicamente el valor de una cantidad de algo, los precios varían para la misma cantidad de ese algo según el momento en que se toma (el precio de hoy puede ser distinto al de ayer, o el de ahora puede ser distinto al de una hora antes) y el lugar donde se generan (el precio en Carrefour de las bananas no es el mismo que el de Jumbo)
¿Tiene sentido usar nil?
La realidad es que si aplicamos los principios que comenté en el post pasado, ningún objeto debería tener variables de instancia con "nil" (salvo por cuestiones implementativas como las que comento más abajo). Si al momento de crear objetos, a estos se le indica cuales son todos sus colaboradores internos, no debería existir ninguno de ellos referenciando a "nil".
Imaginense las ventajas que esto tiene! El objeto "nil" es uno de los más "dañinos" desde el punto de vista de "programación correcta o segura", puesto que su existencia implica que en algún momento se le deberá preguntar si es él (el famoso mensaje isNil o ==null en otros lenguajes), lo cual deriva en un ifTrue: (o if...) y por lo tanto en un posible foco de error y conflicto (error por olvidarse de hacer dicha pregunta, conflicto en el caso de la evolución del modelo, etc.)
Con esto no quiero decir que el objeto "nil" no debería existir o que no se lo debería utilizar, "nil" debe existir por lo que representa, "la nada", pero cuanto menos utilizado sea, mejor.
Siguiendo los consejos que publiqué en el post anterior, el uso de "nil" por lo tanto se disminuye al máximo y por ende también los errores que el uso del mismo induce.
El único motivo que encontré hasta ahora para utilizar "nil" como variable de instancia en un objeto, es para aquellas variables que "cachean" algún objeto y por lo tanto se pueden inicializar de manera lazy. Pero justamente por ser una cache o inicializarse de manera lazy, son colaboradores que solo debe conocer el objeto que los referencia y por lo tanto nunca otro objeto podrá pasarlo en el momento de la creación.
Por otro lado, les comento que en ambientes de objetos transaccionales como GemStone, tener variables que se inicialicen de manera lazy no es tan trivial puesto que al momento de comitiar una transacción se pueden generar conflictos de write-write. Por ejemplo, si desde dos transacciones distintas se le envía el mensaje que inicializa dicha variable al mismo objeto, aquella que commitee segunda tendrá el conflicto que comenté puesto que la primera ya habrá "escrito" dicha variable de instancia.
[1] "nil" en Smalltalk es un objeto, instancia de UndefinedObject y por lo tanto puede recibir mensajes y también se lo puede modificar. null en Java o .Net no es un objeto, es una construcción sintáctica y por lo tanto no posee la misma riqueza lingüística que "nil". ¿Qué significa "mayor riqueza lingüística?, les dejo un ejemplo para que lo deduzcan por su cuenta: (Este ejemplo fue una gran idea de Luciano!)
En un framework que usamos es necesario crear un diccionario cuyos valores pueden ser "nil" o no. Por ejemplo:
ClassXXX>>insertValuesFrom: aCuentaComo pueden ver, este framework no sigue los consejos que di en el post anterior y por ello es necesario verificar si 'sucursal' es "nil" para enviarle el mensaje #objectID en caso de no serlo. Lo mismo sucede con 'calificacion'.
^Dictionary new
at: 'sucursal' put: (aCuenta sucursal isNil ifTrue: [ nil ] ifFalse: [ aCuenta sucursal objectID ]);
at: 'calificacionCuenta'put: (aCuenta calificacion isNil ifTrue: [ nil ] ifFalse: [ aCuenta calificacion codigo ]);
...
yourself.
Como podrán darse cuenta, escribir código de esta manera es error prone (propenso a error), tedioso, molesto, cansador, poco inspirador y poco divertido (se lleva todas las malas). La gran idea de Luciano fue darse cuenta que "nil" es un objeto y por lo tanto puede saber responder los mensaje #objectID y #codigo de tal manera que devuelvan self!. Por lo tanto implementando los siguientes métodos en UndefinedObject:
UndefinedObject>>objectIDno es más necesario hacer los chequeos anteriores!. Transcribo lo que Luciano escribió:
^self
UndefinedObject>>codigo
^self
"De esa manera el código del ejemplo anterior se puede escribir así, que es mucho más claro y se eliminan todos los isNil ifTrue:[] ifFalse:[] que entorpecen la lectura:"
ClassXXX>>insertValuesFrom: aCuenta¿Se ve a simple vista no?... Esto es riqueza lingüística, es la posibilidad de modificar el lenguaje para cumplir con objetivos que no estaban pensados en primera instancia, es poder hacer evolucionar un lenguaje de la misma manera que evoluciona un lenguaje natural. De más está decir que con Java, .Net o C++, etc, esta solución no es factible puesto que "null" no es un objeto.
^Dictionary new
at: 'sucursal' put: aCuenta sucursal objectID;
at: 'calificacionCuenta' put: aCuenta calificacion codigo;
...
yourself
Se puede discutir si desde el punto de vista conceptual es correcto implementar estos mensajes en UndefinedObject... seguramente nuestra conclusión será que no, pero que ayuda, no perjudica y es muy práctico, no quedan dudas.
Así que, me saco el sombreo: Grande Lucho!
sábado, 25 de agosto de 2007
Creación, validación e inmutabilidad de objetos
Esta definición, es según mi entender, una de las más importantes del paradigma. La cantidad de conclusiones y prácticas que se pueden derivar de ellas son múltiples, pero hoy me voy a concentrar en una que nos ha dado muy buen resultado, y tiene que ver con el momento en que se crea un objeto, el momento en que el objeto empieza a existir.
Es muy común ver modelos en donde se crean objetos y a los que se les va "pasando" sus colaboradores internos (variables de instancia) en distintos mensajes. Por ejemplo, para el caso de un fecha, es muy común ver cosas así:
unaFecha := Date new.
unaFecha year: 2007.
unaFecha monthNumber: 2.
unaFecha dayNumber: 1.
Cuando los objetos tienen protocolo de este estilo, generalmente aparecen los problemas que comento a continuación:
1) Se puede crear el objeto "unaFecha" pero nunca indicarles cuales son su año, mes y/o día. O sea, puede existir un objeto que debería representar una fecha... ¡¡pero no lo hace!!, y no lo hace porque no existe una fecha que no esté compuesta por su año, mes y día. ¿Qué problemas prácticos puede ocasionar?. Varios, pero el principal es que antes de que un objeto colabore con "unaFecha" deberá asegurarse que realmente representa una fecha, que es un objeto "completo". Todos los objetos que colaboren con "unaFecha" para saber el año por ejemplo, deberán asegurarse que el mismo no sea "nil". (¿No les parece raro? Desde el punto de vista conceptual, ¿puede un año ser "nil"?... humm, vayan pensándolo)
2) Es muy dificil que "unaFecha" se asegure que realmente representa una fecha correcta. ¿Cuándo lo hace? ¿Cuándo le indican el día? ¿y si le indican el día antes que el año o mes?. Algo es seguro, "unaFecha" no debería permitir representar algo que no es una fecha válida. Por ejemplo, los días 29, 30 y 31 son inválidos para "unaFecha" puesto que su mes es Febrero del 2007. ¿Qué soluciones se utilizan para este problema?. Una solución que se ve muy seguido es crear un mensaje llamado "validate" por ejemplo, que todas las fechas sepan responder indicando si son válidas o no. De esta manera, el objeto que está colaborando con "unaFecha" una vez que le indica todos sus colaboradores le puede enviar el mensaje "validate". ¿Qué problemas puede traer esta solución?. Un problema muy común es por ejemplo que los objetos se olviden de enviar el mensaje "validate". Y digo muy común porque ¿alguna vez ustedes le preguntaron a una fecha si es válida?. Si una fecha existe es porque es válida!!, sino no existiría, no sería una fecha (deberían empezar a ver hacia donde voy...). O sea, yo puedo hablar del 30 de Febrero de 2007, pero por más que tenga "forma" de fecha no lo es y cualquier ser humano me lo haría notar. Otro problema más "computacional" se produce si "unaFecha" es compartido desde distintos threads de ejecución, ¿cómo hacer que mientras uno lo está modificando el otro no vea que es inválido?
3) Estos objetos tienen protocolo que hacen que las fechas sean mutables, o sea que un objeto que en un instante esta representado al "01/01/2007", en otro puede representar al "30/07/2008", nada impide en este modelo que eso suceda. En el punto anterior comenté uno de los problemas que puede tener que los objetos sean mutables y tiene que ver con la concurrencia, pero más importante que este problema que reside en el plano computacional es entenderlo en el plano conceptual. Y lo importante es ver que una fecha NUNCA CAMBIA. El 25 de Diciembre del 2007 es siempre el 25 de Diciembre del 2007 no importa que le sume 5 días, le reste 4 o haga lo que haga. Una fecha no cambia de la misma manera que un número no cambia. El 2 es siempre el 2, no importa lo que hagamos con él. Si un objeto representa el ente 25 de Diciembre del 2007, una vez que lo hace no puede pasar en ningún momento a representar otra cosa. ¿Qué problema práctico traería que esto suceda?. Bueno, básicamente si ese objeto es referenciado desde varios otros objetos, solo aquel que lo modifica sabría que ahora representa otra fecha, el resto no se enteraría.
En conclusión, de estos tres puntos podemos inferir que:
1) Cuando se crea un objeto el mismo debe estar "completo" de entrada, debe representar el ente de la realidad desde el momento de su existencia, de no hacerlo habrá un tiempo (que puede ser indefinido) en el cual no representará nada y por definición no será un "buen" objeto o objeto "at all".
2) Los objetos deben asegurarse lo antes posible que son "válidos", que realmente están representando el ente de la realidad para lo cual fueron especificados. No deberían existir objetos que no representan entes de la realidad.
3) Una vez que un objeto representa un ente de la realidad no puede pasar a representar otro. ¡Solo puede cambiar si el ente que representa cambia! y aunque parezca mentira es mucho más común que los entes de la realidad no cambien a que lo hagan. Utilizando términos computacionales, cuantos más objetos inmutables existan, mejor.
Si repasamos la definición del principio, podemos ver que todas estas conclusiones se pueden inferir directamente. Si un objeto representa un ente de la realidad, apenas el objeto exista debe hacerlo, solo debe representar entes que existan y no puede cambiar si el ente de la realidad no cambia. ¡Así de simple!, ¡pero no se imaginan las derivaciones que tiene! Los resultados de seguir estas "reglas" son muy provechosas, se los puedo asegurar no solo por las conclusiones que sacamos conceptualmente, sino por la experiencia de haberlas seguido y utilizado.
Por lo tanto, el ejemplo que mostré al principio de como crear una instancia de una fecha, debería ser:
unaFecha := Date yearNumber: 2007 monthNumber: 2 dayNumber:1.
¿Simple no?. El método que implementa el mensaje #yearNumber:monthNumber:dayNumber: debería "asertar" que dicha combinación forma una fecha válida y por último, no debería existir menajes como #yearNumber: que permitan modificar una fecha.
Si pueden, traten de usar estos consejos en los modelos que hacen, les aseguro que no se van a arrepentir. Cualquier duda, avisenme!, con gusto los ayudaré.
Les dejo mientras tanto, dos preguntas:
1) Bajo estas condiciones de modelado, ¿tiene sentido utilizar "nil" para algo?, ¿deberían objetos tener variables de instancia con "nil"?
2) ¿Cuándo deben los objetos verificar si son válidos? ¿Qué deben hacer en caso de no serlo?
¡Qué tengan un día objetoso! jaja.
viernes, 17 de agosto de 2007
Relativismo linguistico III
Por suerte ya es viernes a las 17:50 y luego de haber respondido el stack de mails que tenía pendiente aproveché para escribir estas lineas.
Por lo menos si hay algo bueno en tener que viajar una hora para venir al trabajo es que, por más lios que tenga alrededor, puedo leer; y continué con la lectura de Whorf y los capítulos que leí fueron uno más interesantes que el otro. Aquí les mando un pequeño resumen:
Science & Linguistic: En este capítulo es donde formula y explica el relativismo lingüístico. Básicamente dice: "Estamos frente a un nuevo principio de relatividad, que dice que no todos los observadores son guiados por las mismas evidencias físicas a la misma visión del universo, a menos que compartan bases lingüísticas similares, o que pueden de alguna manera ser calibradas".
Linguistics as an Exact Science: Dice que los grandes cambios producidos en la ciencia desde el 1890 no son por nuevos hechos sino por nuevas maneras de pensar sobre los hechos. Realza la importancia del habla sobre el pensamiento con la frase "Las bestias pueden pensar, pero no hablan. Hablar tiene que ser una palabra más noble y digna que Pensar". Según Leonard Bloomfield, la investigación científica empieza con un conjunto de frases que apuntan a ciertas observaciones y experimentos, cuyos resultados no se transforman en científicos hasta que han sido convertidos en lenguaje.
Languages and Logic: Este fue muy interesante. Dice por ejemplo, si se pregunta a alguien la similitudes entre la frase en Inglés "I pull the branch aside" y "I have an extra toe on my foot", seguramente encontrará pocas, o solo que son del mismo tiempo verbal, o sea, presente. Pero en el idioma Shawnee se pronuncia "ni-l'oawa-'ko-n-a" y "hi-l'oawa-'ko-oite", o sea que son frases muy similares!. Su pregunta es ¿cómo puede ser que el mismo concepto o evento en un idioma no tenga ninguna similitud gramatical y en otro sean casi lo mismo?. Transcribo una frase que me pareció muy interesante: "... the English sentences, "The boat is gounded on the beach" and "The boat is manned by picked men", seem to us to be rather similar. Each is about a boat; each tells the relation of the boat to other objects... The linguist would point out the paralelism in grammatical pattern thus: "The boat is xed preposition y". The logician might turn the linguist's analysis into "A is in the state x in relation to y" and then perhaps into fA = xRy. Such symbolic methods lead to fruitful theniques or rational ordering, stimulate our thinking, and bring valuable insight. Yet we should realize that the similarities and contrasts in the original sentences, subsumed under the foregoing forumula, are dependent on the choice of mother tongue and that the properties ot the tongue are eventually reflected as peculiarities of structure in the fabric of logic or mathematics wich we rear"... pero de las misma fraces en Nootka no tienen sujeto y predicado! y por lo tanto no se pueden inferir el mismo tipo de relaciones. Termina dándole con un palo a los Griegos y en especial a Aristóteles cuando se metieron en Lingüística por que según él mostraron un camino equivocado, que los hindúes estaban más avanzados...
En fin, es mucho más interesante leerlo de él que estos pobres resúmenes, pero espero sirva para darles una idea de su manera de pensar.
La semana que viene vuelvo a los objetos!!!
martes, 14 de agosto de 2007
Scripting en Smalltalk
http://code.google.com/p/syx/
viernes, 10 de agosto de 2007
GemStone
Así que si tienen ganas lean el comentario sobre el post anterior donde puse un link interesante a algo que está haciendo GemStone y que copio acá: http://www.infoq.com/news/2007/08/gemstone-ruby
Ralph Jonhson on Erlang y OO
Ralph acaba de escribir un comentario interesante sobre ErLang, un lenguaje que le están dando mucha manija ahora. Una de las cosas que hace es clasificar la gente de OO en tres categorías, la escandinava, la mistica y la de ingeniería de software... según esta clasificación me da gusto ser un ingeniero de software escandinavo y místico!!! jaja (no veo porque las tres categorías que hacen deberían ser disjuntas...)
jueves, 9 de agosto de 2007
Relativismo linguistico II
Ahora, lo que es el inconciente, porque en ese momento me vino a la cabeza lo que estaba leyendo en el capítulo más interesante hasta ahora del libro de Whorf que comenté en el otro post que se llama "The relation of habitual thought and behavior to language", donde entre otras cosas pone como tesis que en los lenguajes Indo-Europeos (castellano, inglés, latín, etc.) "objetivisamos" elementos que no son tanguibles. Según lo que entendí, se refiere a que entre otras cosas, utilizamos adjetivos "de cosas" para sustantivos que no se refieren a cosas. Eso es lo que hizo mi hija al decir que "cuentan alto" o "cuentan bajo"...
Los adjetivos "alto" y "bajo" son utilizados generalmente como medida de "altura" de una cosa, sin embargo en este caso mi hija lo uso para referirse al tono de voz con que contaban cuantos saltos daban, al "volumen" de voz que usaban. Y es muy común en castellano decir "el volumen está alto", cuando en realidad nos referimos a una unidad de medición que nada tiene que ver con la altura.
En el capítulo que comenté hay muchos más y mejores ejemplos de esta "objetivasión" que tenemos en los idiomas indo-europeos y que no existen en otros idiomas, como el Hopi o el Maya. Esta objetivación, según Whorf, nos afecta en la manera de entender el tiempo al cual lo terminamos viendo (otra objetivasion más! el tiempo no se ve!) como "points on a line"!!. Cuando leí esa frase no lo podía creer!, es la metáfora que usamos para desarrollar el modelo de Tiempo que llamamos Chalten (y es open source by the way). En Hopi esto no sucede y otra diferencia interesante es que no poseen tres tiempos verbales como nosotros, sino dos.
Otro tema que Whorf se encarga de destacar es que no se pueden usar las mismas construcciones sintácticas y semánticas de los idiomas indo-europeos en otros idiomas. No todo lo que nosotros entendemos por "verbo" o "adverbio" es aplicable a Hopi o Maya, tienen construcciones distintas!
Según Whorf, debido a que "objetivamos" el tiempo, es que pensamos en "puntos" del pasado y del futuro y debido a ello mantenemos registro de lo que pasó o manejamos agendas sobre lo que puede pasar en esos "puntos" del futuro, entre otras cosas.
La verdad, un capítulo muy interesante. Por lo menos me hizo ver como "objetivisamos" en nuestro idioma las cosas y porque el paradigma de objetos es el mejor para desarrollar.... porque objetivisamos todo!! jaja
miércoles, 8 de agosto de 2007
El último de la terna... de Steve Ballmer
http://www.youtube.com/watch?v=bhUAr-P_39U
Es bueno tener gente que defienda a los desarrolladores como este tipo! jaja.
Fin de Cuatrimestre en POO
1) No poner el tipo de los colaboradores (parámetros) en los nombre de mensajes. Por ejemplo, crean mensajes como "alterContect: aContext withEvent: anEvent" en vez de "alter: aContect with: anEvent". Esto se debe, me da la impresión, a la experiencia que tienen en utilizar lenguajes con sintaxis tipo C donde es necesario escribirlos así puesto que los nombres de los "parámetros" están completamente desasociados del nombre del mensaje. Por ejemplo, en Java escribirían "alterContextWithEvent (aContext, anEvent)".
2) Les cuesta muchísimo no romper el encapsulamiento de los objetos. Es muy común que a un objeto le pidan un colaborador interno y luego interactúen con el en vez de delegarle esa responsabilidad al objeto que posee dicho colaborador. Por ejemplo al objeto "aGameContext" le piden su "history" y luego a dicho objeto le envían el mensaje "add: anEvent" en vez de que directamente el objeto colabore con "aGameContext" de la siguiente manera: "aGameContext addToHistory: anEvent" y sea "aGameContext" quién termine colaborando con "history". Esto se debe, en mi opinión, a que aún siguen viendo a los objetos como estructuras de datos
3) Otro problema que veo constantemente es que escriben métodos enormes por más que insistimos en no hacerlo y en utilizar el refactoring "extract method".
Sin embargo también aprendí algunas cosas interesantes este cuatrimestre. Uno que me acuerdo tiene que ver con modificar "Interval" para que trabaje con "Medidas" y no solo con números. El problema tenía que ver con que en algún lugar Interval hace "(stop - start) / by = 0" y esto no es factible resolverlo si no se implementó el "polimorphic zero". Por lo tanto la solución que propuso un grupo fue hacer "(stop - start) / by + start = start"... muy piola.
sábado, 28 de julio de 2007
Como incentivar a los desarrolladores de software
http://www.youtube.com/watch?v=wvsboPUjrGc
jueves, 26 de julio de 2007
¿Cómo asegurar la evolución de un sistema?
La mejor manera que he encontrado para resolver este problema es, como siempre, delegarle esta responsabilidad a los objetos que forman parte del sistema. Siempre es mejor que la computadora trabaje mucho a que nosotros tengamos que hacerlo puesto que a ella no le cuesta, no protesta por hacerlo y no se equivoca!
Veamos un ejemplo donde es necesario adaptar objetos de una jerarquía de clases de una manera especial. En particular, supongamos que tenemos reportes en nuestro sistema donde hay que mostrar información de eventos (que están en una jerarquía) de una manera muy particular, porque el usuario así lo requiere (siempre caprichosos estos tipos!!).
Una manera de solucionar este problema es adaptar cada evento a lo que el usuario desea ver, de tal manera que cada evento puede generar una o más líneas en el reporte y con información que en principio no es polimórfica entre los eventos. Por lo tanto, se genera una jerarquía de objetos que adaptarán los eventos que están en otra jerarquía. El problema es que ahora los objetos deben decidir que adapter corresponde a cada evento. Una solución es la siguiente: (Los nombres están puestos solo para el ejemplo, no son buenos nombres para un modelo real):
Jerarquía de Eventos:
Event
..EventA
..EventB
..EventC
..EventD
Jerarquía de Adapters de eventos para las líneas del reporte:
ReportXLineAdapter
..LineAdapter1
..LineAdapter2
Luego, cuando hay que crear una adapter se le pide a ReportXLineAdapter el adapter que handlea el evento que se quiere adaptar, enviándole el mensaje #adapterFor: que hace lo siguiente:
ReportXLineAdapter class>>adaptersFor: anEvent
^self allSubclasses select: [ :anAdapterClass | anAdapterClass canHandle: anEvent ].
LineAdapter1 class>>canHandle: anEvent
^anEvent class = EventA
LineAdapter2 class>>canHandle: anEvent
^anEvent class = EventB or: [ anEvent class = EventC ]
Si estuviéramos usando el paradigma estructurado, ¿cómo se haría? ¿Qué están reemplazando estos objetos cuando colaboran entre sí?. Están reemplazando la estructura sintáctica de control de flujo conocida como "case" o "switch". Pero el problema que tiene esta implementación es que no tiene implementado el famoso "default" de dicha estructura de control de flujo.
Este problema se hace perceptible al evolucionar el sistema. Si se agrega un nuevo evento (lo podría hacer cualquier programador) seguramente no sabrá que hay que adaptar ese evento para el reporte, es más, quizá este programador no tenga ni idea que ese reporte fue hecho de esta manera.
Los tecnócratas metodologos dirían que con un buen proceso esto se debe resolver... que si se agrega un nuevo evento hay que utilizar la matriz de trazabilidad que nos indicará qué hay que modificar para que ese reporte funcione, etc. etc. etc., o sea, se nota que nunca hicieron un sistema porque tener un proceso que haga eso es carísimo (sí, CMM cuesta), imposible de mantener y además el programador debe mantener esa matriz de trazabilidad en primera instancia. Pero si puede mantener esa matriz de trazabilidad ¿por qué no modifica el código y listo? ¿para qué dar tanta vuelta?, en fin, un absurdo total.
Lo mejor es hacer que los objetos se "protegan" a sí mismos de la evolución. Por lo tanto, se debería modificar #adaptersFor: para que sepa que eventos él sabe que no serán handleados y si no hay adapter para un evento que debe ser handleado, significa algo anduvo mal.
La modificación sería así:
ReportXLineAdapter class>>adaptersFor: anEvent
| adapters |
adapters := self allSubclasses select: [ :anAdapterClass | anAdapterClass canHandle: anEvent ].
(adapters isEmpty and: [ (notHandledEvents includes: anEvent class) not ]) ifTrue: [
self error: 'Decidir como administrar el event ', anEvent class name ].
^adapters
Por otro lado, se escribiría un test que justamente verificaría que esta decisión de diseño tomada para solucionar este problema se la respete mientras evoluciona el sistema. Para ello se puede utilizar el framework de SUnit, es más, nosotros lo utilizamos para crear varios tipos de tests, no solo unitarios; lo usamos para test de estándares de código, de calidad de código, para test de user story y de arquitectura. No tenemos una categoría para este tipo de test, seguramente caería dentro de los test unitarios del reporte, pero por ahí sería bueno crear una categoría para controlar estas decisiones de micro-arquitectura. En fin, el test sería algo así:
testAllEventsAreHandledByReportXAdapter
| eventClasses |
"Me quedo con los eventos concretos"
eventClasses := Event allSuclasses select: [ :aClass | aClass subclasses notEmpty ] .
eventClasses do: [ :aClass | self shouldnt: [ ReportXLineAdapter xxx: aClass new ] raise: Error ]
De esta manera si se crea un evento y la jerarquía de adapters no se modifica para tenerlo en cuenta, este test fallará y nos enteraremos antes de enviárselo al usuario, que bueno no!. Fijensé que este tipo de problemas no lo soluciona ningún sistema de tipos estático, porque no tiene que ver con tipos sino con minas... eh, lapsus momentaneous, con decisiones de diseño de más alto nivel.
Moraleja: Siempre escribir código pensando en la evolución del sistema, o sea, los objetos deben prevenirse ellos mismos de la evolución y el cambio de sus compañeros. En este caso lo hicimos de dos maneras: la primera constructiva (no se me ocurre mejor nombre) y la segunda preventiva.
La constructiva la logramos modificando el mensaje #adaptersFor: para que construya correctamente los adapters para el evento, en caso contrario genera una excepción.
La preventiva la logramos escribiendo el test que verifica que se cumpla con la decisión de diseño de utilizar este idiom para resolver este problema. Esta opción solo se puede hacer con lenguajes de objetos que permitan trabajar sobre el metamodelo como Smalltalk. Esta opción no se puede realizar con lenguajes que no permitan trabajar sobre el metamodelo o lo hagan de manera limitada (como Java o .Net. ¿Cómo se obtienen la subclasses de una clase en Java o .Net?)
No tuve tiempo de pensar en otra solución para este problema, pero hay algo que me dice que debe haber una mejor opción... En fin, el ejemplo es válido de cualquier manera para pensar en la evolución de un sistema y como asegurarse de no introducir errores.
Asi se vende el software!!
http://www.youtube.com/watch?v=GL4hyATkQ74
viernes, 20 de julio de 2007
Relativismo linguistico
Una de los temas que me hizo pensar este libro fue la diferencia sobre el medio de comunicación de un idioma y un lenguaje de programación. Un idioma tiene medios de comunicación primarios como el habla y secundarios como la escritura. Es interesante ver que los lenguajes de programación solo se comunican a travez de medios secundarios y nunca primarios... no se si ustedes, pero nunca vi hablar a una persona en Smalltalk o Java!, jaja. Quiza cuando lo podamos hacer habremos logrado tener un lenguaje de programación tan bueno como el lenguaje natural.
Hay otros puntos interesantes que nombra este libro que se ven más en detalle en otro libro que estoy leyendo ahora: Language, Thought, and Reality de Benjamin Lee Whorf
El idioma que más utiliza Whorf para demostrar su tesis de "Relativismo Lingüistico" es el Hopi
utilizado por indios americanos que residían en el noreste de lo que ahora es el estado de Arizona de Estados Unidos.
Lo que más me sorprendió hasta ahora de este idioma es la diferencia con la idea de espacio-tiempo que tenemos las culturas occidentales. Nosotros manejamos el espacio y el tiempo como entes separados y no relacionados directamente, mientras que ellos lo manejan como un solo elemento altamente relacionado. Por lo tanto, la noción de "pasado" no solo está relacionado con hace cuanto pasó algo, sino que tan lejos pasó!. Si dos eventos suceden simultaneamente, pero uno sucede "más lejos" que el otro, el que sucedió más lejos para los Hopi podríamos decir que para lo que nosotros entendemos o podemos traducir, perdiendo mucho contenido, sucedió antes!
Otro punto totalmente distinto es como interpretan los Hopi el paso del tiempo. No recuerdo los detalles pero según lo que comenta Whorf está más de acuerdo con el relativismo de Einstein que los lenguajes Occidentales.
En fin, hay un sin fin de casos que ahora no recuerdo con exactitud pero que me hacen pensar cuanto nos estamos perdiendo de entender la realidad por la limitaciones lingüisticas que tenemos por los idiomas que utilizamos, todos ellos con la misma raíz indo-europea. O sea, si lengua y pensamiento van de la mano, aquello que no podemos decir no lo podemos pensar y justamente solo algunos pocos son capaces de "pensar" aquello para lo cual aún no tenemos palabras.
La tesis de Sapir-Whorf no está completamente aceptada por la comunidad científica, pero en definitiva dice que el idioma que usamos influencian nuestra capacidad de pensamiento.
Si aceptamos esta tesis y llevándola al marco del desarrollo de software, ¿afecta el lenguaje de programación como pensamos las soluciones a los problemas que tenemos que resolver? ¿afecta el lenguaje de programación a cómo entendemos la realidad que modelamos?. Yo creo que sí, aunque no solo el lenguaje influye, hay otros temas más, pero definitivamente las soluciones de un javero no son las mismas que un smalltalkero.
Esto no quiere decir que un lenguaje es mejor que otro, simplemente que con algunos podemos ver más fácilmente cosas que con otros...
La lingüistica es muy interesante, cada vez más la relacionan con entender como pensamos, aprendemos, etc. por lo tanto es muy importante que tengamos conocimientos sobre estos temas para poder desarrollar mejor software
Paso V: ¿Conoce usted qué significa desarrollar con Objetos?
Antes de proseguir, quiero dejar claro que en este paradigma si un objeto no tiene cierta responsabilidad es porque la tiene otro, no existe alternativa. Por lo tanto, si le saco la responsabilidad a las llamadas de calcular su costo, se la tengo que dar a otro objeto, no hay alternativa. La pregunta en cuestión es ¿cuál es ese objeto? Nuevamente debemos recurrir a una analogía. Pensemos por un momento como sería una oficina encargada de facturar llamadas telefónicas, que no posea ningún tipo de computadora para automatizar el procedimiento. ¿Qué personas compondrían esta oficina?, ¿cuáles serían sus responsabilidades? Seguramente habría una persona encargada tipificar una llamada para luego determinar su costo en base a una tabla de precios. Estos son los objetos que necesitamos en nuestro modelo, uno para representar a la persona que tipifica y factura (¿o quizá dos?) y otro para representar la tabla de precios.
El primero puede ser instancia de la clase “Facturador”, sabrá responder cuál es el costo de una llamada dada una tabla de precios. La tabla de precios será otro objeto que indicará el costo por pulso de cada tipo de llamada. Juntos, nos permitirán obtener el costo de una llamada y por lo tanto de todas.
Unos párrafos atrás comenté que la solución no estaba “a la vista”. Esto se debe a que los objetos que necesitábamos ni siquiera estaban mencionados en el enunciado del problema; encontrarlos requiere cierto poder de análisis, capacidad de abstracción para crear analogías y porque no, un pizca de imaginación (¿será por esto que algunos ver la programación como un arte?). Lo interesante de este caso es que ustedes pueden, a partir de ahora, hacer analogías similares para resolver los problemas que se les presentan, utilizar la misma técnica y ver que resultados obtienen. Les aseguro que serán muy positivos.
Sin embargo suena un poco raro que un facturador solo responda el costo de una llamada, su nombre parece indicar otra cosa. En realidad lo que estoy esperando de dicha persona (objeto) es una factura para un cliente. Nuevamente nos encontramos con un caso interesante de este problema, empezamos a hablar de factura algo que no había surgido hasta ahora pero que se comenta en el enunciado. Recuerden la máxima de este paradigma: “Todo ente de la realidad que se está modelando debe tener un objeto que lo represente”; por lo tanto si existe una factura en la realidad, una factura debemos tener en nuestro modelo, o sea, en nuestro programa. ¿Se les había ocurrido tener un objeto para representar la factura? De todas las entrevistas que tomé con este problema, nadie, pero nadie modelo una factura.
¿Por qué es importante tener una factura? La respuesta trivial es: ¡porque existe en la realidad! Pero si ustedes son pragmatistas y quieren una respuesta útil, verán que existen varios motivos, como tener que reimprimir una factura sin volver a calcularla, querer consultar cuanto se facturó (sumar todas las facturas), etc. Aún más interesante es que la factura me permita determinar los motivos por los cuales se llegó a los valores que posee, por lo tanto la factura no solo tendría los ítems facturados sino también podría, de alguna manera, permitirnos llegar a entender por qué se calcularon (importante al momento atender reclamos de los clientes, ¿no lo creen así?). En definitiva, necesitamos también una clase factura que será luego la que por pedido del ejercicio, se deberá imprimir en pantalla.
¿Qué sucede con la clase Cliente de la solución original?, ¿es una buena abstracción? Nuevamente debo responder que no. Posee varios errores respecto al ente que debería representar de la realidad. ¿Un cliente en la realidad, conoce todas las llamadas que hizo?, ¿ustedes como usuarios de teléfono, se acuerdan?, claramente no. No es la responsabilidad de un cliente conocer todas las llamadas que hizo. Menos aún porque si fuese así podría mentirle a la telefónica para tener que pagar menos, ¿no les parece? Este último argumento es válido para demostrar que un cliente ¡no puede tener la responsabilidad de imprimir su factura!, ¡sería una locura! Fíjense como el problema que estamos viendo en este caso no tiene que ver con cuestiones computacionales, no es un problema de performance, acoplamiento, encapsulamiento de datos, etc., es un problema de interpretación de la realidad, es un problema semántico, relacionado al significado que le otorgamos a los objetos, al lenguaje que construimos con nuestro modelo. Este último punto es el que marca la diferencia entre saber programar y saber programar con objetos. La diferencia radica en que programar con objetos implica entender el problema que estamos modelando, entender su significado y entender que por cada ente de este problema debe haber un objeto en nuestro modelo. No puede suceder que exista un objeto en nuestro modelo que represente un cliente y que dicho objeto no tenga nada que ver con un cliente real. ¿Se imaginan la reacción de un experto del dominio si le decimos que el encargado de facturar en nuestro programa es un cliente? No se deben crear objetos por cuestiones computacionales o de conveniencia organizativa, se deben crear si existen en la realidad que estamos modelando.
Ya estamos llegando al final, no se desanime. La verdad es que se necesitan unos cuantos objetos más, como el número de teléfono, alguien que se encargue de tipificar si una llamada es local, nacional o internacional a partir del número de origen y destino, etc., pero creo que a esta altura el objetivo del artículo está cumplido, que usted pueda responderse con sinceridad, sin necedad, si sabe o no desarrollar con objetos correctamente. ¡No se culpe sino es así!, es totalmente comprensible puesto que hay muy pocos buenos libros del tema, los cursos que abordan este problema generalmente lo hacen desde una perspectiva tecnológica con el objetivo de enseñar un lenguaje de programación, y sobre todo, es necesario cambiar la manera de pensar respecto de qué es un programa y cómo se debe crear un programa cuando se trabaja con objetos.
Lo importante es que usted recuerde por lo menos estas frases y trate de aplicarlas:
- Un programa es un modelo de un problema de la realidad
- Cuando se utiliza el paradigma de objetos para crear dicho modelo, se cuenta únicamente con objetos que representan entes de la realidad y mensajes que se envían entre sí para llevar adelante una tarea.
- Es muy importante descubrir qué objetos existen en la realidad y otorgar correctamente las responsabilidades a estos. Hacerlo permite reutilizar una solución.
- No todos los objetos están a la vista, hay que usar la imaginación para descubrir aquellos que existen implícitamente en la realidad. En otras palabras, hacer explicito lo implícito.
- Muchos de los objetos que no están a la vista, viven en la ambigüedad del lenguaje natural que usamos los seres humanos para comunicarnos y el la información contextual de una conversación. Es muy importante entrenar nuestra capacidad para encontrar estos objetos.
- La herencia o subclasificación es una herramienta poderosa pero pésimamente usada; en algunos casos dificulta el reuso.
Espero que hayan disfrutado de este artículo, por lo menos yo lo hice escribiéndolo. Si alguna vez quieren ver la solución completa del ejercicio vayan a la materia de Programación Orientada a Objetos que damos en la Facultad de Ciencias Exactas de la UBA o preguntenme!! jaja.
jueves, 19 de julio de 2007
Paso IV: ¿Conoce usted qué significa desarrollar con Objetos?
Debemos resolver ahora el algoritmo de cálculo del costo. Tenemos una sola clase, Llamada, para “codificar” ese algoritmo, pero al hacerlo ¡la llamada tendrá que determinar si es local, nacional o internacional!, ¡estamos más o menos en lo mismo! Humm, ¿será momento de empezar a cuestionarnos todo lo que creemos y buscar alternativas distintas?, ¿será el momento de utilizar el pensamiento lateral?
Si haber hecho este cambio de diseño no mejoró el cálculo del costo, puede significar que: 1) el cambio no es bueno 2) el cambio es correcto pero no hay una mejor manera de calcularlo o 3) no es correcto asignarle la responsabilidad de calcular el costo a la llamada.
De las tres opciones, la más revolucionaria es la 3, y es justamente la correcta. Usted dirá “¿cómo es posible que una llamada no sepa responder su costo?, ¿para qué existe entonces?, ¿quién se encarga de calcularlo?”. La realidad es que una llamada no tiene por qué saber responder su costo.
Pensemos por un momento que podemos hablar con una llamada, que le podemos hacer preguntas y que ella nos responde, ¿qué pasaría si le preguntaría su costo?, ¿qué respondería? La verdad es que no respondería nada porque ¡ella sola no puede saberlo! Para determinar el costo tiene que conocer la tabla de costos, que tipo de llamada es, etc., y nada de esto es esencial a una llamada. Una llamada es un ente que nos podrá responder el número de teléfono origen y destino, la hora en que se realizó, cuanto duró, pero no mucho más.
Un ejemplo que ayuda a entender este concepto es el siguiente: supongamos que estamos en una casa de alquiler de videos y necesitamos saber cuanto cuesta alquilar uno de ellos, ¿cómo resolvemos este problema?, ¿le preguntamos al video cuanto cuesta? Indudablemente no, si lo hiciéramos las personas de alrededor dudarían de nuestra cordura, y con razón. Lo que haríamos sería preguntarle a la persona que atiende la tienda el costo del mismo y él seguramente utilizaría una lista de precios (o se acordaría de ella, que en definitiva es usarla) para respondernos. Lo mismo sucede con la llamada, el problema es análogo y por lo tanto, también la solución. Una llamada nunca podrá responder cuál es su costo, para eso están otros objetos que luego analizaremos, aunque imagino que los irán intuyendo.
Si aún no los convencí con estos argumentos, les doy otro motivo por el cual no es correcto tener tres clases por cada tipo de llamada. Hacerlo implica que al momento de crear la llamada, alguien tiene que decidir de qué clase es. Esto implica que una vez decidida su clase, la llamada no puede cambiar de categoría puesto que un objeto no puede cambiar de clase (esto no es completamente verdad, pero hacerlo implicaría trabajar a un meta nivel distinto). Sin embargo pueden existir momentos donde no se sepa el tipo de una llamada o que una llamada haya sido mal tipificada.
Este error es muy difícil de verlo, más aún porque estamos acostumbrados a escribir código de prueba de la siguiente manera:
…
llamada1 = new LlamadaLocal (…);
llamada2 = new LlamadaNacional (…);
llamada3 = new LlamadaInternacional (…);
//etc
Es difícil de verlo porque sucede que en este caso es usted, el programador, quien está decidiendo el tipo de la llamada, a que clase pertenece, y el problema es justamente que usted lo decida. En este paradigma los objetos deben decidir qué hacer, no usted el programador. Y esta regla vale realmente para cualquier paradigma de programación que utilice. Uno de los objetivos que tenemos que buscar como programadores, es que la computadora haga más cosas y nosotros, los seres humanos, menos, simplemente porque la computadora “no se equivoca”. Por lo tanto, tenemos que lograr que sean los mismos objetos los que decidan que tipo de llamada es una llamada.
Utilizando el ejemplo de los videos se entiende mejor. Un video podría ser un estreno o un clásico. Esto se determina a partir de que tan nuevo o viejo es, por lo tanto el costo de alquilar uno u otro depende de una característica temporal. Si para resolver esta categorización se crearan las clases “VideoDeEstreno” y “VideoClasico”, no se podría cambiar la categoría de un video, de estreno a clásico por ejemplo, puesto que se debería cambiar la clase del video. Más simple es tener un conjunto de videos de estreno y otro conjunto con videos clásicos y por lo tanto el cambio de categoría de un video es simplemente sacarlo de un conjunto y agregarlo en otro.
Repasemos. De las cuatro clases que teníamos en la solución original para representar las llamadas, bajamos a una y le sacamos la responsabilidad de calcular el costo. Ahora tenemos que decidir a quién se la damos. Este es uno de los puntos que más me gusta de este problema, porque la solución no está “a la vista”.
Ya se hizo muy largo este post... mañana seguiré
miércoles, 18 de julio de 2007
Parte III: ¿Conoce usted qué significa desarrollar con Objetos?
Analicemos ahora por qué no es una buena solución. La verdad es que hay varios motivos. El primer error grave es crear una clase por cada uno de los tipos de llamadas. ¿Por qué?, porque hacerlo implica realizar una categorización sobre las llamadas que no es “esencial” a la llamada en si, sino accidental a la tipificación utilizada para facturar su costo. Crear estos conjuntos de clases implica asumir que las llamadas en todo contexto, deben saber responder su costo, cuando quizá no sea importante para otro problema. Usted seguramente dirá: “el ejercicio habla sobre obtener el costo de las llamadas, ¿por qué debería preocuparme sobre como serán utilizadas las llamadas en otro problema o contexto?”. Justamente una de las características de trabajar correctamente con objetos es saber interpretar la esencia de los mismos, para que luego puedan ser reutilizados en distintos contextos. Es por este motivo que trabajar con objetos correctamente permite “reutilizar código”, esa famosa característica que se vendió del paradigma y que fue pésimamente relacionada con la “herencia”. Acá estamos viendo un ejemplo claro donde la “herencia” va en contra del reuso, puesto que esta solución no serviría para un sistema de marketing de llamadas, para nombrar un caso.
¿Qué es, entonces, la esencia de un objeto? Responder esta pregunta es meternos en un tema más bien filosófico que computacional, pero créanme que programar tiene mucho más de filosófico de lo que creemos. Para dar una respuesta más o menos rápida, debemos entender que un objeto es una “representación” de un “ente” de la realidad. En otras palabras, un objeto en mi programa debe existir solo si existe un “ente” en la realidad que sea representado por dicho objeto. Ustedes dirán, “pero si tenemos llamadas locales, nacionales e internacionales, ¿cuál es el problema en tener esas tres clases?”. El problema es que estamos confundiendo la finalidad de la herramienta denominada “herencia” o “subclasificación” con la categorización de objetos. Este error se repite constantemente porque la subclasificación implica categorización, sin embargo la inversa no es verdad y es muy común ver que los programadores utilicen “herencia” para resolver todo problema de clasificación, como en este caso, donde es necesario clasificar las llamadas en locales, nacionales e internacionales.
Repasemos un poco lo dicho hasta ahora. Vimos que no está bien tener una clase por cada tipo de llamada porque el motivo de esta clasificación no corresponde a las llamadas en sí, sino al problema de facturarlas. Hacerlo impide su reutilización en otros contextos. Veamos ahora un punto de vista distinto. Qué sucedería si tuviera una sola clase, por ejemplo Llamada, y tres conjuntos (no importa si están implementados con un vector, lista, diccionario, etc.), uno para las llamadas locales, otra para las nacionales y otra para las internacionales, ¿no se solucionaría el problema de la clasificación de llamadas? La realidad es que sí, con algunas ventajas adicionales, como poder mover una llamada de un conjunto a otro fácilmente, o que una llamada pueda estar en más de un conjunto a la vez (seguramente un conjunto de otro problema o contexto, como por ejemplo tipificar llamadas por duración). En definitiva una solución que posee menos “acoplamiento” entre la llamada y su tipificación (¿se acuerda de la frase “máxima cohesión, mínimo acoplamiento”?).
Mañana veremos como solucionar el problema del costo con esta nueva solución.martes, 17 de julio de 2007
Parte II: ¿Conoce usted qué significa desarrollar con Objetos?
Empecemos ahora a analizar una solución del ejercicio que plantee la semana pasada.
Del enunciado se deduce claramente que es necesario representar llamadas locales, nacionales e internacionales. Como todas ellas son llamadas, lo más lógico es usar “herencia” de tal manera que tengamos una clase abstracta a la que denominaremos “Llamada” y tres subclases, una para representar las llamadas locales (LlamadaLocal) otra para las nacionales (LlamadaNacional) y otra para las internacionales (LlamadaInternacional).
A partir de estas tres clases concretas podemos empezar a resolver el ejercicio, otorgando a dichas abstracciones la responsabilidad de devolver su costo. Para ello, está claro que debemos implementar el método “costo” en estas tres clases (seguramente será un método abstracto en Llamada).
Pensemos ahora como sería la implementación de este método. Para que una llamada pueda devolvernos su costo, es necesario que sepa cuánto duró y en que momento se hizo (hora y día). Indudablemente estos deben ser colaboradores (variables de instancia) de las llamadas, de esta manera pueden ser utilizados en la implementación del algoritmo del costo. Como pueden ver, el ejercicio es muy trivial. Una vez resuelto cuales son las abstracciones principales que debemos tener y la funcionalidad más importante (calcular el costo), el resto no posee casi importancia.
Para terminar de resolverlo, es necesario imprimir por pantalla algo que muestre el abono mensual, el costo de las llamadas y por último un total. ¿Dónde podemos ubicar este código? Luego de pensar un poco queda claro que es necesario tener algún objeto que represente a un cliente, el cual contendrá la colección de llamadas realizadas y la implementación del método “imprimirFactura”. Creo que no hay duda que dicho objeto es el lugar más conveniente para implementar esta funcionalidad.
Una posible implementación de algunos métodos sería (utilizando Java)
Class Cliente
{
private Llamada[] llamadas;
{
this.llamadas = llamadas;
}
{
imprimirAbono (fecha);
imprimirCostoDeLlamadas (fecha);
…. //etc.
}
}
A esta altura del partido, usted se puede estar preguntado: ¿Por qué utilizo este ejercicio como ejemplo de este artículo y en las entrevistas si es tan sencillo? ¿Acaso un ejercicio tan sencillo puede ocasionar problemas? La solución presentada es directa, con estructuras claras, objetos sencillos, ¿no es acaso una buena solución? ¿No es acaso similar a la solución que usted pensó? ¿No es la solución que usted presentaría si estuviese en la entrevista o haciendo el parcial de la facultad?
La verdad es que este ejercicio tan sencillo demuestra rápidamente el conocimiento sobre el paradigma de objetos que posee una persona. Si usted cree que la solución presentada es una buena solución, le pido disculpa por lo que le voy a decir, pero entonces usted no sabe desarrollar con objetos. Siento decírselo de manera tan ruda y directa, pero la verdad es que esta solución (que hace el 90% de las personas) es una pésima solución, está plagada de errores de interpretación y modelado; es una solución que no aprobaría el parcial de la facultad.
En este momento usted puede estar experimentando varios sentimientos. Puede estar sorprendido por lo que digo e interesado en conocer por qué, o puede estar molesto y pensar que esta leyendo burradas. Le pido un poco de paciencia, que trate de seguir leyendo para ver si concuerda o no con mi punto de vista.
Le comento que encaré este artículo pensando en producir una molestia en el orgullo del lector y así lograr que no se olvide fácilmente de él. Otro objetivo es que usted vea un mal ejemplo, puesto que muchas veces es mejor aprender de los errores.
lunes, 16 de julio de 2007
Idea de Dan Ingalls
http://www.youtube.com/watch?v=Rj4moeKDGY4
viernes, 13 de julio de 2007
¿Conoce usted qué significa desarrollar con Objetos?
El artículo es sobre un ejercicio que tomamos en Mercap para determinar el nivel técnico de las personas que se presentan para trabajar con nosotros. Espero lo disfruten.
--------
¿Conoce usted realmente qué significa desarrollar con objetos? Esta es una pregunta que he realizado durante mucho tiempo como gerente de desarrollo, a las personas que he entrevistado para trabajar conmigo y también a los alumnos que he tenido/tengo en la facultad. ¿Por qué realizo una pregunta tan básica? La realizo porque luego de tantos años de vida del paradigma de objetos (recuerden que el primer lenguaje de objetos fue Simula ’67, el 67 se debe a que fue releaseado en 1967!), luego de tantos años donde no se pone más en duda si el paradigma objetos es mejor que el estructurado (quizá no tantos porque esta dicotomía se empieza a dilucidar con el advenimiento de Java), aún se ven ejemplos de diseño y código que presentan revistas, libros, etc., donde la respuesta es claramente no, aún se ven lugares donde se “confunde” a la gente sobre el significado de los objetos, aún me sigo desilusionando con los candidatos de mis entrevistas.
Explicar QUE es el paradigma de objetos es una solución para este problema. Sin embargo ya se hizo tantas veces, ya se escribió tanto al respecto (aunque hay mucha basura dando vuelta) que no es mi intención repetirlo en un artículo (además de ser imposible por cuestiones de espacio). Cansado de dar explicaciones teóricas, voy a concentrarme esta vez en un ejercicio práctico, que le permitirá a usted contestar la pregunta presentada como título de este artículo; usted podrá decir “SI, se desarrollar con objetos”, o “NO, aún debo aprender más”.
Vamos por lo tanto a realizar un ejercicio sencillo, que lo utilizo en mis entrevistas y que rápidamente, una vez resuelto por el entrevistado, permite determinar si la persona en cuestión entiende o no de objetos. Este ejercicio es una simplificación de un parcial que varias veces hemos tomado en la materia de “Programación Orientada a Objetos” de
1) La facturación se realiza de manera mensual
2) La facturación está compuesta por:
a. Un abono mensual básico
b. Consumo por llamadas Locales
c. Consumo por llamadas Nacionales e Internacionales
3) Las llamadas locales tienen distintos valores según la franja horaria en la que se realizan y el día. Para los días hábiles, de
4) Las llamadas Internacionales tienen un costo distinto según el país al que se llame
5) Las llamadas Nacionales tienen un costo distinto según la localidad a la que se llame
1) No es necesario realizar una interfaz de usuario visual.
2) No es necesario realizar persistencia de los datos (o sea, conexión a base de datos, archivos, etc.). Alcanza con simular los datos creándolos en memoria
3) Como salida alcanza ver por pantalla como sería una factura (sin preocuparse por darle un formato especial)
Por favor, tómese por lo menos un par de minutos para pensar una solución. Piense qué objetos y clases tendría, cuál sería la responsabilidad de dichos objetos, etc. Nada complicado, solo un par de minutos.
-----------
La semana que viene empiezo a postear la solución! Tiene el fin de semana para pensarlo!! jaja.