domingo, 19 de septiembre de 2010

Diseño reincidente (I) - IntroMix 2010, filosofía zen, las articulaciones y el bambú

Más o menos a mediados de la década de los noventa del siglo pasado mi conocimiento del inglés era nefasto. Finalizada la primera década de este siglo no puedo decir que sea mucho mejor, aunque hoy en día puedo decir que entiendo bastante bien lo que leo e, incluso, lo que escucho. Pero entonces me costaba mucho más. Cediendo finalmente al deseo vocacional (originalmente quise estudiar Matemáticas Y —importante ese «y»— Física, pero decantándome por Ingeniería Industrial), acabé metido en Informática y, como todo buen «amante» de la misma, abracé temprano la Orientación a Objetos. Tan temprano como tuve conocimiento de ella (como una década y pico después de su «invención»). Y de esa manera un tanto atropellada y particular en la que yo abrazo las cosas, claro.

Dos libros fueron relevantes en esos tiempos: 'Design Patterns: Elements of Reusable Object-Oriented Software' y 'Object-Oriented Software Construction'. El segundo lo pude sacar de la biblioteca de la escuela, mientras que al primero tuve acceso únicamente vía el préstamo de libros entre universidades y tras fotocopiarlo frenética y completamente en el plazo de los tres días que tenía para devolverlo a la universidad de Barcelona. Hoy en día tengo las versiones en castellano de ambos libros que apenas hojeo, pero que me gusta tener ahí como recordatorio de que fueron libros importantes en su momento. De hecho no descarto leerlos nuevamente, esta vez comprendiendo plenamente (espero) lo que cuentan. Confieso que he estado tentado de pedir las versiones originales por petardeo nostálgico.

Júntense ganas de aprender, juventud marcada por un desorden de capacidad de atención no diagnosticado (o con hormonas insaciables a la par que insatisfechas) y que la fuente de conocimiento estaba escrita en lengua bárbara y casi ininteligible, y no ha de resultar descabellado, impensable ni insólito que en mi cabeza se conformaran unas interpretaciones de los patrones de diseño [@ Wikipedia] y del diseño por contrato [@ Wikipedia (en)] bastante particulares que poco, o nada, tenían que ver con los originales. Exagero un poco, que ya se sabe que soy heredero de andaluces (y alemanes/holandeses e italianos, además de canariones, pero que no apartan demasiado a mi deseo perenne de exagerar). En el fondo creo que algo sí que entendí.


Mi juvenil testa se llenó de ideas un tanto alocadas que, a la primera de cambio, empecé a aplicar. Cuando en 2002 me encomendaron —en realidad casi me encomendé yo mismo aprovechando que era el jefe del proyecto— la tarea de diseñar el sustrato de un producto en el que trabajaríamos durante los siguientes años, empecé a aplicar todas esas ideas malinterpretadas, a la par que maravillosas, que se fueron concretando, en los siguientes años, en una serie de arquitecturas específicas. Cada una refinaba la anterior dejando atrás lo que no me gustaba y añadiendo lo que iba descubriendo/aprendiendo en el transcurrir de los meses. Puedo afirmar que los años comprendidos entre 2002 y 2005 fueron muy buenos años en lo referente a aprendizaje acelerado y exponencial de todas estas cosas que tanto me atraían.

Fue también en 2002 cuando empecé a abrazar, de la mano de Jose Miguel Santos —espero algún día escribir una entrada en su honor—, la filosofía de los test unitarios [@ Wikipedia (en)]. Entonces aún no había escuchado/leído nada de Mocks [@ Wikipedia (en)] ni de Stubs, así que acabé ingeniándomelas para montar tests que pudiesen cambiar/usar piezas de la arquitectura por otras que no hicieran gran cosa o que, en su defecto, trabajasen sobre versiones de datos embebidas. De hecho, los primeros desarrollos de las aplicaciones siempre las basábamos en Bamboo.Prevalence [@ Sourceforge], una adaptación para .Net de la idea original que tuvo Klaus Wuestefeld [su idea] y que se concretó en System Prevalence [@ Wikipedia (en)] de persistencia. De esta forma, además de forzarnos a pensar en las operaciones del sistema como comandos [@ Wikipedia], podíamos posponer las preocupaciones de las las particularidades del diseño usando un sistema de almacenamiento relacional. (¿A alguien se le escapa a estas alturas que Hibernate [Pagina oficial] intenta precisamente solventar el salto semántico entre el modelo de orientación a objetos y el modelo relacional?)

