«No man can serve two masters» [1]. Una frase impactante tomada del Nuevo Testamento, cuya traducción contextual al español: «No se puede servir a dos señores, no se puede a Dios y al diablo», expresa la quizá irónica fantasía de hacer las cosas bien o mal, sin la inclusión de medias tintas. Me gusta abstraerme y considerar esta gran frase cuando pienso en el diseño de los sistemas: ¿Estamos haciendo las cosas realmente bien ó mal?, porque parcialmente bien o parcialmente mal no pareciera una respuesta acertada en un mundo donde por detrás solo hay matemática intangible. Pues aquí, en esta delgada e invisible línea paradójica, se divide la ingeniería del software entre ciencia y arte –debates a parte–.
Como desarrolladores de este concepto intangible, se puede experimentar todos los días la diferencia entre una buena y una mala programación de interfaces. Tal como expresa Gregory T. Brown en [2] :
Algunos módulos con los que trabajamos parecen hablar bien de nosotros, expresando claramente su papel en nuestro proyecto y en voz alta. Otros murmuran sin sentido y, a veces nos asustan de forma inesperada.
Por muchas razones, resulta válido pensar que un código puede ser tan «bueno» como su interfaz de programación (API) lo sea. Y es que una buena API proporciona lo que se necesita y en la forma que se necesita. Por el contrario: un «mal» diseño de API, envuelve el ámbito en un círculo vicioso donde el objetivo se vuelve hacer las cosas para que funcionen, esperando el día de «La temida Refactorización»; y es aquí, donde precisamente debemos elegir si estar con Dios o con el Diablo.
En el particular contexto de Ruby On Rails (RoR), Ruby como lenguaje de programación funcional y Orientado a Objetos, provee de un conjunto de herramientas con las que se pueden diseñar APIs de ensueño, pero son esas mismas herramientas las que pueden ocasionar caos y desorden, cuando se usan de forma errónea. Tal como explica Gregory Brown en [2], de entre las excepcionales características de Ruby, existen dos que deben considerarse a la hora del diseño de APIs: La Flexibilidad en los Argumentos y los Bloques de Código. Así, estas poderosas herramientas, combinadas con buenas prácticas pudieran resultar en una «buena» construcción de software.
Considérese el siguiente ejemplo de uso de bloques para simplificación de interfaces tomado de [2]:
Suponiendo que se está desarrollando una aplicación cliente-servidor para transferencia de mensajes y que el código luce algo como:
server = Server.new server.handle(/Entrada/i){"Entrada> #{Time.now}"} server.handle(/Log/i){"operacion #{Time.now}"} server.handle(/Salida(\w+)/){ |m| "Salida #{m[1]}!"} server.run
No resulta complicado advertir que la instancia de objeto «server» se está utilizando repetidas veces. Esto en principio pudiese considerarse como una señal de que aún existen oportunidades de mejora. De esta forma, se pudiese colocar el código más elegante a través del uso de bloques de la siguiente manera:
Server.run do handle(/Entrada/i) { "Entrada> #{Time.now}" } handle(/Log/i) {"operacion determinada #{Time.now}"} handle(/Salida (\w+)/) { |m| "Salida #{m[1]}!" } end
Dado que en Ruby es posible ejecutar un bloque sin necesidad de tener ámbito de instancia, la clase Server luciría así:
class Server # otros metodos anteriores def self.run(port=3333,&block) server = Server.new(port) server.instance_eval(&block) server.run end end
Esta implementación de clase es todo lo que se requiere para poner en marcha de forma elegante las operaciones antes mencionadas. De este ejemplo, además, se desprende una lista de recomendaciones que Brown propone a la hora de utilizar bloques como parte de interfaz de procedimientos:
- Cuando se crean clases de colecciones que requieran iterar, se puede escribir en la parte superior aprovechando el objeto Enumerable, en lugar de reinventar la rueda.
- Cuando se presentan capas de código compartido que difieren sólo en la capa del medio, se puede crear un helper que contenga un bloque para evitar duplicados.
- Usando combinación de bloques, a través del método instance_eval(), se pueden ejecutar bloques sin necesidad de contemplar el contexto de instancia de objetos, lo cual pudiera favorecer a la construcción de interfaces personalizadas y flexibles.
Así como las prácticas sugeridas en este ejemplo, existen infinidad de ellas que se pueden aplicar cuando se diseñan APIs; lo importante es en todo momento, situarse en el contexto del problema, hacer uso de todas las herramientas provistas por Ruby –en su elegante combinación entre programación funcional y orientada a objetos–, y por supuesto, evaluar siempre el costo-beneficio de las soluciones para evitar los extremos. No se puede estar con Dios y con el Diablo.
Referencias
[1] Santa Biblia
[2] Gregory T. Brown.(2009).Ruby Best Practices. O’ Reilly