Desaprendiendo

12May08

La historia de cómo llega un nuevo feed al blogroll de uno no siempre se recuerda. No es el caso del blog de un tipo llamado Reg Braithwaite:

Durante la última RailsConf Hispana tuvimos la suerte de contar con Obie Fernandez. En su keynote citó un post titulado algo así como: ¿Y si los modismos y lenguajes potentes solo funcionan con equipos pequeños?

[...] Toda nuestra experiencia en los últimos sesenta años sugiere que la productividad se desploma según el tamaño del equipo aumenta. Así que si lo que quieres es más código de un equipo más grande, tienes que invertir intensamente en maneras de extraer valor de gente improductiva en entornos improductivos.

Pero, ¿qué hay de la otra dirección? Si todo lo que sabemos acerca de los equipos sugiere que los más pequeños tienen la mayor productividad, ¿no sería una elección válida invertir en herramientas y procesos especialmente afinados para maximizar la productividad de los equipos pequeños? [...]

El caso es que aunque Obie mencionó al autor del post no me quedé con el nombre, así que unos días después le envié un correo preguntándole acerca del mismo. Obie, muy amablemente, me remitió el link al post original y así fue como este blog engrosó mi ya abultado blogroll.

El blog es absolutamente recomendable, bitbybit — el hermano pequeño de este blog — ya lo ha referenciado en varias ocasiones.

Al tema…

Me interesa ahora una de sus últimas entradas en la que se refiere a cómo la experiencia se convierte en un impedimento a la hora de aprender. A medida que uno acumula conocimientos, todo nuevo conocimiento es contrastado con los ya existentes y si no existe coincidencia el nuevo tiende a ser rechazado. Esto explica que una persona joven, vacía de conocimientos, abrace mucho más fácilmente nuevas ideas al no tener nada con que contrastarlas, a diferencia de personas más experimentadas cuyos conocimientos, paradójicamente, se convierten en un lastre a la hora de aprender cosas nuevas.

No es la primera ni la segunda vez que leo acerca del desprendizaje como mecanismo casi más importante que el propio aprendizaje en el proceso de crecimiento personal. Por ejemplo, se me ocurre que, en lo que a nosotros respecta, pasar de lenguajes estáticos a lenguajes dinámicos es mucho más duro que aprender un lenguage dinámico por primera vez. El conocimiento previo no ayuda, más bien dificulta el aprendizaje en este caso. Seguramente el secreto esté en ser capaz de separar lo realmente valioso de lo que no lo es, ignorar el conocimiento de lo que ya no haga falta, por muy útil que resultase en el pasado, por mucho cariño que le hayamos cogido o por muy confortables que nos haga sentir, para poder seguir avanzando y que la experiencia se convierta realmente en un grado.

Cuando por fin uno consigue que un nueva idea forme parte de una base de conocimientos que inicialmente la rechazaba, cosa que suele ser dura de conseguir, entonces el cerebro nos premia con una grata sensación, yo diría que incluso adictiva, y sentimos que somos una persona distinta, evolucionada.

Según cuentan Dave Thomas y Andy Hunt en The Pragmatic Programmer, durante la alcaldía de Rudolph Giuliani en Nueva York un serie de estudios sobre la delincuencia en la ciudad indicaban que existía una relación entre los barrios con más ventanas rotas y aquellos con un mayor índice de delincuencia. Estos estudios sostenían que dejar una ventana rota sin arreglar tendía a atraer nuevos desperfectos a su alrededor: más ventanas rotas, basuras sin recoger, pintadas y graffitis… y finalmente un incremento de la delincuencia en la zona.

Giuliani, además de ser el alcalde de Nueva York durante el 11-S, es conocido por ser el alcalde que limpió sus calles de prostitutas, vagabundos y delincuentes. Entre las medidas que se siguieron, siguiendo las teorías de este tipo de estudios, se encuentran la de reparar los desperfectos urbanos visibles en el menor tiempo posible, e intensificar la vigiliancia y ser más estrictos en cuanto a los delitos menores, como no pagar el billete de metro u orinar en la vía pública.

Cuando en su momento hablaba acerca de cómo el código bonito atrae al código bonito estaba presentando la argumentación inversa a la que los PragProgs nos proponen con la metáfora de las ventanas rotas. Dejar ventanas rotas en tu código no solo supone aumentar la deuda técnica sino tambien aumentar las probabilidades de que ese código empeore aún más en el futuro. Supongo que no es tanto una cuestión intrínseca al desarrollo de software, sino más bien relacionada con la psicología del programador: un código lleno de ventanas rotas, basura y pintadas motiva muy poco a la hora de mantenerlo; probablemente no nos importe dejar algún desperfectillo más.