Otra de las cosas que sucedieron en 2002 fue que caí —en este caso era reacio a abrazar— en las redes de .Net. Yo quería a toda costa hacer el proyecto con Java, pero el entonces presidente de la compañía en la que trabajaba dijo que C# «era el futuro». Cosas peores se han dicho y afirmado durante la Historia de la Humanidad (del estilo «¡La virgen se me ha aparecido y me ha hablado!»), pero he de reconocer que al final a mí me gustó mucho C# y le agradezco que me forzara y me violentara en aquella ocasión y de esa forma. Aunque luego demostró equivocarse en todas las demás predicciones que preconizó. Y fueron muchas. Anecdóticamente, pasados unos años, llegó un día, levantando los brazos cual predicador que dirige su discurso a un Dios magnánimo que lo ilumina e impregna de sabiduría, y exclamó que Java era el futuro y que C# era un subproducto de Microsoft. «Vete a tomar por donde amargan los pepinos», fue mi respuesta.

El gradiente resultante de la multiplicación de todas estas fuerzas se concretó en el uso a destajo de las interfaces. Venía de varios años programando en Delphi [@ Wikipedia] y apenas había tenido tiempo de profundizar ampliamente en Java cuando me vi arrastrado a usar C#. Me enamoré de las interfaces tan pronto las comprendí (las de Delphi entonces no eran verdaderas interfaces). Y, por mucho que digan los que lo defienden, una clase abstracta no es lo mismo. Ya explicaré por qué yo creo que no son lo mismo, aunque reconozco que hay un elemento de gusto particular en ello. Solo para empezar, y dados los lenguajes de los que estamos hablando (C# y Java), la herencia múltiple no existe y te queda usar interfaces. Pero yo no uso apenas herencia múltiple.

Con este plan, lo primero que hago siempre es escribir todas las interfaces de todos los elementos que creo son susceptibles de presentar algún cambio y de las entidades arquitectónicamente relevantes. Hablando un día con Esteban [HCoder.org], en nuestros años de universidad, usó una de las mejores metáforas que he escuchado hasta la fecha para explicar la flexibilidad que se puede conseguir con un buen diseño orientado a objetos. Ponía como ejemplo las articulaciones del cuerpo humano y que un buen diseño tenía que ser articulable para ganar en flexibilidad. Imaginen un brazo que únicamente tuviera un hueso que uniese el hombro con la mano pero respetase la longitud total. Para que se hagan una idea, intenten rascarse la nariz con el brazo recto (como si estuviera escayolado de extremo a extremo) sin doblar el codo. Con suerte, dada la longitud, con un brazo tan poco flexible llegarían a rascarse el culo, pero habría que pedirle a otro que te rascara la nariz.

A la metáfora anterior yo le suelo añadir otra que me contó otro amigo, Juan Manuel, cuando apenas teníamos dieciséis o dieciocho años y que, por las edades de las que hablo, no tenía nada que ver con la programación ni la orientación a objetos y sí mucho con los desamores de juventud. Era esa del roble, el bambú y la tormenta. Internet está plagada de adaptaciones de la parábola al alcance de un googlazo (acabo de acuñar un nuevo término), así que no necesito profundizar más.

Cuando explico a alguien la importancia del diseño y de las decisiones tomadas, la conclusión es que el software (el código) supera mejor la adversidad (la tormenta) cuando es flexible. Esto significa que no necesariamente hay que recubrirlo de capas y de capas de robustez para que sea impenetrable. Simplemente hay que hacerlo crecer entre tormentas frecuentes (los tests unitarios) para que gane en sabiduría y fortaleza. La sabiduría es el conocimiento destilado hasta alcanzar la utilidad imprescindible para ser aplicable, desproveyéndolo de toda terquedad e información innecesaria. El código fuente no deja de ser una forma de conocimiento tácito (un modelo escrito de cómo funcionan las cosas), por lo que un código fuertemente testado es un código que desarrolla los elementos justos y necesarios, y no más, para funcionar en condiciones adversas. Por eso mismo empiezo siempre escribiendo únicamente las interfaces, que meto todas —a modo de licencia poética de inspiración meyeriana— dentro de un subdirectorio que denomino "contratos". Todas esas interfaces representan las articulaciones, los elementos funcionales susceptibles de ser ampliados, modificados o eliminados en el transcurrir de las siguientes decisiones (también en 2002 comencé a abrazar la filosofía ágil [@ Wikipedia]). No escribo nada sin tener la interfaz antes. Matizando lo que defiende la idea del «Test First» [@ Wikipedia], yo hago «Interface first, then Test… Or not». Pero eso es otra historia.

[Continuará …]

4 comentarios:

Esteban Manchado dijo...

Coño, se me había olvidado :-D

Si no recuerdo mal, la metáfora incluía que si no tenías flexibilidad, y forzabas (en tu ejemplo) a rascarte la nariz... te cargabas el código/brazo.

¿Y cómo es eso de que a veces no escribes pruebas? ¡! :-D

Uno+Cero dijo...

Esa parte no la recuerdo. Será que has repetido muchas veces la misma instrucción y le habrás añadido cosas. A otros. Ahí justo, donde tú dices "forzar", yo metía la del roble y el bambú. En cualquier caso, lo que yo suelo decir es algo así como «cuando quieres darte cuenta, y enmendar el error de no articularlo (principalmente porque no tuviste la precaución de meter test que te indicasen qué era lo conveniente), intentarás meterle una articulación ortopédica. Ahí se aplica aquel famoso dicho de la fortaleza de una cadena es la de su eslabón más débil. Al primer contratiempo, te las verás y desearás para que el brazo no se te caiga en dos trozos». Es todo un discurso que tengo superensayado :-)

