20 de octubre de 2009

La Importancia del Lenguaje

Aprovechando la inercia creada por los últimos posts de Alcides, quiero dedicar una pequeña nota a la importancia de los lenguajes de programación.

Existe una hipótesis en lingüística y psicología denominada como el Principio de Relatividad Lingüística, también llamada la Hipótesis Saphir-Whorf. Esta hipótesis sostiene que la lengua que se habla tiene una influencia en la manera de pensar, conceptualizar y en la forma de experimentar el mundo.

Esta es una hipótesis muy debatida, ya que por un lado, hay proponentes de esta hipótesis que aseguran que el lenguaje condiciona completamente al pensamiento, mientras que por otro lado, se afirma que el pensamiento es anterior al lenguaje y no al revés. Ahora, dejemos que los científicos hagan su trabajo y últimamente concluyan si fue primero la gallina o el huevo. Lo que si es innegable es que la lengua que usamos para comunicarnos si tiene un impacto en la manera en que componemos e hilamos nuestros pensamientos.

No sé si a ti amable lector, que eres bilingüe o incluso políglota, te ha pasado que al pensar sobre cierto tema particular te sientes más cómodo al hacerlo en una lengua que en otra. A mi en lo particular, me ciento más cómodo pensando sobre programación en ingles que en español. No es malinchismo ni nada que se le parezca. Simplemente que cuando comenzaba a aprender sobre el tema, eran relativamente pocos los libros y revistas que había disponibles en español (ni hablar que algunas traducciones eran de plano pésimas, pero esa es otra historia), por lo cual me vi obligado a satisfacer mi curiosidad y mis necesidades académicas con publicaciones escritas en la lengua de Shakespeare.

Ahora bien, ¿alguna vez nos hemos puesto a pensar en el impacto que tiene el Lenguaje de Programación que usamos para nuestro trabajo diario en la efectividad con la que desempeñamos el mismo? En otras palabras, el lenguaje que estoy usando en mi proyecto actual ¿Ayuda o inhibe el proceso mental que me permite hallar respuestas a los problemas que enfrento el día de hoy?

Debemos recordar que si bien todos los lenguajes de programación son, de hecho Equivalentes Turing, eso no significa que todos sean igualmente adecuados para cada tarea.

Por ejemplo, hay muchos filósofos e intelectuales que han comentado que los mejores lenguajes para el pensamiento son el latín, el griego y el alemán (…) Es curioso notar que a pesar de ser la lengua más hablada del mundo, nadie ha afirmado jamás que el ingles sea una buena lengua para el pensamiento. Pensemos en ello un momento.

Recordemos que el desarrollo de software es una actividad eminentemente intelectual. Es quizás, junto con la poesía y otras formas de creación literaria, una de las actividades más puramente intelectuales en las que se puede embarcar el ser humano. Nuestro trabajo consiste 80% o un 90% en pensar, y después un 10% o un 20% en otras cosas como teclear, etc.

Entonces, ¿hay acaso lenguajes que son mejores para el pensamiento que otros?, ¿cuales? y por otro lado, el hecho de que la mayor parte de los proyectos, libros y artículos de revistas se escriban en Visual Basic, C# y Java ¿no es razón suficiente para concentrar mis esfuerzos en esos lenguajes?

De hecho, hay lenguajes que son mejores para el pensamiento que otros: smalltalk, lisp y posiblemente ruby, son algunos de ellos. Aunque en muchos aspectos estos lenguajes difieren enormemente entre sí (como el griego y el alemán también lo hacen), tienen una característica que a mi modo de ver es esencial: son lenguajes tersos.  ¿A qué me refiero con eso? A que:

Permiten un contenido semántico rico con un mínimo de elementos sintácticos.

Esto no significa otra cosa que el hecho de que estos lenguajes permiten expresar una gran cantidad de conceptos utilizando muy pocas palabras reservadas, puntuación, operadores, etcétera.