Como Giuliani, podemos intentar mantener nuestro código libre pequeños “desperfectos”, atajando los “delitos menores” cuando todavía son manejables, y así evitar que la entropía nos acabe complicando la vida más adelante.

P.D. Es que hoy me he pasado el día arreglando ventanas ;)

Rompo el silencio con un post rápido para anunciar que ya ha pasado de beta privada a beta pública el hermano pequeño de este blog:

http://twitter.com/bitbybit

Con minientradas como…

- Una gema para bajar y aplicar parches del trac de rails solamente indicando el número de ticket y así poder dar +1s a tus amiguitos (vía fxn en #rails-contrib)

- El secreto mejor guardado de la distribución de git: soporte de autocompletado y prompt en el shell (vía ernesto_jimenez)

...y más cosas chulis que no necesiten un post completo aquí pero que esten relacionadas, cómo no, con el desarrollo de software en la postmodernidad y la complejidad de mantener las cosas simples.

Aunque Demeter ya ha sido mencionado aquí, revisitar estas cosas de vez en cuando siempre viene bien. Lo que nos dice la ley de Demeter es que desde un método debemos evitar invocar métodos que no sean del mismo objeto o de sus “amigos” inmediatos. Así, un ejemplo de violación de esta regla sería un código como este:

@course.school.owner.country.currency_code

La razón por la que este código es problemático es que, al invocarlo, estamos suponiendo la existencia de un árbol de objetos concreto. Estamos suponiendo que un curso tiene una escuela, que la escuela tiene un propietario, que este pertence a un país, y que, finalmente, el país tendrá asociada una moneda. Cualquier cambio en esta premisa supondrá que esto deje de funcionar. A este fenómeno se le llama acoplamiento, y si no se controla adecuadamente puede dar lugar a un código menos mantenible de lo deseable. En este tipo de situaciones, introducir un pequeño cambio en una clase suele suponer tener que hacer pequeños retoques en un montón de clases, muchas de las cuales tendrán poco o nada que ver con el cambio introducido.

Cualquier cadena de llamadas como la del ejemplo debe advertirnos de un posible problema con Demeter. Otra mal olor al respecto, si se testea usando mocks, es que se tengan que definir muchas expectativas en cadena.

Para evitar este problema, en Rails podemos usar el método delegate, que nos lo pone muy fácil. Nuestro modelo quedaría más o menos así:

class Course
  belongs_to :school
  delegate :currency_code, :to => :school
  ...
 
class School
  belongs_to :owner, :class => User
  delegate :currency_code, :to => :owner
  ...
 
class User
  belongs_to :country
  delegate :currency_code, :to => :country
  ...

Con esto podríamos cambiar nuestro código original por este…

@course.currency_code

...y con ello minimizar el impacto de futuros cambios en cualquier parte del modelo.

Camping es un framework web para Ruby. Le dicen minimalista por su extraordiaria sencillez. De hecho, seguramente no sirva para hacer aplicaciones webs “serias” pero, justo por eso, es realmente divertido. No en vano su autor es Why the lucky stiff, un tipo bien conocido dentro de la comunidad Ruby entre otras cosas por ser autor de la guía de un lenguage de programación más delirante de la historia, probablemente.

En esta presentación de la RubyConf 2007 podéis encontrar una introducción a Camping. Sin embargo lo que me interesa de la charla es la pregunta que trata de responder y que da título a la misma: ¿Por qué Camping importa?. La pregunta es relevante desde el momento en que presentamos el framework como algo que seguramente no sirve para poner nada en producción. ¿Por qué importa la RubyConf? —si la RailsConf es más grande y tiene más éxito—, incluso, ¿por qué importa Ruby? —si todo el mundo sabe que con Java se puede solucionar cualquier problema:

Para alimentar al hacker que llevas dentro

Lo que me hace recordar la memorable cita de la peli La chaqueta metálica, de Kubrick:

Dentro de cada amarillo hay un americano luchando por salir!

Alimentar al hacker que llevo dentro seguramente no sea la razón que más le convenza a tu jefe cuando le propongas utilizar una tecnología o hacer las cosas de una manera determinada. Seguramente tendremos que buscar otros argumentos. Pero, no nos engañemos, a menudo esa es la principal motivación de los programadores a la hora de tomar decisiones. Así que no lo disimulemos más: Los programadores necesitamos alimentar al hacker que llevamos dentro. Porque nos estimula, nos motiva y, finalmente, nos hace productivos (y eso sí que les mola a los jefes)

Y es que cambiar el mundo es una tarea ardua, y de vez en cuando viene bien cargar las pilas. En este sentido, y de esto soy cada vez más consciente, algunas plataformas/lenguages/frameworks son más hackeables que otros y, por tanto, reportan mayores satisfacciones a nuestro inseparable compañero de fatigas. UNIX, no hay duda, es la plataforma hackeable por excelencia. De Ruby enamoran sus capacidades de metaprogramación.

Dentro de cada programador Microsoft hay un hacker luchando por salir ;)