Rara vez prescindo de pruebas. Lo que yo digo es que nunca es lo primero que hago (que son los interfaces o el «mapa», como también lo llamo a veces). Después de eso, a veces hago un primer tanteo de funcionamiento del código a ver si los contratos se ajustan a lo que busco. A partir de ahí ya sí que voy haciendo código y tests, de forma casi indistinguible.

Luis dijo...

Una duda que yo tengo, la gente que ha trabajado en .Net cuando pasa a Java suelen decir que es mucho más lento desarrollar en Java, que .Net te da un montón de cosas ya hechas que en Java te tienes que hacer tu a base de picar código.

¿Es cierto, de verdad se desarrolla más lento en Java?

Uno+Cero dijo...

Luis, por simplificar, ya que cada caso particular es un universo en sí mismo, diría que hay (al menos) cuatro niveles que solventar cuando cambias de lenguaje:

1. Nivel sintáctico/semántico del lenguaje
2. Nivel de marco de trabajo y bibliotecas complementarias
3. Nivel de herramientas de trabajo
4. Nivel de administrador de entornos

Como digo, simplificando mucho, máxime cuando un mismo lenguaje de programación se puede usar para desarrollar tanto un servicio de sistema como una arquitectura descentralizada entre diferentes servidores usando diferentes tecnologías de invocación remota de objetos. Lo dicho, por simplificar, y centrándonos en un uso inespecífico de entrada, podemos decir que esos cuatro niveles son los que hay que "superar" para ser productivo en el nuevo lenguaje.

El primero es obvio. Se trata de la sintaxis y de las particularidades semánticas del lenguaje. Entre Java y .Net no hay apenas diferencias apreciables en el uso "normal". Tienen particularidades para usos concretos (idioms), pero no creo que lleve mucho dominarlos. Este ni me preocupa.

El segundo entraña un poco más de complejidad porque se supone que el lenguaje por sí mismo no hace nada sin las bibliotecas (SDK) que lo acompañan. Otra vez más resulta rápido buscar qué clases/biblioteca sirven para hacer qué cuando se da el salto de .Net a Java (y viceversa). Cuando tengo una duda recurro a San Google y enseguida encuentro código que entiendo perfectamente y puedo adaptar a mis necesidades basándome en lo que conozco de .Net. Si tienes el bagaje suficiente en programación, en particular con .Net, lo mínimo de Java lo puedes conseguir en una semana y tomar "decisiones acertadas" a la hora de resolver un código particular.

Es a partir del tercero cuando se "sufre" un poco más. Si llevas mucho tiempo trabajando con un entorno de desarrollo, como puede ser Visual Studio, cuesta un rato pillar las particularidades de Eclipse, por ejemplo. Particularmente prefiero NetBeans, pues me recuerda mucho más a VS, pero en la empresa se usa Eclipse para todo, así que he tenido que adaptarme o morir. Te pasas un par de días despistado echando pestes a ver dónde se hace cada cosa y para qué pueden servir los trillones de opciones de configuración que tiene el entorno. Cuesta, como digo, pero acabas pillándole el tranquillo en un tiempo más o menos prudencial (yo acabé sintiéndome cómodo a los tres o cuatro días). Alcancé eso que llaman nivel competente consciente. A día de hoy estoy a un paso del nivel competente inconsciente.

El cuarto nivel es el más jodido de todos y depende muchísimo de para qué estés programando. Aquí sí es dónde yo he notado una diferencia abrumadora. Ya no tiene tanto que ver con el nivel de programación o del marco de trabajo, sino del entorno de explotación. Administrar JBoss puede ser un suplicio. Tomcat es otra castaña, por mucho que los de Java se lleven las manos a la cabeza cuando lean esto. Si has trabajado con IIS, con el que resulta muy sencillo hacer cualquier cosa, se nota mucho cuando intentas hacer un despliegue en los entornos mencionados antes y que, además, te funcione a la primera. Siempre te dejas algo y te pasas horas buscando el motivo por el que no te funcionaba.

El mayor problema que veo cuando uno tiene que cambiar de lenguaje es que intenta solventar todos esos niveles al mismo tiempo. Vamos, que te enfrentas a un problema que abarca todo el espectro y tienes que pensar holísticamente. Estás intentando descifrar por qué motivo no te funciona la concatenación de una cadena con un entero mientras intentas comprender por qué se tiene que usar la clase de contexto para esa invocación concreta al Servlet sufriendo lo que no está escrito para que el IDE te marque la línea donde deberías estar depurando y, para colmo, no tienes claro que el error jeroglificocríptico que te está lanzando JBoss sea por un problema de una invocación al método incorrecto o que se te haya pasado algo en el XML de configuración.