Ahora bien, no estoy afirmando que todos debiéramos renunciar a nuestros trabajos actuales o ponernos en huelga para exigir que los proyectos se desarrollen exclusivamente en lisp. Sería fantástico que todos tuviéramos por lo menos una vez la experiencia de desarrollar un proyecto grande en estos lenguajes. Sin embargo, esto no siempre es posible. Desgraciadamente, aunque no es un buen lenguaje para el pensamiento, el ingles sigue siendo la lengua más hablada en el mundo, y una gran cantidad de ciencia, tecnología e incluso literatura se realizan todos los días utilizando esa lengua.

¿Entonces porqué aprender griego clásico o smalltalk? Porque nos dan más y mejores herramientas para pensar. En varias ocasiones me he tenido que romper la cabeza tratando de darle la vuelta a un problema que parece que no tiene solución en Visual Basic o en Javascript. Y en muchas de esas ocasiones lo que me ha salvado el día es pensar “¿Cómo lo resolverías si el lenguaje no fuera una limitante?”… Usualmente recuerdo una técnica o idea que aprendí en Python o en Lisp que me permiten destrabar mi proceso mental y llegar a una solución práctica.

Alguna vez leí que Google contrata mejores programadores que su competencia porque la mayoría de sus programadores programa en dos o tres lenguajes distintos que no son necesariamente los lenguajes más comerciales del momento.

Esto me lleva a un corolario de la hipótesis Saphir-Whorf con la que comencé esta nota:

El lenguaje de programación no es un condicionante para escribir código malo.

Me gustaría leer sus comentarios.

Saludos

16 de octubre de 2009

El Paradigma OOP (Parte 2)

En la entrada anterior finalizé haciendo referencia al lenguaje de programación SmallTalk. En esta entrada continuaré con el tema que venía desarrollando.


Así como Pascal fué en su momento, el paradigma de la programación estructurada ( Algoritmos + Estructuras de Datos = Programas). Smalltalk ha sido hasta ahora el paradigma de la programación orientada a objetos.


Otros lenguajes tales como C++, Java y Object Pascal, no son realmente orientados a objetos en toda la extensión de la palabra; son mas bien lenguajes híbridos, lenguajes procedurales con algunas extensiones que permiten programar con objetos, pero nada que ver con la orientación a objetos pura y real de Smalltalk, en donde incluso un bloque de código es un objeto, y como tal tiene un protocolo de mensajes definido. Vaya, incluso lo que en los lenguajes procedurales conocemos como "estructuras de control", en Smalltalk son mensajes y sus parámetros son bloques de código. Nada que ver con los lenguajes procedurales híbridos, esto en sí mismo representa una forma radicalmente distinta de ver el código.


Así pues, fué que reconociendo mi ignorancia y por tanto mis carencias y necesidades, decidí que fuera precisamente Smalltalk quien me sacara del paradigma de la programación procedural y me enseñara el verdadero paradigma de la programación orientada a objetos.


Comencé entonces a hacer mis primeros pininos en Smalltalk y me sorprendió muy gratamente.
De entrada transcribí mi programa mascota "NomEdad" un programita sumamente simple que pide una cadena y un número y despliega como resultado una suma en un letrero.


La versión en Java de este programita es la siguiente:

/* nomedad.java */
import java.io.*;
class nomedad {
    public static void main (String[] args) throws IOException {
        Integer   edad;
        String   strnombre;
        BufferedReader   brin;
        int   n;
        /* Inicialización */
        brin = new BufferedReader(new
InputStreamReader(System.in));
        /* Comienza rutina principal */
        System.out.print("Hola, ¿Cuál es tu nombre? ");
        strnombre = new String(brin.readLine());
        System.out.print("¿En que año naciste? ");
        edad = new Integer(brin.readLine());
        n = 2006 - edad.intValue();
        System.out.print("Hola, ");
        System.out.println(strnombre);
        System.out.print("En el año 2006 tendrás ");
        System.out.print(n);
        System.out.println(" años");
    }
}