Yo soy la prueba. Seguro que dentro de cada programador Java, pasa lo mismo. Amigos, dejadle salir de vez en cuando (antes de que os hagan jefes y sea demasiado tarde!). Es muy saluable.

Las organizaciones que comprenden esta realidad toman iniciativas al respecto, por ejemplo, permitiendo a sus empleados trabajar en proyectos alternativos o montando periódicamente jornadas de hacking.

Si no tienes la suerte de trabajar en uno de estos sitios tendrás que ocuparte tú mismo de ello. Y aquí os cedo la palabra: ¿Cuál ha sido vuestro último hacking? ¿Cuánto hace de eso?

¿El mío?, la semana pasada: un frontend web para git hecho en Camping. No llegará a ningún lado, pero yo y mi hacker pasamos un buen rato!

Shoulda es un plugin para hacer BDD en Rails. Se trata de una alternativa sencilla a RSpec que, a diferencia de este, no proporciona un framework completo sino una serie de helpers, macros y assertions sobre la librería estándar de testing de Ruby. Así, permite escribir tests especificar comportamientos, por ejemplo, de la siguiente manera:

class PostTest < Test::Unit::TestCase
  load_all_fixtures
 
  should_belong_to :user
  should_have_many :tags, :through => :taggings
 
  should_require_unique_attributes :title
  should_require_attributes :body, :message => /wtf/
  should_require_attributes :title
  should_only_allow_numeric_values_for :user_id
end

He robado el ejemplo de la misma página principal del plugin. El caso es que, para satisfacer el test, podríamos escribir una clase parecida a esta:

class Post < ActiveRecord::Base
  belongs_to :user
  has_many :tags, :through => :taggings
 
  validates_uniqueness_of :title
  validates_presence_of :body, :message => "wtf"
  validates_presence_of :title
  validates_numericability_of :user_id
end

No lo he llegado a testear, pero tiene pinta de que más o menos esa sería la implementación. La cuestión es que no dejo de tener la sensación de que hay algo incorrecto en esta manera de testear. El test se corresponde línea a línea con la implementación. Es lo mismo, pero dicho con otro lenguaje. Sería perfectamente posible generar la clase a partir del test. Con lo que llego a la conclusión de que lo único que hacemos siguiendo este enfoque es escribir dos veces lo mismo, duplicar la información y, por tanto, hacer menos mantenible nuestra aplicación.

Me inquieta especialmente pensar que Shoulda no es más que una refactorización sobre la manera estándar de testear en Rails. Por ejemplo:

class PostTest < Test::Unit::TestCase
  ...
  # Esto:
  should_require_attributes :title
 
  # Es lo mismo que esto:
  def test_should_require_title
    post = Post.new
    assert !post.valid?
    assert post.errors.on(:title)
  end

De hecho, con un poquito de metaprogramación podríamos facilmente escribir nuestra propia versión del should_require_attributes:

def should_require_attributes(attribute)
  define_method "test_should_require_#{attribute}" do
    klass = self.name.gsub(/Test$/, '').constantize
    object = klass.new
    assert !object.valid?
    assert object.errors.on(attribute)
  end
end

Y he aquí la angustiosa conclusión a la que llego, que me hace despertar con taquicardias por los noches: Si los tests declarativos de Shoulda me parecen incorrectos, y los tests declarativos de Shoulda son solo una refactorización y, por tanto, equivalentes funcionalmente a la manera normal de testear en Rails:

TODO LO DECLARATIVO DE MIS MODELOS NO TIENE SENTIDO SER TESTEADO A NIVEL DE UNIDAD

Dicho queda, en mayúsculas y negrita. A ver si alguien encuentra el error en mi razonamiento, el detalle que se me escapa, desmonta mi teoría, y por fin me devuelve el sosiego, la paz interior y el flow.