Pues bien este programita que al escribirlo en Java por primera vez me causó algunos dolores de cabeza debido a las sutilezas del lenguaje, que copia la sintaxis de C pero no necesariamente su semántica, debido a que las variables se crean pero no se inicializan, debido a que el Standard Output si puede usarse directamente pero el Standard Input no, debido a la sintaxis para declarar excepciones y debido a otras linduras tales como el kilométrico import ...bla bla bla... class ... bla bla ba ... public static void main ( String [] args ) throws ...bla bla bla... etc, etc. (Años después sufriría yo igual o peor con Visual Basic .NET y toda su verborrea).

Este mismo programa en Smalltalk ( Squeak) lo pude escribir en poco más de cinco líneas de código claro, legible y entendible:

"nomedad.st"
| nombre edad |
    nombre := FillInTheBlank request:'Hola, ¿Cual es tu nombre?'.
    edad := FillInTheBlank request:'¿En que año naciste?'.
    edad := 2006 - (edad asInteger).
    Transcript show:'Hola '; show: nombre; cr.
    Transcript show:'En el año 2006 tendras '; show: edad; show:'
años'; cr.

¡¡Que gran diferencia!! ¡¡Solo seis líneas de código, claro y entendible !!.

Esto a pesar de no ser necesariamente Smalltalk idiomático.


Es impresionante saber que en 1980, Smalltalk ya tenía su propia máquina virtual, su propio byte-code, compilador JIT, entorno de programación IDE, ambiente gráfico, implementación del patrón de diseño Model-View-Controller (MVC), entre otras cosas. Elementos que mas de diez años después se venderían como "innovaciones" por parte de Sun con Java y más de veinte años después por parte de Micro$oft con .NET (quien por cierto apenas está logrando cuajar bien su implementación del MVC).


Esta primera aproximación nos muestra ya algunas características del lenguaje Smalltalk que son importantes para entender el paradigma que nos ocupa de las cuales seguiremos escribiendo en nuestro siguiente post.


15 de octubre de 2009

El paradigma de programación orientada a objetos

¿¿Paradig... queee?? diríamos algunos. Y aquí comienza precisamente el viaje que menciona Alfredo en su introducción a este blog ... :-)

Paradigma en su sentido original tradicional significa «modelo / ejemplo» y, en su sentido más amplio contemporáneo implica «cosmovisión», es decir:

La forma de entender y percibir el mundo y a nosotros mismos.

Y es que muchos de nosotros no solo ignoramos el significado real del término, sino que ni si quera nos percatamos de su existencia en la vida cotidiana y mucho menos nos enteramos que existen otros paradigmas diferentes a los que conocemos, vaya: otras formas de ver y entender el mundo (otros modelos y puntos de vista igualmente válidos) además de la nuestra.

En mi experiencia personal, esto sucedió allá por el verano del año 2002, en aquel entonces me sentía orgulloso de haber programado mi último proyecto de sistemas totalmente en C++ (¡válgame Dios! Tal era mi ignorancia). Fue en aquel año cuando Alfredo me hizo favor de compartirme algo sobre el Refactoring y los Patrones de Diseño (yo la verdad en ese tiempo estaba mucho más dedicado al estudio y aprendizaje de otros sistemas operativos tales como GNU/Linux, FreeBSD y MacOS). Al comenzar a investigar mas sobre el tema, me di cuenta de un par de cosas que no había notado antes.

  • Primero: Me di cuenta que no entendía realmente bien la terminología Orientada a Objetos (OO), a pesar de haberla conocido y usado durante varios años (y de haber programado en C++ mi último sistema)
    (P.ej. el caso del tan llevado y traído concepto de polimorfismo, que cada quien entiende a su manera aún en los libros «de texto» sobre la materia, pero bueno ese es otro tema...)

  • Segundo: Me di cuenta que no solamente no entendía bien la terminología sino que ignoraba total y absolutamente (vaya no tenía ni la menor idea) los conceptos reales detrás de la misma.
    En otras palabras me di cuenta de que estaba yo frente algo totalmente nuevo, diferente y desconocido para mí hasta ese momento y que además no se parecía en nada a todo lo que yo conocía hasta ese entonces y tampoco se parecía en nada a lo que me habían explicado ni en la escuela ni a lo que venía escrito en los libros de programación «orientada a objetos».