Llego, leyendo acerca de las motivaciones para refactorizar según Jay Fields, al interesante concepto de la deuda técnica. Se trata de una metáfora según la cual desarrollar de manera rápida y sucia, reduciendo la calidad, supone adquirir una deuda. Una deuda, como la financiera, cuyos intereses pagaremos irremediablemente en el futuro pero que, eventualmente, podremos disminuir de golpe refactorizando.

Es fácil encontrar paralelismos. Como con cualquier deuda, la técnica limita tu capacidad de acción. De manera constante y puntual tienes que acudir a tu cita con el pago y esto te impide llevar a cabo cambios importantes. Pierdes agilidad, en definitiva. Si nos limitamos a pagar los intereses, la deuda seguirá estando ahí de por vida.

Lo que me parece más interesante del enfoque es la idea de que en ocasiones la única manera de alcanzar un objetivo es adquirir una deuda. Y que vivir con una determinada deuda puede ser perfectamente posible si se obtiene un beneficio mayor. Hoy en día muchas operaciones financieras de éxito se basan en una gestión beneficiosa de alguna deuda (sin ir más lejos, comprarse una casa!)

Por tanto, desarrollar en determinadas circunstancias reduciendo la calidad puede resultar aceptable siempre y cuando seamos conscientes de la deuda que adquirimos y la mantengamos a raya refactorizando habitualmente. Esto no es fácil porque, a diferencia de otras deudas, la técnica no es cuantitativa, con lo que no es posible trazar nitidamente el umbral de deuda que vas a ser capaz manejar en el futuro sin que se te vaya de las manos.

Aunque, personalmente, me motiva más la idea de escribir código bonito que la de gestionar la deuda técnica ;)

Para Martin Fowler, lo explica en Refactoring: Improving the Design of Existing Code, los comentarios actúan en bastantes ocasiones como desodorante de los malos olores de nuestro código. A menudo, un código abundantemente comentado se trata de un código de mala calidad. Los comentarios, la necesidad de los comentarios, ponen de relieve que nuestro código no es suficientemente sencillo, claro o expresivo.

La recomendación propuesta es: Cada vez que sientas la necesidad de comentar tu código, primero trata de refactorizarlo de manera que el comentario se haga innecesario. La mayor parte de las veces es posible.

Por ejemplo, si tienes un método muy largo en el que se realizan varias cosas y te parece que estaría bien comentar qué hace cada parte del mismo, tu problema es que tienes un método muy largo con más de una responsabilidad. Seguramente puedas extraer varios métodos, y buscarles nombres que sean autoexplicativos. Así no necesitarás comentarios.

El problema de los comentarios, además, es que finalmente no son más que documentación manual con los problemas que ya hemos mencionado otras veces: tienden a quedar desactualizados, nunca son del todo precisos por expresarse en lenguaje natural, etc. Con lo que buena parte de los argumentos en contra de la documentación puede aplicarse también en este caso.

Si bien es cierto que un mal código comentado siempre será mejor que un mal código no comentado, parece que en estos casos comentar el código es la solución “parche”, ya que no soluciona realmente, solo enmascara.

En un mundo ideal, en el que escribimos solamente código bonito, utilizando lenguages expresivos y siguiendo el principio de la menor sorpresa, nuestro código (junto a sus tests o specs) deberían ser completamente autoexplicativos.

En el MundoRealTM a veces no tendremos más remedio que comentar, pero más que para explicar el qué o el cómo, para explicar el porqué de un código que pueda resultar confuso o inesperado. Y no pasa nada, los lenguages nos ofrecen los comentarios para utilizarlos. Pero no abusemos de ellos, tratemos de mejorar nuestro código primero y seguro que no los necesitaremos tanto.

[Referencia al apunte que ha inspirado este]

¿Por qué es divertida la programación? ¿Qué deleites pueden esperar quienes la practiquen?

Lo primero es el puro gozo de crear cosas. Al igual que el niño disfruta con sus castillos de arena, el adulto lo hace construyendo cosas, especialmente cosas que él mismo diseña. Yo creo que este goce es reflejo del goce de Dios en crear cosas, que se manifiesta en la diferencia de cada hoja, de cada copo de nieve.

Lo segundo es el placer de hacer cosas que sean útiles a otras personas. En el fondo, queremos que otros utilicen nuestro trabajo y que lo encuentren útil. En este sentido, la programación no es esencialmente diferente del cenicero de arcilla que el niño hace para “la oficina de Papá”.

Lo tercero es la fascinación de formar objetos complejos de partes móviles engranadas como rompecabezas y verlos funcionar en ciclos sutiles, agotando las consecuencias de los principios incorporados desde el principio. La programación contiene, llevada al extremo, toda la fascinación de la máquina de pinball o de la máquina de discos.

Lo cuarto es el placer del aprendizaje continuo, que nace de la naturaleza no repetitiva de la tarea. De una manera u otra el problema siempre es nuevo, y quien lo resuelve siempre aprende algo: a veces teórico, a veces práctico y a veces de ambos tipos.

Finalmente está el deleite de trabajar sobre un medio tan maleable. El programador, como el poeta, trabaja solo ligeramente apartado del puro material del pensamiento. Construye castillos en el aire, de aire, creando a través del ejercicio de la imaginación. Pocos medios para la creación son tan flexibles, tan fáciles de pulir y de trabajar, tan capaces de contener grandes estructuras conceptuales. [...]

Sin embargo la construcción del programa, a diferencia de las palabras del poeta, es real en el sentido de que se mueve y funciona, produciendo salidas visibles y separadas de la construcción en si misma. Imprime resultados, dibuja imágenes, produce sonidos, mueve brazos. La magia del mito y la leyenda se han hecho realidad en nuestros días. Uno teclea el conjuro adecuado y la pantalla cobra vida, mostrando cosas que nunca antes fueron ni pudieron ser.

La programación es divertida, por tanto, porque gratifica profundos anhelos creativos y deleita sensibilidades que compartimos con todos los hombres.

— Fred Brooks, The Mythical Man-Month (1975)

[NOTA: Aquí podéis encontrar el texto original en inglés. Cualquier mejora a la traducción será bienvenida]

Para poder, siguiendo el hilo del último post, reducir repentinamente el scope de una iteración llegado el caso será necesario haber desarrollado siguiendo un determinado orden.

Una opción tentadora es desarrollar dirigidos por la arquitectura. Es tentadora desde el punto de vista individual, ya que, “oye, ya que estoy con la base de datos, voy a crearme todas las tablas que necesito”, aunque sean de distintas funcionalidades porque “al final las voy a tener que hacer igual y ya que estoy…”. Cuando acabo con las tablas me ocupo de los modelos. Con la misma idea: “venga, me escribo todos los tests de mis modelos, así luego implementarlos es sencillo, además gracias a los test sé cuando he acabado de trabajar con el modelo. Así que lo hago y me olvido del modelo…”. Y así sucesivamente.

Desde el punto de vista del grupo también es tentador (y clásico) tener gente experta en cada capa. El super-DBA, amo y señor de la base de datos. El que se lleva mejor con el backend, muy familiarizado con el TDD y que deja un modelo testado y coherente. Luego tengo al crack del front-end, que es un figura tanto de css y html como de javascript y flash. Sin duda resulta muy atractiva la idea de la especialización, con los mejores profesionales en cada área parece que el producto final vaya a tener más calidad y además de manera más productiva.

El problema de dejar que la arquitectura dirija el desarrollo es que, en general, durante una iteración no se tiene nada acabado hasta el final. Esto hace imposible reducir el scope en caso de que lo necesitemos. Tampoco se tiene una idea precisa del avance de la iteración, no sabemos si llevamos retraso o no hasta el último momento, pues no hay ninguna funcionalidad acabada y no se dispone de ningún otro indicador al respecto. Por último, los problemas de integración tienden a aparecer solo al final, pues no es hasta entonces cuando realmente se puede probar la funcionalidad completa.

Seguir un enfoque donde sea la funcionalidad la que dirija el desarrollo quizá sea más exigente. Implementar una funcionalidad completa cada vez (incluso dividiendo cada funcionalidad en mini-funcionalidades) exige al desarrollador ser capaz de cambiar el chip continuamente entre capas y además manejarse comodamente en todas ellas. En realidad exige más del desarrollador pero, a cambio, le ofrece mucha más diversión. En todo caso, además de diversión al programador (cosa que sí tiene importancia y por eso se menciona), seguir un enfoque de este tipo ofrece otros beneficios. Estamos creando software funcional mucho más pronto. Esto nos permite tener una idea clara del avance de la iteración y permite identificar desviaciones muy pronto (algún día hablaremos del gráfico burndown). Tenemos más capacidad de reacción y en todo caso siempre software funcional, algo que entregar. El riesgo relacionado con la integración se limita ya que los problemas de integración se afrontan pronto y sobre el scope reducido de cada funcionalidad —y es que eso de la integración continua no consiste solo en instalarse el CruiseControl.

En definitiva, somos más flexibles y estamos más preparados para el cambio. ¿Que vamos a necesitar profesionales con más talento? Desde luego. ¿Acaso alguien duda de que se necesitan profesionales con mucho talento para desarrollar software en serio?