Fué entonces cuando experimenté lo que ahora sé que se llama un «cambio de paradigma» (o « paradigm shift» como le dicen en inglés)… Ese famoso «A-ha!» que se menciona en la introducción de este blog.

Fue entonces cuando, reconociendo mi supina ignorancia en la materia, me dí a la tarea de comenzar a aprender realmente lo que significaba este paradigma de programación que tal vez sea (por lo menos en México) el mas malinterpretado de todos; para lo cual, en lugar de recurrir a los lugares y libros de texto comunes (C++, Java, Object-Pascal, etc, de hecho tenía y había leído ya varios de ellos), decidí ir a la raíz del asunto y remitirme a las fuentes originales.

Resulta bastante notorio y algo sumamente indicativo que de una misma comunidad hayan surgido el Refactoring, los Patrones de Diseño, las Pruebas Unitarias, los Métodos Agiles, y en los 70's las interfaces gráficas, los IDE's y las máquinas virtuales. Y que además varios de sus miembros sean distinguidas autoridades en la materia ( Kent Beck, Ward Cunningham, Martin Fowler, Ralph Johnson, Joseph Yoder, et al, entre muchos otros)

Y me estoy refiriendo precisamente a la comunidad creada alrededor de uno de los mejores lenguajes de programación que se hayan inventado jamás: SMALLTALK.


14 de octubre de 2009

El antipatrón flecha y el idioma "Dí, no preguntes"

Porque a un objeto nunca se le deben preguntar las cosas... ¡y menos sobre sus partes íntimas!

Hace algún tiempo tenía una conversación con mi amigo Alcides, respecto a unos métodos que él estaba implementando para la capa de datos de un proyecto en el que estaba trabajando. De inmediato noté una configuración familiar: el antipatrón flecha.

Básicamente lo que vemos en un antipatrón flecha es algo así:

   if foo then begin
     if bar then begin
       if baz then begin
         DoSomething()
       end{if}
     end{if}
   end;{if}

Como pueden ver, la disposición del código es algo parecido a la forma de una flecha y de ahí su nombre.

Ahora bien, ¿porqué se considera un antipatrón? Hay varias razones para ello. En primer lugar, obscurece el propósito del código. Es realmente difícil entender que es lo que se está tratando de hacer en medio de esa maraña de if's anidados (si piensan que esto no esta tan mal, agreguen los else's correspondientes). Lo que es más, los seres humanos solo tenemos una capacidad limitada para seguir mentalmente los detalles de lo que estamos haciendo. Por lo general, más allá de 3 niveles de anidación las cosas se vuelven muy difíciles de seguir.

Las causas de que este patrón son muchas y variadas:

  • Seguir ciegamente la regla de "un solo punto de entrada y un solo punto de salida por función".
  • Combinar condiciones complejas y ciclos.
  • Lenguajes de programación sin evaluación booleana por circuito corto.
  • Programadores que no saben que significa el punto anterior... ejem... but I digress...
  • Simple y llana "cultura".

No voy a entrar en una controversia teológica sobre el punto uno, ya que para muchas personas es tan intocable como la divinidad de Elvis Presley.

En código escrito en lenguajes estructurados (sin soporte para objetos), este código es muy común y realmente es difícil llegar a una alternativa para el mismo, fuera de la factorización continua y cuidadosa. Sin embargo, una de las causas de raíz por las que siguen surgiendo incluso en código orientado a objetos, es porque seguimos pensando en términos estructurados.

En lenguajes estructurados, normalmente pensamos de la siguiente manera: llamar-función, obtener-resultado, evaluar-resultado, decisión. Esto está bien en un modelo que favorece el control centralizado de todo lo que sucede en mi programa, sin embargo no están adecuado para un modelo orientado a objetos.

Cuando pensamos en objetos, debemos recordar que estos forman una "red" de entidades que colaboran para lograr un objetivo. Cuando un objeto necesita realizar una acción y para ello requiere del servicio de otro objeto, simplemente va y le pide a aquel objeto que realice la acción necesaria de su parte.

Esto es similar a lo que podría suceder en una oficina cualquiera:

  • Si el jefe es una persona que anima a sus subalternos a tomar responsabilidad de su trabajo, es más probable que delegue en su equipo las actividades para las que están mejor calificados, dejando libre tiempo para que él se concentre en la visión más amplia de las cosas y la planeación estratégica.
  • En cambio, si el jefe es del tipo "controlador", querrá seguir paso a paso cada una de las acciones que llevan a cabo sus empleados, literalmente "respirando sobre sus hombros", para "asegurarse que no cometan errores".

Ok con la nota social, pero ¿Qué tiene que ver eso con los objetos?. Bueno, veamos un caso típico.

Cuando programamos operaciones en una base de datos, es común el manejo de transacciones lógicas, como por ejemplo, al guardar un pedido, se crea una entrada en la tabla de pedidos y una en la tabla de detalle por cada elemento en el pedido. Si por alguna razón alguna de las partes no puede guardarse, toda la transacción debiera abortarse. Ok, una forma típica de codificar esto es:

Conexion.Begin();
...
if Orden.GuardaNueva() then begin
  if Orden.GuardaDetalle() then begin
    if Cliente.IncrementaSaldo(Order.Total()) then begin
      Conexion.Commit()
    end{if}
    else begin
      MensajeError := "No se pudo actualizar el saldo del" +
                      " cliente";
      Conexion.Rollback()
    end{else}
  end{if}
  else begin
    MensajeError := "No se pudo guardar el detalle de la" +
                    " orden";
    Conexion.Rollback()
  end{else}
end;{if}
else begin
  MensajeError := "No se pudo crear la orden";
  Conexion.Rollback()
end{else}

Nada bonito, ¿verdad? Sin embargo, lo de menos es la apariencia. Estas cosas tienen la pésima costumbre de crecer. Es difícil seguir la lógica. Y ¿qué tal si falla la primera condición? ¡Miren hasta donde está su else!

Una opción puede ser aplicar el consejo de Brian Kernighan en The Practice of Programming e invertir el sentido de los if's y unirlos mediante else's:

Conexion.Begin();
...
if not Orden.GuardaNueva() then begin
  MensajeError := "No se pudo actualizar el saldo del" + 
                  "cliente";
  Conexion.Rollback()
end{if}
else if not Orden.GuardaDetalle() then begin
  MensajeError := "No se pudo guardar el detalle de la" +
                  "orden";
  Conexion.Rollback()
end{if}
else if not Cliente.IncrementaSaldo(Order.Total()) then begin
  MensajeError := "No se pudo crear la orden";
  Conexion.Rollback()
end;{if}
else begin
  Conexion.Commit()
end{if}

esto es definitivamente una mejora. Sin embargo, ¿qué pasa si hacemos esto mismo a la usanza de la POO? Tendremos lo siguiente:

Conexion.Begin();
...
Orden.GuardaNueva();
Orden.GuardaDetalle();
Cliente.IncrementaSaldo(Order.Total());
Conexion.Commit();

Esta es la esencia del idioma "tell, don't ask". No necesitamos estar micro-administrando a cada objeto que participa en nuestro código. No debemos hacerlo.

Está bien usar funciones y propiedades para obtener el estado de un objeto, siempre y cuando no se utilize el resultado para tomar decisiones «fuera» de dicho objeto. Cualquier decisión basada enteramente en el estado de un objeto debe hacerse «dentro» del objeto en sí.

Cada objeto tiene sus propias responsabilidades, incluyendo la de verificar que las cosas hayan salido bien y notificar en caso contrario—la forma en que normalmente se hace esto es mediante el uso de “signals” o excepciones, pero eso es material de otro post.

Saludos