Saasbook-1 1 1-Es
Saasbook-1 1 1-Es
Saasbook-1 1 1-Es
Es un placer ver un libro de texto que hace énfasis en la producción de software real
y útil. También aplaudo el énfasis en obtener resultados desde las etapas iniciales del
proceso. Nada estimula más la moral y la actividad de los estudiantes.
Frederick P. Brooks, Jr., Premio Turing y autor de The Mythical Man-Month
Muy probablemente, preferiría los graduados de este programa a los de cualquier otro
que haya visto.
Brad Green, director de Ingeniería, Google Inc.
Muchos ingenieros software de C3 Energy informan sistemáticamente de que este libro y
su correspondiente curso online les han capacitado para alcanzar rápidamente la compe-
tencia en desarrollo SaaS. Recomiendo este libro y curso únicos a cualquiera que quiera
desarrollar o mejorar sus habilidades de programación SaaS.
Thomas M. Siebel, director ejecutivo, C3 Energy, y fundador y ex-director ejecutivo,
Siebel Systems (compañía líder de software de gestión de las relaciones con los clientes)
Cubre ampliamente y en profundidad todo lo que usted necesita para empezar en SaaS.
Vicente Cuellar, Director ejecutivo, Wave Crafters, Inc.
El libro llenó una laguna en mis conocimientos sobre computación en la nube y las clases
fueron fáciles de seguir. Quizás lo más emocionante fue escribir una aplicación en la
nube, subirla y desplegarla en Heroku.
Peter Englmaier, Universidad de Zürich, Suiza
Una excelente iniciación a Ruby, Rails y los enfoques orientados a pruebas. Cubre los
fundamentos con gran profundidad y experiencia, es la introducción perfecta al desarro-
llo web moderno. Debería ser un requisito para los nuevos ingenieros.
Stuart Corbishley, Clue Technologies/CloudSeed, Sudáfrica.
Un libro excelente que le hará estar preparado para desarrollar aplicaciones SaaS pro-
gresivamente en pocos días. Los screencasts y las secciones Pastebin tienen un valor
inestimable. Un enfoque muy práctico al desarrollo ágil de software. ¡Aprenderá técni-
cas de ingeniería software sin ni siquiera darse cuenta!
Rakhi Saxena, profesor titular, Universidad de Delhi, India
Los autores han conseguido una muy bienvenida yuxtaposición de teoría y práctica para
cualquier curso de Ingeniería Software de nivel inicial a avanzado. Por una parte,
cubren fundamentos clave de Ingeniería Software, incluyendo procesos de desarrollo,
ingeniería de requisitos, pruebas, arquitectura software, gestión de la configuración, im-
plementación y despliegue. Por otra, transmiten todo esto fundamentado en un enfoque
“del mundo real”, centrado en Ruby/Rails y su rico ecosistema de herramientas y téc-
nicas de desarrollo ágiles y orientadas a pruebas y comportamientos, con vía directa
al despliegue en la nube de software de calidad y que funciona. He utilizado la edición
beta del libro con mucho éxito en mi curso universitario avanzado de ingeniería software,
siendo un complemento maravilloso para mis clases y el proyecto en equipo.
Ingolf Krueger, catedrático, Universidad de California en San Diego
Un libro realmente bueno de introducción al desarrollo ágil práctico. Todo lo que usted
necesita recogido en un único libro con multitud de ejemplos prácticos.
Dmitrij Savicev, Sungard Front Arena, Suecia
Desarrollando software como servicio (SaaS): un
enfoque ágil utilizando computación en la nube
Primera edición, 1.1.1-es
20 Febrero 2015
Copyright 2014 Strawberry Canyon LLC. Todos los derechos reservados.
No puede reproducirse ninguna parte de este libro o sus materiales relacionados de
ninguna forma sin el consentimiento escrito del titular del copyright.
Versión: 1.1.1-es
Tanto el libro impreso como la versión electrónica se han preparado con LATEX, tex4ht
y scripts Ruby que usan Nokogiri (basada en libxml2) para procesar la salida XHTML y
HTTParty para mantener los URI de Pastebin y los screencasts actualizados automática-
mente en el texto. Los ficheros Makefile, de estilo y la mayoría de scripts necesarios están
disponibles bajo licencia BSD en http://github.com/armandofox/latex2ebook.
Las portadas y gráficos de todas las versiones fueron diseñados por Arthur Klepchukov.
Publisher’s Cataloging-in-Publication
i
ii
Acerca de los traductores
Raquel M. Crespo García es profesora titular interina en el Departamento de Ingeniería Telemática de
la Universidad Carlos III de Madrid, donde lleva desempeñando su actividad docente e investigadora
desde 2011. Previamente, trabajó como consultora en Daedalus - Data, Decisions, and Language,
S.A. Es doctora en Tecnologías de las Comunicaciones por la Universidad Carlos III de Madrid, inge-
niera de Telecomunicación por la Universidad Politécnica de Madrid y tiene el Certificado de Aptitud
Pedagógica por la Universidad Complutense de Madrid. Ha participado en 16 proyectos de investi-
gación internacionales, nacionales y regionales y publicado más de 30 artículos en revistas, congresos y
workshops internacionales, así como 2 capítulos de libros. Sus intereses de investigación son variados,
desde sistemas inteligentes y analítica del aprendizaje a revisión entre iguales o aprendizaje basado en
juegos, pero siempre girando en torno a la tecnología educativa y persiguiendo la mejora del proceso
de aprendizaje.
Juan Pedro Somolinos Pérez atesora más de 15 años de experiencia en el mundo del desarrollo de
software. Es ingeniero de Telecomunicación por la Universidad Politécnica de Madrid y certificado
MCSD de Microsoft. Ha trabajado como jefe de proyecto, consultor asociado de Microsoft y consultor
de desarrollo con distintas tecnologías, desarrollando software empresarial y aplicaciones web para
muy diversos sectores como banca, industria, administraciones públicas e IT. Desde hace 10 años viene
desarrollando su labor profesional en la investigación y desarrollo de sistemas de seguridad en Internet
y filtrado de contenidos, tanto SaaS (software como servicio) como suites de seguridad y aplicaciones
de control parental para PC y móviles.
iii
iv
Dedicatoria
v
vi
Resumen de contenidos
Prólogo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xiv
ix
6 Entorno de cliente SaaS: introducción a JavaScript 182
6.1 JavaScript: visión general . . . . . . . . . . . . . . . . . . . . . . . . . . . . 184
6.2 JavaScript en el lado cliente . . . . . . . . . . . . . . . . . . . . . . . . . . . 187
6.3 Funciones y constructores . . . . . . . . . . . . . . . . . . . . . . . . . . . . 193
6.4 DOM y jQuery . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 196
6.5 Eventos y funciones callback . . . . . . . . . . . . . . . . . . . . . . . . . . 199
6.6 AJAX: JavaScript asíncrono y XML . . . . . . . . . . . . . . . . . . . . . . 206
6.7 Pruebas de JavaScript y AJAX . . . . . . . . . . . . . . . . . . . . . . . . . 212
6.8 Aplicaciones de página única y API JSON . . . . . . . . . . . . . . . . . . . 221
6.9 Falacias y errores comunes . . . . . . . . . . . . . . . . . . . . . . . . . . . 225
6.10 Pasado, presente y futuro de JavaScript . . . . . . . . . . . . . . . . . . . . . 229
6.11 Para saber más . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 232
6.12 Ejercicios propuestos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 233
x
9 Mantenimiento de código heredado 326
9.1 Código heredado y metodología ágil . . . . . . . . . . . . . . . . . . . . . . 328
9.2 Exploración de código heredado . . . . . . . . . . . . . . . . . . . . . . . . 331
9.3 Realidad sobre el terreno y pruebas de caracterización . . . . . . . . . . . . . 336
9.4 Comentarios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 339
9.5 Métricas, smells de código y SOFA . . . . . . . . . . . . . . . . . . . . . . . 340
9.6 Refactorización a nivel de método . . . . . . . . . . . . . . . . . . . . . . . 345
9.7 La perspectiva clásica . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 351
9.8 Falacias y errores comunes . . . . . . . . . . . . . . . . . . . . . . . . . . . 356
9.9 Observaciones finales: refactorización continua . . . . . . . . . . . . . . . . 357
9.10 Para saber más . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 358
9.11 Ejercicios propuestos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 360
xi
xii
13 Epílogo 474
13.1 Perspectivas sobre SaaS y SOA . . . . . . . . . . . . . . . . . . . . . . . . . 474
13.2 Echando la vista atrás . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 474
13.3 Mirando hacia adelante . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 477
13.4 Últimas palabras . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 480
13.5 Para saber más . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 480
Para mi es un placer redactar el prólogo a este libro de Armando Fox y David Patterson,
porque lo considero extraordinario. Y esto es así inicialmente por dos principales razones:
por el contenido y el envoltorio, por el fondo y la forma, por el mensaje y el medio.
En relación con el contenido: El libro presenta de una forma revisada y moderna todo
el proceso de desarrollo software conjuntamente con las metodologías ágiles, que de forma
natural encajan con la filosofía de software como servicio y computación en la nube. Como
instrumentos para ello se presentan y usan una variedad de herramientas de desarrollo y se
utiliza Ruby on Rails como entorno. El libro lo podríamos calificar de muy completo, por
abarcar todo lo que se necesita: desde los aspectos tecnológicos a los metodológicos, y todo
ello con sus correspondientes herramientas de soporte (lenguajes y entornos de desarrollo,
fundamentos de arquitectura, patrones de diseño, pruebas, gestión de versiones y colabo-
ración, etcétera). Pero también lo podemos calificar de muy útil y práctico, pues relaciona
lo que se necesita a la hora de desarrollar una aplicación SaaS o aplicar metodologías de
desarrollo, tanto para el desarrollo de nuevas aplicaciones como de aquellas que tienen que
trabajar con software heredado. Por tanto, toda persona que desee conocer el estado del arte
en desarrollo software encontrará en esta obra una referencia autorizada y actualizada.
Por el envoltorio: No se trata éste de un libro aislado, sino que se enmarca en el contexto
de todo un rico ecosistema educativo que han creado los autores. En primera instancia, el libro
es más que un libro propiamente dicho, pues hay una máquina virtual asociada a él y además
hay múltiples referencias a vídeos accesibles en internet que complementan las explicaciones.
Pero si fuera sólo por estos detalles, no estaría justificado hablar de ecosistema. En segundo
lugar hay que mencionar los MOOCs (Massive Open Online Courses, Cursos online masivos
y abiertos) que se ofrecen a través de la plataforma edX. El libro sirve de apoyo al MOOC y el
MOOC sirve de apoyo al libro. Pero ahí no queda la cosa: los materiales de estos MOOCs se
ofrecen también para ser desplegados como SPOCs (Small Private Online Courses, Cursos
online privados a pequeña escala) tanto para el uso interno por los propios profesores en la
Universidad de California en Berkeley, como para que terceros los utilicen en sus cursos,
posiblemente adaptándolos a sus temarios. Todo este ecosistema se realimenta positivamente
en el sentido que incrementar su calidad. Lo masivo de los MOOCs no solamente sirve para
difundir la formación al mundo, sino que sirve en sentido contrario para obtener todo tipo
de comentarios y sugerencias que en última instancia mejoran la calidad del curso y por
extensión la del libro. En todo este ecosistema, los autores no se olvidan de nadie, ni de las
necesidades de los alumnos, ni las de los profesores que quieran impartir estos contenidos.
En esencia, ambos aspectos, los del desarrollo software y los del ecosistema educativo,
xv
de las pruebas; la cuestión es cómo conseguir que los principiantes lo asuman. Por tanto,
necesitábamos una metodología software que trabaje bien con código heredado, que haga
hincapié en las pruebas, que integre clientes no técnicos y que fomente trabajar en equipo en
vez de como lobos solitarios.
Casualmente, más o menos al mismo tiempo que SaaS aparecía en escena, un grupo de
desarrolladores propuso el Manifiesto Ágil , que supuso un cambio radical respecto a las
metodologías anteriores. Uno de los principios básicos de la agilidad es “reaccionar a los
cambios sobre seguir un plan” de modo que se adapta mucho mejor a la naturaleza cam-
biante de SaaS que las metodologías clásicas de “planificación y documentación”, como
cascada, espiral o RUP. Otro principio ágil es “colaboración con el cliente sobre la nego-
ciación del contrato”, que lleva a mantener reuniones semanales con clientes no técnicos.
Dos principios fundamentales de las metodologías ágiles son desarrollo guiado por com-
portamiento y desarrollo orientado a pruebas, que significa que las pruebas se escriben
antes que el código, de modo que se convierten realmente en ciudadanos de primera clase en
las metodologías ágiles. Conceptos ágiles como programación en pareja y scrum hacen
hincapié en el trabajo en equipo. Las técnicas ágiles son apropiadas incluso para evolucionar
código heredado, como veremos.
Por tanto, la segunda mitad del libro explica la metodología ágil en el contexto del desa-
rrollo y despliegue de una aplicación SaaS implementada con Ruby on Rails. Además, cada
capítulo repasa la perspectiva de las metodologías clásicas, orientadas a planificar y docu-
mentar, sobre temas como los requisitos, pruebas, gestión y mantenimiento. Este contraste
permite al lector decidir por sí mismo cuándo es más conveniente usar cada metodología,
tanto para aplicaciones SaaS como no SaaS.
tenido más de 100.000 alumnos, de los cuales más de 10.000 han conseguido el certificado
por completar el curso. ¡Una prueba beta de los libros y materiales mucho mayor de lo que
nunca habíamos imaginado!
Los MOOC también son una valiosa ayuda para los profesores. Algunos profesores han
hecho matricularse a sus alumnos en el MOOC para aprovechar las actividades de progra-
mación de corrección automática. Otros han cambiado su metodología docente, haciendo
que los alumnos vean los vídeos y dedicando el tiempo de clase a resolver problemas y otras
actividades; lo que se conoce como “flip the classroom”. Y otros han usado los vídeos para
preparar su propio material. Continuamente, se crean nuevas actividades y se mejoran los
sistemas de corrección automática.
De hecho, los profesores interesados pueden incluso conseguir una versión privada del
MOOC —un SPOC (Small Private Online Course)— que pueden particularizar de acuerdo
a sus necesidades, manteniendo las ventajas de la corrección automática de actividades de
programación y otras funcionalidades del MOOC. La página de recursos del profesor2 , en
el sitio web del libro, proporciona información sobre cómo solicitar un SPOC, así como
un informe describiendo la experiencia de otros profesores con SPOC en sus clases. Los
instructores de SPOC pueden participar en una audio-conferencia bisemanal para comentar
problemas e ideas con colegas que usan el mismo material.
Organización
Este libro está organizado en dos partes principales: la primera cubre las principales ideas
y las tecnologías esenciales de la metodología ágil y SaaS, mientras la segunda se centra en
las herramientas y técnicas para aplicar el ciclo de vida ágil y gestionar de forma efectiva el
diseño, desarrollo y despliegue SaaS.
Estas partes se corresponden con dos unidades principales de material, más un proyecto
opcional pero recomendado, que consituye una tercera. La unidad 1, que se corresponde
a grandes rasgos con los contenidos del MOOC CS169.1x, cubre los fundamentos del de-
sarrollo de una aplicacion SaaS sencilla usando Rails y el ciclo de vida ágil. La unidad 2
introduce conceptos más avanzados de ingeniería software como patrones de diseño, traba-
jar con código heredado y fundamentos de rendimiento y seguridad de SaaS (“DevOps”),
correspondiendo aproximadamente al contenido de BerkeleyX CS169.2x. Cada una de es-
tas unidades incluye actividades auto-evaluables, materiales complementarios online para los
profesores (como bancos de preguntas y exámenes), etc. En la unidad 3, los alumnos apli-
can las habilidades adquiridas en la primera y/o segunda parte para desarrollar en equipo
un proyecto abierto. Actualmente no tiene un MOOC asociado (aunque estamos explorando
ideas), pero el Manual del profesor sintetiza las lecciones que hemos aprendido facilitando
proyectos de alumnos, exitosos (y menos exitosos).
En Berkeley, cubrimos los tres componentes en un único curso intensivo de 14 semanas
(3 horas de clase magistral, 1 hora de seminario/lectura, y 8 horas de trabajo por semana),
en el cual se solapan parcialmente 4 iteraciones ágiles del proyecto en grupo con la unidad
2. El Manual del profesor describe nuestro programa, así como otras posibles opciones, por
ejemplo:
• Una secuencia de dos cursos, cubriendo las unidades 1 y 2 en el primer curso y dedi-
cando el segundo curso a un proyecto semestral o cuatrimestral.
xix
• Un único curso que cubra sólo las unidades 1 y 3, limitando la complejidad del proyecto
a las habilidades aprendidas en la unidad 1.
• Un único curso que cubra todas las unidades pero omitiendo elementos específicos para
respetar las restricciones de tiempo, como por ejemplo, excluir Javascript (capítulo 6)
o DevOps (capítulo 12).
! ! !
! ! !
! ! !
! ! !
Figura 1. Estos 12 libros suman más de 5000 páginas. Los autores de este libro han leído más de 50 libros para preparar este
texto. La mayoría aparecen en los listados de las secciones “Para saber más” al final de los correspondientes capítulos.
xxii
! ! !
! ! !
! ! !
! ! !
Figura 2. Otros 12 libros que hemos leído que también suman más de 5000 páginas. La mayoría aparecen en los listados de
las secciones “Para saber más” al final de los correspondientes capítulos.
xxiii
Ofrecimos una edición alfa del libro a 115 alumnos de UC Berkeley y a miles de estu-
diantes del MOOC en el semestre de primavera de 2012. Basándonos en sus comentarios,
la edición beta estaba lista para otoño de 2012, fecha en que se usó en Berkeley y otras es-
cuelas. En mayo de 2013 se lanzó una segunda edición beta, con material nuevo basado en
un cuidadoso estudio del estándar curricular de ACM/IEEE Computer Society, se probó de
nuevo con alumnos de Berkeley y del MOOC en otoño de 2013, llegando finalmente a esta
(¡muy bien probada!) primera edición.
Agradecimientos
Queremos agradecer a todos nuestros colegas del sector privado la realimentación recibida
acerca de nuestras ideas sobre el curso y el libro, muy especialmente a estas fabulosas per-
sonas, listadas por orden alfabético de su empresa: Peter Vosshall, Amazon Web Services;
Tony Ng, eBay; Tracy Bialik, Brad Green y Russ Rufer, Google Inc.; Peter Van Hardenberg,
Heroku; Jim Larus, Microsoft Research; Brian Cunnie, Edward Hieatt, Matthew Kocher, Ja-
cob Maine, Ken Mayer y Rob Mee, Pivotal Labs; Jason Huggins, SauceLabs; y Raffi Kriko-
rian, Twitter.
Agradecemos también a nuestros compañeros del mundo académico sus comentarios
sobre nuestro enfoque e ideas, especialmente a Fred Brooks, Universidad de Carolina del
Norte en Chapel Hill; Marti Hearst y Paul Hilfinger, UC Berkeley; Timothy Lethbridge, Uni-
versidad de Ottawa; John Ousterhout, Universidad de Stanford; y Mary Shaw, Universidad
Carnegie-Mellon.
Nuestro más sincero agradecimiento a los expertos que han revisado capítulos concretos:
Danny Burkes, Pivotal Labs; Timothy Chou, Stanford; Daniel Jackson, MIT; Jacob Maine,
Pivotal Labs; John Ousterhout, Universidad de Stanford; y Ellen Spertus, Mills College.
Gracias a Alan Fekete, de la Universidad de Sydney, por dirigirnos al Currículum de
Ingeniería Software de 2013 de la ACM/IEEE Computer Society a tiempo para poder consi-
derarlo.
REFERENCIAS xxv
Estamos especialmente agradecidos a los beta testers que utilizaron las versiones iniciales
del libro en sus clases, comenzando por Samuel Joseph de la Universidad Hawaii Pacific, que
también fue facilitador principal de los MOOC CS169.1x y CS169.2x6 , y cuyas amplias
contribuciones al desarrollo y mejora tanto de los materiales del curso como del libro nos
convencieron de que debíamos haberle pedido que se pusiera el gorro oficial de editor. Otras
de las primeras personas que comenzaron a utilizar nuestro libro y que que continúan pro-
porcionándonos valiosa realimentación y contribuyendo a los materiales del curso incluyen
a Daniel Jackson, del MIT; Richard Ilson, de la Universidad de Carolina del Norte en Char-
lotte; Ingolf Krueger, de la Universidad de California, San Diego; Kristen Walcott-Justice, de
la Universidad de Colorado, Colorado Springs; Rose Williams, de la Universidad de Bing-
hamton; y Wei Xu, de la Universidad de Tsinghua, que fue el primero en probar este material
en una clase fuera de los Estados Unidos y que propició nuestra relación con la editorial de
la Universidad de Tsinghua para publicar la edición china de este libro.
La biblioteca de recursos de este libro incluye una colección de sitios externos sobre
desarrollo SaaS excelentes. Por su ayuda poniéndonos en contacto con los productos y servi-
cios correctos que podrían ofrecerse libres de coste a los alumnos, y las valiosas discusiones
sobre cómo utilizarlos en un entorno educativo, agradecemos a Ann Merrihew, Kurt Messer-
smith, Marvin Theimer, Jinesh Varia y Matt Wood, Amazon Web Services; Kami Lott y
Chris Wanstrath, GitHub; Maggie Johnson y Arjun Satyapal, Google Inc.; James Linden-
baum, Heroku; Juan Vargas y Jennifer Perret, Microsoft; Rob Mee, Pivotal Labs; Dana Le,
Salesforce; y John Dunham, SauceLabs.
Agradecemos a los profesores Kristal Curtis y Shoaib Kamil su ayuda para reinventar
la clase presencial, que ha conducido a este esfuerzo, y a los profesores Michael Driscoll y
Richard Xia por ayudarnos a convertir la corrección automática a gran escala en una realidad
para los miles de estudiantes que se matricularon en el curso en línea. Por último, pero no por
ello menos importante, gracias a nuestro dedicado equipo de laboratorio por su colaboración
en varias ediciones de la clase desde 2008: Alex Bain, Aaron Beitch, Allen Chen, James
Eady, David Eliahu, Max Feldman, Amber Feng, Karl He, Arthur Klepchukov, Jonathan Ko,
Brandon Liu, Robert Marks, Jimmy Nguyen, Sunil Pedapudi, Omer Spillinger, Hubert Wong,
Tim Yung y Richard Zhao.
También queremos expresar nuestra gratitud a Andrew Patterson, Grace Patterson y Owyn
Patterson por su ayuda con la promoción del libro, así como a sus jefes Heather Patterson,
Michael Patterson, David Patterson y Zackary Patterson.
Finalmente, ¡muchas gracias a los cientos de estudiantes de UC Berkeley y las decenas
de miles de alumnos del MOOC por su ayuda depurando errores y su interés por el material!
Notas
1 http://www.saas-class.org
2 http://www.saasbook.info/instructors
3 http://screencast.saasbook.info
4 http://radlab.cs.berkeley.edu
5 http://github.com/armandofox/latex2ebook
6 http://saas-class.org
xxvi NOTAS
NOTAS 1
Introducción al software como
1 servicio y desarrollo ágil de
software
Sir Maurice Wilkes Fue en una de mis idas y venidas entre la sala del EDSAC y el equipo de perforación
(1913–2010) recibió el [de cintas] cuando “vacilando en los ángulos de las escaleras” la revelación de que
Premio Turing de 1967 por pasaría una buena parte del resto de mi vida buscando errores en mis propios programas
diseñar y construir EDSAC se apoderó de mí con toda su fuerza.
en 1949, uno de los
primeros ordenadores de Maurice Wilkes, Memoirs of a Computer Pioneer, 1985
programa almacenado. El
Premio Turing1 es el premio
de mayor reconocimiento en
1.1 Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
Ciencias de la Computación,
otorgado anualmente por la 1.2 Procesos para desarrollo de software: ciclos de vida clásicos . . . . . 6
ACM (Association for 1.3 Procesos de desarrollo de software: el Manifiesto Ágil . . . . . . . . . 11
Computing Machinery)
desde 1966. Recibe el
1.4 Arquitectura orientada a servicios . . . . . . . . . . . . . . . . . . . 17
nombre del pionero en 1.5 Software como servicio . . . . . . . . . . . . . . . . . . . . . . . . . . 20
computación Alan Turing, e 1.6 Computación en la nube . . . . . . . . . . . . . . . . . . . . . . . . . 22
informalmente se considera
el “Premio Nobel de las 1.7 Código elegante vs. código heredado . . . . . . . . . . . . . . . . . . 25
Ciencias de la 1.8 Aseguramiento de la calidad del software: las pruebas . . . . . . . . 26
Computación”. 1.9 Concisión, síntesis, reutilización y herramientas . . . . . . . . . . . . 28
(Este libro usa recuadros
laterales para incluir aquello 1.10 Recorrido guiado por el libro . . . . . . . . . . . . . . . . . . . . . . 31
que los autores consideran 1.11 Cómo NO leer este libro . . . . . . . . . . . . . . . . . . . . . . . . . 34
apartes interesantes o
1.12 Falacias y errores comunes . . . . . . . . . . . . . . . . . . . . . . . 36
breves biografías de
pioneros de la computación 1.13 La ingeniería del software es algo más que programar . . . . . . . . 37
que complementan el texto 1.14 Para saber más . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
principal. Esperamos que
los lectores los disfruten). 1.15 Ejercicios propuestos . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
3
Conceptos
Al comienzo de cada capítulo se presenta un resumen de los conceptos más importantes. Para
este capítulo de introducción son los siguientes:
• Los procesos para el desarrollo de software o ciclos de vida clásicos (conocidos también
como ciclos de vida de planificación y documentación) se basan en una cuidadosa
planificación anticipada, que se documenta extensivamente y gestiona de forma meticulosa
para que el desarrollo del software sea más predecible. Algunos ejemplos famosos son los
ciclos de vida en cascada, espiral , y el proceso unificado de Rational (Rational Unified
Process, RUP).
• Por el contrario, el ciclo de vida ágil se basa en desarrollar prototipos de forma incremental,
lo que implica recibir comentarios continuos por parte del cliente en cada iteración, cada
una de las cuales conlleva desde una a cuatro semanas.
• La arquitectura orientada a servicios (Service Oriented Architecture, SOA) crea aplica-
ciones a partir de componentes que actúan como servicios interoperables, lo que permite
construir nuevos sistemas a partir de estos componentes con mucho menos esfuerzo. Y lo
que es más importante, desde el punto de vista de la ingeniería del software, SOA hace
posible la construcción de grandes servicios a partir de otros más pequeños, proceso que,
según nos enseña la historia, tiene mayor probabilidad de ser un éxito que desarrollar un
único proyecto extenso. Una de las razones es que, al tener un tamaño menor, permite el
uso del desarrollo ágil, que cuenta con mejores antecedentes.
• El software como servicio (Software as a Service, SaaS) es un caso especial de SOA que
despliega software en un único sitio haciéndolo disponible para millones de usuarios a través
de Internet y sus dispositivos móviles personales, lo que proporciona beneficios tanto para
los usuarios como para los desarrolladores. La existencia de una única copia del software y
el entorno competitivo en el que se encuentran los productos SaaS conduce a una evolución
del software más rápida para SaaS que para el software distribuido en forma de paquete.
• La evolución del código heredado es vital en el mundo real, a pesar de ser un aspecto
ignorado a menudo en los libros y cursos de ingeniería del software. Las metodologías
ágiles mejoran el código en cada iteración, de forma que las habilidades ganadas en el
proceso también se aplican al código heredado.
• La computación en la nube proporciona el procesamiento y almacenamiento fiables y
escalables para SaaS mediante el uso de Warehouse Scale Computers, que contienen
hasta 100.000 servidores. Las economías de escala permiten ofrecer la computación en la
nube como un servicio, donde el cliente sólo paga por el uso real.
• La calidad del software se define como la provisión de valor de negocio tanto a clientes
como a desarrolladores. El aseguramiento de la calidad del software (Quality Assurance,
QA) proviene de la realización de pruebas a muchos niveles: unitarias, modulares, de
integración, de sistema y de validación.
• La claridad mediante la concisión, síntesis, reutilización y la automatización
mediante herramientas son cuatro vías para mejorar la productividad software. El
entorno de desarrollo Ruby on Rails sigue estas directrices para que los desarrolladores
de SaaS sean más productivos. No se repita (Don’t Repeat Yourself, DRY ) aconseja no
repetir código cuando se persigue su reutilización, ya que sólo debería haber una única
representación de cada pieza de conocimiento.
Dado que el cambio es la norma en la metodología ágil, ésta supone un excelente ciclo de vida
para SaaS, y es en la que se centra este libro.
4 CAPÍTULO 1. INTRODUCCIÓN A SAAS Y DESARROLLO ÁGIL
Figura 1.1. Comparativa entre Amazon.com y Healthcare.gov durante sus tres primeros meses. (Thorp 2013) Tras un
comienzo lleno de obstáculos, la fecha límite se extendió desde el 15 de diciembre de 2013 hasta el 31 de marzo de 2014, lo
que explica un objetivo de cliente por día más bajo en diciembre. Nótese que la disponibilidad de ACA no incluye los
períodos de “mantenimiento programado”, los cuáles sí están incluidos en el caso de Amazon (Zients 2013). La tasa de
errores se contabilizó sobre errores significativos en los formularios que se enviaron a las empresas de seguros (Horsley
2013). El sitio web fue ampliamente etiquetado como inseguro por expertos en seguridad, ya que los desarrolladores
trabajaron bajo una enorme presión para conseguir la funcionalidad, y no se prestó mucha atención a los aspectos de
seguridad (Harrington 2013).
1.1 Introducción
Ahora, esto es sencillo. Es un sitio web donde podéis comparar y comprar seguros médi-
cos asequibles, de la misma forma que compráis un billete de avión en Kayak, o un tele-
visor en Amazon. . . Desde el martes, cualquier americano podrá visitar HealthCare.gov
para descubrir lo que se conoce como el mercado de los seguros. . . Así que contádselo a
vuestros amigos, a vuestra familia. . . Aseguraos de que se inscriben. Vamos a ayudar a
nuestros compañeros americanos para que estén asegurados. (Aplausos)
Presidente Barack Obama, Comentarios sobre la Ley del Cuidado de Salud a Bajo
Precio, Prince George’s Community College, Maryland, 26 de Septiembre de 2013
. . . ya han pasado seis semanas desde que se abrieron los mercados de la Ley del Cuidado
de Salud a Bajo Precio. Creo que es justo decir que hasta ahora el lanzamiento ha sido
difícil, y creo que todo el mundo entiende que no estoy contento con el hecho de que el
lanzamiento haya sido, ya sabéis, forjado por toda una serie de problemas por los que
estoy profundamente preocupado.
Presidente Barack Obama, Declaración sobre la Ley del Cuidado de Salud a Bajo Precio,
The White House Press Briefing Room, 14 de noviembre de 2013
Cuando se aprobó la Ley del Cuidado de Salud a Bajo Precio (Affordable Care Act,
ACA) en 2010, se percibió como el programa social estadounidense más ambicioso en dé-
cadas, y fue tal vez el logro cumbre de la administración Obama. Equivalente a las millones
de personas que compran productos en Amazon.com, HealthCare.gov —también conocido
como el sitio web de la Ley del Cuidado de Salud a Bajo Precio— iba a permitir a millones
de americanos sin asegurar que contrataran pólizas de seguros. Además de tardar tres años
en construirse, fracasó en su estreno el 1 de octubre de 2013. La figura 1.1 compara Ama-
zon.com con Healthcare.gov en su primeros tres meses de funcionamiento, demostrando que
no sólo era lento, propenso a errores e inseguro, sino que además no era accesible durante
una gran parte del tiempo.
¿Por qué compañías como Amazon.com pueden desarrollar software para una base de
clientes mucho más amplia y que funciona mucho mejor? Mientras que los medios de co-
municación descubrieron muchas decisiones cuestionables, una sorprendente cantidad de la
culpa recayó en la metodología usada para desarrollar el software (Johnson and Reed 2013).
1.1. INTRODUCCIÓN 5
Dado su enfoque, como dijo un comentarista, “La verdadera noticia habría sido que hubiera
funcionado”. (Johnson 2013a)
Es un honor para nosotros tener la oportunidad de explicar cómo las compañías de Internet
y otros desarrollan servicios software con éxito. Tal y como ilustra esta introducción, este
campo no es una disciplina académica aburrida en la que a pocos les importa lo que ocurre;
los proyectos software fallidos pueden convertirse en infames, y pueden incluso derrocar
presidentes. Por otro lado, los proyectos software exitosos pueden crear servicios que usen
millones de personas cada día, lo que nos lleva a empresas como Amazon, Facebook y Google
que se han convertido en nombres familiares. Todo aquel involucrado en dichos servicios está
orgulloso de que se le asocie con ellos, a diferencia de lo que ocurre con ACA.
Además, este libro no es el típico estudio bienintencionado de lo que hacer y no hacer
en cada fase del desarrollo de software. Concreta conceptos recientes con demostraciones
prácticas de cómo diseñar, implementar y desplegar una aplicación en la nube. La imagen
de la máquina virtual asociada a este libro contiene todo el software que pueda necesitar
para hacerlo (ver Apéndice A). Además de leer lo que hemos escrito, puede ver nuestras de-
mostraciones y escuchar nuestras voces como parte de los 27 screencasts en los siguientes
capítulos. Incluso puede vernos impartir este material, puesto que este libro está asociado
con un curso en línea masivo y abierto (Massive Online Open Course, MOOC ) al
que puede acceder desde EdX.org2 . CS169.1x y CS169.2x ofrecen fragmentos de vídeo de 6
a 10 minutos de duración que generalmente corresponden uno a uno con todas las secciones
del libro, incluyendo ésta. Estos MOOC ofrecen autoevaluación rápida de las tareas de pro-
gramación y cuestionarios para proporcionarle una realimentación sobre cómo ha asimilado
el material, además de un foro en línea para preguntar y responder dudas.
El resto de este capítulo explica por qué pueden suceder desastres como ACA y cómo
evitar que se repita esta desafortunada historia. Empezaremos nuestro viaje con los orígenes
de la propia ingeniería del software, que empezó con metodologías de desarrollo de software
que enfatizaban la planificación y documentación. Después, revisaremos estadísticas sobre
cuán bien funcionaban las metodologías clásicas o de planificación y documentación, lo
que reflejará, desgraciadamente, que los resultados de proyectos como ACA son demasiado
comunes, si bien no tan conocidos. Los frecuentes resultados decepcionantes de seguir la
sabiduría de la ingeniería del software inspiró a algunos desarrolladores de software a orga-
nizar una revuelta en palacio. Aunque el Manifiesto por el desarrollo ágil de software fue
muy controvertido cuando se anunció, el desarrollo ágil de software ha acallado las críticas
con el paso del tiempo. El desarrollo ágil permite que equipos pequeños superen a gigantes
empresariales, especialmente en proyectos pequeños. Nuestro siguiente paso en este viaje
demostrará cómo las arquitecturas orientadas a servicios permiten una combinación exitosa
de grandes servicios software, como Amazon.com, a partir de muchos servicios software más
pequeños desarrollados por equipos pequeños de desarrollo ágil.
Como apunte final, pero no menos importante, suele ser poco habitual que los desarrolla-
dores de software empiecen sus desarrollos desde cero. Es mucho más común que se centren
en mejorar extensas colecciones de código ya existente. El próximo destino en nuestro viaje
revelará que, al contrario de lo que ocurre con las metodologías clásicas, que abogan por
realizar primero un diseño perfecto y luego implementarlo, el proceso ágil dedica la mayor
parte del tiempo a mejorar código que funciona. Por tanto, mejorando en la práctica de
la metodología ágil, también practica las habilidades necesarias para hacer evolucionar co-
lecciones de código existentes.
Para empezar nuestro viaje, presentamos la metodología software usada para desarrollar
6 CAPÍTULO 1. INTRODUCCIÓN A SAAS Y DESARROLLO ÁGIL
HealthCare.gov.
La imprevisibilidad general del desarrollo de software a finales de los años 60, junto con
desastres software similares a ACA, condujeron al estudio de cómo desarrollar software de
alta calidad siguiendo una agenda y presupuesto predecibles. Estableciendo la analogía con
otras ramas de la ingeniería, se acuñó el término ingeniería del software (Naur and Randell
1969). El objetivo era descubrir métodos para construir software que fueran tan predecibles
en calidad, coste y tiempo como aquellos usados para construir puentes en ingeniería civil.
Un objetivo estratégico de la ingeniería del software fue introducir la disciplina de la
ingeniería en lo que a menudo era desarrollo de software sin planificar. Antes de empezar
a escribir código, pensar en un plan para el proyecto, incluyendo documentación extensa y
detallada para todas las fases del plan. El progreso, por tanto, se medía conforme al plan. Los
cambios en el proyecto se debían reflejar en la documentación y, posiblemente, en el plan.
El objetivo de todos estos procesos de “planificación y documentación” del desarrollo de
software es mejorar la previsibilidad mediante una documentación exhaustiva, que debe ser
modificada siempre que los objetivos cambien. Los autores del libro lo expresaron así (Leth-
bridge and Laganiere 2002; Braude 2001):
El Grupo CGI consiguió
el contrato para desarrollar
el back-end (motor) del sitio Se debe documentar durante todas las etapas del desarrollo, abarcando los requisitos,
web de ACA. El presupuesto diseño, manuales de usuario, instrucciones para el personal que realiza las pruebas y los
inicial estimado ascendió planes del proyecto.
desde 94 hasta 292 millones
Timothy Lethbridge and Robert Laganiere, 2002
de dólares (Begley 2013).
Esta misma compañía
estaba involucrada en un
registro canadiense de La documentación es el alma de la ingeniería del software.
armas de fuego cuyos Eric Braude, 2001
costes se dispararon, desde
una estimación inicial de 2
millones hasta los 2.000 Este proceso está recogido incluso en un estándar oficial de documentación: el estándar
millones de dólares. IEEE/ANSI 830/1993.
Cuando MITRE investigó los Algunos gobiernos como el de Estados Unidos han elaborado regulaciones para prevenir
problemas con la web de
la corrupción cuando se adquiere equipamiento nuevo, lo que conduce a contratos y espe-
ACA Massachusetts,
determinó que el Grupo CGI cificaciones extensos. Puesto que el objetivo de la ingeniería del software era hacer del
no tenía experiencia desarrollo de software algo tan predecible como la construcción de un puente, incluyendo
suficiente para desarrollar el especificaciones elaboradas, los contratos gubernamentales eran la correspondencia natural
sistema, perdieron
información, no probaron las
al desarrollo de software con metodologías clásicas. Por ello en Estados Unidos, como en
funciones adecuadamente y muchos otros países, estas regulaciones no dejaron otra opción a los desarrolladores de ACA
realizaron una gestión más que seguir un ciclo de vida clásico.
pésima del proyecto Tal y ocurre en otras ramas de la ingeniería, los gobiernos tienen cláusulas de escape en
(Bidgood 2014).
los contratos que les permiten adquirir productos aunque se entreguen tarde. Irónicamente, el
empresa contratada gana más dinero cuanto más tiempo le lleve desarrollar el software. Por
lo tanto, el arte está en negociar el contrato y las cláusulas de sanción. Como puntualizó un
1.2. PROCESOS PARA DESARROLLO DE SOFTWARE: CICLOS DE VIDA CLÁSICOS7
comentarista sobre ACA (Howard 2013), “Las empresas que típicamente obtienen contratos
son aquellas que son buenas consiguiendo contratos, generalmente no tan buenas ejecután-
dolos”. Otro comentó que las metodologías clásicas no son apropiadas para las prácticas
modernas, sobre todo cuando los contratistas gubernamentales se centran en maximizar las
ganancias (Chung 2013).
Una de las primeras versiones de estos procesos de desarrollo de software clásicos se
desarrolló en 1970 (Royce 1970). Dicha versión sigue esta secuencia de fases:
1. Análisis y especificación de requisitos
2. Diseño arquitectural
3. Implementación e integración
4. Verificación
5. Operación y mantenimiento
Dado que cuanto antes se encuentre un error, más barato es arreglarlo, la filosofía de
este proceso es completar una fase antes de pasar a la siguiente, eliminando así el máximo
número de errores lo antes posible. Completar las primeras fases satisfactoriamente puede
evitar trabajo innecesario en fases posteriores. Dado que este proceso puede llevar años, la
documentación exhaustiva ayuda a asegurar que no se pierde información importante si una
persona deja el proyecto y que personal nuevo pueda ponerse al día rápidamente cuando se
une al proyecto.
Puesto que el flujo discurre desde arriba hacia abajo hasta completarse, este proceso se
denomina desarrollo de software en cascada o ciclo de vida del desarrollo de software en Windows 95 fue
cascada. Como es de esperar, dada la complejidad de cada etapa del ciclo de vida en cascada, presentado durante una
fiesta al aire libre que costó
los lanzamientos del producto son grandes eventos acompañados de mucho espectáculo, para
300 millones de dólares3
los cuales los ingenieros trabajaban febrilmente. para la cual Microsoft
En el ciclo de vida en cascada, la larga duración del software se reconoce mediante una contrató al cómico Jay Leno,
fase de mantenimiento que repara los errores según se detectan. Las versiones nuevas del iluminaron el Empire State
Building de Nueva York con
software desarrolladas en un modelo en cascada pasan por las mismas fases, y llevan general- los colores del logotipo de
mente entre 6 y 18 meses. Microsoft Windows, y
El modelo en cascada puede funcionar bien para tareas bien especificadas, como los vue- acreditaron la canción “Start
los espaciales de la NASA, pero presenta problemas cuando los clientes cambian de idea Me Up” de los Rolling
Stones como el himno de la
acerca de lo que quieren. Uno de los ganadores del Premio Turing expresa esta observación celebración.
así:
Cuenta con descartar una [implementación]; lo harás de cualquier forma.
Fred Brooks, Jr.
Es decir, es más fácil para los clientes entender qué quieren cuando han visto un prototipo,
y para los ingenieros entender cómo desarrollar un prototipo mejor cuando ya lo han hecho
una primera vez.
Esta observación conduce a un ciclo de vida del software, desarrollado en la década de los
80, que combina la construcción prototipos con el modelo en cascada (Boehm 1986). La idea
es iterar a través de una secuencia de cuatro fases, donde cada iteración da como resultado
un prototipo mejorado partiendo de la versión anterior. La figura 1.2 muestra este modelo de
desarrollo a través de las cuatro fases, lo que da nombre a este ciclo de vida: el modelo en
espiral . Las fases del modelo son:
8 CAPÍTULO 1. INTRODUCCIÓN A SAAS Y DESARROLLO ÁGIL
Determine
Evaluate Alternatives,
Objectives and
Identify and Resolve Risks
Constraints
Opera-
tional
Proto- Proto- Proto- Proto-
type 1 type 2 type 3 type
Develop and
Plan Next Iteration
verify prototype
Figura 1.2. El ciclo de vida en espiral combina el ciclo en cascada con la creación de prototipos. Comienza en el centro,
recorriendo la espiral con cada iteración a través de las cuatro fases y resultando en un prototipo revisado hasta que el
producto está listo para su lanzamiento.
Big Design Up Front, 3. Desarrollar y verificar el prototipo que resultará de esta iteración
abreviado BDUF , se usa
para designar los procesos 4. Planificar la siguiente iteración
de software como cascada,
espiral y RUP que dependen En lugar de documentar todos los requisitos al principio, como en el modelo en cascada,
de una planificación y los documentos de requisitos se van desarrollando a lo largo de cada iteración según se nece-
documentación exhaustivas.
También se conocen
sita, y evolucionan con el proyecto. Las iteraciones involucran al cliente antes de que el
habitualmente como producto esté acabado, lo que reduce las posibilidades de malentendidos. Sin embargo, como
procesos pesados, se preveía originalmente, estas iteraciones duraban entre 6 y 24 meses, por lo que ¡los clientes
dirigidos por tienen tiempo de sobra para cambiar de idea durante una iteración! Por lo tanto, el modelo en
planificación,
disciplinados o espiral todavía se apoya en planificación y documentación exhaustivas, aunque se espera que
estructurados. el plan evolucione con cada iteración.
Dada la importancia del desarrollo de software, se han propuesto numerosas variaciones
de las metodologías clásicas más allá de estas dos. Una propuesta reciente se conoce como
proceso unificado de Rational (Rational Unified Process, RUP) (Kruchten 2003),
1.2. PROCESOS PARA DESARROLLO DE SOFTWARE: CICLOS DE VIDA CLÁSICOS9
que combina características tanto del ciclo en cascada como del ciclo en espiral así como
estándares para los diagramas y la documentación. Usaremos RUP como representante de
la corriente de pensamiento más actual en ciclos de vida de clásicos. Al contrario de lo que
ocurre con los modelos en cascada y en espiral, RUP está más relacionado con cuestiones
comerciales que técnicas.
Al igual que los modelos en cascada y en espiral, RUP consta de una serie de fases:
1. Inicio o concepción (inception): define el caso de negocio del software y el alcance del
proyecto para establecer los plazos y el presupuesto, usados para juzgar el proceso y
justificar gastos, y la evaluación inicial de los riesgos para los plazos y el presupuesto.
2. Elaboración (elaboration): trabaja con las partes interesadas para identificar casos de
uso, diseñar la arquitectura del software, establecer el plan de desarrollo y construir el
prototipo inicial.
3. Construcción (construction): codifica y prueba el producto, resultando en el primer
lanzamiento del mismo.
4. Transición (transition): traslada el producto de desarrollo a producción en un entorno
real, incluyendo las pruebas de validación del cliente y la formación del usuario.
A diferencia del modelo en cascada, cada fase involucra una iteración. Por ejemplo, un
proyecto puede tener una iteración de la fase de inicio, dos iteraciones de la fase de elabo-
ración, cuatro iteraciones de la fase de construcción, y dos iteraciones de la fase de transición.
Tal y como ocurría con el modelo en espiral, un proyecto puede asimismo iterar repetida-
mente a través de las cuatro fases.
Además de las fases del proyecto que cambian dinámicamente, RUP identifica seis “dis-
ciplinas de la ingeniería” (conocidas como flujos de trabajo) que el personal involucrado en
el proyecto debe cubrir colectivamente:
1. Modelado de negocio (Business Modeling)
2. Requisitos (Requirements)
3. Análisis y diseño (Analysis and Design)
4. Implementación (Implementation)
5. Pruebas (Test)
6. Despliegue (Deployment)
Estas disciplinas son más estáticas que las fases, ya que existen nominalmente a lo largo
de todo el tiempo de vida del proyecto. Sin embargo, algunas disciplinas aparecen más en
fases tempranas (como el modelado de negocio), otras lo hacen periódicamente a lo largo del
proceso (como las pruebas), y algunas aparecen más hacia el final (despliegue). La figura 1.3
muestra la relación entre las fases y las disciplinas, representando mediante áreas la cantidad
de esfuerzo necesario en cada disciplina a lo largo del tiempo.
Un inconveniente poco afortunado de enseñar los ciclos de vida clásicos a los estudiantes
es que el desarrollo de software les puede parecer tedioso (Nawrocki et al. 2002; Estler et al.
2012). Esto no supone una razón lo suficientemente importante como para no enseñarlos,
10 CAPÍTULO 1. INTRODUCCIÓN A SAAS Y DESARROLLO ÁGIL
Figura 1.3. El ciclo de vida conocido como proceso unificado de Rational permite que el proyecto tenga múltiples iteraciones
en cada fase e identifica habilidades que debe tener el equipo de trabajo, que varían en esfuerzo a lo largo del tiempo. RUP
tiene también tres “disciplinas de apoyo” que no se muestran en esta figura: gestión de configuración y del cambio, gestión
de proyecto y entorno. (Imagen tomada de Wikipedia Commons por Dutchgilder).
dada la importancia de que el desarrollo de software sea un proceso previsible; las buenas
noticias son que hay alternativas que funcionan igual de bien para muchos proyectos, por lo
que encajan mejor para enseñarlos en clase, tal y como describiremos en la siguiente sección.
Resumen. Las actividades básicas de la ingeniería del software son las mismas en todos
los procesos de desarrollo de software o ciclos de vida, pero su interacción a lo largo del
tiempo respecto a los lanzamientos del producto difiere según los modelos. El ciclo de
vida en espiral se caracteriza por realizar gran parte del diseño antes de escribir código,
completando cada fase antes de pasar a la siguiente. El ciclo de vida en espiral itera a lo
largo de todas las fases de desarrollo para producir prototipos, pero como en el ciclo en
cascada, los clientes sólo se ven involucrados en ciertos momentos del desarrollo espa-
ciados entre 6 y 24 meses. El ciclo de vida denominado proceso unificado de Rational,
más reciente, incluye fases, iteraciones y prototipos, identificando además las habilidades
que necesita el personal involucrado en el proyecto. Todos los procesos de desarrollo se
apoyan en una planificación cuidadosa y una documentación exhaustiva, y todos miden el
progreso con respecto a un plan.
Autoevaluación 1.2.1. ¿Cuáles son las principales similitudes y diferencias entre los proce-
sos como el de espiral o RUP frente al proceso en cascada?
⇧ Todos se apoyan en la planificación y documentación, pero el proceso en espiral y el pro-
ceso RUP usan iteraciones y prototipos para mejorar según avanza el tiempo, frente a un
único camino largo hasta el producto final.
Autoevaluación 1.2.2. ¿Cuáles son las diferencias entre las fases de estos procesos clási-
cos?
⇧ Las fases del modelo en cascada separan la planificación (requisitos y diseño de la ar-
1.3. PROCESOS DE DESARROLLO DE SOFTWARE: EL MANIFIESTO ÁGIL 11
1.3 Procesos de desarrollo de software: el manifiesto por el desarrollo Ariane 5 vuelo 501. El
4 de junio de 1996, 37
ágil de software segundos antes del
lanzamiento de un sistema
Si un problema no tiene solución, puede que no sea un problema sino un hecho —no para de seguimiento, ocurrió un
ser resuelto, sino al que enfrentarse con el paso del tiempo—. error de desbordamiento
con consecuencias
Shimon Peres
espectaculares4 cuando un
número de coma flotante fue
Aunque los procesos clásicos trajeron disciplina al desarrollo de software, todavía hubo convertido a un entero más
proyectos de software que fracasaron tan estrepitosamente que viven en la infamia. Los pro- pequeño. Esta excepción no
gramadores han oído las tristes historias de la explosión del cohete Ariane 5 , la sobredosis podía suceder en el Ariane
4, un cohete más lento, por
de radiación letal Therac-25 , la desintegración de la sonda Mars Climate y el abandono lo que volver a utilizar
del proyecto Virtual Case File del FBI tan frecuentemente, que se han convertido en clichés. componentes que fueron
Ningún ingeniero de software querría tener estos proyectos en su currículum. exitosos con este sistema
Un artículo incluso confeccionó un “muro de la vergüenza del software” con docenas sin probar exhaustivamente
el problema resultó caro: se
de proyectos de gran visibilidad que, en conjunto, fueron responsables de unas pérdidas de perdieron varios satélites
17.000 millones de dólares, la mayoría de los cuales fueron abandonados (Charettte 2005). por un valor de 370 millones
de dólares.
12 CAPÍTULO 1. INTRODUCCIÓN A SAAS Y DESARROLLO ÁGIL
a)#So&ware#Projects#(Johnson#1995)# b)#So&ware#Projects#(Taylor#2000)##
c)#So&ware#Projects#(Jones#2004)## d)#So&ware#Projects#(Johnson#2013)#
Figura 1.4. a) Un estudio sobre proyectos software desveló que el 53% de los proyectos excedían su presupuesto en un factor
de 2.9 y su planificación temporal en un factor de 3.2, mientras que otro 31% de los proyectos software se cancelaron antes
de terminarse (Johnson 1995). El coste anual estimado en Estados Unidos para dichos proyectos fue de 100.000 millones de
dólares. b) Una encuesta de los miembros de la British Computer Society (BCS) reveló que sólo 130 de 1027 proyectos
cumplieron con su planificación y presupuesto. La mitad de los proyectos eran de mantenimiento o conversión de datos y la
otra mitad eran nuevos desarrollos, pero los proyectos exitosos se repartieron entre 127 de los primeros y sólo 3 de los
últimos (Taylor 2000). c) Un estudio de 250 proyectos grandes, cada uno contando con el equivalente a más de un millón de
líneas de código en C, encontró resultados igual de decepcionantes (Jones 2004). d) Una encuesta que incluía sólo los
ejemplos más grandes de entre 50.000 proyectos, es decir, aquellos que han costado al menos 10 millones de dólares en
desarrollo (Johnson 2013b), obtiene los resultados más nefastos, lo que sugiere que HealthCare.gov sólo tenía un 10% de
probabilidades de éxito.
La figura 1.4 resume cuatro estudios sobre proyectos de software. Habiendo tan sólo entre
un 10 y un 16% finalizados a tiempo y ajustándose a su presupuesto, había más proyectos
cancelados o abandonados que los que se ajustaron a sus objetivos. Un vistazo más detallado
al 13% de proyectos exitosos en el estudio b) resulta aún más revelador, ya que menos del 1%
de los desarrollos nuevos cumplieron con sus planificaciones y presupuestos. Aunque los tres
primeros estudios tienen entre 10 y 25 años de antigüedad, el estudio d) data de 2013. Cerca
del 40% de estos grandes proyectos fueron cancelados o abandonados, y el 50% finalizaron
más tarde de lo previsto, sobrepasando el presupuesto y con funcionalidad incompleta. Usan-
do la historia como guía, el pobre presidente Obama sólo tenía una probabilidad de uno entre
diez de que HealthCare.gov tuviera un estreno exitoso.
El desarrollo ágil se Tal vez el “momento de la reforma” de la ingeniería del software fue el Manifiesto por
conoce en algunos casos
como proceso ligero o el desarrollo ágil de software en febrero de 2001. Un grupo de desarrolladores de software
indisciplinado. se reunieron para idear un ciclo de vida de software más ligero. He aquí exactamente lo que
la Alianza del desarrollo ágil apuntaló en la puerta de la “Iglesia de los Procesos Clásicos”:
ayudando a otros a aplicar dicha práctica. A través de este trabajo hemos podido valorar:
• Individuos e interacciones por encima de procesos y herramientas
[El Manifiesto por el desarrollo ágil de software] es otro intento de minar la disciplina
de la ingeniería del software. . . En la profesión de ingeniero de software, hay ingenieros
y hay hackers. . . Me parece que esto no es más que un intento por legitimar el com-
portamiento del hacker. . . La profesión de la ingeniería del software sólo cambiará a
14 CAPÍTULO 1. INTRODUCCIÓN A SAAS Y DESARROLLO ÁGIL
Figura 1.5. Diez preguntas que ayudan a decidir si usar un ciclo de vida ágil (la respuesta es no) o un ciclo de vida clásico (la
respuesta es sí) (Sommerville 2010). Nos parece llamativo que cuando se plantean estas preguntas a grupos de estudiantes
durante una clase, todas las respuestas apuntan virtualmente a la metodología ágil. Tal y como atestigua este libro, las
herramientas software de código abierto son excelentes, y están disponibles para los estudiantes (pregunta 6). Nuestro
estudio en empresas (ver Prólogo) descubrió que los estudiantes a punto de graduarse tenían buenas habilidades de
programación (pregunta 9). Las otras ocho respuestas son un “no” rotundo para el caso de proyectos de estudiantes.
mejor cuando los clientes se nieguen a pagar por software que no hace lo que con-
trataron. . . Cambiar la cultura que fomenta la mentalidad hacker por otra basada en
prácticas predecibles de ingeniería del software sólo ayudará a transformar la ingeniería
del software en una disciplina respetada de la ingeniería.
Steven Ratkin, “Manifesto Elicits Cynicism,” IEEE Computer, 2001
¡Un par de críticos incluso llegaron a publicar el caso contra la metodología ágil como un
libro de 432 páginas! (Stephens and Rosenberg 2003)
La comunidad investigadora de ingeniería del software continuó comparando los ciclos de
vida clásicos con los de la metodología ágil y concluyó —para sorpresa de algunos cínicos—
que la metodología ágil podía, de hecho, funcionar bien dependiendo de las circunstan-
cias. La figura 1.5 muestra 10 preguntas de un popular libro de texto sobre ingeniería del
software (Sommerville 2010) cuyas respuestas sugieren cuándo usar la metodología ágil y
cuándo usar los métodos clásicos.
Hay que recordar que el último y más reciente estudio en la figura 1.4 muestra los re-
sultados decepcionantes de grandes proyectos software, que no usan la metodología ágil. La
figura 1.6 muestra el éxito de pequeños proyectos software —definidos como aquellos cuyo
coste es menor de un millón de dólares— que generalmente usan la metodología ágil. Con
tres de cada cuatro de estos proyectos finalizados a tiempo, de acuerdo con el presupuesto
establecido y con toda la funcionalidad implementada, los resultados contrastan fuertemente
con los de la figura 1.4. El éxito ha avivado la popularidad de la metodología ágil, y estudios
recientes la identifican como el método de desarrollo primario del 60% al 80% de todos los
equipos de programación en 2013 (ET Bureau 2012, Project Management Institute 2012).
Un artículo incluso señaló que la metodología ágil se usaba en la mayoría de los equipos de
programación distribuidos geográficamente, lo que es mucho más difícil de conseguir (Estler
et al. 2012).
Por lo tanto, nos centraremos en la metodología ágil en los seis capítulos de la parte II
del libro, aunque cada capítulo también ofrece una perspectiva de las metodologías clásicas
1.3. PROCESOS DE DESARROLLO DE SOFTWARE: EL MANIFIESTO ÁGIL 15
So#ware(Projects((Johnson(2013)(
4%$
On$*me,$on$
20%$ budget$
Late,$over$budget,$
missing$func*on$
76%$ Terminated$or$
abandoned$
Figura 1.6. Estudio de los ejemplos pequeños del total de 50.000 proyectos, definidos como aquellos que costaron menos de 1
millón de dólares en desarrollo (Johnson 2013b). Estos proyectos funcionan mucho mejor que los de la figura 1.4.
en temas como requisitos, pruebas, gestión del proyecto y mantenimiento. Este contraste
permite a los lectores decidir por sí mismos cuándo es apropiada cada metodología.
Aunque ahora vemos cómo construir cierto software con éxito, no todos los
proyectos son pequeños. A continuación mostramos cómo diseñar software de forma
que sea posible la combinación de servicios como en el caso de Amazon.com.
Resumen. En contraste con los ciclos de vida clásicos, el ciclo de vida ágil trabaja con los
clientes para añadir funcionalidad continuamente a los prototipos funcionales hasta que
el cliente queda satisfecho, permitiéndole cambiar lo que quiera mientras el proyecto se
desarrolla. La documentación se realiza fundamentalmente en base a historias de usuario
y casos de prueba, y no se mide el progreso respecto a un plan predefinido. En su lugar, el
progreso se mide anotando la velocidad , que esencialmente es la tasa a la que el proyecto
va completando sus características.
Autoevaluación 1.3.1. Verdadero o falso: una gran diferencia entre el desarrollo en espiral
y el ágil es la construcción de prototipos y la interacción con clientes durante el proceso.
⇧ Falso. Ambos construyen prototipos funcionales pero incompletos que el cliente ayuda
a evaluar. La diferencia es que los clientes están involucrados cada dos semanas en la
metodología ágil, frente a períodos de hasta dos años en la metodología en espiral.
16 CAPÍTULO 1. INTRODUCCIÓN A SAAS Y DESARROLLO ÁGIL
El éxito de los pequeños proyectos de la figura 1.6 se puede replicar en proyectos más
grandes usando una arquitectura software diseñada para crear servicios combinables: la ar-
quitectura orientada a servicios (Service Oriented Architecture, SOA).
Por desgracia, SOA fue uno de esos términos mal definidos, usado en exceso, y tan so-
brevalorado que algunos pensaron que sólo era un término publicitario vacío, como el de
programación modular . SOA significa que los componentes de una aplicación actúan
como servicios que interactúan entre ellos, y se pueden usar independientemente y combi-
narlos de diversas formas en otras aplicaciones. La opción contraria se considera un “silo
de software”, que raramente tiene interfaces de programación de aplicaciones (Application
Programming Interfaces, API) externas de los componentes internos.
Si no se estima correctamente lo que el cliente quiere realmente, el coste asociado a
enmendar ese error y probar algo distinto o producir una variante similar pero no idéntica
que agrade a un subconjunto de usuarios es mucho más bajo usando SOA que con “silos” de
software.
Por ejemplo, Amazon comenzó en 1995 con un silo de software para su sitio de venta
online. De acuerdo con el blog del inicialmente Amazoniano Steve Yegge5 , en 2002 el CEO y
fundador de Amazon ordenó un cambio hacia lo que llamaríamos SOA hoy. Yegge afirma que
Jeff Bezos difundió un correo electrónico a todos los empleados redactado en las siguientes
líneas:
1. Todos los equipos expondrán de ahora en adelante sus datos y funcionalidad a través
de interfaces de servicio.
4. No importa qué tecnología se use. HTTP, CORBA, Pub/Sub, protocolos propios —no
importa—. A Bezos le da igual.
5. Todos las interfaces de servicio, sin excepción, se deben diseñar desde el inicio para
ser externos. Esto quiere decir que el equipo debe planificar y diseñar para ser capaz
de exponer la interfaz a desarrolladores del mundo exterior. Sin excepción.
6. Cualquiera que no haga esto será despedido.
7. Gracias. ¡Que tengan un buen día!
18 CAPÍTULO 1. INTRODUCCIÓN A SAAS Y DESARROLLO ÁGIL
credit card
processing
user editor
users orders
reviews reviews
Bookstore Service
Bookstore Service
Favorite Books
Service
users
Social
Network
Service
Figura 1.7. Izquierda: versión silo de un servicio ficticio de venta de libros, con todos los subsistemas detrás de una única
API. Derecha: versión SOA de un servicio ficticio de venta de libros, donde los tres subsistemas son independientes y están
disponibles a través de las API.
Una revolución software similar tuvo lugar en Facebook en 2007 —tres años después del
lanzamiento de la compañía— cuando se lanzó la Plataforma Facebook. Apoyándose en
SOA, la Plataforma Facebook permitió a desarrolladores de terceras partes crear aplicaciones
que interaccionaran con la funcionalidad del núcleo de Facebook tales como qué le gusta a los
usuarios, quiénes son sus amigos, quiénes están etiquetados en sus fotos, etc. Por ejemplo, el
New York Times fue uno de los primeros desarrolladores en utilizar la Plataforma Facebook.
Los usuarios de Facebook que se encontraban leyendo el New York Times online el 24 de
mayo de 2007 de repente se percataron de que podían ver qué artículos estaban leyendo sus
amigos y cuáles les gustaban. Como contrapunto de una red social que usaba un silo de
software, Google+ no tenía API cuando se lanzó el 28 de junio de 2011, y sólo contaba con
una API muy pesada tres meses después: seguir el flujo completo de todo lo que un usuario
de Google+ veía.
Para concretar estas nociones, supongamos que queríamos crear un servicio de venta de
libros en primer lugar como silo y después como SOA. Ambos contarán con los mismos tres
subsistemas: críticas, perfiles de usuarios y compras.
En la parte izquierda de la figura 1.7 se muestra la versión silo. El silo significa que los
subsistemas pueden, internamente, compartir directamente el acceso a datos en diferentes
subsistemas. Por ejemplo, el subsistema de críticas puede obtener la información del perfil
de un usuario del subsistema de usuarios. Sin embargo, todos los subsistemas están dentro
de una gran API externa (“la tienda de libros”).
En la parte derecha de la figura 1.7 se puede ver la versión SOA del servicio de venta
de libros, donde todos los subsistemas están separados y son independientes. Aunque todo
está dentro del “límite” del centro de datos de la tienda de libros, representado mediante un
rectángulo punteado, los subsistemas interactúan entre ellos como si estuvieran en diferen-
tes centros de datos. Por ejemplo, si el subsistema de críticas quiere información sobre un
usuario, no puede acceder directamente a la base de datos de usuarios. En lugar de eso, tiene
1.4. ARQUITECTURA ORIENTADA A SERVICIOS 19
que preguntar al servicio de usuarios, a través de la API que se proporcione para tal fin. Una
restricción similar se aplica a la compra de libros.
La “aplicación de venta de libros” es tan sólo una combinación particular de estos servi-
cios. Por lo tanto, se pueden volver a combinar estos servicios con otros para crear nuevas
aplicaciones. Por ejemplo, una aplicación de “mis libros favoritos” podría combinar el ser-
vicio de usuarios y críticas con una red social, de forma que un usuario pueda ver lo que
sus amigos de su red social piensan sobre los libros de los que ha escrito una crítica (ver
figura 1.7).
La distinción crítica de SOA es que ningún servicio puede nombrar o acceder a los datos
de otro servicio; sólo puede hacer peticiones de esos datos a través de una API externa. Si
los datos que quiere no están disponibles a través de la API, mala suerte. Hay que mencionar
que SOA no encaja con el modelo software tradicional de capas, en el que cada capa superior
se construye directamente a partir de las primitivas de la capa inmediatamente inferior en
un silo de software. SOA implica porciones verticales que abarcan varias capas, y estas
porciones se conectan entre ellas para formar un servicio. Mientras que SOA generalmente
conlleva un poco más de trabajo comparado con construir un servicio con un silo de software,
la recompensa es un gran potencial de reutilización. Otra ventaja de SOA es que las API
específicas hacen que las pruebas sean más sencillas.
SOA posee dos inconvenientes ampliamente aceptados. En primer lugar, cada invocación
del servicio involucra un mayor coste al tener que bucear a través de la profunda pila de soft-
ware de una interfaz de red, por lo que hay un impacto en el rendimiento en SOA. En segundo
lugar, mientras que un sistema silo es muy probable que esté totalmente inaccesible durante
un fallo, los ingenieros de software que usan SOA tienen que lidiar con el molesto caso de
fallos parciales, por lo que SOA provoca que la planificación de dependencias suponga un
reto mayor.
La enorme ventaja de SOA es que utilizamos pequeños servicios construidos con éxito, en
parte porque podemos usar metodologías ágiles para construirlos, para después combinarlos
para crear servicios más grandes.
Por desgracia, si el presidente Obama hubiera leído este capítulo a tiempo como para en-
viar un correo electrónico al estilo Bezos a los responsables de ACA antes de su lanzamiento,
la historia lo hubiera considerado un presidente con más éxito. Para los futuros presidentes
que se encuentren entre nuestros lectores: ¡persona precavida vale por dos!
Resumen. Aunque el término está casi perdido en un mar de confusión, una arquitectura
orientada a servicios (SOA) sólo es una estrategia para el desarrollo de software en la
que todos los subsistemas están únicamente disponibles como servicios externos, lo que
significa que se pueden combinar de diferentes formas. Utilizar las herramientas y pautas
de este libro hará que sus aplicaciones encajen en la estrategia SOA.
Autoevaluación 1.4.1. Otra perspectiva de SOA es que tan sólo es una estrategia de sentido
común para mejorar la productividad del programador. ¿Qué mecanismos de productividad
ejemplifican mejor SOA: claridad a través de concisión, síntesis, reutilización o automati-
zación mediante herramientas?
⇧ ¡Reutilización! El objetivo es hacer que las API internas sean visibles de forma que los
programadores puedan apoyarse en el hombro de otros.
20 CAPÍTULO 1. INTRODUCCIÓN A SAAS Y DESARROLLO ÁGIL
1. Puesto que los clientes no tienen que instalar la aplicación, no se tienen que preocupar
de si el hardware de su dispositivo es de la marca correcta o suficientemente rápido, así
como tampoco de si tienen la versión correcta del sistema operativo.
2. Los datos asociados al servicio se mantienen generalmente en el propio servicio, de
forma que los clientes no tienen que preocuparse de guardar copias de seguridad, de
perderlos por un fallo en el hardware local, o de incluso perder el dispositivo en sí,
como ocurre a veces con teléfonos o tablets.
3. Cuando un grupo de usuarios quieren interactuar de forma colectiva con el mismo
conjunto de datos, SaaS es una opción natural.
Figura 1.8. Ejemplos de entornos de programación SaaS y los lenguajes de programación en los que están escritos.
su ordenador. Otro ejemplo es TurboTax Online, que ofrece el mismo envoltorio para otro
producto clásico.
No resulta sorprendente, dada la popularidad de SaaS, la gran variedad de entornos de de-
sarrollo que pretenden ayudar. La figura 1.8 lista varios de ellos. En este libro usaremos Ruby
on Rails (“Rails”), aunque las ideas que cubriremos también aplicarán a otros entornos de de-
sarrollo. Hemos elegido Rails porque proviene de una comunidad que ya adoptó el ciclo de
vida ágil, por lo que sus herramientas funcionan especialmente bien para dicha metodología.
Ruby es un ejemplo típico de lenguaje moderno de scripting que incluye gestión de
memoria automática y tipado dinámico. Con la inclusión de avances importantes de los
lenguajes de programación, Ruby va más allá de lenguajes como Perl al soportar múltiples
paradigmas de programación tales como la orientación a objetos y la programación funcional.
Entre las características útiles adicionales que ayudan a mejorar la productividad a través
de la reutilización se incluyen los mix-ins, que congregan comportamientos relacionados y
facilitan su inclusión en muchas clases distintas, y la metaprogramación, que permite que
programas escritos en Ruby sinteticen código en tiempo de ejecución. La reutilización se
mejora también con el soporte de Ruby para clausuras a través de bloques y para yield. El
capítulo 3 contiene una pequeña descripción de Ruby para aquellos que ya conocen Java, y
el capítulo 4 es una introducción a Rails.
Además de nuestra opinión sobre la superioridad de Rails para la metodología ágil y
SaaS, Ruby y Rails son de uso común. Por ejemplo, Ruby aparece habitualmente entre los 10
lenguajes de programación más populares. Una aplicación SaaS bien conocida y asociada a
Rails es Twitter, que comenzó siendo una aplicación Rails en 2006 y creció desde los 20.000
tweets por día en 2007 hasta los 200.000.000 en 2011, tiempo durante el cual varias partes de
la aplicación han sido reemplazadas por otros entornos.
Para aquellos que no estén familiarizados aún con Ruby on Rails, esto les dará la oportuni-
dad de practicar una importante habilidad de la ingeniería del software: utilizar la herramienta
apropiada para cada trabajo, ¡incluso si eso significa aprender a utilizar una nueva herramien-
ta o lenguaje! De hecho, una característica atractiva de la comunidad de Rails es que sus
colaboradores mejoran constantemente la productividad inventando nuevas herramientas que
automatizan tareas realizadas manualmente en un principio.
Hay que mencionar que las habituales actualizaciones de una aplicación SaaS —debido a
que existe una única copia del software— se alinean perfectamente con el ciclo de vida ágil.
Por tanto, Amazon, eBay, Facebook, Google y otros proveedores SaaS confían en el ciclo
de vida ágil, y compañías de software tradicionales como Microsoft usan cada vez más la
metodología ágil en su desarrollo de productos. El proceso ágil encaja perfectamente con la
22 CAPÍTULO 1. INTRODUCCIÓN A SAAS Y DESARROLLO ÁGIL
Resumen. El software como servicio (SaaS) es una opción atractiva tanto para clientes
como para proveedores ya que el cliente universal (el navegador web) facilita que los
usuarios utilicen el servicio y la única versión del software en un sitio centralizado hace
más sencillo que el proveedor distribuya y mejore el servicio. Dada la habilidad y deseo
de actualizar frecuentemente las aplicaciones SaaS, el proceso de desarrollo ágil de soft-
ware es muy habitual, y por ello existen numerosos entornos de desarrollo que soportan
metodología ágil y SaaS. Este libro usará en concreto Ruby on Rails.
Autoevaluación 1.5.2. Verdadero o falso: si usa el proceso de desarrollo ágil para desarro-
John McCarthy llar aplicaciones SaaS, puede usar Python y Django o lenguajes basados en el entorno .NET
(1927–2011) recibió el
Premio Turing en 1971 y fue de Microsoft y ASP.NET en lugar de Ruby on Rails.
el creador de Lisp y pionero ⇧ Verdadero. Entre los entornos de trabajo para la metodología ágil y SaaS se incluyen
de los grandes ordenadores Django y ASP.NET.
de tiempo compartido. Los
clusters de hardware de Ahora que ya hemos hablado de SaaS haciendo hincapié en su apoyo en arquitecturas
consumo y el despliegue de
interconexiones rápidas han
orientadas a servicios, podemos pasar a ver el hardware subyacente que hace posible SaaS.
ayudado a a hacer de esta
visión de los “servicios de
computación” de tiempo 1.6 Computación en la nube
compartido una realidad.
Si los ordenadores del tipo por el que he apostado se convierten en los ordenadores del
futuro, tal vez la computación se organizará algún día como un servicio público tal y
como el sistema telefónico es un servicio público . . . El servicio de computación puede
convertirse en la base de una nueva e importante industria.
John McCarthy, en la celebración del centenario del MIT en 1961
Según crecían los centros de datos de Internet, algunos proveedores de servicios se dieron
cuenta de que sus costes per capita eran sustancialmente menores de lo que les costaba a
otros tener sus propios centros de datos más pequeños funcionando, en gran parte debido
a las economías de escala al comprar y controlar 100.000 ordenadores al mismo tiempo.
Asimismo se benefician de un uso mayor dado que muchas compañías podían compartir estos
24 CAPÍTULO 1. INTRODUCCIÓN A SAAS Y DESARROLLO ÁGIL
enormes centros de datos, lo que (Barroso and Hoelzle 2009) llamó ordenadores de escala
de almacén (Warehouse Scale Computers), mientras que centros de datos más pequeños a
veces funcionaban sólo a entre un 10% a un 20% de la capacidad total. Así, estas compañías
se dieron cuenta de que podían obtener beneficios haciendo disponible su hardware de centro
de datos en un régimen de pago por uso.
El resultado se conoce como servicios públicos en la nube o computación como ser-
vicio (utility computing), que ofrecen computación, almacenamiento, y comunicación a
céntimos por hora (Armbrust et al. 2010). Además, no existe coste adicional por escala: usar
1.000 ordenadores durante una hora no tiene un coste mayor que usar 1 ordenador durante
1.000 horas. Los principales ejemplos de computación con “capacidad de escala infinita” con
pago por uso son Amazon Web Services, Google AppEngine y Microsoft Azure. La nube
El rápido crecimiento
pública significa que a día de hoy cualquiera con una tarjeta de crédito y una buena idea puede
de FarmVille El récord
inicial en número de arrancar una compañía SaaS que pueda crecer hasta millones de usuarios sin tener primero
usuarios de un juego social que construir y hacer funcionar un centro de datos.
era de 5 millones. FarmVille Hoy llamamos a este viejo sueño de considerar la computación como un servicio público
tuvo un millón de jugadores
en los primeros 4 días tras
computación en la nube (Cloud Computing). Creemos que la computación en la nube
su anuncio, 10 millones tras y SaaS están cambiando el sector de la informática, y determinar todas las consecuencias
dos meses y 28 millones de de esta revolución llevará el resto de la década . De hecho, esta revolución es una de las
jugadores diarios y 75 razones por las que decidimos escribir este libro, ya que creemos que la ingeniería de SaaS
millones de jugadores
mensuales tras 9 meses.
para computación en la nube es totalmente diferente a la ingeniería del software empaquetado
Afortunadamente, FarmVille para ordenadores y servidores.
usó el servicio Elastic
Compute Cloud (EC2)
ofrecido por Amazon Web Resumen
Services, y mantuvo esta
popularidad sencillamente • Internet proporciona la comunicación para SaaS.
pagando por el uso de
clusters más grandes. • La computación en la nube proporciona la escalabilidad y fiabilidad necesarias
para el hardware de computación y almacenamiento que necesita SaaS.
• La computación en la nube consiste en clusters de servidores básicos que se
conectan mediante conmutadores de redes de área local, con una capa software que
proporciona la redundancia necesaria para hacer fiable este rentable hardware.
Autoevaluación 1.6.1. Verdadero o falso: los centros de datos internos pueden conseguir el
mismo ahorro de costes que los ordenadores de escala de almacén (Warehouse Scale Com-
puters, WSC) si usan SOA y adquieren el mismo tipo de hardware.
⇧ Falso. Aunque imitar las buenas prácticas de WSC podría reducir costes, la mejor ventaja
competitiva de los WSC proviene de las economías de escala, lo que a día de hoy se traduce
en 100.000 servidores, eclipsando a la mayoría de los centros de datos internos.
1.7. CÓDIGO ELEGANTE VS. CÓDIGO HEREDADO 25
sidades. Trataremos este tipo de software en este libro por tres razones. Primero, porque se
puede reducir el esfuerzo de construir un programa si se busca código existente que se pueda
reutilizar. Un proveedor es el software de código abierto. Segundo, porque resulta ventajoso
aprender cómo escribir código que facilite su mejora a sus sucesores, ya que incrementa las
posibilidades de que sea un software de vida larga. Finalmente, porque al contrario de lo
que ocurre con los ciclos de vida clásicos, en la metodología ágil se revisa el código con-
tinuamente para mejorar su diseño y para añadir funcionalidad desde la segunda iteración.
Por lo tanto, las habilidades que se practican con la metodología ágil son exactamente aque-
llas que se necesitan para posibilitar la evolución del código heredado —sin importar cuándo
fue creado— y el uso dual de las técnicas ágiles nos hace mucho más fácil cubrir el código
heredado en un sólo libro.
Resumen. El software exitoso puede durar décadas y se espera que evolucione y mejore,
al contrario de lo que ocurre con el hardware, que se finaliza en el momento de ser ma-
nufacturado y puede considerarse obsoleto a los pocos años. Uno de los objetivos de este
libro es enseñar cómo incrementar las posibilidades de producir código elegante de forma
que el software resultante tenga una vida larga y útil.
A continuación definiremos qué se entiende por calidad del software y veremos cómo
comprobar dicha calidad para incrementar las probabilidades de escribir código elegante.
Los prototipos software que constituyen el alma de la metodología ágil suelen ayudar con
la validación más que con la verificación, ya que los clientes a veces cambian de idea sobre
lo que quieren una vez que empiezan a ver el producto en funcionamiento.
La principal estrategia para la verificación y validación son las pruebas; la motivación Imposibilidad de las
tras las pruebas es que cuanto antes se encuentren los errores, más barato resultará reparar- pruebas exhaustivas
los. Dado el gran número de diferentes combinaciones de entradas, las pruebas no pueden Supongamos que probar un
programa lleva tan sólo un
ser exhaustivas. Una forma de reducir el espacio de combinaciones es realizar diferentes
nanosegundo y que
pruebas en diferentes fases del desarrollo de software. Empezando desde abajo hacia arriba, únicamente tiene una
las pruebas unitarias se aseguran de que un único procedimiento o método haga lo que se entrada de 64 bits que
espera. El siguiente nivel hacia arriba son las pruebas modulares, las cuales comprueban queremos probar de forma
exhaustiva. (Obviamente, la
el funcionamiento entre unidades individuales. Por ejemplo, las pruebas unitarias funcionan mayoría de programas
dentro de una sola clase mientras que las pruebas modulares funcionan a través de varias tardan más en ejecutar, y
clases. Encima de este nivel están las pruebas de integración, que aseguran que las in- tienen más entradas). Tan
terfaces entre unidades tienen suposiciones consistentes y se comunican correctamente. Este sólo este sencillo caso
necesitaría 264
nivel no prueba la funcionalidad de las unidades. En el nivel más alto están las pruebas nanosegundos, o ¡500 años!
de sistema o pruebas de validación, que comprueban que el programa integrado cumple
las especificaciones. En el capítulo 8, describiremos una alternativa a las pruebas, llamada
métodos formales.
Tal y como se mencionó brevemente en la sección 1.3, la estrategia de pruebas para la
versión XP de la metodología ágil es escribir las pruebas antes de escribir el código. Después
se escribe el código mínimo necesario para pasar la prueba, lo que asegura que el código
se prueba siempre y reduce las probabilidades de escribir código que fuera descartado. XP
divide esta filosofía de probar-primero en dos partes, dependiendo del nivel de las pruebas.
Para pruebas de sistema, validación e integración, XP utiliza el diseño guiado por com-
portamiento (Behavior-Driven Design, BDD), que ocupará el capítulo 7. Para las pruebas
unitarias y modulares, XP utiliza desarrollo orientado a pruebas (Test-Driven Development,
TDD), que será el tema del capítulo 8.
• En sus múltiples variantes, las pruebas ayudan a verificar que el software cumpla
las especificaciones y valida que el diseño haga lo que el cliente quiere.
• Para atacar la imposibilidad de realizar pruebas exhaustivas, dividiremos para
vencer centrándonos en pruebas unitarias, pruebas modulares, pruebas de inte-
gración, y pruebas de sistema o pruebas de validación. Cada prueba de nivel
superior delega comprobaciones más detalladas en las pruebas de niveles inferiores.
• La metodología ágil ataca las pruebas escribiéndolas antes de escribir el código,
usando un diseño guiado por comportamiento o bien diseño orientado a pruebas,
dependiendo del nivel de las pruebas.
Autoevaluación 1.8.1. Aunque todas las pruebas siguientes ayudan en la verificación, ¿qué
clase de pruebas son las que más ayudan con la validación: unitarias, modulares, de inte-
gración o de validación?
⇧ La validación se preocupa de que el software haga lo que el cliente realmente quiere, frente
a que el código cumpla las especificaciones, por lo que las pruebas de validación son las que
28 CAPÍTULO 1. INTRODUCCIÓN A SAAS Y DESARROLLO ÁGIL
con mayor probabilidad muestren la diferencia entre hacer algo bien y hacer lo correcto.
Tras esta revisión del aseguramiento de la calidad, veamos cómo hacer que los desarro-
lladores sean productivos.
La Ley de Moore significaba que los recursos hardware se duplicaban cada 18 meses
durante cerca de 50 años. Estos ordenadores mucho más rápidos y con memorias mucho
más extensas podían ejecutar programas mucho más grandes. Para construir aplicaciones
más grandes que pudieran aprovechar la potencia de estos ordenadores, los ingenieros de
software tuvieron que mejorar su productividad.
Los ingenieros desarrollaron cuatro mecanismos fundamentales para mejorar su produc-
tividad:
John Backus
La segunda versión (que en Ruby es legal) es, sin lugar a dudas, más corta y fácil de leer (1924–2007) recibió el
y entender, y probablemente será más fácil de mantener. Es sencillo imaginar una confusión Premio Turing en 1977 en
momentánea sobre el orden de los argumentos en la primera versión, además de una mayor parte por sus “profundas,
carga cognitiva de leer el doble de caracteres (ver capítulo 3). influyentes y duraderas
contribuciones al diseño de
La otra forma de mejorar la claridad es elevar el nivel de abstracción. Esto significó sistemas de programación
inicialmente la invención de lenguajes de programación de más alto nivel como Fortran y prácticos de alto nivel, en
COBOL. Este paso elevó la ingeniería del software desde el lenguaje ensamblador para un particular a través de su
trabajo en Fortran”, primer
ordenador concreto a lenguajes de más alto nivel que podían funcionar en múltiples orde-
lenguaje de programación
nadores cambiando sencillamente el compilador. de alto nivel usado
Con el continuo progreso de la eficiencia del hardware, cada vez más programadores extensamente.
estaban dispuestos a delegar tareas al compilador y el sistema en tiempo de ejecución de las
que hacían inicialmente de forma manual. Por ejemplo, Java y otros lenguajes similares se
hicieron cargo de la gestión de memoria, diferenciándose de lenguajes anteriores como C y
C++. Los lenguajes de scripting como Python y Ruby han aumentado aún más el nivel de
abstracción. Algunos ejemplos son la reflexión, que permite que los programas se observen
a sí mismos, y la metaprogramación, que permite a los programas modificar su propia
estructura y comportamiento en tiempo de ejecución. Para resaltar algunos ejemplos que
mejoran la productividad a través de la concisión, usaremos este icono “Conciso”.
El segundo mecanismo de productividad es la síntesis; es decir, la implementación es
generada automáticamente en lugar de creada manualmente. La síntesis lógica para los in-
genieros de hardware significaba que podían describir el hardware como funciones de lógica
y obtener transistores muy optimizados que implementaran dichas funciones. El ejemplo
clásico de síntesis de software es Bit blit. Esta primitiva gráfica combina dos mapas de bits
bajo el control de una máscara. La estrategia más inmediata incluiría una expresión condi-
cional en el bucle más interno para elegir el tipo de máscara, pero este procedimiento era
lento. La solución consistió en escribir un programa que pudiera sintetizar el código apro-
piado de propósito específico sin la expresión condicional en el bucle. Haremos hincapié en
ejemplos que mejoren la productividad generando código con este icono “CodeGen” de un
engranaje.
El tercer mecanismo de productividad es reutilizar fragmentos de diseños anteriores en
lugar de escribir todo desde cero. Dado que es más fácil hacer pequeños cambios en software
que en hardware, el software se presta más que el hardware a reutilizar componentes que
no encajan del todo. Marcaremos los ejemplos que mejoran la productividad a través de la
reutilización con este icono “Reutilización”, que apunta al reciclado.
Los métodos y funciones se inventaron en los primeros días del software para que dife-
rentes partes del programa pudieran reutilizar el mismo código con distintos valores de los
parámetros. Liberarías estándar para las funciones de entrada/salida y para las funciones
matemáticas fueron el siguiente paso, de forma que los programadores pudieran reutilizar el
código escrito por otros.
Los métodos de las librerías permiten reutilizar implementaciones de tareas individuales.
Sin embargo, lo más común es que los programadores quieran reutilizar y gestionar coleccio-
nes de tareas. El siguiente paso en la reutilización de software fue por tanto la programación
orientada a objetos, donde se puede reutilizar la misma tarea con distintos objetos a través
del uso de herencia en lenguajes como C++ y Java.
Mientras que la herencia apoyaba la reutilización de las implementaciones, otra opor-
tunidad de reutilización es una estrategia general para hacer algo incluso aunque la imple-
mentación cambie. Los patrones de diseño, inspirados por trabajos en ingeniería civil
30 CAPÍTULO 1. INTRODUCCIÓN A SAAS Y DESARROLLO ÁGIL
(Alexander et al. 1977), emergieron para cubrir esta necesidad. El soporte de lenguajes de
programación para reutilizar patrones de diseño incluye el tipado dinámico, lo que facilita
composiciones y abstracciones, y los mix-ins, los cuales ofrecen la posibilidad de obtener
la funcionalidad de múltiples métodos sin ninguna de las patologías derivadas del uso de
la herencia múltiple, que sufren algunos lenguajes de programación orientados a objetos.
Python y Ruby son ejemplos de lenguajes con características que ayudan a reutilizar patrones
de diseño.
Hay que resaltar que reutilizar no significa copiar y pegar código de forma que se tenga
código muy similar en muchos sitios. El problema derivado de copiar y pegar código es que
puede que no se cambien todas las copias del código cuando se trata de arreglar un error o
de añadir una nueva funcionalidad. He aquí una directriz de la ingeniería del software que
protege de la repetición:
Cada fragmento de conocimiento debe estar contenido en una representación única, ine-
quívoca y acreditada dentro de un sistema.
Andy Hunt and Dave Thomas, 1999
Esta directriz está capturada en el lema y acrónimo: no se repita (Don’t Repeat Yourself,
DRY ). Utilizaremos el icono de una toalla para mostrar ejemplos de DRY en los próximos
capítulos (dry significa secar en inglés, por ello elegimos la toalla para representar este lema).
Resumen. La Ley de Moore inspiró a los ingenieros de software para mejorar su produc-
tividad mediante:
Autoevaluación 1.9.1. ¿Qué mecanismo supone el argumento más débil en lo que se refiere
a beneficios relativos a la productividad en compiladores y lenguajes de programación de
alto nivel: claridad a través de concisión, síntesis, reutilización o automatización mediante
herramientas?
⇧ Los compiladores hacen que los lenguajes de programación de alto nivel sean prácti-
cos, permitiendo a los programadores mejorar la productividad mediante la programación de
código más conciso en un lenguaje de alto nivel. Además, sintetizan el código a bajo nivel
basándose en lo que reciben como entrada en el lenguaje de alto nivel. Los compiladores son
definitivamente herramientas. Aunque se puede discutir si los lenguajes de alto nivel facili-
tan la reutilización, en este caso éste sería el beneficio más débil de los cuatro mencionados
anteriormente para el caso de los compiladores.
Con esta introducción a nuestras espaldas, podemos explicar lo que sigue y los caminos
que se pueden tomar a partir de aquí. Para hacer y entender, tal y como aconseja Confucio,
32 CAPÍTULO 1. INTRODUCCIÓN A SAAS Y DESARROLLO ÁGIL
Legacy (Ch. 9)
Figura 1.9. Una iteración del ciclo de vida ágil del software y su relación con los capítulos de este libro. Las flechas de líneas
discontinuas indican una relación más tangencial entre los pasos de una iteración, mientras que las flechas de líneas
continuas indican el flujo habitual. Como se ha mencionado previamente, el proceso ágil se da tanto en aplicaciones
existentes heredadas como en nuevas aplicaciones, aunque el cliente pueda desempeñar un papel menor en las aplicaciones
heredadas.
hay que empezar leyendo el Apéndice A. En él se explica cómo obtener y usar la biblioteca
de recursos del libro.
El resto del libro está dividido en dos partes. La primera parte cubre el tema de software
como servicio, mientras que la segunda trata sobre desarrollo de software moderno, con un
marcado énfasis en la metodología ágil.
El capítulo 2 inicia la parte I con una explicación de la arquitectura de una aplicación
SaaS, utilizando una analogía basada en altitudes, partiendo desde una vista a 100.000 pies
de altura hasta los 500 pies de altura. Durante el descenso aprenderemos la definición de
numerosos acrónimos que le pueden resultar ya familiares —API, CSS, IP, REST, TCP, URL,
URI y XML— así como algunas palabras de moda: cookies, lenguajes de marcado, números
de puerto y arquitecturas de tres capas. Aún más importante, este capítulo demuestra la
importancia de los patrones de diseño, y en particular del patrón modelo-vista-controlador
que constituye el corazón de Rails.
En lugar de contarle únicamente cómo construir software duradero y ver cómo lo olvida,
creemos que usted debe practicar para entender. Es mucho más fácil practicar buenas direc-
trices si las herramientas animan a ello, y creemos que las mejores herramientas para SaaS a
1.10. RECORRIDO GUIADO POR EL LIBRO 33
día de hoy soportan el entorno de desarrollo de Rails, escrito en Ruby. Por lo tanto, en el capí-
tulo 3 se presenta una introducción a Ruby. Dicha introducción es breve, ya que asume que
el lector ya conoce otro lenguaje de programación orientado a objetos, en este caso Java. Tal
y como se mencionó anteriormente, creemos que los ingenieros de software exitosos nece-
sitarán aprender nuevos lenguajes y herramientas habitualmente durante sus carreras, por lo
que aprender Ruby y Rails constituye una buena práctica.
A continuación, el capítulo 4 introduce aspectos básicos de Rails, mientras que los aspec-
tos más avanzados de Rails se comentarán en el capítulo 5. Hemos dividido el material en dos
capítulos para que aquellos lectores que lo deseen puedan empezar a escribir aplicaciones tan
pronto como les sea posible, lo cual sólo requiere la lectura del capítulo 4. Aunque aprender
y comprender el material presentado en el capítulo 5 constituye un reto mayor, las aplica-
ciones que desarrolle pueden repetir menos fragmentos de código (DRY) y ser más concisas
si usa conceptos como vistas parciales, validaciones, métodos de callback del ciclo de vida,
filtros, asociaciones y claves foráneas. Aquellos lectores ya familiarizados con Ruby y Rails
deberían omitir estos capítulos.
Una vez familiarizados con Ruby y Rails, el capítulo 6 introduce el lenguaje de progra-
mación JavaScript, su productivo entorno jQuery y la herramienta de pruebas Jasmine. Al
igual que el entorno de desarrollo de Rails amplifica la potencia y productividad de Ruby
para crear el lado servidor de las aplicaciones SaaS, el entorno jQuery amplifica la potencia
y productividad de JavaScript para mejorar el lado cliente. Y tal y como RSpec hace posible
escribir potentes pruebas automatizadas para aumentar nuestra confianza en nuestro código
de Ruby y Rails, Jasmine hace posible escribir pruebas similares para incrementar nuestra
confianza en nuestro código JavaScript.
Con estos antecedentes, los siguientes seis capítulos de la parte II ilustran importantes
principios de la ingeniería del software usando herramientas de Rails para construir y desple-
gar una aplicación SaaS. La figura 1.9 muestra una iteración del ciclo de vida ágil, la cual
usaremos como entorno en el que colocar los próximos capítulos del libro.
El capítulo 7 trata sobre cómo hablar con el cliente. El diseño guiado por compor-
tamiento (Behavior-Driven Design, BDD) apuesta por escribir pruebas de validación
que clientes sin nociones de programación puedan entender, denominadas historias de
usuario, y el capítulo 7 muestra cómo escribirlas de forma que se puedan convertir además
en pruebas de integración. Para ayudar a automatizar esta tarea se introduce también la herra-
mienta Cucumber . Esta herramienta, que facilita la escritura de las pruebas, se puede usar
con cualquier lenguaje y entorno de desarrollo, no sólo con Rails. Dado que las aplicaciones
SaaS suelen estar de cara al usuario, el capítulo también cubre cómo realizar un prototipo de
una interfaz gráfica útil usando el prototipo “Lo-Fi”. También hace referencia al concepto de
velocidad y cómo usarlo para medir el progreso como la tasa en la que se entregan nuevas
características, introduciendo una herramienta basada en SaaS, Pivotal Tracker para seguir
y calcular dichas medidas.
El capítulo 8 cubre el tema del desarrollo orientado a pruebas ( Test-Driven Devel-
opment, TDD). El capítulo demuestra cómo escribir código elegante, que se pueda probar,
e introduce la herramienta de pruebas RSpec para escribir pruebas unitarias, la herramienta
Autotest para automatizar la ejecución de pruebas y la herramienta SimpleCov para medir la
cobertura de pruebas.
El capítulo 9 describe cómo trabajar con código existente, incluyendo cómo mejorar
código heredado. Además, muestra cómo usar BDD y TDD para entender y refactorizar
código, y cómo usar las herramientas Cucumber y RSpec para facilitar esta tarea.
34 CAPÍTULO 1. INTRODUCCIÓN A SAAS Y DESARROLLO ÁGIL
cada capítulo son más abiertos que las preguntas de autoevaluación. Para que los lectores
tengan una perspectiva de a quién se le ocurrieron las grandes ideas que están aprendiendo
y en las que se apoyan las tecnologías de la información, utilizaremos recuadros en los már-
genes para presentar a 20 ganadores del Premio Turing. (Dado que no hay Premio Nobel en
IT, nuestro mayor honor es conocido como el “Premio Nobel de la Computación”. O aún
mejor, deberíamos llamar al Premio Nobel el “Premio Turing de Física”).
Hemos decidido de forma deliberada que el libro sea conciso, dado que los distintos
lectores querrán ampliar algunos detalles en diferentes áreas. Proporcionamos enlaces a la
documentación en línea sobre las clases y métodos de Ruby y Rails, a definiciones de concep-
tos importantes con los que puede no estar familiarizado, y en general a la Web para ampliar
la información relacionada con el material. Para la versión Kindle del libro, los enlaces de-
berían funcionar estando conectado a Internet; en la versión impresa, las direcciones de los
enlaces aparecen al final de cada capítulo.
Señor, danos la sabiduría para pronunciar palabras amables y dulces, porque mañana
puede que tengamos que tragárnoslas.
Sen. Morris Udall
que quiere el cliente. Sin embargo, esta perspectiva ignora el coste asociado al diseño y las
pruebas, que puede constituir una parte substancial del coste total de los proyectos software.
El coste cero de producción es también un pensamiento utilizado para justificar las copias
ilegales de software y otros datos electrónicos, dado que aquellos que se dedican a la piratería
aparentemente creen que nadie debería pagar por los costes del desarrollo, sólo por los de
producción.
Esta cita da una buena idea de los fundamentos que hay detrás de la versión de la
metodología ágil llamada programación extrema (eXtreme Programming, XP), que tratamos
en este libro. Mantenemos las iteraciones cortas, de forma que el cliente vea la siguiente
versión del prototipo incompleta pero funcionando cada una o dos semanas. Las pruebas
se escriben antes que el código, para pasar después a escribir la mínima cantidad de código
posible que permita pasar las pruebas. La programación en pareja significa que el código
está bajo constante revisión, en lugar de sólo en ocasiones especiales. La metodología ágil
ha pasado de ser una herejía de las metodologías software a la manera predominante de pro-
gramar en tan sólo una docena de años, y cuando se combina con una arquitectura orientada
a servicios, hace posible la implementación fiable de servicios complejos.
Si bien no hay una dependencia intrínseca entre SaaS, metodología ágil y entornos de
desarrollo altamente productivos como Rails, la figura 1.11 sugiere que existe una sinergia
entre ellas. El desarrollo ágil significa un progreso continuo mientras se trabaja de cerca con
el cliente, y SaaS apoyado sobre computación en la nube hacen posible que el cliente utilice
la última versión de forma inmediata, cerrando de esta forma el bucle de realimentación
(ver capítulos 7 y 12). SaaS y la computación en la nube encajan con el patrón de diseño
modelo–vista–controlador (ver capítulo 11), y que es expuesto por los entornos de desarrollo
altamente productivos para SaaS (ver capítulos 2, 4 y 5). Estos entornos y herramientas
eficientes diseñados para soportar el desarrollo ágil eliminan los obstáculos para practicar
dicha metodología (ver capítulos 7, 8 y 10). Creemos que estas tres “joyas de la corona”
forman un “triángulo virtuoso” que conducen hacia el desarrollo de buen software como
servicio a tiempo y dentro de presupuesto, y constituyen las bases de este libro.
Este triángulo virtuoso ayuda también a explicar el carácter innovador de la comunidad
de Rails, donde nuevas e importantes herramientas se desarrollan habitualmente para mejorar
38 CAPÍTULO 1. INTRODUCCIÓN A SAAS Y DESARROLLO ÁGIL
SAAS ON
CLOUD
COMPUTING
ENGINEERING
SAAS
HIGHLY
PRODUCTIVE AGILE
FRAMEWORKS DEVELOPMENT
& TOOLS
Framework and
tools remove obstacles
to practicing Agile development
Figura 1.11. El triángulo virtuoso del desarrollo SaaS está formado por las tres joyas de la corona de la ingeniería del
software (1) Saas en computación en la nube, (2) desarrollo ágil y (3) entornos de desarrollo y herramientas altamente
productivos.
1.14. PARA SABER MÁS 39
la productividad, sencillamente por lo fácil que resulta hacerlo. Esperamos que futuras edi-
ciones de este libro incluyan herramientas que no existen a día de hoy y que serán tan útiles
que, ¡no podremos ni imaginar cómo habremos podido trabajar sin ellas!
Como profesores, dado que las metodologías clásicas les parecen tediosas a muchos es-
tudiantes, estamos encantados al ver que las respuestas a las 10 preguntas planteadas en la
figura 1.5 recomiendan encarecidamente el uso de la metodología ágil para los proyectos
estudiantiles. Sin embargo, creemos que merece la pena que las metodologías clásicas les
resulten familiares a los lectores, ya que encajan mejor en algunas tareas, algunos clientes las
requieren y ayudan a explicar algunas partes del enfoque ágil. Por tanto, incluimos algunas
secciones cerca del final de todos los capítulos de la parte II para ofrecer la perspectiva de las
metodologías clásicas.
Como investigadores, estamos convencidos de que el software del futuro se desarrollará
y apoyará cada vez más en servicios en la nube, por lo que la metodología ágil continuará
creciendo en popularidad en parte gracias a la gran sinergia existente entre ambas. Es por ello
que estamos en un punto álgido de la tecnología donde el futuro del desarrollo de software es
más ameno tanto de aprender como de enseñar. Entornos de desarrollo altamente productivos
como Rails permiten entender esta valiosa tecnología permitiendo desarrollar en tiempos
increíblemente cortos. La razón principal por la que hemos escrito este libro es para ayudar
a más gente a tener conciencia de ello y aprovechar esta extraordinaria oportunidad.
Creemos que si usted aprende los contenidos de este libro y utiliza la biblioteca de recur-
sos que viene con él, podrá construir su propia versión (simplificada) de un servicio software
popular como FarmVille o Twitter mientras aprende y sigue robustas prácticas de desarro-
llo software. Aunque ser capaz de imitar servicios que disfrutan de gran éxito hoy en día y
desplegarlos en la nube en unos pocos meses es impactante, estamos aún más expectantes
por ver lo que usted creará con este conjunto de nuevas habilidades. ¡Esperamos ver su
código elegante convirtiéndose en algo duradero y convertirnos en unos de sus más fervientes
seguidores!
S. Begley. As Obamacare tech woes mounted, contractor payments soared. Reuters, Oc-
tober 17, 2013. URL http://www.nbcnews.com/politics/politics-news/stress-
tests-show-healthcare-gov-was-overloaded-v21337298.
J. Bidgood. Massachusetts appoints official and hires firm to fix exchange problems. New
York Times, February 7, 2014. URL http://www.nytimes.com/news/affordable-
care-act/.
B. W. Boehm. Software engineering: R & D trends and defense needs. In P. Wegner, editor,
Research Directions in Software Technology, Cambridge, MA, 1979. MIT Press.
B. W. Boehm. A spiral model of software development and enhancement. In ACM SIGSOFT
Software Engineering Notes, 1986.
E. Braude. Software Engineering: An Object-Oriented Perspective. John Wiley and Sons,
2001. ISBN 0471692085.
R. Charettte. Why software fails. IEEE Spectrum, 42(9):42–49, September 2005.
L. Chung. Too big to fire: How government contractors on HealthCare.gov maximize prof-
its. FMS Software Development Team Blog, December 7, 2013. URL http://blog.
fmsinc.com/too-big-to-fire-healthcare-gov-government-contractors.
M. Cormick. Programming extremism. Communications of the ACM, 44(6):109–110, June
2001.
H.-C. Estler, M. Nordio, C. A. Furia, B. Meyer, and J. Schneider. Agile vs. structured
distributed software development: A case study. In Proceedings of the 7th International
Conference on Global Software Engineering (ICGSE’12)), pages 11–20, 2012.
ET Bureau. Need for speed: More it companies switch to agile code development. The Eco-
nomic Times, August 6, 2012. URL http://articles.economictimes.indiatimes.
com/2012-08-06/news/33065621_1_thoughtworks-software-development-
iterative.
M. Fowler. The New Methodology. martinfowler.com, 2005. URL http://www.
martinfowler.com/articles/newMethodology.html.
E. Harrington. Hearing: Security flaws in Obamacare website endanger AmericansHealth-
Care.gov. Washington Free Beacon, 2013. URL http://freebeacon.com/hearing-
security-flaws-in-obamacare-website-endanger-americans/.
S. Horsley. Enrollment jumps at HealthCare.gov, though totals still lag. NPR.org, Decem-
ber 12, 2013. URL http://www.npr.org/blogs/health/2013/12/11/250023704/
enrollment-jumps-at-healthcare-gov-though-totals-still-lag.
A. Howard. Why Obama’s HealthCare.gov launch was doomed to fail. The Verge, Octo-
ber 8, 2013. URL http://www.theverge.com/2013/10/8/4814098/why-did-the-
tech-savvy-obama-administration-launch-a-busted-healthcare-website.
C. Johnson and H. Reed. Why the government never gets tech right. New York Times, Octo-
ber 24, 2013. URL http://www.pmi.org/en/Professional-Development/Career-
Central/Must_Have_Skill_Agile.aspx.
REFERENCIAS 41
J. Johnson. The CHAOS report. Technical report, The Standish Group, Boston, Mas-
sachusetts, 1995. URL http://blog.standishgroup.com/.
J. Johnson. HealthCare.gov chaos. Technical report, The Standish Group, Boston,
Massachusetts, October 22, 2013a. URL http://blog.standishgroup.com/images/
audio/HealthcareGov_Chaos_Tuesday.mp3.
J. Johnson. The CHAOS manifesto 2013: Think big, act small. Technical report, The
Standish Group, Boston, Massachusetts, 2013b. URL http://www.standishgroup.com.
W. W. Royce. Managing the development of large software systems: concepts and tech-
niques. In Proceedings of WESCON, pages 1–9, Los Angeles, California, August 1970.
I. Sommerville. Software Engineering, Ninth Edition. Addison-Wesley, 2010. ISBN
0137035152.
M. Stephens and D. Rosenberg. Extreme Programming Refactored: The Case Against XP.
Apress, 2003.
42 NOTAS
M. Swaine. Back to the future: Was Bill Gates a good programmer? What does Prolog have
to do with the semantic web? And what did Kent Beck have for lunch? Dr. Dobb’s The
World of Software Development, 2001. URL http://www.drdobbs.com/back-to-the-
future/184404733.
A. Taylor. IT projects sink or swim. BCS Review, Jan. 2000. URL http://archive.bcs.
org/bulletin/jan00/article1.htm.
F. Thorp. ‘Stress tests’ show HealthCare.gov was overloaded. NBC News, November 18,
2013. URL http://www.nbcnews.com/politics/politics-news/stress-tests-
show-healthcare-gov-was-overloaded-v21337298.
J. Zients. HealthCare.gov progress and performance report. Technical report, Health and
Human Services, December 1, 2013. URL http://www.hhs.gov/digitalstrategy/
sites/digitalstrategy/files/pdf/healthcare.gov-progress-report.pdf.
Notas
1 http://es.wikipedia.org/wiki/Premio_Turing
2 https://www.edx.org/
3 http://www.youtube.com/watch?v=DeBi2ZxUZiM
4 http://www.youtube.com/watch?v=kYUrqdUyEpI
5 https://plus.google.com/112678702228711889851/posts/eVeouesvaVX
6 http://developers.slashdot.org/story/08/05/11/1759213/
7 http://www.pastebin.com/u/saasbook
Ejercicio 1.7. (Debate) Medido en líneas de código, ¿cuál es el programa más largo del
mundo? A efectos de este ejercicio, asuma que puede ser un conjunto de software empaque-
tado como un producto.
Ejercicio 1.8. (Debate) ¿Qué lenguaje de programación cuenta con el mayor número de
programadores activos?
Ejercicio 1.9. (Debate) ¿En qué lenguaje de programación se escribe el mayor número de
líneas de código al año? ¿Cuál tiene el mayor número de líneas de código activo acumulati-
vamente?
Ejercicio 1.10. (Debate) Haga una lista de las 10 aplicaciones más importantes, en su
opinión. ¿Cuál se desarrollaría y mantendría mejor utilizando cada uno de los cuatro ci-
clos de vida expuestos en este capítulo? Describa las razones para cada elección.
Ejercicio 1.11. (Debate) Teniendo en cuenta la lista de las 10 aplicaciones más importantes
del ejercicio anterior, ¿cómo de importantes son cada una de las cuatro técnicas de produc-
tividad descritas en este capítulo?
Ejercicio 1.12. (Debate) Dada la lista de las 10 aplicaciones más importantes del ejercicio
anterior, ¿qué aspectos serían difíciles de probar y tendrían que confiar en métodos for-
males? ¿Podrían ser más importantes algunas técnicas de pruebas que otras, para alguna
de las aplicaciones? Exponga sus motivos.
Ejercicio 1.17. Describa las diferencias entre los principios del modelo en cascada y los
modelos clásicos que utilizan iteraciones.
Ejercicio 1.18. Describa las diferentes prácticas que constituyen los componentes princi-
pales de la metodología ágil y los distintos modelos clásicos.
Ejercicio 1.19. Diferencie las fases de desarrollo software de los modelos clásicos.
44 NOTAS
Parte I
Dennis Ritchie Creo que la gran idea de Unix fue su interfaz simple y limpia: abrir, cerrar, leer y escribir.
(izquierda,
1941–2011) y Ken
Thompson (derecha, Unix and Beyond: An Interview With Ken Thompson, IEEE Computer 32(5), Mayo 1999
1943–) compartieron el
Premio Turing en 1983 por
sus contribuciones 2.1 100.000 pies: arquitectura cliente-servidor . . . . . . . . . . . . . . . 48
fundamentales al diseño de
2.2 50.000 pies: Comunicación —HTTP y los URI— . . . . . . . . . . . 50
sistemas operativos en
general y a la invención de 2.3 10.000 pies: representación —HTML y CSS— . . . . . . . . . . . . . 54
Unix en particular. 2.4 5.000 pies: arquitectura de 3 capas y escalado horizontal . . . . . . . 58
2.5 1.000 pies: arquitectura modelo-vista-controlador . . . . . . . . . . . 61
2.6 500 pies: Active Record para los modelos . . . . . . . . . . . . . . . . 64
2.7 500 pies: rutas, controladores y REST . . . . . . . . . . . . . . . . . 66
2.8 500 pies: Template View . . . . . . . . . . . . . . . . . . . . . . . . . 71
2.9 Falacias y errores comunes . . . . . . . . . . . . . . . . . . . . . . . 72
2.10 Patrones, arquitectura y las API de larga duración . . . . . . . . . . 73
2.11 Para saber más . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
2.12 Ejercicios propuestos . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
47
Conceptos
La arquitectura de software describe cómo se conectan entre sí los subsistemas que
conforman un programa informático para cumplir los requisitos funcionales y no fun-
cionales de la aplicación. Un patrón de diseño describe una solución arquitectónica
genérica para una familia de problemas similares, obtenida generalizando a partir de
la experiencia de desarrolladores que han solucionado previamente dichos problemas.
Si se examinan las aplicaciones SaaS, los patrones de diseño resultan evidentes en todos
los niveles de detalle:
Los entornos SaaS actuales, como Rails, reúnen una década de valiosa experiencia
de desarrollo encapsulando estos patrones de diseño de forma que los creadores de
aplicaciones SaaS los puedan aplicar fácilmente.
48 CAPÍTULO 2. ARQUITECTURA DE LAS APLICACIONES SAAS
1. A Web client (Firefox) requests the Rotten Potatoes home page from a Web server (WEBrick).
2. WEBrick obtains content from the Rotten Potatoes app and sends this content back to Firefox
¿Qué está ocurriendo? Acabamos de ver la perspectiva más sencilla de una aplicación
web: es un ejemplo de la arquitectura cliente-servidor . Firefox es un ejemplo de cliente:
un programa cuya especialidad es pedir información al servidor y (generalmente) permitir al
usuario interactuar con dicha información. WEBrick, que se activó al teclear rails server,
es un ejemplo de servidor: un programa cuya especialidad es esperar a que los clientes reali-
cen una petición para proporcionarles una respuesta. WEBrick espera a que algún navegador
como Firefox contacte con él y dirige las peticiones del navegador a la aplicación Rotten-
Potatoes. La figura 2.1 resume cómo funciona una aplicación SaaS, vista a 100.000 pies de
altura.
Diferenciar clientes y servidores permite que cada tipo de programa esté altamente es-
pecializado en su tarea: el cliente puede tener una interfaz atractiva y de rápida respuesta,
mientras que el servidor se concentra en atender de forma eficiente a muchos clientes si-
multáneamente. Firefox y otros navegadores (Chrome, Safari, Internet Explorer) son clientes
utilizados por millones de personas (a los que llamaremos clientes de producción). WE-
Brick, por otra parte, no es un servidor de producción, sino un “miniservidor” con la fun-
cionalidad justa para permitir que un usuario por vez (usted, el programador) interaccione
con la aplicación web. Un sitio web real utilizaría un servidor de producción como el servi-
dor web Apache3 o Microsoft Internet Information Server4 , cualquiera de los cuales puede
desplegarse en cientos de ordenadores sirviendo eficientemente a millones de usuarios con
numerosas copias del mismo sitio.
2.1. 100.000 PIES: ARQUITECTURA CLIENTE-SERVIDOR 49
Site
§2.1 100,000 feet Browser
rotten-
• Client-server (vs. P2P) (Firefox, Internet
potatoes.
Chrome...)
com
§2.2 50,000 feet
• HTTP & URIs
§2.3 10,000 feet
Web
• XHTML & CSS server App
html Database
(Apache, server (Postgres,
§2.4 5,000 feet css Microsoft IIS, (rack) SQLite)
• 3-tier architecture WEBrick)
• Horizontal scaling Logic tier Persistence
Presentation tier
tier
§2.6 500 feet: Active Record models (vs. Data Mapper) • Active Record • REST • Template View
§2.7 500 feet: RESTful controllers (Representational • Data Mapper • Transform View
State Transfer for self-contained actions)
§2.8 500 feet: Template View (vs. Transform View)
Figura 2.2. Utilizando la altitud como analogía, esta figura ilustra estructuras SaaS importantes a varios niveles de detalle, y
sirve como hoja de ruta global de la exposición que se hará en este capítulo. Cada nivel se tratará en las secciones indicadas.
Antes de que se propusieran estándares abiertos para la web en 1990, los usuarios tenían
que instalar clientes propietarios diferentes y mutuamente incompatibles para cada servicio
de Internet que utilizaban: Eudora (el predecesor de Thunderbird) para leer el correo elec-
trónico, AOL o CompuServe para acceder a portales de contenido propietario (papel desem-
peñado a día de hoy por portales como MSN y Yahoo), etc. Hoy en día el navegador web ha
suplantado en gran parte a los clientes propietarios y se puede llamar de forma justificada el
“cliente universal”. No obstante, los clientes y servidores propietarios siguen constituyendo
ejemplos de la arquitectura cliente-servidor, con clientes especializados en realizar preguntas
en nombre de los usuarios, y servidores especializados en responder a preguntas recibidas de
numerosos clientes. La arquitectura cliente-servidor es, por tanto, nuestro primer ejemplo de
patrón de diseño —una estructura, comportamiento, estrategia o técnica reutilizable que
captura una solución probada a una colección de problemas similares mediante la separación
de aspectos que cambian de aquellos que permanecen iguales—. En el caso de las arqui-
tecturas cliente-servidor, lo que permanece inmutable es la separación de responsabilidades
entre el cliente y el servidor, a pesar de los cambios entre implementaciones de clientes y
servidores. Debido a la ubicuidad de la web, utilizaremos el término SaaS para referirnos a
“sistemas cliente-servidor construidos para trabajar utilizando estándares abiertos de la World
Wide Web”.
En el pasado, la arquitectura cliente-servidor implicaba que el servidor era un programa
mucho más complejo que el cliente. Hoy en día, con potentes ordenadores y navegadores web
que soportan animaciones y efectos 3D, una caracterización más fiel sería que los clientes y
servidores comparten una complejidad comparable aunque se han especializado en sus di-
ferentes roles. En este libro nos concentraremos en aplicaciones centradas en el servidor;
aunque cubriremos algunos aspectos de programación JavaScript en el lado cliente en el
50 CAPÍTULO 2. ARQUITECTURA DE LAS APLICACIONES SAAS
Resumen
• Las aplicaciones web SaaS son ejemplos del patrón de arquitectura cliente-
servidor , en el cual el software cliente generalmente está especializado en interac-
tuar con el usuario y enviar peticiones al servidor en su nombre, mientras que el
software servidor está especializado en el manejo de grandes volúmenes de dichas
peticiones.
• Puesto que las aplicaciones web utilizan estándares abiertos que cualquiera puede
implementar libre de cargos, a diferencia de los estándares propietarios usados en
las antiguas aplicaciones cliente-servidor, el navegador web se ha convertido en el
“cliente universal”.
• Una alternativa a la arquitectura cliente-servidor es la arquitectura peer-to-peer, en
la que todas las entidades actúan como clientes y como servidores. Aunque podría
discutirse si esta arquitectura es más flexible, también dificulta la especialización
del software para realizar realmente bien cualquiera de los dos trabajos.
¿Qué hay del :3000 que añadimos al final de localhost en el ejemplo? Múltiples
agentes de una red pueden estar ejecutándose en la misma dirección IP. De hecho, en el
ejemplo anterior, tanto el cliente como el servidor se estaban ejecutando en el mismo orde-
nador (el suyo). Por lo tanto, TCP/IP utiliza números de puerto, entre 1 y 65535, para
distinguir entre diferentes agentes de red bajo la misma dirección IP. Todos los protocolos IANA. Internet Assigned
basados en TCP/IP, incluyendo HTTP, deben especificar tanto la máquina (host) como el Numbers Authority6 es la
entidad encargada de
puerto (port) cuando inician una conexión. Cuando el usuario le pide a Firefox que vaya a asignar números de puerto
localhost:3000/movies, le está indicando que en la máquina localhost (es decir, “este por defecto oficiales para
ordenador”), un programa servidor está escuchando en el puerto 3000 esperando a que los numerosos protocolos, y
navegadores le contacten. Si no se especifica el puerto (3000) de forma explícita, se usará gestiona el nivel más alto o
zona raíz (“root”) de DNS.
por defecto el puerto 80 para conexiones http o el 443 para conexiones https (seguras).
Resumiendo, la comunicación HTTP se inicia cuando un agente abre una conexión hacia
52 CAPÍTULO 2. ARQUITECTURA DE LAS APLICACIONES SAAS
GET http://srch.com:80/main/search?q=cloud&lang=en#top
POST http://localhost:3000/movies/3
Figura 2.3. Una petición HTTP está compuesta por un método HTTP y un URI . Un URI completo comienza con un
esquema, como por ejemplo http o https, e incluye los componentes descritos previamente. Los componentes opcionales se
muestran entre paréntesis. Un URI parcial omite alguno o todos los componentes más a la izquierda, rellenándolos o
resolviéndolos en tal caso respecto al URI base, determinado por cada aplicación. Una buena práctica es utilizar los URI
completos.
1. A Web client (Firefox) requests the Rotten Potatoes home page from a Web server (WEBrick).
a) Firefox constructs an HTTP request using the URI http://localhost:3000 to contact an HTTP
server (WEBrick) listening on port 3000 on the same computer as Firefox itself (localhost).
b) WEBrick, listening on port 3000, receives the HTTP request for the resource '/movies' (the list of
all movies in Rotten Potatoes).
2. WEBrick obtains content from the Rotten Potatoes app and sends this content back to Firefox
Figura 2.4. A 50.000 pies de altura, podemos ampliar el paso 1 de la figura 2.1.
Ahora ya podemos explicar qué ocurre cuando se carga la página principal de Rotten-
Potatoes en unos términos algo más precisos, tal y como muestra la figura 2.4.
Para profundizar, exploraremos a continuación cómo se representa el contenido en sí.
Resumen
• Los navegadores y servidores web se comunican usando el protocolo de trans-
ferencia de hipertexto (HyperText Transfer Protocol , HTTP). HTTP se
apoya en el protocolo de control de transmisión/protocolo de Internet
(Transmission Control Protocol/Internet Protocol , TCP/IP) para intercam-
biar secuencias ordenadas de bytes de forma fiable.
• Cada ordenador conectado a la red TCP/IP tiene una dirección IP, como por ejem-
plo 128.32.244.172, aunque el sistema de nombres de dominio (Domain Name
System, DNS) permite el uso de nombres más fáciles de usar y recordar. El nom-
bre localhost está reservado para referirse al ordenador local y se corresponde con
la dirección IP reservada 127.0.0.1.
• Cada aplicación que se ejecuta en un determinado ordenador debe “escuchar” en un
puerto TCP distinto, numerados desde el 1 hasta el 65535 (216 1). Los servidores
HTTP (web) utilizan el puerto 80.
• Para ejecutar localmente una aplicación SaaS, se debe configurar el servidor HTTP
para que escuche en un puerto de localhost. WEBrick, el servidor ligero de Rails,
utiliza el puerto 3000.
• Un identificador de recursos uniforme (Uniform Resource Identifier , URI )
identifica un recurso disponible en Internet. La interpretación de dicho nombre de
recurso varía según la aplicación.
renciar sintácticamente cada parte. Vea el screencast 2.3.1 en el que se comentan algunos
aspectos destacados de HTML 5, la versión actual del lenguaje, y luego continúe leyendo.
Screencast 2.3.1. Introducción a HTML.
http://vimeo.com/34754506
HTML está compuesto por una jerarquía de elementos anidados, cada uno de los cuales
está formado por una etiqueta de apertura, como <p>, una parte de contenido (en algunos
casos) y una etiqueta de cierre, como </p>. La mayoría de las etiquetas de apertura pueden El uso de corchetes
contener también atributos, como en el caso de <a href="http://...">. Algunas angulares para marcar las
etiquetas que no tienen parte de contenido se autocierran, como en el caso del salto de línea, etiquetas proviene de
<br clear="both" />, que deja vacíos los márgenes derecho e izquierdo. SGML (Standard
Generalized Markup
Language o lenguaje
de marcado
Existe una desafortunada confusión en la terminología alrededor de la genealogía de generalizado
HTML7 . HTML 5 incluye características tanto de sus predecesores (versiones 1 a 4 de estándar ), una
HTML) como de XHTML (eXtended HyperText Markup Language, lenguaje de marcado estandarización codificada
de hipertexto extendido), el cual representa un subconjunto de XML (eXtensible Markup del Generalized Markup
Language de IBM,
Language, lenguaje de marcado extensible), un lenguaje de marcado extensible que se puede desarrollado en los años 60
usar tanto para representar datos como para describir otros lenguajes de marcado. De hecho, para codificar documentos
XML es una forma común de representación de datos para intercambio de información entre de proyecto que pudiera leer
dos servicios en arquitecturas orientadas a servicios, como veremos en el capítulo 8, cuando un ordenador.
extendamos RottenPotatoes para obtener la información de la película de un servicio de base
de datos de películas independiente. Es difícil recordar las diferencias entre las variantes de
XHTML y HTML y no todos los navegadores soportan todas las versiones. Excepto que se
indique lo contrario, de ahora en adelante cuando aparezca HTML nos estaremos refiriendo a
HTML 5 y trataremos de evitar el uso de características que no estén ampliamente soportadas.
Los atributos id y class de las etiquetas HTML resultan de especial interés puesto que
desempeñan un papel esencial en la conexión de la estructura HTML de una página con su
apariencia visual. El siguiente screencast ilustra el uso de la barra de herramientas del de-
sarrollador web (Web Developer) de Firefox para identificar rápidamente los identificadores
(id) y clases (class) de los elementos HTML de una página.
Screencast 2.3.2. Inspeccionando los atributos id y class.
http://vimeo.com/34754568
CSS utiliza notaciones de selectores como div#nombre para identificar un elemento div
cuyo id es nombre, y div.nombre para especificar un elemento div cuya clase es nombre.
En un documento HTML, sólo un elemento puede tener un identificador (id) dado, mientras
que varios elementos (incluso de distintos tipos de etiquetas) pueden compartir la misma
clase (class). Las tres características de un elemento —su tipo de etiqueta, y sus atributos
id (si lo tiene) y class (si lo tiene)— se pueden usar para identificar un elemento como
candidato para aplicar un formato de visualización.
Tal y como muestra el siguiente screencast, el estándar CSS (Cascading Style Sheets, Para ver un ejemplo
hojas de estilo en cascada) permite asociar instrucciones de “estilos” para aplicar un extremo de lo que se puede
hacer con CSS, visite CSS
formato visual con elementos HTML usando las clases e identificadores de dichos elementos. Zen Garden8 .
El screencast cubre sólo algunas construcciones CSS básicas, resumidas en la figura 2.5. La
sección “Para saber más” al final del capítulo recoge varios sitios web y libros que describen
CSS en detalle, incluyendo cómo usar CSS para alinear contenido en una página, algo que
los diseñadores solían hacer manualmente usando tablas HTML.
56 CAPÍTULO 2. ARQUITECTURA DE LAS APLICACIONES SAAS
Figura 2.5. Algunas construcciones CSS, incluyendo las descritas en el screencast 2.3.3. La tabla superior muestra algunos
selectores CSS, que identifican los elementos a los que aplicar el estilo; la tabla inferior muestra algunas propiedades, cuyos
nombres suelen ser autoexplicativos, y ejemplos de valores que se les pueden asignar. No todas las propiedades son válidas
en todos los elementos.
Utilizando esta nueva información, la figura 2.6 amplía los pasos 2 y 3 de los resúmenes
de secciones previas sobre cómo funciona SaaS.
2.3. 10.000 PIES: REPRESENTACIÓN —HTML Y CSS— 57
1. A Web client (Firefox) requests the Rotten Potatoes home page from a Web server (WEBrick).
a) Firefox constructs an HTTP request using the URI http://localhost:3000 to contact an HTTP
server (WEBrick) listening on port 3000 on the same computer as Firefox itself (localhost).
b) WEBrick, listening on port 3000, receives the HTTP request for the resource '/movies' (the list of
all movies in Rotten Potatoes).
2. WEBrick obtains content from the Rotten Potatoes app and sends this content back to Firefox
a) WEBrick returns content encoded in HTML, again using HTTP. The HTML may contain references
to other kinds of media such as images to embed in the displayed page. The HTML may also
contain a reference to a CSS stylesheet containing formatting information describing the desired
visual attributes of the page (font sizes, colors, layout, and so on).
Figura 2.6. SaaS visto a 10.000 pies de altura. Comparado con la figura 2.4, se ha ampliado el paso 2 para describir el
contenido devuelto por el servidor web, mientras que el paso 3 se ha extendido para describir el papel de CSS en la forma en
que el navegador web renderiza el contenido.
Resumen
Autoevaluación 2.3.1. Verdadero o falso: todo elemento HTML debe tener un ID.
⇧ Falso. El identificador (id) es opcional, aunque debe ser único si se incluye.
Autoevaluación 2.3.2. Dado el siguiente marcado HTML:
58 CAPÍTULO 2. ARQUITECTURA DE LAS APLICACIONES SAAS
o el nombre e información del perfil del usuario, se guardan en la capa de persistencia. LAMP. Los primeros sitios
Algunas opciones comunes para la capa de persistencia suelen ser bases de datos de código SaaS fueron creados
utilizando los lenguajes de
abierto como MySQL o PostgreSQL, aunque previamente a su proliferación, otras bases de scripting Perl y PHP, cuya
datos comerciales, como Oracle o IBM D2, solían ser opciones habituales. disponibilidad coincidió con
Las “capas” en el modelo de tres capas se refieren a capas lógicas. En un sitio con poco el éxito inicial de Linux, un
contenido y bajo volumen de tráfico, el software de las tres capas puede ejecutarse en un único sistema operativo de código
abierto, y MySQL, una base
ordenador físico. De hecho, RottenPotatoes ha estado haciendo precisamente eso: su capa de de datos de código abierto.
presentación está representada por WEBrick, y su capa de persistencia es una sencilla base Miles de sitios están aún
de datos de código abierto llamada SQLite, que almacena su información directamente en basados en la pila LAMP
archivos del ordenador local. En producción, es más común que cada capa abarque uno o más —Linux, Apache, MySQL y
PHP o Perl—.
ordenadores físicos. Tal y como muestra la figura 2.7, en un sitio web típico, las peticiones
HTTP entrantes se dirigen a uno o varios servidores web, los cuales van seleccionando uno
o varios de los servidores de aplicación disponibles para manejar la generación de contenido
dinámico, permitiendo añadir o eliminar ordenadores de cada capa según sea necesario para
atender la demanda.
Sin embargo, tal y como se comenta en la sección de “Falacias y errores comunes”, hacer
la capa de persistencia “sin compartición” (shared-nothing) es mucho más complicado. La
figura 2.7 muestra la estrategia maestro-esclavo, que se utiliza cuando las lecturas de la
base de datos son mucho más frecuentas que las escrituras: cualquier esclavo puede realizar
lecturas, sólo el maestro puede realizar escrituras y el maestro actualiza los esclavos con
los resultados de las escrituras tan pronto como sea posible. Sin embargo, en el fondo, esta
técnica sólo pospone el problema del escalado en lugar de resolverlo. Tal y como escribió
uno de los fundadores de Heroku9 :
Una pregunta que me suelen hacer sobre Heroku es: “¿cómo escaláis la base de datos
SQL?”. Podría contar muchas cosas sobre utilización de cachés, sharding y otras técnicas
para disminuir la carga de la base de datos. Pero la respuesta real es: no lo hacemos.
Las bases de datos SQL son fundamentalmente no escalables y no tenemos, ni nosotros,
ni nadie, ninguna varita mágica para hacerlas de repente escalables.
Adam Wiggins, Heroku10
Ahora ya podemos añadir un nivel más de detalle a nuestra explicación; en la figura 2.8
aparece un nuevo paso 2a.
Resumen
• La arquitectura de tres capas incluye una capa de presentación, que renderiza las
vistas e interactúa con el usuario; una capa de lógica, que ejecuta el código de la
aplicación SaaS; y una capa de persistencia, que almacena los datos de la aplicación.
• El hecho de que HTTP sea un protocolo sin estado permite que las capas de pre-
sentación y de lógica sean sin compartición (shared-nothing ), de forma que
puede utilizarse la computación en la nube para añadir más ordenadores a cada capa
en función de la demanda. No obstante, la capa de persistencia es más difícil de
escalar.
• Dependiendo de la escala (tamaño) del despliegue, se puede alojar en un único or-
denador más de una capa, o una única capa puede requerir muchos ordenadores.
60 CAPÍTULO 2. ARQUITECTURA DE LAS APLICACIONES SAAS
App server
Database
master
Load Balancer
Load Balancer
Web server App server
Load Balancer
Database
Web server slave 1
App server
Asset server
App server Database
slave N
Presentation Logic (application) Persistence
tier tier tier
Figura 2.7. La arquitectura de tres capas sin compartición (shared-nothing ), llamada así porque las entidades
pertenecientes a una capa generalmente no se comunican entre ellas, permiten añadir ordenadores a cada capa de forma
independiente para atender la demanda. Los balanceadores de carga, que distribuyen la carga de trabajo de forma
equitativa, pueden ser tanto dispositivos hardware como servidores web especialmente configurados para tal fin. El hecho de
que HTTP sea un protocolo sin estado hace posible la no compartición: dado que todas las peticiones son independientes,
puede asignarse cualquier servidor en la capa de presentación o capa lógica a cualquier petición. Sin embargo, escalar la
capa de persistencia constituye un reto mucho mayor, como se explica en el texto.
1. A Web client (Firefox) requests the Rotten Potatoes home page from a Web server (WEBrick).
a) Firefox constructs an HTTP request using the URI http://localhost:3000 to contact an HTTP
server (WEBrick) listening on port 3000 on the same computer as Firefox itself (localhost).
b) WEBrick, listening on port 3000, receives the HTTP request for the resource '/movies' (the list of
all movies in Rotten Potatoes).
2. WEBrick obtains content from the Rotten Potatoes app and sends this content back to Firefox
a) Via the Rack middleware (written in Ruby), WEBrick calls Rotten Potatoes code in the application
tier. This code generates the page content using movie information stored in the persistence tier
implemented by a SQLite database using local files.
b) WEBrick returns content encoded in HTML, again using HTTP. The HTML may contain references
to other kinds of media such as images to embed in the displayed page. The HTML may also
contain a reference to a CSS stylesheet containing formatting information describing the desired
visual attributes of the page (font sizes, colors, layout, and so on).
Figura 2.8. SaaS visto a 5.000 pies de altura. Comparado con respecto a la figura 2.6, se ha añadido el paso 2a, que describe
las acciones del servidor SaaS en términos de la arquitectura de tres capas.
2.5. 1.000 PIES: ARQUITECTURA MODELO-VISTA-CONTROLADOR 61
Autoevaluación 2.4.1. Explique por qué la computación en la nube podría tener un impacto
menor en SaaS si la mayoría de las aplicaciones SaaS no siguieran la arquitectura de no
compartición (shared-nothing).
⇧ La computación en la nube permite añadir y eliminar ordenadores fácilmente pagando
únicamente por lo que se usa, pero es la arquitectura de no compartición (shared-nothing) la
que permite “absorber” directamente los nuevos ordenadores en una aplicación en ejecución
y “liberarlos” cuando ya no son necesarios.
Autoevaluación 2.4.2. En la capa de ____ de las aplicaciones SaaS de tres capas, la escala-
bilidad es mucho más compleja de conseguir que añadiendo sencillamente más ordenadores.
⇧ Persistencia.
de todas las películas, otra presenta información detallada de una película en particular, y otra
más aparece cuando se crea una nueva película o se edita una ya existente.
Por último, los controladores median en ambos sentidos de la interacción: cuando un
usuario interactúa con una vista (por ejemplo, haciendo clic en algún elemento de la página
web), se invoca una acción específica del controlador correspondiente a la acción realizada
por el usuario. Cada controlador se corresponde con un modelo y en Rails cada acción de
controlador se maneja mediante un método Ruby concreto dentro de dicho controlador. El
controlador puede pedir al modelo que recupere o modifique información; dependiendo del
resultado de estas acciones, el controlador decide qué vista se presentará a continuación al
usuario, y proporciona dicha vista con la información necesaria. Dado que RottenPotatoes
sólo tiene un modelo (el correspondiente a las películas), también tiene un único controlador,
el controlador de películas. Las acciones definidas en dicho controlador pueden manejar cada
tipo de interacción del usuario con cualquier vista de las películas (haciendo clic en enlaces o
botones, por ejemplo) y contienen la lógica necesaria para obtener los datos del modelo para
renderizar cualquiera de las vistas de las películas.
Puesto que las aplicaciones SaaS siempre han estado centradas en las vistas y siempre
se han apoyado en una capa de persistencia, elegir MVC en Rails como arquitectura subya-
cente puede parecer la opción más obvia. Sin embargo, otras opciones son posibles, como
las mostradas en la figura 2.9, extraídas del catálogo de patrones para arquitecturas de apli-
cación de empresas de Martin Fowler11 . Las aplicaciones constituidas en su mayor parte por
contenido estático con sólo una pequeña porción de contenido dinámico, tales como sitos
web del tiempo atmosférico, podrían elegir el patrón Template View (Vista de Plantilla). Por
su parte, el patrón Page Controller (Controlador de Página ) es adecuado para una aplicación
que se pueda estructurar fácilmente como un pequeño número de páginas distintas, conce-
diendo a cada una su propio controlador sencillo que sólo sabe cómo generar dicha página.
Para una aplicación que guía al usuario a través de una secuencia de páginas (como, por ejem-
plo, darse de alta en una lista de correo) pero sólo tiene un pequeño conjunto de modelos,
podría ser suficiente el patrón Front Controller (Controlador Frontal), en el que un único
controlador maneja todas las peticiones entrantes en lugar de tener controladores separados
que manejan las peticiones para cada modelo.
La figura 2.10 resume nuestra interpretación más reciente de la estructura de una apli-
cación SaaS.
2.5. 1.000 PIES: ARQUITECTURA MODELO-VISTA-CONTROLADOR 63
models/ model 1
models
model model
model commands
model views
Ctrl 1
1
Front model 2
views
Ctrl 1 view 1 Ctrler
views
Ctrl 2
2
Ctrl 2 view 2 models/ model 3
model
commands
model views
Ctrl 3 view 3 Ctrl 3
3
views
Figura 2.9. Comparación de patrones de arquitectura de aplicaciones web. Los modelos se corresponden con rectángulos
redondeados, los controladores con rectángulos y las vistas con los iconos de documentos. El patrón Page Controller
(izquierda), usado por Sinatra, tiene un controlador para cada página lógica de la aplicación. El patrón Front Controller
(arriba, centro), utilizado por los servlets de Java 2 Enterprise Edition (J2EE), tiene un único controlador que depende de
métodos de diversos modelos para generar una vista de entre una colección. El patrón Template View (abajo, centro),
utilizado por PHP, enfatiza la construcción de la aplicación alrededor de las vistas, utilizando lógica en los modelos para
generar contenido dinámico dentro de las vistas en parte de ellas; el controlador está implícito en el entorno. El patrón
modelo-vista-controlador o MVC (derecha), utilizado por Rails y Java Sprint, asocia un controlador y un conjunto de vistas
con cada tipo de modelo.
1. A Web client (Firefox) requests the Rotten Potatoes home page from a Web server (WEBrick).
a) Firefox constructs an HTTP request using the URI http://localhost:3000 to contact an HTTP
server (WEBrick) listening on port 3000 on the same computer as Firefox itself (localhost).
b) WEBrick, listening on port 3000, receives the HTTP request for the resource '/movies' (the list of
all movies in Rotten Potatoes).
2. WEBrick obtains content from the Rotten Potatoes app and sends this content back to Firefox
a) Via the Rack middleware (written in Ruby), WEBrick calls Rotten Potatoes code in the application
tier. This code generates the page content using movie information stored in the persistence tier
implemented by a SQLite database using local files.
i) Rack routes the request to the index action of the Movies controller; the resource named by
this route is the list of all movies
ii) The Ruby function implementing the index action in the Movies controller asks the Movie
model for a list of movies and associated attributes.
iii) If successful, the controller identifies a View that contains the HTML markup for presenting the
list of movies, and passes it the movie information so that an HTML page can be constructed.
If it fails, the controller identifies a View that displays an error message.
iv) Rack passes the constructed view to WEBrick, which sends it back to Firefox as the HTTP
reply.
b) WEBrick returns content encoded in HTML, again using HTTP. The HTML may contain references
to other kinds of media such as images to embed in the displayed page. The HTML may also
contain a reference to a CSS stylesheet containing formatting information describing the desired
visual attributes of the page (font sizes, colors, layout, and so on).
Figura 2.10. Se ha ampliado el paso 2a para mostrar el papel que desempeña la arquitectura MVC para satisfacer una
petición en una aplicación SaaS.
64 CAPÍTULO 2. ARQUITECTURA DE LAS APLICACIONES SAAS
Resumen
• El patrón de diseño modelo-vista-controlador o MVC distingue entre modelos
que implementan la lógica de negocio, vistas que muestran información al usuario y
le permiten interactuar con la aplicación, y controladores que median en la interac-
ción entre vistas y modelos.
• En las aplicaciones SaaS que siguen MVC, cada acción de usuario que puede rea-
lizarse en una página web —hacer clic en un botón, enviar un formulario relleno
o arrastrar y soltar algún elemento— se gestiona mediante alguna acción del con-
trolador, que consulta el(los) modelo(s) necesario(s) para obtener la información
requerida y generar la vista en respuesta a la acción.
• MVC es un patrón apropiado para aplicaciones SaaS interactivas con varios tipos de
modelos, en las que tiene sentido asociar controladores y vistas a cada tipo de mode-
lo. Otros patrones de arquitectura pueden resultar más apropiados para aplicaciones
más pequeñas con un número menor de modelos o un repertorio de operaciones más
limitado.
Para recuperar la información sobre una película, necesitaríamos leer cada línea del
fichero y dividirla en campos según la separación en comas. Por supuesto, encontraríamos
problemas con la película Food, Inc., que contiene una coma en su título:
http://pastebin.com/LFSX4LSH
1 Food , Inc . , PG ,2008 -09 -07 , The current method of raw ...
Figura 2.11. Una posible tabla RDBMS para almacenar información de películas. La columna id proporciona a cada fila
una clave primaria o identificador único y persistente. La mayoría de las bases de datos se pueden configurar para asignar
las claves primarias de forma automática de diferentes formas; Rails utiliza una convención muy habitual, asignar números
enteros en orden creciente.
http://pastebin.com/KubsyZHq
1 " Food , Inc . " ," PG " ," 2008 -09 -07 " ," The current method of raw ... "
. . . lo que funcionaría correctamente hasta que intentáramos introducir la película Waiting for
"Superman". Tal y como muestra este ejemplo, diseñar hasta el formato de almacenamiento
más sencillo implica problemas espinosos, y requeriría escribir código para convertir un ob-
jeto almacenado en memoria en un formato de representación para el almacenamiento —
proceso conocido como serialización (en inglés marshalling o serialization) del objeto—
y viceversa —deserialización (en inglés unmarshalling o deserialization)—.
Edgar F. “Ted” Codd
Afortunadamente, la necesidad de la persistencia de objetos es tan común que varios
(1923–2003) recibió el
patrones de diseño han evolucionado para satisfacerla. Un subconjunto de dichos patrones Premio Turing en 1981 por
hacen uso de almacenamiento estructurado —sistemas de almacenamiento que permiten la invención del formalismo
especificar simplemente la estructura de los objetos almacenados deseada en lugar de tener del álgebra relacional ,
en el que se basan las
que escribir código explícito para crear dicha estructura y, en algunos casos, especificar las bases de datos relacionales.
relaciones entre objetos de diferentes tipos—. Los sistemas de gestión de bases de datos
relacionales (Relational Database Management Systems, RDBMS) evolucionaron a princi-
pios de los años 70 como sistemas de almacenamiento estructurado elegantes cuyo diseño
se basaba en un formalismo para representar la estructura y las relaciones. Hablaremos de
los RDBMS en detalle más adelante, pero en resumen un RDBMS almacena una colección
de tablas, cada una de las cuales almacena entidades con un conjunto común de atributos.
Cada fila de la tabla se corresponde con una entidad, y las columnas de dicha fila con los
valores de los atributos que caracterizan a dicha entidad. La tabla de películas (movies) de
RottenPotatoes incluye columnas que representan el título (title), clasificación (rating),
fecha de estreno (release_date) y descripción (description), con lo que una fila de la
tabla sería como se muestra en la figura 2.11.
Dado que es responsabilidad de los modelos gestionar los datos de la aplicación, se debe
establecer alguna correspondencia entre las operaciones sobre los objetos del modelo en
memoria (por ejemplo, un objeto que representa una película) y cómo se representa y ma-
nipula dicho objeto en la capa de persistencia. El objeto en memoria se representa a menudo
mediante una clase que, entre otras cosas, proporciona una forma de representar los atributos
del objeto, tales como el título y la clasificación en el caso de una película. Rails se inclina
por el uso del patrón arquitectónico Active Record (registro activo). En dicho patrón,
una única instancia de una clase de modelo (en nuestro caso, la entrada correspondiente a
una única película) se corresponde con una única fila en una tabla específica del RDBMS.
El objeto del modelo posee comportamientos integrados que operan directamente sobre la
representación del objeto en la base datos:
• Crear (Create) una nueva fila en la tabla (que representa un nuevo objeto).
• Leer (Read) una fila existente para trasladarla a una instancia única del objeto.
66 CAPÍTULO 2. ARQUITECTURA DE LAS APLICACIONES SAAS
• Actualizar (Update) una fila existente con nuevos valores de los atributos a partir de
una instancia modificada del objeto.
• Eliminar (Delete) una fila (destruyendo los datos del objeto definitivamente).
Esta colección de comandos suele abreviarse como CRUD. Más adelante añadiremos la
funcionalidad necesaria para que los espectadores puedan opinar sobre sus películas favoritas,
de forma que existirá una relación o asociación uno-a-muchos entre un espectador y sus
opiniones; Active Record aprovecha los mecanismos existentes en el RDBMS basados en
claves foráneas (de las que hablaremos más adelante) para facilitar la implementación de
estas asociaciones en los objetos en memoria.
Resumen
• Una tarea importante del modelo en una aplicación SaaS basada en MVC es la per-
sistencia de los datos, que requiere una conversión entre la representación en memo-
ria de un objeto y su representación en el sistema de almacenamiento permanente.
• Diversos patrones de diseño han evolucionado para satisfacer este requisito, en mu-
chos casos haciendo uso de almacenamiento estructurado como los sistemas de
gestión de bases de datos relacionales (Relational Database Management Systems,
RDBMSs) para simplificar no sólo el almacenamiento de los datos del modelo, sino
también el mantenimiento de las relaciones entre modelos.
• Las cuatro operaciones básicas soportadas por los RBDMS son crear, leer, modificar
y eliminar (o de forma abreviada, CRUD, por sus siglas en inglés: Create, Read,
Update, Delete).
• En el patrón de diseño Active Record (registro activo), cada modelo sabe cómo
realizar las operaciones CRUD para su tipo de objeto. La biblioteca ActiveRecord de
Rails proporciona una amplia funcionalidad para que las aplicaciones SaaS utilicen
este patrón.
Por tanto, cada petición HTTP entrante debe tener una correspondencia con el controlador y
método apropiados. Esta correspondencia se denomina ruta (route).
Tal y como mostraba la figura 2.3, una petición HTTP se caracteriza por la combinación
de su URI y el método HTTP, a veces también llamado verbo HTTP. De la casi media
docena de métodos definidos por el estándar HTTP, los que se utilizan más habitualmente
en las aplicaciones web y arquitecturas orientadas a servicios son GET, POST, PUT y DELETE.
Dado que el término método puede referirse tanto a una función como al método HTTP de
una petición, cuando hablemos de rutas utilizaremos método para referirnos al verbo HTTP
asociado a la petición y acción del controlador o sencillamente acción para aludir al código
de aplicación (método o función) que maneja la petición.
Una ruta, pues, asocia un URI y método HTTP con un controlador y acción específicos.
En el año 2000, Roy Fielding propuso, en su tesis doctoral, una forma coherente de asociar
peticiones a acciones que se adapta particularmente bien a la arquitectura orientada a ser-
vicios. Su idea era identificar las distintas entidades manipuladas por una aplicación web
como recursos, y diseñar las rutas de forma que cualquier petición HTTP contuviera toda
la información necesaria para identificar tanto un recurso en particular como la acción a rea-
lizar sobre el recurso. Denominó a esta idea transferencia de estado representacional
(Representational State Transfer ), o REST de forma abreviada.
Aunque es sencillo de explicar, REST es un principio de organización sorprendentemente
potente para las aplicaciones SaaS, ya que obliga al diseñador de la aplicación a pensar cuida-
dosamente de qué condiciones o suposiciones depende exactamente cada petición para que
sean autocontenidas, y en cómo se puede representar cada tipo de entidad manipulada por
la aplicación como un “recurso” sobre el que se puedan realizar diversas operaciones. Las
aplicaciones diseñadas de acuerdo con estas directrices se dice que proporcionan API REST,
y los URI que se corresponden con acciones particulares se conocen como URI REST.
En Rails, las correspondencias de las rutas se generan mediante código en el fichero
config/routes.rb, del que aprenderemos más en el capítulo 4. Aunque Rails no fuerza
que las rutas sean REST, su soporte de rutas integrado asume por defecto que lo serán. La
figura 2.12 explica la información mostrada cuando se teclea rake routes en una ventana rake ejecuta tareas de
de terminal estando dentro del directorio rottenpotatoes. En un URI como por ejemplo mantenimiento definidas en
/movies/:id, los tokens (símbolos) que empiezan por ‘:’ son parámetros de la ruta; en el archivo Rakefile de
RottenPotatoes.
este caso, :id representa el atributo id (clave primaria) de una instancia del modelo. Por rake --help muestra
ejemplo, la ruta GET /movies/8 se corresponderá con la segunda fila de la figura 2.12, la otras opciones.
cual tiene un valor de 8 para :id; por lo tanto es una petición que devolverá y mostrará los
detalles de la película cuyo identificador (id) en la tabla de películas es 8, en caso de que
dicha película exista. Análogamente, la ruta GET /movies se corresponderá con la primera
fila, pidiendo un listado de todas las películas (la acción Index), y la ruta POST /movies se
corresponde con la cuarta fila y crea una entrada en la base de datos para una nueva película (la
ruta POST /movies no especifica ningún id porque la nueva película no tendrá identificador
hasta después de haber sido creada). Hay que hacer hincapié en que las acciones de listado
(Index) y creación tienen la misma URI pero distintos métodos HTTP, lo que hace que sean
rutas diferentes.
Las interfaces REST simplifican de forma crítica la participación en una arquitectura
orientada a servicios ya que, si todas las peticiones son autocontenidas, las interacciones
entre servicios no necesitan establecer o depender del concepto de sesión en curso, como
ocurre en muchas aplicaciones SaaS al interactuar con usuarios a través del navegador web
. Es por esto que la orden de Jeff Bezos (sección 1.4) de que todos los servicios internos de
68 CAPÍTULO 2. ARQUITECTURA DE LAS APLICACIONES SAAS
Figura 2.12. Resumen de la salida de rake routes mostrando las rutas reconocidas por RottenPotatoes y la acción CRUD
representada por cada ruta. La columna más a la derecha muestra las acciones Rails del controlador de películas que serían
invocadas cuando una petición coincida con el URI y método HTTP dados. La correspondencia entre rutas y métodos se
basa, en gran medida, más en convenciones que en la configuración, tal como veremos en el capítulo 4.
Amazon tuvieran API “externalizables” fue una apuesta con gran visión de futuro.
De hecho, las prácticas actuales sugieren que incluso cuando se crea una aplicación SaaS
de cara al usuario, diseñada para ser usada a través de un navegador web, deberíamos pensar
en la aplicación principalmente como una colección de recursos accesibles a través de unas
API REST que resultan ser accesibles a través de un navegador web. Por desgracia, esto pre-
senta un problema menor, del que tal vez ya se haya dado cuenta si tiene experiencia previa en
programación web. Las rutas mostradas en la figura 2.12 hacen uso de cuatro métodos HTTP
diferentes —GET, POST, PUT y DELETE— e incluso utilizan diferentes métodos para distin-
En realidad, la mayoría de guir rutas con el mismo URI. Sin embargo, por razones históricas, los navegadores web sólo
los navegadores
implementan GET (para seguir un enlace) y POST (para enviar formularios). Para compensar,
implementan también
HEAD, que solicita los el mecanismo de enrutado de Rails permite a los navegadores utilizar POST para peticiones
metadatos de un recurso, que generalmente requerirían PUT o DELETE. Rails marca los formularios web asociados con
pero no tenemos por qué dichas peticiones de forma que, cuando se envía la petición, Rails puede reconocerla como
preocuparnos aquí de esto.
un caso especial y cambiar internamente el método HTTP “visto” por el controlador por PUT
o DELETE según convenga. El resultado es que el programador de Rails puede trabajar bajo el
supuesto de que PUT y DELETE están realmente soportados, incluso cuando los navegadores
no los implementen. La ventaja, como veremos, es que se puede usar el mismo conjunto de
rutas y métodos de controlador para manejar tanto peticiones que provienen de un navegador
(es decir, de un ser humano) como peticiones enviadas por otro servicio en una arquitectura
orientada a servicios.
Estudiando esta importante dualidad más en detalle, observe en la figura 2.12 que las
rutas new y create (filas tercera y cuarta de la tabla) parecen estar ambas involucradas en la
creación de una nueva película. ¿Por qué se necesitan dos rutas para esta acción? La razón es
que en una aplicación web de cara al usuario, se requieren dos interacciones para crear una
nueva película, tal y como muestra el screencast 2.7.1; por el contrario, en SOA el servicio
remoto puede crear una única petición que contenga toda la información necesaria para crear
la nueva película, por lo que no se necesitaría nunca utilizar la ruta new.
2.7. 500 PIES: RUTAS, CONTROLADORES Y REST 69
Figura 2.13. Las peticiones y rutas no REST son aquellas que dependen del resultado de peticiones previas. En una
arquitectura orientada a servicios, un cliente de un sitio REST podría realizar una petición inmediatamente para revisar su
carro de la compra (línea 6), mientras que un cliente de un sitio no REST tendría que realizar las acciones de las líneas 3–5
para preparar primero la información implícita de la que depende la línea 6.
REST puede parecer una elección de diseño obvia, pero hasta que Fielding caracterizó
nítidamente la filosofía REST y comenzó a promulgarla, muchas aplicaciones web se diseña-
ban sin utilizar dicha filosofía. La figura 2.13 muestra cómo un hipotético sitio de comercio
electrónico que no sigue la filosofía REST podría implementar la funcionalidad de permi-
tir que los usuarios inicien sesión, añadir un artículo específico a su carrito de la compra, y
tramitar la compra en sí. Para este hipotético sitio no REST, cada petición tras el inicio de
sesión (línea 3) depende de información implícita: la línea 4 asume que el sitio “recuerda” de
quién es el usuario cuya sesión está activa en ese momento para mostrarle su página de inicio,
y la línea 7 asume que el sitio “recuerda” quién ha estado añadiendo artículos a su carrito de
la compra para proceder a realizar la transacción. Por el contrario, cada URI de un sitio que
sigue la filosofía REST contiene la información necesaria para satisfacer la petición sin de-
pender de dicha información implícita: después de que Dave inicie su sesión, el hecho es que
su identificador de usuario es 301 está presente en cada petición, y su carrito de la compra
está identificado explícitamente mediante su identificador de usuario en lugar de estar basado
implícitamente en la noción de un usuario con una sesión activa en ese momento.
70 CAPÍTULO 2. ARQUITECTURA DE LAS APLICACIONES SAAS
• Cuando las rutas y recursos siguen la filosofía REST, la misma lógica del controlador
puede, generalmente, dar servicio tanto a páginas de cara al usuario a través de
un navegador web, como a peticiones procedentes de otros servicios en una SOA.
Aunque los navegadores web sólo soportan los métodos HTTP GET y POST, la lógica
del entorno puede compensar este hecho de forma que el programador pueda trabajar
bajo la suposición de que todos los métodos están disponibles.
Autoevaluación 2.7.1. Verdadero o falso: si una aplicación tiene una API REST, debe estar
realizando operaciones CRUD.
⇧ Falso. El principio REST se puede aplicar a cualquier tipo de operación, siempre y cuando
la aplicación represente sus entidades como recursos y especifique qué operaciones están
permitidas sobre cada tipo de recurso.
Autoevaluación 2.7.2. Verdadero o falso: dar soporte a operaciones REST simplifica la
integración de una aplicación SaaS con otros servicios en una arquitectura orientada a ser-
vicios.
⇧ Verdadero.
2.8. 500 PIES: TEMPLATE VIEW 71
Haml HTML
%br{:clear => ’left’} <br clear="left"/>
%p.foo Hello <p class="foo">Hello</p>
%p#foo Hello <p id="foo">Hello</p>
.foo <div class="foo">...</div>
#foo.bar <div id="foo" class="bar">...</div>
Figura 2.14. Algunas construcciones Haml usadas habitualmente y su resultado HTML. Una etiqueta Haml que empiece
con % debe contener una etiqueta y todo su contenido en una única línea, como se muestra en las líneas 1–3 de la tabla, o
bien debe aparece por sí misma en la línea como ocurre en las líneas 4–5, en cuyo caso todo el contenido de la etiqueta debe
tener una sangría de dos espacios en las líneas subsiguientes. Observe que Haml especifica los atributos class e id
utilizando una notación deliberadamente similar a los selectores CSS.
De acuerdo con el modelo MVC, las vistas deben contener el mínimo código posible.
Aunque técnicamente Haml permite incluir código Ruby arbitrariamente complejo en una
plantilla, la sintaxis para incluir un fragmento de código multilínea es deliberadamente com-
plicada, para disuadir a los programadores de incluirlo. De hecho, la única “computación”
en la vista Index de RottenPotatoes se limita a iterar sobre una colección (proporcionada
por el modelo a través del controlador) y generar una fila de tabla HTML para mostrar cada
72 CAPÍTULO 2. ARQUITECTURA DE LAS APLICACIONES SAAS
elemento.
Por el contrario, las aplicaciones PHP mezclan a menudo grandes fragmentos de código
en las plantillas de la vista,y aunque un programador PHP disciplinado podría separar las
vistas del código, el entorno PHP en sí no proporciona ningún soporte concreto para ello, ni
premia el esfuerzo. Los defensores de MVC argumentan que distinguir el controlador de la
vista facilita pensar primero en estructurar la aplicación como un conjunto de acciones REST,
y después en renderizar los resultados de dichas acciones en vistas en una etapa diferente. La
sección 1.4 exponía los argumentos a favor de la arquitectura orientada a servicios; en este
punto ya debería estar claro cómo la separación de modelos, vistas y controladores, y la
adhesión a un estilo de controlador que sigue la filosofía REST, conduce de forma natural a
aplicaciones cuyas acciones son fáciles de “externalizar” como acciones autónomas de una
API.
Autoevaluación 2.8.1. ¿Qué papel desempeña el sangrado en la vista Index de las películas
descrita en el screencast 2.8.1?
⇧ Cuando un elemento HTML contiene otros elementos, el sangrado indica a Haml la es-
tructura de anidamiento, de forma que pueda generar las etiquetas de cierre, como </tr>, en
los lugares adecuados.
Autoevaluación 2.8.2. En la vista Index de las películas, ¿por qué las marcas de Haml en
la línea 11 comienzan con –, mientras que las marcas en las líneas 13–16 empiezan por =?
⇧ En la línea 10 sólo necesitamos ejecutar el código, para iniciar el bucle for. En las líneas
13–16 queremos incluir el resultado de la ejecución del código en la vista.
STOP
Falacia. Rails no escala (o Django, o PHP u otros entornos de desarrollo).
Rails) se pueden escalar casi arbitrariamente añadiendo ordenadores en cada capa utilizando
computación en la nube. El reto consiste en escalar la base de datos, tal y como explica el
siguiente error común.
Error. Almacenar todos los datos del modelo en un RDBMS en un único
! servidor, limitando por tanto la escalabilidad.
El potencial de los RDBMS es una espada de doble filo. Resulta sencillo crear estructuras
de bases de datos propensas a problemas de escalabilidad que pueden no aparecer hasta que
el servicio crece hasta los cientos de miles de usuarios. Algunos desarrolladores afirman que
Rails agrava este problema dado que sus abstracciones del modelo son tan productivas que
resulta tentador usarlas sin pensar en las consecuencias en términos de escalabilidad. Por
desgracia, al contrario de lo que sucede con las capas de servidor web y de aplicación, no
podemos solventar este problema desplegando sencillamente muchas copias de la base de
datos, ya que esto resultaría en distintos valores para diferentes copias del mismo elemento
(el problema de la consistencia de los datos). Aunque técnicas como la réplica maestro-
esclavo y el sharding de la base de datos ayudan a que la capa de base de datos sea más
parecida a las capas de presentación y lógica sin compartición, la escalabilidad extrema de
las bases de datos es todavía un área de investigación y trabajo de ingeniería.
Error. Centrarse prematuramente en la eficiencia por ordenador de la apli-
! cación SaaS.
Aunque la arquitectura sin compartición (shared-nothing) facilita el escalado horizontal,
todavía se necesitan ordenadores para conseguirlo. Añadir un ordenador era caro (compra del
ordenador), consumía tiempo (configuración e instalación del ordenador) y era permanente
(si la demanda decrecía después, se estaría pagando por un ordenador inactivo). Con la
aparición de la computación en la nube, se resolvieron los tres problemas, ya que se pueden
añadir ordenadores de forma instantánea por unos cuantos céntimos a la hora y liberarlos
cuando dejan de ser necesarios. Por tanto, hasta que una aplicación SaaS crece lo suficiente
como para necesitar cientos de ordenadores, los desarrolladores SaaS deberían centrarse en
el escalado horizontal en lugar de la eficiencia por ordenador.
que se conoce hoy en día como los 23 patrones de diseño GOF, centrándose en estructuras y
comportamientos a nivel de clase. A pesar de la popularidad de los patrones de diseño como
herramienta, han sido objeto de algunas críticas; por ejemplo, Peter Norvig, actual director
de investigación de Google, ha argumentado que algunos patrones de diseño sólo compen-
san deficiencias de lenguajes de programación de tipado estático como C++ y Java, y que
la necesidad de dichos patrones desaparece en lenguajes dinámicos como Lisp o Ruby. A
pesar de cierta controversia, patrones de todo tipo siguen siendo una herramienta valiosa para
ingenieros de software para identificar estructuras en su trabajo y aplicar soluciones probadas
a problemas recurrentes.
De hecho, observamos que al decidir construir una aplicación SaaS, hemos predetermi-
nado el uso de algunos patrones y excluido otros. La elección de estándares web determina el
uso de un sistema cliente-servidor; elegir computación en la nube determina la arquitectura
de 3 capas para permitir el escalado horizontal. Modelo–vista–controlador no está predeter-
minado, pero lo elegimos porque encaja correctamente para las aplicaciones web centradas
en vistas y que se han apoyado históricamente en una capa de persistencia, sin descartar otros
patrones posibles como aquellos mostrados en la figura 2.9. REST tampoco está predetermi-
nado, pero lo hemos elegido porque simplifica la integración en una arquitectura orientada
a servicios y porque puede se puede aplicar inmediatamente a las operaciones CRUD, tan
comunes en las aplicaciones MVC. El uso de Active Record es tal vez una elección más
controvertida —como veremos en los capítulos 4 y 5, sus potentes características simplifican
las aplicaciones considerablemente, pero un mal uso de las mismas puede conllevar proble-
mas de escalabilidad y rendimiento que son menos probables en modelos de persistencia más
sencillos.
Si estuviéramos construyendo una aplicación SaaS en 1995, nada de lo anterior habría
resultado obvio ya que no se habían acumulado suficientes ejemplos de aplicaciones SaaS de
éxito como para “extraer” patrones exitosos e incorporarlos a entornos como Rails, compo-
nentes software como Apache, y middleware como Rack. Siguiendo los competentes pasos
De hecho, el propio Rails de los arquitectos software que nos han precedido, podemos aprovechar su habilidad para
fue extraído originalmente separar las cosas que cambian de las que permanecen igual a través de muchos ejemplos de
de una aplicación autónoma SaaS, y proporcionar herramientas, entornos y principios de diseño que soporten este estilo
escrita por el grupo
consultor 37signals. de desarrollo. Como mencionamos anteriormente, esta separación es clave para posibilitar la
reutilización.
Por último, merece la pena recordar que un factor clave en el éxito de la Web ha sido la
adopción de protocolos y formatos bien definidos cuyo diseño permite la separación de cosas
Tim Berners-Lee, un que cambian de aquellas que permanecen inalteradas. TCP/IP, HTTP y HTML han experi-
científico informático del mentado varias revisiones importantes, pero todas incluyen formas de detectar qué versión se
CERN12 , lideró el desarrollo está usando, de forma que un cliente pueda saber si está hablando con un servidor más antiguo
de HTTP y HTML en 1990.
Ambos son a día de hoy
(o viceversa) y ajustar su comportamiento en concordancia. Aunque trabajar con múltiples
administrados por una versiones de protocolos y lenguajes supone un obstáculo adicional para los navegadores, ha
entidad sin ánimo de lucro y conducido a un resultado extraordinario: una página web creada en 2011, usando un lenguaje
neutral respecto a los de marcado basado en tecnología de los años 60, puede recuperarse utilizando protocolos de
proveedores, el World Wide
Web Consortium (W3C)13 .
red desarrollados en 1969 y visualizarse en un navegador creado en 1992. Separar las cosas
que cambian de las que no es parte del camino para crear software duradero.
2.11. PARA SABER MÁS 75
• The World Wide Web Consortium (W3C)16 administra los documentos oficiales que
describen los estándares abiertos de la Web, incluidos HTTP, HTML y CSS.
• El validador XML/XHTML17 es uno de los muchos que se pueden utilizar para asegu-
rarse de que las páginas proporcionadas por la aplicación SaaS cumplen los estándares.
• El sitio web Object Oriented Design18 dispone de numerosos recursos útiles para pro-
gramadores que utilizan lenguajes orientados a objetos, incluyendo un buen catálogo
de patrones de diseño GoF con descripciones gráficas de cada patrón, algunos de los
cuales se describen en mayor detalle en el capítulo 11.
Notas
1 http://es.wikipedia.org/wiki/ErrordedivisiondelIntelPentium
2 http://rottentomatoes.com
3 http://projects.apache.org/projects/http_server.html
4 http://www.iis.net
5 http://drive.google.com
6 http://iana.org
7 http://www.w3.org/TR/html5/introduction.html#history-1
8 http://csszengarden.com
9 http://heroku.com
10 http://adam.heroku.com/past/2009/7/6/sql_databases_dont_scale/
11 http://martinfowler.com/eaaCatalog
12 http://info.cern.ch
13 http://w3.org
14 http://w3schools.com
15 http://www.stubbornella.org/content
16 http://w3.org
17 http://validator.w3.org
18 http://oodesign.com
Ejercicio 2.2. Suponga que las cookies HTTP no existieran. ¿Podría pensar otra forma de
seguir a un usuario a través de diferentes vistas de páginas? (Pista: Involucra modificar el
URI y fue un método muy usado antes de la invención de las cookies).
Ejercicio 2.3. Busque una página web donde el validador de XHTML del W3C1 encuentre,
al menos, un error. Por desgracia, debería resultarle fácil. Revise los mensajes de error de
validación y trate de entender qué significa cada uno.
Ejercicio 2.4. Indique qué números de puerto están involucrados en los siguientes URI y por
qué:
1. https://paypal.com
2. http://mysite.com:8000/index
3. ssh://[email protected]/tmp/file (PISTA: recuerde que IANA establece
String#ord devuelve el
primer codepoint de la
números de puerto por defecto para algunos servicios de red)
cadena de caracteres (valor
numérico correspondiente a Ejercicio 2.5. Tal y como se describe en la documentación de la API de búsqueda de Duck-
un carácter dentro de un DuckGo2 , puede buscar un término con el motor de búsqueda DuckDuckGo construyendo
conjunto de caracteres). Si un URI que incluya el término buscado como un parámetro llamado q, como por ejemplo
la cadena está codificada en http://api.duckduckgo.com/?q=saas para buscar el término “saas”. Sin embargo,
ASCII , ord devuelve el
código ASCII del primer como muestra la figura 2.3, algunos caracteres no pueden formar parte del URI por ser
carácter. De esta forma, “caracteres especiales”, como por ejemplo espacios, ’?’ y ’&’. Dada esta restricción, cons-
"%".ord muestra el truya un URI válido para realizar una búsqueda con DuckDuckGo de los términos “M&M”
código ASCII de %,
y “100%?”.
mientras que
"%".ord.to_s(16)
muestra su equivalente en Ejercicio 2.6. ¿Por qué las rutas de Rails se corresponden con acciones del controlador y
hexadecimal.
no con acciones del modelo o las vistas?
Ejercicio 2.7. Dado un diseño de alto nivel, identifique la arquitectura software diferen-
ciando entre arquitecturas software comunes como tres capas, pipe-and-filter (tuberías y
filtros) o cliente-servidor.
Ejercicio 2.8. Investigue el impacto de la elección de las arquitecturas software en el diseño
de un sistema sencillo. Busque alternativas a las arquitecturas cliente-servidor, petición-
respuesta, etc.
2.12. EJERCICIOS PROPUESTOS 77
3 Entorno SaaS: introducción a Ruby
para programadores Java
Jim Gray Bueno, el artículo <omitido> está en buena compañía (y por la misma razón).
(1944–Desaparecido en el El artículo sobre árboles B se rechazó al principio.
mar en 2007) fue un El artículo sobre transacciones se rechazó al principio.
simpático gigante de la El artículo sobre matrices de datos se rechazó al principio.
informática. Fue el primer
doctorado en Informática en
El artículo sobre la regla de los 5 minutos se rechazó al principio.
la Universidad de California, Pero las extensiones lineales del trabajo anterior se han aceptado.
Berkeley, y fue tutor de Así que, ¡vuélvelo a enviar!, ¡¡¡POR FAVOR!!!
cientos de docentes y
Jim Gray, email enviado a Jim Larus sobre un artículo rechazado, 2000
estudiantes de doctorado de
todo el mundo. Recibió el
Premio Turing en 1998 por
sus contribuciones a la 3.1 Visión general y los tres pilares de Ruby . . . . . . . . . . . . . . . . 80
investigación del 3.2 Todo es un objeto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
procesamiento de
transacciones y bases de 3.3 Toda operación es una llamada a un método . . . . . . . . . . . . . . 85
datos y al liderazgo técnico 3.4 Clases, métodos y herencia . . . . . . . . . . . . . . . . . . . . . . . . 88
en la implementación de 3.5 Toda programación es metaprogramación . . . . . . . . . . . . . . . 92
sistemas.
3.6 Bloques: iteradores, expresiones funcionales y clausuras . . . . . . . 95
3.7 Mix-ins y tipado dinámico . . . . . . . . . . . . . . . . . . . . . . . . 99
3.8 Cree sus propios iteradores con yield . . . . . . . . . . . . . . . . . . 101
3.9 Falacias y errores comunes . . . . . . . . . . . . . . . . . . . . . . . 103
3.10 Observaciones finales: uso de expresiones idiomáticas . . . . . . . . . 104
3.11 Para saber más . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
3.12 Ejercicios propuestos . . . . . . . . . . . . . . . . . . . . . . . . . . . 106
79
Conceptos
Este capítulo introduce las equivalencias en Ruby de técnicas orientadas a objetos de
otros lenguajes como Java y otros mecanismos de Ruby que no tienen equivalencia en
Java y que ayudan con la reutilización y con el desarrollo DRY.
• Debido al tipado dinámico, usted no necesita considerar el tipo del objeto para
determinar si puede llamar a un método particular sobre él —sólo lo sabe si el
objeto puede responder al método—. Algunos llaman a esta característica duck
typing : “Si parece un array, camina como un array, y suena como un array,
entonces trátalo como un array”.
1. Todo es un objeto. En Java, algunos tipos primitivos como los enteros se tienen que
“encapsular” para hacer que se comporten como objetos.
2. Toda operación es una llamada a un método de algún objeto, y devuelve un valor. En
Java, la sobrecarga de operadores es diferente a la sobreescritura de un método, y es
posible tener funciones de tipo void que no devuelven ningún valor.
3. Toda codificación es metaprogramación: se pueden añadir o cambiar clases y métodos
en cualquier momento, incluso cuando el programa se está ejecutando. En Java, todas
las clases se deben declarar en tiempo de compilación, e incluso ahí, su aplicación no
puede modificar las clases que vienen de base en Java.
Estos tres principios se cubrirán cada uno en su propia sección. Los principios #1 y #2
son sencillos. El principio #3 confiere a Ruby la mayor parte de su poder para aumentar la
productividad, pero se encuadra dentro del dicho de que un gran poder conlleva una gran
responsabilidad. Usar con gusto las características de metaprogramación de Ruby hará que
su código sea más elegante y DRY, pero abusar de ellas lo hará quebradizo e impenetrable.
La sintaxis básica de Ruby no debería sorprenderle si está familiarizado con otros lengua-
jes actuales de scripting. La figura 3.1 muestra la sintaxis de los elementos básicos de Ruby.
Las sentencias están separadas por líneas nuevas (lo más común) o por punto y coma (rara-
mente). La indentación es insignificante. Aunque Ruby es lo suficientemente conciso para
que una sóla línea de código pueda exceder una línea de pantalla, se puede romper una sola
sentencia con una nueva línea si esto no causa ninguna ambiguedad para su procesamiento
por el analizador sintáctico (parser). Puede ser útil un editor con un buen resaltado de sintaxis
si no está seguro de que la ruptura de una línea sea legal.
Un símbolo, como :octocat, es una cadena inmutable cuyo valor es ella misma; se suele
usar en Ruby para las enumeraciones, como un tipo enum en C o Java, aunque también tiene
otras finalidades. Sin embargo, un símbolo no es lo mismo que una cadena de caracteres
—tiene su propio tipo primitivo, y no se pueden realizar sobre él operaciones que sí se harían
con una cadena de caracteres, aunque el símbolo se puede convertir a una cadena de caracteres
llamando al método to_s—. Por ejemplo, :octocat.to_s da como resultado "octocat", y
"octocat".to_sym da como resultado :octocat.
Las expresiones regulares o regexps (a menudo regex y regexes para que se puedan
pronunciar) son parte de la caja de herramientas de todo programador. Una expresión regular
3.1. VISIÓN GENERAL Y LOS TRES PILARES DE RUBY 81
Figura 3.1. Elementos básicos de Ruby y estructuras de control, con elementos opcionales entre [corchetes].
82 CAPÍTULO 3. ENTORNO SAAS: INTRODUCCIÓN A RUBY
Count
+ 1 or more a+ a aaaa
? 0 or 1 a? a aaaa
^ start of line, also NOT in set ^a a ab ba
$ end of line a$ a dcba ab
\w "word" character \w a 9 =
([a-zA-Z0-9_])
\W "nonword" character \W = $ a
([^a-zA-Z0-9_])
\n newline \n -- -- a
permite comparar una cadena de caracteres con un patrón con posibles “comodines”. El
soporte de las expresiones regulares de Ruby se parece al de otros lenguajes modernos de
programación: las expresiones aparecen entre barras inclinadas y pueden estar seguidas de
una o más letras que modifican su comportamiento, por ejemplo, /regex/i, para indicar que la
expresión regular debería ignorar a la hora de comparar si los caracteres están en mayúsculas.
Como muestra la figura 3.2, hay constructores especiales dentro de las expresiones regulares
que se pueden usar para compararse con muchos tipos de caracteres, y pueden especificar
el número de veces que debe aparecer una coincidencia y si esa coincidencia debe estar
“anclada” al principio o al final de la cadena de caracteres. Por ejemplo, a continuación vemos
una expresión regular en una línea que coindice con una hora del día, como por ejemplo
“8:25pm”:
http://pastebin.com/S61dtePp
1 time_regex = /^\ d \ d ?:\ d \ d \ s *[ ap ] m$ / i
Esta expresión regular coincide con un dígito al principio de una cadena (^\d), que op-
cionalmente puede estar seguido por otro dígito (\d?), seguido de dos puntos, otros dos
dígitos, cero o más espacios (\s*), una a o una p, y una m al final de la cadena (m$), y que
ignora si está en mayúsculas o no (la i tras la barra inclinada). Otra manera de coincidir con
uno o más dígitos sería [0-9][0-9]? y otra manera de hacer coincidir exactamente dos dígitos
sería [0-9][0-9].
Ruby permite el uso de paréntesis dentro de las expresiones regulares para capturar la
coincidencia de una determinada cadena o subcadenas de caracteres Por ejemplo, ésta es la
3.1. VISIÓN GENERAL Y LOS TRES PILARES DE RUBY 83
La segunda línea intenta ver si la cadena x coincide con la expresión regular. Si coin-
cide, el operador =~ devuelve la posición en la cadena (siendo 0 el primer carácter) en la
que se encuentra la coincidencia, la variable global $1 tendrá el valor "8", $2 tendrá "25",
y $3 tendrá "P". Estas variables se resetean la próxima vez que compruebe la coinciden-
cia con otra expresión regular. Si no hay coincidencia, =~ devolverá nil. Dese cuenta de
que nil y false no son iguales, pero las dos devuelven false cuando se usan en expresiones
condicionales (de hecho, son los únicos dos valores en Ruby que hacen eso). En su modo
idiomático, los métodos que son verdaderamente booleanos (es decir, que sólo pueden de-
volver true o false) devuelven false, mientras que los métodos que devuelven un objeto
cuando todo va bien, devuelven nil cuando fallan.
Finalmente, dese cuenta de que =~ funciona tanto en cadenas como en objetos de tipo
Regexp, así que las siguientes líneas son legales y equivalentes, y tendrá que elegir cuál es
más fácil de entender según el contexto del código donde se encuentren.
http://pastebin.com/8kKJZKpb
1 " Catch 22 " =~ /\ w +\ s +\ d +/
2 /\ w +\ s +\ d +/ =~ " Catch 22 "
Resumen
• El símbolo es un tipo primitivo distinguido en Ruby, una cadena inmutable cuyo
valor es ella misma. Los símbolos se suelen usar en Ruby para denotar “algo es-
pecial”, como ser una opción de un conjunto de opciones fijas como en una enu-
meración. Los símbolos no son lo mismo que una cadena de caracteres, pero se
pueden convertir fácilmente de uno a otro con los métodos to_s y to_sym.
• Las sentencias en Ruby se separan con nuevas líneas o, menos común, con puntos y
comas.
Autoevaluación 3.1.1. ¿Cuáles de las siguientes expresiones en Ruby son iguales entre sí?:
(a) :foo (b) %q{foo} (c) %Q{foo} (d) ’foo’.to_sym (e) :foo.to_s
⇧ (a) y (d) son iguales entre sí; (b), (c), y (e) son iguales entre sí
Autoevaluación 3.1.2. ¿Qué se almacena en $1 cuando la cadena 25 to 1 se compara con
las siguientes expresiones regulares?:
(a) /(\d+)$/
(b) /^\d+([^0-9]+)/
⇧ (a) la cadena “1” (b) la cadena “ to ” (incluyendo el espacio previo y el espacio posterior)
84 CAPÍTULO 3. ENTORNO SAAS: INTRODUCCIÓN A RUBY
Resumen
• La notación a.b significa “llama al método b sobre el objeto a”. Al objeto a se le
conoce como receptor, y si no puede gestionar la llamada, se la pasará a su super-
clase. A este proceso se le llama buscar un método en un receptor.
• Ruby soporta reflexión completa, con la que puede preguntar a los objetos sobre
ellos mismos.
Figura 3.3. La primera columna muestra la sintaxis simplificada en Ruby para operaciones comunes, la segunda muestra la
llamada explícita al método, y la tercera columna muestra cómo realizar esa misma llamada usando send, que acepta tanto
una cadena como un símbolo (más idiomático) para designar el nombre del método.
objeto. Muchos métodos de Ruby, incluyendo send, aceptan tanto un símbolo como una
cadena de caracteres como argumento, así que el primer ejemplo de la tabla se puede escribir
también como 10.send(’modulo’,3).
Una implicación crítica de que “toda operación es una llamada a un método” es que con-
ceptos como el de la conversión de tipos apenas se aplican en Ruby. En Java, si escribimos
f+i, siendo f un número real e i un entero, las reglas de conversión dicen que i se transformará
internamente en número real para que pueda ser añadido a f. Si escribiéramos i+s, siendo s
un String, obtendríamos un error en tiempo de compilación.
Por el contrario, en Ruby, + es simplemente un método que puede estar definido de
manera diferente por cada clase, de manera que su comportamiento depende por completo de
la implementación de este método en el receptor. Como f+i es una simplificación de f.+(i),
el comportamiento del método + (definido presuntamente en la clase de f o en alguna de sus
clases antecesoras) depende de cómo este método maneje los diferentes tipos de valores de
i. De este modo, tanto 3+2 como "foo"+"bar" son expresiones legales en Ruby, que dan
como resultado 5 y "foobar" respectivamente, pero la primera está llamando a + definido en
Numeric (clase antecesora de Fixnum) y la segunda está llamando a + definido en String.
Como hemos visto antes, puede comprobar que "foobar".method(:+) y 5.method(:+)
se refieren a métodos distintos. Aunque esto podría considerarse como sobrecarga de ope-
radores en otros lenguajes, es algo más general: como sólo importa el nombre del método
para el envío, veremos en la sección 3.7 cómo esta característica da pie a un mecanismo muy
poderoso de reutilización llamado mix-in.
En Ruby, la notación NombreClase#metodo se usa para indicar el método de instan-
cia metodo en la clase NombreClase, mientras que NombreClase.metodo se refiere al
método de clase (estático) metodo en la clase NombreClase. Usando esta notación, pode-
mos decir que la expresión 3+2 llama a Fixnum#+ sobre el receptor 3, mientras que la
expresión "foo"+"bar" llama a String#+ sobre el receptor "foo".
De manera similar, en Java es muy común ver conversiones explícitas de una variable a
una subclase, como por ejemplo Foo x = (Foo)y, donde y es una instancia de una subclase
3.3. TODA OPERACIÓN ES UNA LLAMADA A UN MÉTODO 87
de Foo. En Ruby esto no tiene sentido porque las variables no tienen tipos, y no importa si
el método que responde está en la clase del receptor o en alguno de sus antecesores.
Un método se define con def nombre_metodo(arg1,arg2) y termina con end; todas
las sentencias entre medias corresponden a la definición del método. En Ruby, toda expresión
tiene un valor —por ejemplo, el valor de una asignación es el valor de su lado derecho, así
que el valor de x=5 es 5— y si un método no incluye un return(algo) explícitamente, lo
que se devuelve es el valor de la última expresión del método. El siguiente método trivial
devuelve 5:
http://pastebin.com/xGYTktUK
1 def trivia l_meth od # no arguments ; can also use tri vial_m ethod ()
2 x = 5
3 end
http://pastebin.com/Y9RC9KgM
1 class Movie
2 def initialize ( title , year )
3 @title = title
4 @year = year
5 end
6 def title
7 @title
8 end
9 def title =( new_title )
10 @title = new_title
11 end
12 def year ; @year ; end
13 def year =( new_year ) ; @year = new_year ; end
14 # How to display movie info
15 @@i nclude _year = false
16 def Movie . include_year =( new_value )
17 @@in clude_ year = new_value
18 end
19 def full_title
20 if @@ includ e_year
21 " #{ self . title } (#{ self . year }) "
22 else
23 self . title
24 end
25 end
26 end
27
28 # Example use of the Movie class
29
30 beautiful = Movie . new ( ' Life is Beautiful ' , ' 1997 ')
31
32 # What 's the movie 's name ?
33 puts " I 'm seeing #{ beautiful . full_title } "
34
35 # And with the year
36 Movie . include_year = true
37 puts " I 'm seeing #{ beautiful . full_title } "
38
39 # Change the title
40 beautiful . title = ' La vita e bella '
41 puts " Ecco , ora si chiama '#{ beautiful . title }! ' "
Figura 3.4. Una definición de clase simple en Ruby. Las líneas 12 y 13 nos recuerda que es idiomático combinar frases
cortas en una sola línea usando puntos y comas; algunos programadores se aprovechan de lo conciso que es Ruby para
introducir espacios entre los puntos y coma, para mayor legibilidad.
90 CAPÍTULO 3. ENTORNO SAAS: INTRODUCCIÓN A RUBY
se asocian a cada instancia de un objeto. Las variables locales title y year que se pasan como
argumentos están fuera del ámbito (indefinidas) una vez estemos fuera del constructor, por
eso tenemos que capturar esos valores en las variables de instancia, si es que nos importan.
Las líneas 6–8 definen un método getter o método de acceso para la variable de
instancia @title. Se preguntará por qué no se puede escribir directamente beautiful.@title
si beautiful fuera una instancia de Movie. Se debe a que en Ruby, a.b siempre significa
“Llama al método b del receptor a”, y @title no es el nombre de ningún método en la clase
Movie. De hecho, ni siquiera es un nombre legal para un método, porque sólo los nombres de
variables de instancia y de clase pueden empezar con el símbolo @. En este caso, el método de
acceso title es un método de instancia de la clase Movie. Esto significa que cualquier objeto
que sea instancia de Movie (o de cualquiera de sus subclases, si tuviera) podría responder a
este método.
Las líneas 9–11 definen el método de instancia title=, que es distinto del método de ins-
tancia title. Los métodos cuyos nombres finalizan con un =, son métodos setter o métodos
modificadores, y, al igual que pasaba con los métodos de acceso, se necesita este tipo de
métodos porque no se puede escribir beautiful.@title = ’La vita e bella’. Sin embargo,
como se muestra en la línea 40, podemos escribir beautiful.title = ’La vita e bella’.
¡Cuidado! Si está acostumbrado a Java o Python, es muy común confundir esta sintaxis
con la asignación a un atributo, pero realmente es una llamada a método, y de hecho se
puede escribir como beautiful.send(:title=, ’La vita e bella’). Y como es una llamada a
método, éste devuelve un valor: en ausencia de una sentencia return explícita, el valor que
devuelve un método es justo el valor de la última expresión evaluada en dicho método. Como
en este caso la última expresión en el método es la asignación @title=new_title y el valor
de cualquier asignación es su lado derecho, el método devuelve el valor de new_title que
se le pasó por parámetro.
Al contrario que Java, que permite atributos además de métodos de acceso y modifi-
cadores, el ocultamiento de datos en Ruby o encapsulamiento es total: el único acceso a
variables de instancia o de clase desde fuera de la clase es mediante llamadas a métodos. Esta
restricción es una razón por la que Ruby es considerado un lenguaje más “puro’ para la OO
que Java. Pero como el modo poético nos permite omitir paréntesis y escribir movie.title
en vez de movie.title(), no se necesita sacrificar la concisión para conseguir este encapsu-
lamiento tan fuerte.
Las líneas 12–13 definen los métodos de acceso y modificación para year, mostrando
que se pueden usar puntos y comas además de nuevas líneas para separar sentencias en Ruby,
si así el código queda menos denso. De todas maneras, como veremos más adelante, Ruby
ofrece una forma mucho más concisa de definir métodos de acceso y métodos modificadores
usando metaprogramación.
La línea 14 es un comentario, que en Ruby empieza por # y se extiende hasta el final de
la línea.
La línea 15 define una variable de clase, o lo que se conoce en Java como una va-
riable estática, que define si el año de lanzamiento de una película se incluye cuando se
imprime su nombre. Al igual que el método modificador de title, necesitamos uno para
include_year= (líneas 16–18), pero la presencia de Movie en el nombre del método
(Movie.include_year=) indica que es un método de clase. Nótese que no hemos definido
un método de acceso para la variable de clase; esto significa que no se puede inspeccionar el
valor de esta variable de clase fuera de esta clase.
Las líneas 19–25 definen el método de instancia full_title, que usa el valor de @@in-
3.4. CLASES, MÉTODOS Y HERENCIA 91
Java Ruby
class MiString extends String class MiString < String
class MiColeccion extends Array class MiColeccion < Array
implements Enumerable include Enumerable
Variable estática: static int unEntero= 3 Variable de clase: @@un_entero = 3
Variable de instancia: this.foo = 1 Variable de instancia: @foo = 1
Método estático: Método de clase:
public static int foo(...) def self.foo ... end
Método de instancia: public int foo(...) Método de instancia: def foo ... end
Figura 3.5. Resumen de algunas características que tienen traducción directa entre Ruby y Java.
clude_year para decidir cómo mostrar el título completo de una película. La línea 21 mues-
tra que la sintaxis #{} se puede usar para interpolar (sustituir) el valor de una expresión
en una cadena de dobles comillas, como con #{self.title} y #{self.year}. Para ser más
exactos, #{} evalúa la expresión encerrada entre llaves y llama al método to_s en el valor
resultante, pidiéndole que se convierta a una cadena de caracteres que se pueda insertar en la
cadena exterior. La clase Object (que es el antecesor de todas las clases excepto de Basic-
Object) define un método to_s por defecto, pero la mayoría de clases lo sobreescribe para
producir una representación suya más bonita.
• class Foo abre una clase (nueva o ya existente) para añadir o cambiar métodos en
ella. Al contrario que en Java, no es una declaración, sino código real que se ejecuta
inmediatamente, creando un nuevo objeto Class y asignándolo a la constante Foo.
• @x especifica una variable de instancia y @@x especifica una variable de clase (es-
tática). El espacio de nombres es distinto, así que @x y @@x son variables diferentes.
• Sólo se puede acceder a las variables de clase y a las variables de instancia de una
clase desde esa misma clase. Cualquier acceso desde el “mundo exterior” requiere
una llamada a un método de acceso o un método modificador.
• Se puede definir un método de clase en la clase Foo usando tanto
def Foo.some_method o def self.some_method.
http://pastebin.com/zxsur5MX
1 # Note : Time . now returns current time as seconds since epoch
2 class Fixnum
3 def seconds ; self ; end
4 def minutes ; self * 60 ; end
5 def hours ; self * 60 * 60 ; end
6 def ago ; Time . now - self ; end
7 def from_now ; Time . now + self ; end
8 end
9 Time . now
10 # = > Mon Nov 07 10:18:10 -0800 2011
11 5. minutes . ago
12 # = > Mon Nov 07 10:13:15 -0800 2011
13 5. minutes - 4. minutes
14 # = > 60
15 3. hours . from_now
16 # = > Mon Nov 07 13:18:15 -0800 2011
Figura 3.6. Haciendo aritmética simple con fechas mediante la redefinición de la clase Fixnum. Unix se inventó en el año
1970, así que sus diseñadores eligieron representar el tiempo como el número de segundos que hay desde la media noche
(GMT) del 1 de enero de 1970, fecha a la que a veces se le llama como el principio de la época. Por conveniencia, un objeto
Time en Ruby responde a los métodos de operaciones aritméticas operando sobre esta representación si es posible, aunque
internamente Ruby puede representar cualquier tiempo anterior o posterior.
Por supuesto, no podemos escribir 1.minute.ago, porque sólo definimos un método lla-
mado minutes, no minute. Podríamos definir métodos adicionales con nombres singulares
que duplicasen la funcionalidad de los métodos que ya tenemos, pero esto vulnera la filosofía
DRY. Para ello, podemos aprovecharnos de la potente construcción para metaprogramación
method_missing de Ruby. Si la llamada a un método no se puede encontrar en la clase re-
ceptora o en cualquiera de sus clases antecesoras, Ruby intentará llamar a method_missing
en el receptor, pasándole el nombre y los argumentos del método inexistente. La imple-
mentación por defecto de method_missing simplemente apunta a la implementación de la
superclase, pero podemos sobreescribir esto para implementar versiones “singulares” de los
métodos de cálculo del tiempo que hemos visto antes:
http://pastebin.com/G0ztHTTP
1 class Fixnum
2 def method _missi ng ( method_id , * args )
3 name = method_id . to_s
4 if name =~ /^( second | minute | hour ) $ /
5 self . send ( name + 's ')
6 else
7 super # pass the buck to superclass
8 end
9 end
10 end
Convertimos el ID del método (que se ha pasado como símbolo) a una cadena de carac-
teres, y usamos una expresión regular para ver si la cadena coincide con cualquiera de las
palabras hour, minute, second. Si coincide, pluralizamos el nombre, y enviamos el nombre
del método pluralizado a self, es decir, al objeto que recibió la llamada original. Si no coin-
cide, ¿qué deberíamos hacer? Pensará que deberíamos avisar de un error, pero como Ruby
tiene herencia, debemos dar la posibilidad de que una de nuestras clases antecesoras sea ca-
paz de manejar la llamada del método. Llamar a super sin ningún argumento, pasa intacta la
llamada del método original con sus argumentos originales a la serie de herencia.
Intente extender este ejemplo con un método llamado days, para que se pueda escribir
2.days.ago y 1.day.ago. El uso con gusto de method_missing para lograr mayor con-
cisión es parte del lenguaje Ruby. La explicación que hay al final de la sección 3.6 muestra
94 CAPÍTULO 3. ENTORNO SAAS: INTRODUCCIÓN A RUBY
cómo se usa para construir documentos XML, y la sección 4.3 muestra cómo mejora la legi-
bilidad del método find en la parte de ActiveRecord del entorno Rails.
Resumen
• attr_accessor es un ejemplo de metaprogramación: crea nuevo código en tiempo
de ejecución, en este caso métodos de acceso (getters) y modificadores (setters)
para una variable de instancia. Este estilo de metaprogramación es extremadamente
común en Ruby.
• Cuando ni el receptor ni ninguna de sus clases antecesoras pueden manejar la
llamada de un método, se llama al método method_missing en el receptor.
method_missing puede inspeccionar el nombre y los argumentos del método in-
existente, y puede o bien tomar las acciones necesarias para manejar la llamada o
pasarla hacia arriba, hacia la clase padre, al igual que hace la implementación de
method_missing por defecto.
El método each es un iterador disponible en todas las clases de Ruby que son de tipo
colección. each toma un argumento —un bloque— y pasa cada elemento de la colección al
bloque en cada iteración. Como puede ver, un bloque se rodea de las palabras do y end; si el
bloque toma argumentos, la lista de argumentos se encierra tras el do entre |símbolos tubería|.
El bloque en este ejemplo toma un argumento: cada vez en el bloque, m toma el valor del
próximo elemento en movies.
Al contrario que en los métodos con nombre, un bloque puede acceder también a
cualquier variable que esté accesible en el ámbito donde aparece el bloque. Por ejemplo:
http://pastebin.com/vy3sZHEQ
1 separator = '= > '
2 movies . each do | m |
3 puts " #{ m . title } #{ separator } #{ m . year } "
4 end
una instantánea del ámbito, que puede ser reconstruida después cuando el bloque se ejecuta.
Este hecho se explota en muchas de las características de Rails que mejoran el estilo DRY,
incluyendo la generación de vistas (que veremos en la sección 4.4) y validaciones de mod-
elos y filtros de controladores (sección 5.1), porque permiten separar la definición de qué
va a ocurrir del cuándo va a ocurrir en el tiempo y dónde va a ocurrir en la estructura de la
aplicación.
El hecho de que los bloques sean clausuras ayudaría a explicar la aparente anomalía que
que se explica a continuación. Si la primera referencia a una variable local se realiza dentro
de un bloque, esa variable se “captura” por el ámbito del bloque y queda indefinida cuando
finaliza el bloque. Así que, por ejemplo, el siguiente código no funcionaría, asumiendo que
la línea 2 es la primera referencia a separator dentro del ámbito:
http://pastebin.com/t8KaAa1y
1 movies . each do | m |
2 separator = '= > ' # first assignment is inside a block !
3 puts " #{ m . title } #{ separator } #{ m . rating } " # OK
4 end
5 puts " Separator is #{ separator } " # === FAILS !! ===
En un lenguaje con ámbitos léxicos como Ruby, las variables son visibles al ámbito donde
son creadas y a todos los ámbitos encerrados en éste. Como en el código de arriba separator
está creada dentro del ámbito del bloque, sólo será visible dentro de ese bloque.
Resumiendo, each es sólo un método de instancia sobre una colección que toma un solo
argumento (un bloque) y ofrece elementos a ese bloque uno por uno. El uso de las operaciones
sobre colecciones está asociado a los bloques, una expresión idiomática común que Ruby
toma prestado de la programación funcional . Por ejemplo, para duplicar cada elemento
dentro de una colección, podríamos escribir:
http://pastebin.com/M6pqwJMy
1 new_ collec tion = collection . map do | elt |
2 2 * elt
3 end
Si la interpretación sintáctica no resulta ambigua, es más propio usar llaves para delimitar
un bloque corto (de una línea) en vez de usar do...end:
http://pastebin.com/nPQHG2yE
1 new_ collec tion = collection . map { | elt | 2 * elt }
Así que, ¿no hay
bucles for? Aunque Ruby tiene una gran variedad de operadores sobre colecciones; la figura 3.7 lista algunos
Ruby permite for i in
collection, el método de los más útiles. Con algo de práctica, empezará a expresar operaciones sobre colecciones
each nos permite en términos de estas expresiones funcionales automáticamente, en vez de con bucles impe-
aprovecharnos del tipado rativos. Por ejemplo, para devolver una lista de todas las palabras en algún fichero (es decir,
dinámico, que veremos componentes léxicos que consisten en términos y que están separados por componentes que
en breve, para mejorar la
reutilización de código. no son términos) que empiezan por vocal, ordenados y sin duplicados:
http://pastebin.com/dFJjugTf
1 # downcase and split are defined in String class
2 words = IO . read ( " file " ) .
3 split (/\ W +/) .
4 select { | s | s =~ /^[ aeiou ]/ i }.
5 map { | s | s . downcase }.
6 uniq .
7 sort
(Recuerde que Ruby permite dividir una sentencia en diferentes líneas para mayor legi-
bilidad siempre y cuando quede claro dónde termina la sentencia. Los puntos al final de cada
línea dejan claro que la sentencia continúa, porque un punto debe estar seguido necesaria-
3.6. BLOQUES: ITERADORES, EXPRESIONES FUNCIONALES Y CLAUSURAS 97
Figura 3.7. Algunos métodos comunes en Ruby para colecciones. Para aquellos que esperan un bloque, mostramos el
número de argumentos que espera el bloque; en blanco para aquellos métodos que no esperan un bloque. Por ejemplo, una
llamada a sort, cuyo bloque espera 2 argumentos, debería ser como: c.sort { |a,b| a <=> b }. Todos estos métodos
devuelven un objeto nuevo en vez de modificar el receptor. Algunos métodos también tienen la variante destructiva, que
termina con !, por ejemplo sort!, que modifican los argumentos que se pasan como parámetro (y también devuelve el valor
nuevo). Use los métodos destructivos con mucho cuidado.
Resumen
Autoevaluación 3.6.1. Escriba una línea de Ruby que compruebe si una cadena de carac-
teres s es un palíndromo; es decir, si se lee igual de atrás a delante que de delante a atrás.
Pista: Use los métodos de la figura 3.7, y no olvide que no debería importar si son mayúscu-
las o minúsculas: Reconocer es un palíndromo.
⇧ s.downcase == s.downcase.reverse
Podrías pensar que valdría con s.reverse=~Regexp.new(s), pero no funcionaría si s con-
tuviera metacaracteres de expresiones regulares, como $.
3.7. MIX-INS Y TIPADO DINÁMICO 99
Resumen
• Ruby usa módulos para agrupar varios mix-in. Un módulo se mezcla en una clase
poniendo include NombreModulo después de la sentencia class NombreClase.
• La clase que implementa algún conjunto de comportamientos característico de otra
clase, posiblemente usando algún mix-in, se suele decir que “suena como” la clase
a la que se parece. El esquema de Ruby para permitir un mix-in sin comprobación
estática de tipos se le suele denominar tipado dinámico.
• Al contrario que en las interfaces de Java, un mix-in no necesita una declaración
formal. Pero como Ruby no tiene tipos estáticos, es su responsabilidad asegurarse
de que la clase que incluye el mix-in satisfaga las condiciones establecidas en la
documentación del mix-in, o tendrá un error en tiempo de ejecución.
Autoevaluación 3.7.1. Suponga que mezcla Enumerable en una clase Foo que no ofrece el
método each. ¿Qué error ocurrirá cuando llame a Foo.new.map { |elt| puts elt }?
⇧ El método map de Enumerable intentará llamar a each de su receptor, pero como el
nuevo objeto Foo no define each, Ruby lanzará un error de tipo Undefined Method.
Autoevaluación 3.7.2. Qué sentencia es correcta y por qué: (a) include ’enumerable’ (b)
include Enumerable
⇧ (b) es la correcta, porque include espera el nombre de un módulo que (como el nombre de
las clases) es una constante y no una cadena de caracteres.
3.8. CREE SUS PROPIOS ITERADORES CON YIELD 101
http://pastebin.com/yAYDz8nS
1 # return every n ' th element in an enumerable
2 module Enumerable
3 def every_nth ( count )
4 index = 0
5 self . each do | elt |
6 yield elt if index % count == 0
7 index += 1
8 end
9 end
10 end
11
12 list = (1..10) . to_a # make an array from a range
13 list . every_nth (3) { | s | print " #{ s } , " }
14 # = > 1 , 4 , 7 , 10 ,
15 list . every_nth (2) { | s | print " #{ s } , " }
16 # => 1, 3, 5, 7, 9,
Figura 3.8. Un ejemplo de uso de yield en Ruby, que se basa en un constructor introducido en el lenguaje CLU. Tenga en
cuenta que definimos every_nth en el módulo Enumerable que muchas colecciones mezclan, como se describe en la
sección 3.7.
En muchos lenguajes, podemos encapsular el código que se repite de las líneas 1–7 y 9–
102 CAPÍTULO 3. ENTORNO SAAS: INTRODUCCIÓN A RUBY
11 en los métodos llamados make_header y make_footer, y luego hacer que los métodos
que generan cada página hagan esto:
http://pastebin.com/0sTEMcdN
1 def one_page
2 page = ' '
3 page << make_header ()
4 page << " Hello "
5 page << make_footer ()
6 end
7 def another_page
8 page = ' '
9 page << make_header ()
10 page << " World "
11 page << make_footer ()
12 end
Como este código parece repetitivo, podemos envolver las dos llamadas en un sólo
método:
http://pastebin.com/TsvTN5ZT
1 def make_page ( contents )
2 page = ' '
3 page << make_header ()
4 page << contents
5 page << make_footer ()
6 end
7 #
8 def one_page
9 make_page ( " Hello " )
10 end
11 def another_page
12 make_page ( " World " )
13 end
Pero en el capítulo 2 aprendimos que los patrones de diseño útiles nacen del deseo de
separar las cosas que cambian de las que permanecen igual. yield ofrece una manera mejor
de encapsular la parte común —la que se repite “alrededor” del contenido del usuario— en
su propio método:
http://pastebin.com/7TbZ12p4
1 def make_page
2 page = ' '
3 page << make_header ()
4 page << yield
5 page << make_footer ()
6 end
7 def one_page
8 make_page do
9 " Hello "
10 end
11 end
12 def another_page
13 make_page do
14 " World "
15 end
16 end
http://pastebin.com/Nqe8MwA5
1 def make_page
2 make_header << yield << make_footer
3 end
4
5 def one_page
6 make_page { " Hello " }
7 end
8 def another_page
9 make_page { " World " }
10 end
Resumen
• En el cuerpo de un método que toma un bloque como parámetro, yield transfiere el
control al bloque y opcionalmente le pasa un argumento.
• Como el bloque es una clausura, su ámbito es el mismo que tenía efecto cuando se
definió el bloque, incluso aunque el método que cede el control al bloque se esté
ejecutando en un ámbito completamente diferente.
• yield es el mecanismo general que hay detrás de los iteradores: un iterador es sim-
plemente un método que recorre una estructura de datos y usa yield para pasar un
elemento cada vez al receptor del iterador.
Autoevaluación 3.8.1. Fijándonos en la figura 3.8, observe que every_nth usa elt como el
nombre de una variable de instancia en las líneas 5 y 6. Suponga que en la línea 13 usamos
elt en vez de s para nombrar a la variable local del bloque. ‘?Qué efecto tendría este cambio,
si tiene alguno, y por qué?
⇧ No tendría ningún efecto. every_nth y el bloque que le pasamos se ejecutan en ámbitos
distintos, así que no hay “colisión” con la variable local nombrada como elt.
• Leer a.b como “atributo b del objeto a” en vez de “llamada al método b sobre el objeto
a”.
• Pensar en términos de clases y tipos estáticos tradicionales, en vez de en tipado
dinámico. Cuando se llama a un método sobre un objeto, o se hace un mix-in, lo único
que importa es si el objeto responde a ese método. El tipo o la clase de ese objeto es
irrelevante.
• Escribir bucles for explícitos en vez de usar un iterador como each y los métodos de
colección que lo explotan a través de un mecanismo mix-in como Enumerable. Use
expresiones idiomáticas funcionales como select, map, any?, all?, etc.
• Pensar en attr_accessor como una declaración de atributos. Esta y otras abrevia-
ciones relacionadas le ahorran trabajo si quiere hacer que un atributo se pueda leer o
escribir de manera pública. Pero no necesita “declarar” un atributo de ninguna manera
(con la existencia de variable de instancia es suficiente) y, con toda probabilidad, al-
gunos atributos no deberían ser visibles de manera pública. Evite la tentación de usar
attr_accessor como si estuviera escribiendo declaraciones de atributos en Java.
Si viene a Ruby sin conocer lenguajes como Lisp o Schema, las expresiones idiomáti-
cas de programación funcionales pueden serle nuevas. A menos que esté familiarizado con
JavaScript, probablemente no ha usado clausuras antes. Y a menos que conozca CLU, acos-
tumbrarse al concepto yield de Ruby le puede llevar tiempo.
Hay un pequeño chiste entre programadores que dice que “puede escribir Fortran en
cualquier lenguaje”. Este comentario quizás es injusto para Fortran —usted puede escribir
código bueno o malo en cualquier lenguaje—, pero la intención de usar esa expresión es
la de desaconsejarle trasladar hábitos de programación de un lenguaje a otro donde no son
apropiados, perdiendo así la oportunidad de usar un mecanismo del lenguaje nuevo que puede
ofrecer una solución más elegante.
Por tanto, nuestro consejo es que persevere en el lenguaje nuevo hasta que esté cómodo
con sus expresiones idiomáticas. Evite la tentación de traducir literalmente su código desde
otro lenguaje sin considerar primero si hay una manera más propia de Ruby para codificar lo
que necesita. Repetiremos este consejo cuando atajemos JavaScript en el capítulo 6.
Aprender a usar un lenguaje nuevo y aprovechar al máximo sus expresiones idiomáticas
es una habilidad vital para profesionales de software. No son tareas fáciles, pero esperamos
que centrar nuestra exposición en las características únicas y elegantes de Ruby y JavaScript
evoque su curiosidad intelectual en vez de gruñidos de resignación, y que finalmente aprecie
el valor de manejar una variedad de herramientas especializadas para elegir la más productiva
en cada nueva tarea.
• Muchos principiantes en Ruby tienen problemas con yield, que no tiene un equivalente
en Java, C or C++ (aunque versiones recientes de Python y JavaScript sí tienen meca-
nismos similares). El artículo de corrutinas de Wikipedia ofrece buenos ejemplos de
los mecanismos generales de corrutina que soporta yield. Ruby Best Practices Brown
2009 se centra en cómo aprovechar lo mejor posible las “poderosas herramientas” de
Ruby, como bloques, módulos/tipado dinámico, metaprogramación, etc. Ésta es una
buena lectura si quiere escribir Ruby como un verdadero programador de Ruby, en vez
de escribir código Java en Ruby.
Notas
1 http://validator.w3.org
2 https://api.duckduckgo.com/api
3 http://pastebin.com
4 http://builder.rubyforge.org/
5 http://ruby-doc.org/core-1.9.3/Enumerable.html
6 http://ruby-doc.org/docs/ProgrammingRuby
7 http://ruby-doc.org/
8 http://www.scribd.com/doc/2236084/Whys-Poignant-Guide-to-Ruby
Ejercicio 3.2. Teniendo en cuenta que superclass devuelve nil cuando se le llama sobre
BasicObject pero devuelve un valor no nil en el resto de ocasiones, escribe un método Ruby
que, cuando se le pase un objeto, escriba la clase del objeto y sus clases ancestras hasta
BasicObject.
Ejercicio 3.3. Ben Bitdiddle pregunta: “Si i es un entero y f es un número en coma flotante
en Ruby, y escribo i+f, al hacer la suma, ¿ i se convierte en float o f se convierte en un
entero?” Explique por qué la pregunta de Ben está mal formulada si se aplica a Ruby.
Ejercicio 3.4. Iluminado por la respuesta del Ejercicio 3.3 , ahora Ben observa que escribir
i+=f es legal en Ruby. Su pregunta es: “¿ += es un operador de separación en Ruby, o es
sólo una forma simplificada de escribir i=i+f?” Diseñe y ponga a prueba un experimento
para determinar la respuesta.
Metaprogramación
3.12. EJERCICIOS PROPUESTOS 107
Ejercicio 3.5. Siguiendo el ejemplo de la sección 3.5, aproveche el tipado dinámico de Time
para definir el método at_beginning_of_year que le deje escribir:
http://pastebin.com/NxicVYaP
1 Time . now . a t _ b e g i n n i n g _ o f _ y e a r + 1. day
2 # = > 2011 -01 -02 00:00:00 -0800
Ejercicio 3.8. Recuerde que los dos primeros enteros en la serie de Fibonacci son 1 y 1, y que
cada uno de los siguientes números en la serie son la suma de los dos anteriores. Construya
una clase que devuelva un iterador para los primeros n números de la serie de Fibonacci.
Debería ser capaz de usar la clase como sigue:
108 NOTAS
http://pastebin.com/W5nm61P9
1 # Fibonacci iterator should be callable like this :
2 f = FibSequence . new (6) # just the first 6 Fibonacci numbers
3 f . each { | s | print (s , ': ') } # = > 1:1:2:3:5:8:
4 f . reject { | s | s . odd ? } # = > [2 , 8]
5 f . reject (&: odd ?) # = > [2 , 8] ( a shortcut !)
6 f . map { | x | 2* x } # = > [2 , 2 , 4 , 6 , 10 , 16]
Pista: Mientras que los objetos de su clase implementen each, puede mezclar Enume-
rable para tener reject, map, y demás.
Ejercicio 3.9. Implemente un iterador llamado each_with_flattening que se comporte
como sigue:
http://pastebin.com/t79i1ZNu
1 [1 , [2 , 3] , 4 , [[5 , 6] , 7]]. e a c h _ w i t h _ f l a t t e n i n g { | s | print " #{ s } , " }
2 >> 1 , 2 , 3 , 4 , 5 , 6 , 7
¿Qué supuesto(s) debe hacer su iterador sobre su receptor? ¿Qué supuesto(s) debe hacer
sobre los elementos de su receptor?
Ejercicio 3.10. Extienda el módulo Enumerable con un iterador nuevo, each_permuted,
que devuelva los elementos de una colección en un orden aleatorio. El iterador debe asumir
que la colección responde a each pero no debería suponer nada más sobre los elementos.
Pista: Igual puedes usar el método rand de la librería estándar de Ruby.
Ejercicio 3.11. Un árbol binario ordenado es aquel en el que cada nodo tiene un valor y
hasta 2 hijos, cada uno de los cuales es también un árbol binario ordenado, y el valor de
cualquier elemento del subárbol izquierdo de un nodo es menor que el valor de cualquier
elemento en el subárbol derecho del nodo.
Defina una clase colección llamada BinaryTree que ofrezca los métodos de instancia
<< (insertar elemento), empty? (devuelve cierto si el árbol no tiene elementos), y each (el
iterador estándar que devuelve un elemento cada vez, en el orden que usted desee).
Ejercicio 3.12. Extienda la clase de su árbol binario ordenado para que ofrezca los sigu-
ientes métodos, cada uno de los cuales toma un bloque: include?(elt) (devuelve cierto si
el árbol incluye a elt), all? (cierto si un bloque dado es cierto para todos los elementos),
any? (cierto si un bloque dado es cierto para alguno de sus elementos), sort (ordena los
elementos). Pista: Una sóla línea de código es suficiente para hacer todo esto.
Ejercicio 3.13. Similar al ejemplo de days.ago de la sección 3.5, defina las conversiones
apropiadas entre euros, dólares americanos y yenes para poder realizar las siguientes con-
versiones:
http://pastebin.com/JhsBT11Z
1 # assumes 1 Euro =1.3 US dollars , 1 Yen =0.012 US dollars
2 5. dollars . in (: euros ) # = > 6.5
3 (1. euro - 50. yen ) . in (: dollars ) # = > 0.700
Ejercicio 3.14. ¿Cuál de estos métodos realiza modificaciones para que realmente sucedan
tal y como esperaba?
3.12. EJERCICIOS PROPUESTOS 109
http://pastebin.com/M7dfp9gZ
1 def my_swap (a , b )
2 b,a = a,b
3 end
4
5 class Foo
6 attr_accessor :a , : b
7 def my_swap_2 ()
8 @b , @a = @a , @b
9 end
10 end
11
12 def m y _ s t r i n g _ r e p l a c e _ 1 ( s )
13 s . gsub ( / Hi / , ' Hello ')
14 end
15
16 def m y _ s t r i n g _ r e p l a c e _ 2 ( s )
17 s . gsub !( / Hi / , ' Hello ')
18 end
Ejercicio 3.15. Extienda la clase Time con el método humanize que devuelva una frase
informativa describiendo la hora del día más cerca al cuarto de hora, en modo 12 horas, y
haciendo mención especial a medianoche:
http://pastebin.com/4znyp5BZ
1 >> Time . parse ( " 10:47 pm " ) . humanize
2 # = > " About a quarter til eleven "
3 >> Time . parse ( " 10:31 pm " ) . humanize
4 # = > " About half past ten "
5 >> Time . parse ( " 10:07 pm " ) . humanize
6 # = > " About ten "
7 >> Time . parse ( " 23:58 " ) . humanize
8 # = > " About midnight "
9 >> Time . parse ( " 00:29 " ) . humanize
10 # = > " About 12:30"
4 Entorno SaaS: introducción a Rails
Charles Antony Hay dos maneras de construir un diseño software: Una es hacerlo tan simple que obvia-
Richard Hoare (1934–, mente no tenga deficiencias, y la otra es hacerlo tan complicado que no tenga deficiencias
conocido como “Tony” por obvias. La primera es la más difícil. . . La persecución de la simplicidad extrema es el pre-
casi todo el mundo) recibió cio a pagar por la fiabilidad.
el Premio Turing en 1980
por sus “contribuciones Tony Hoare
fundamentales a la
definición y diseño de
lenguajes de programación”.
4.1 Fundamentos de Rails: de cero a CRUD . . . . . . . . . . . . . . . . 112
4.2 Bases de datos y migraciones . . . . . . . . . . . . . . . . . . . . . . 117
4.3 Modelos: fundamentos de Active Record . . . . . . . . . . . . . . . . 119
4.4 Controladores y vistas . . . . . . . . . . . . . . . . . . . . . . . . . . 124
4.5 Depuración: cuando las cosas van mal . . . . . . . . . . . . . . . . . 131
4.6 Envío de formularios: new y create . . . . . . . . . . . . . . . . . . . 134
4.7 Redirección y la hash flash . . . . . . . . . . . . . . . . . . . . . . . . 136
4.8 Terminando las acciones CRUD: editar/actualizar y destruir . . . . . 140
4.9 Falacias y errores comunes . . . . . . . . . . . . . . . . . . . . . . . 143
4.10 Observaciones finales: diseño para SOA . . . . . . . . . . . . . . . . 144
4.11 Para saber más . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145
4.12 Ejercicios propuestos . . . . . . . . . . . . . . . . . . . . . . . . . . . 146
111
Conceptos
Las ideas generales de este capítulo tratan sobre cómo Rails permite simplificar la creación
de aplicaciones SaaS.
• La depuración en SaaS requiere comprender los diferentes lugares en los que algo
puede ir mal durante el proceso de una petición SaaS, y hacer esa información
visible al desarrollador.
Todas estas facilidades de Rails se han diseñado para agilizar la creación de aplica-
ciones que van a funcionar en una Arquitectura Orientada a Servicios y para explotar
patrones de diseño probados para SaaS.
112 CAPÍTULO 4. ENTORNO SAAS: INTRODUCCIÓN A RAILS
Figura 4.1. La estructura estándar de directorios de un proyecto Rails incluye un directorio app para la lógica real de la
aplicación, con subdirectorios para los modelos, las vistas, y los controladores, mostrando la elección de Rails por la
arquitectura MVC ya incluso en la distribución de los ficheros del proyecto.
114 CAPÍTULO 4. ENTORNO SAAS: INTRODUCCIÓN A RAILS
http://pastebin.com/UQTR5UQh
especifican peticiones autocontenidas de qué operación realizar y sobre qué entidad o re-
curso). Edite el fichero config/routes.rb, que se autogeneró con el comando rails new.
Sustituya el contenido del fichero con lo siguiente (el fichero está formado básicamente por
comentarios, así que en realidad no va a borrar mucho):
http://pastebin.com/JpnwuT56
1 M y r o t t e n p o t a t o e s :: Application . routes . draw do
2 resources : movies
3 root : to = > redirect ( '/ movies ')
4 end
Resumen. Usted ha usado los siguientes comandos para iniciar su nueva aplicación Rails:
• rails new establece la nueva aplicación; el comando rails también tiene subco-
mandos para arrancar la aplicación de manera local con WEBrick (rails server)
y para otras tareas de gestión.
• Rails y las demás gemas de las que depende su aplicación (añadimos el sistema
de plantillas Haml y el depurador de Ruby) se listan en el fichero Gemfile de la
aplicación, el que usa Bundler para automatizar el proceso de crear un entorno con-
sistente para su aplicación, tanto en modo desarrollo como en modo producción.
• Para añadir rutas en config/routes.rb, el método resources de una línea ofre-
cido por el sistema de encaminamiento de Rails, nos ha permitido establecer un
grupo de rutas para acciones CRUD en un recurso REST.
• Los ficheros de registro que se encuentran en el directorio log recogen información
de los errores cuando algo falla.
116 CAPÍTULO 4. ENTORNO SAAS: INTRODUCCIÓN A RAILS
Figura 4.2. Como indica la explicación, las rutas pueden incluir términos “comodín” como :controlador y :accion, que
determinan el controlador y la acción que se va a invocar. Cualquier otro término que empiece con :, más cualquier
parámetro adicional codificado en el URI, estará disponible en la hash params.
Autoevaluación 4.1.1. Recuerde la página genérica de bienvenida de Rails que vio cuando
creó la aplicación. En el fichero development.log, ¿qué está pasando cuando se imprime
la línea Started GET "assets/rails.png"? (Pista: recuerde los pasos necesarios para
traer una página que contiene activos embebidos, como se describe en la sección 2.3).
⇧ El navegador está pidiendo la imagen embebida del logo de Rails para mostrar en la página
de bienvenida.
Autoevaluación 4.1.2. ¿Cuáles son los dos pasos que debe realizar para que su aplicación
use una gema Ruby concreta?
⇧ Tiene que añadir una línea en su fichero Gemfile para añadir la gema y volver a ejecutar
bundle install.
4.2. BASES DE DATOS Y MIGRACIONES 117
1. Crear una migración describiendo los cambios a realizar. Como con rails new, Rails
ofrece un generador de migraciones que le ayuda con el código repetitivo, además de
ofrecer varios métodos helper (asistentes) para describir la migración.
2. Aplicar la migración a la base de datos de desarrollo. Rails define una tarea rake para
esto.
3. Asumiendo que la migración se ha realizado satisfactoriamente, actualizar el esquema
de la base de datos de pruebas ejecutando rake db:test:prepare.
4. Ejecutar las pruebas y, si todo va bien, aplicar la migración a la base de datos de
producción y desplegar el código nuevo a producción. El proceso para aplicar las
migraciones en producción depende del entorno de despliegue; el apéndice A.8 cubre
cómo hacer esto usando Heroku, el entorno de despliegue en la nube que se usa para
los ejemplos de este libro.
118 CAPÍTULO 4. ENTORNO SAAS: INTRODUCCIÓN A RAILS
http://pastebin.com/rVw3riS9
1 class CreateMovies < ActiveRecord :: Migration
2 def up
3 create_table ' movies ' do | t |
4 t . string ' title '
5 t . string ' rating '
6 t . text ' description '
7 t . datetime ' release_date '
8 # Add fields that let Rails automatically keep track
9 # of when movies are added or modified :
10 t . timestamps
11 end
12 end
13
14 def down
15 drop_table ' movies ' # deletes the whole table and all its data !
16 end
17 end
Figura 4.3. Una migración que crea una nueva tabla movies, especificando los campos deseados junto con sus tipos. La
documentación para la clase ActiveRecord::Migration (de la cual heredan todas las migraciones) forma parte de la
documentación de Rails6 , y describe más detalles y otras opciones de migración.
Usaremos los tres primeros pasos de este proceso para añadir una tabla nueva que al-
macene el título de cada película, la clasificación, la descripción y la fecha de lanzamiento,
para coincidir con el capítulo 2. Cada migración necesita un nombre, y como esta migración
creará la tabla de películas, elegimos llamarla CreateMovies. Ejecute el comando rails
generate migration create_movies y, si todo va bien, encontrará un nuevo fichero
bajo db/migrate cuyo nombre empieza por el día y la hora de creación y termina con el
nombre que le ha dado, por ejemplo, 20111201180638_create_movies.rb. (Este sistema
de nombrado permite a Rails aplicar las migraciones en el orden en que se crearon, porque
los nombres de los ficheros se ordenarán por orden de fecha). Edite este fichero para que se
parezca al de la figura 4.3. Como puede ver, las migraciones ilustran el uso idiomático de los
bloques: el método ActiveRecord::Migration#create_table toma un bloque de 1 argu-
mento y cede a ese bloque un objeto representando la tabla que se está creando. Este objeto
tabla ofrece los métodos string, datetime y demás, e invocarles hace que se generen las
columnas en la tabla de la base de datos recién creada; por ejemplo, t.string ’title’ crea una
columna llamada title que puede guardar una cadena de caracteres, que para la mayoría de
las bases de datos significa una cadena de hasta 255 caracteres.
Guarde el fichero y teclee rake db:migrate para aplicar realmente la migración y
crear esta tabla. Note que esta tarea doméstica también almacena el número mismo de la
migración en la base de de datos y, por defecto, sólo se ejecutan las migraciones que to-
davía no se han aplicado. (Teclee rake db:migrate de nuevo y verifique que no hace
nada esta segunda vez). rake db:rollback “deshará” la última migración ejecutando el
método down. (Compruébelo. Y luego vuelva a ejecutar rake db:migrate para volver a
a aplicar la migración). Sin embargo, algunas migraciones, como aquellas que borran datos,
no se pueden “deshacer”; en estos casos, el método down lanzará una excepción de tipo
ActiveRecord::IrreversibleMigration.
4.3. MODELOS: FUNDAMENTOS DE ACTIVE RECORD 119
Resumen
• Rails define tres entornos —desarrollo, producción y pruebas— cada uno con su
propia copia de la base de datos.
• Una migración es un script que describe un conjunto específico de cambios en
una base de datos. Como las aplicaciones evolucionan y añaden características,
se añaden migraciones para expresar los cambios requeridos en una base de datos y
que soportan esas nuevas características.
• Cambiar una base de datos usando una migración requiere tres pasos: crear la mi-
gración, aplicar la migración a su base de datos de desarrollo y (si aplica), después
de probar su código, aplicar la migración a su base de datos de producción.
• El generador rails generate migration añade el código repetitivo de una mi-
gración nueva, y la clase ActiveRecord::Migration contiene métodos de ayuda
para definirla.
• rake db:migrate aplica sólo las migraciones que todavía no se han aplicado en la
base de datos de desarrollo. El método para aplicar las migraciones a una base de
datos de producción depende del entorno de despliegue.
Explicación. Entornos
Los distintos entornos también pueden sobreescribir comportamientos específicos de la
aplicación. Por ejemplo, el modo en producción puede especificar optimizaciones que
ofrezcan un mejor rendimiento, pero compliquen la depuración si se usan en el modo
desarrollo. El modo de pruebas puede “simular” interacciones externas, por ejemplo,
guardando a un fichero correos salientes, en vez de enviarlos de verdad. El fichero config/
environment.rb especifica las instrucciones generales de arranque de la aplicación, pero
config/environments/production.rb permite establecer opciones específicas usadas
sólo en modo producción, y lo mismo con los ficheros development.rb y test.rb del
mismo directorio.
http://pastebin.com/sGHfp79H
1 ## ## Create
2 starwars = Movie . create !(: title = > ' Star Wars ' ,
3 : release_date = > ' 25/4/1977 ' , : rating = > ' PG ')
4 # note that numerical dates follow European format : dd / mm / yyyy
5 requiem = Movie . create !(: title = > ' Requiem for a Dream ' ,
6 : release_date = > ' Oct 27 , 2000 ' , : rating = > 'R ')
7 # Creation using separate ' save ' method , used when updating existing records
8 field = Movie . new (: title = > ' Field of Dreams ' ,
9 : release_date = > '21 - Apr -89 ' , : rating = > ' PG ')
10 field . save !
11 field . title = ' New Field of Dreams '
12 ## ## Read
13 pg_movies = Movie . where ( " rating = ' PG '" )
14 anci ent_mo vies = Movie . where ( ' release_date < : cutoff and rating = : rating ' ,
15 : cutoff = > ' Jan 1 , 2000 ' , : rating = > ' PG ')
16 ## ## Another way to read
17 Movie . find (3) # exception if key not found ; find_by_id returns nil instead
18 ## ## Update
19 starwars . u p d a t e _ a t t r i b u t e s (: description = > ' The best space western EVER ' ,
20 : release_date = > ' 25/5/1977 ')
21 requiem . rating = 'NC -17 '
22 requiem . save !
23 ## ## Delete
24 requiem . destroy
25 Movie . where ( ' title = " Requiem for a Dream " ')
26 ## ## Find returns an enumerable
27 Movie . where ( ' rating = " PG " ') . each do | mov |
28 mov . destroy
29 end
Figura 4.4. Aunque las funcionalidades del Modelo en el patrón MVC suelen ser llamadas por el controlador, estos
pequeños ejemplos le ayudarán a familiarizarse con las características básicas de ActiveRecord antes de escribir el
controlador.
de qué tipos deberían ser. Tercero, da a cada atributo del objeto Movie los métodos de
acceso y modificación (getters y setters) parecido como hacía attr_accessor, excepto que
estos atributos hacen más que modificar una variable de instancia. Antes de continuar, teclee
Movie.all, que devuelve una colección de todos los objetos de la tabla asociada a la clase
Movie.
Con fines demostrativos, especificamos la fecha de lanzamiento en la línea 6 usando un
formato diferente al de la línea 3. Como Active Record sabe por el esquema de la base de
datos que la fecha de lanzamiento, release_date, es una columna de tipo fecha, datetime
(recuerde el fichero de migración de la figura 4.3), tratará de convertir cualquier valor que le
pasemos a ese atributo en un valor de tipo fecha.
Recuerde por la figura 3.1 que los métodos cuyos nombres terminan en ! son “peligrosos”.
create! es peligroso porque si algo va mal al crear el objeto y almacenarlo en la base de datos,
se lanza una excepción. La versión no peligrosa, create, devuelve el objeto recién creado
si todo va bien o nil si algo falla. Para un uso interactivo, preferimos create! para no tener
que comprobar el valor devuelto cada vez que lo invocamos, pero en una aplicación es más
común usar create y comprobar el valor devuelto.
Las líneas 7–11 (Save) muestran que los objetos de los modelos Active Record en memo-
ria son independientes de las copias en la base de datos, que deben ser actualizadas explícita-
mente. Por ejemplo, las líneas 8–9 crean un nuevo objeto Movie en memoria, sin almacenarlo
en la base de datos. (Puede confirmarlo ejecutando Movie.all después de las líneas 8–9. No
verá Field of Dreams entre las películas listadas). La línea 10 sí hace que el objeto persista
en la base de datos. La distinción es crítica: la línea 11 cambia el valor del campo title de la
película, pero sólo de la copia en memoria —haga Movie.all de nuevo y verá que la copia de
la base de datos no se ha visto modificada—. Tanto save como create hacen que el objeto
se escriba en la base de datos, pero no ocurre éso si sólo se cambian los valores del atributo.
Las líneas 12–15 (Read) muestran una forma de consultar objetos en la base de datos. El
método where se llama así por la palabra clave WHERE en SQL (Structured Query Language,
lenguaje estructurado de consultas) usado por la mayoría de sistemas RDBMS, incluyendo
SQLite3. Puede especificar una restricción directamente por medio de una cadena de carac-
teres, como en la línea 13, o usando sustitución de palabras clave como en las líneas 14–15.
Siempre se prefiere la sustitución de palabras clave porque, como veremos en el capítulo 12,
permite que Rails impida los ataques de inyección SQL (SQL inyection) a su propia apli-
cación. Al igual que con create!, la fecha se convierte correctamente de una cadena de
caracteres a un objeto Time, y desde ahí a la representación interna de tiempo que tenga la
base de datos. Como las condiciones especificadas pueden corresponder con múltiples obje-
tos, where siempre devuelve un Enumerable sobre el cual usted puede llamar a cualquiera
de los métodos de Enumerable, como los que vimos en la figura 3.7.
La línea 17 (Read) muestra una de las primeras formas de consultar objetos, que es la de
devolver un sólo objeto correspondiente a la clave primaria dada. Recuerde por la figura 2.11
que a todo objeto almacenado en un RDBMS se le asigna una clave primaria desprovista de
semántica pero que garantiza que es única en esa tabla. Cuando creamos nuestra tabla con la
migración, Rails incluyó una clave primaria numérica por defecto. Como la clave primaria
de un objeto es única y permanente, a menudo se usa para identificar el objeto en un URI
REST, como vimos en la sección 2.7.
Las líneas 18–22 (Update) muestran cómo modificar un objeto. Al igual que ocurría con
create vs. save, tenemos dos opciones: usar update_attributes para actualizar la base de
datos inmediatamente, o cambiar los valores de un atributo en el objeto en memoria y luego
122 CAPÍTULO 4. ENTORNO SAAS: INTRODUCCIÓN A RAILS
http://pastebin.com/3bjg6YYx
1 # Seed the Rot tenPot atoes DB with some movies .
2 more_movies = [
3 {: title = > ' Aladdin ' , : rating = > 'G ' ,
4 : release_date = > '25 - Nov -1992 '} ,
5 {: title = > ' When Harry Met Sally ' , : rating = > 'R ' ,
6 : release_date = > '21 - Jul -1989 '} ,
7 {: title = > ' The Help ' , : rating = > 'PG -13 ' ,
8 : release_date = > '10 - Aug -2011 '} ,
9 {: title = > ' Raiders of the Lost Ark ' , : rating = > ' PG ' ,
10 : release_date = > '12 - Jun -1981 '}
11 ]
12
13 more_movies . each do | movie |
14 Movie . create !( movie )
15 end
Figura 4.5. Se conoce como inicializar el añadir datos iniciales a la base de datos, y es distinto de las migraciones, que se
usan para gestionar cambios en el esquema de la base de datos. Copie este código en db/seeds.rb y ejecute rake db:seed
para hacerlo correr.
hacerlo persistente con save! (que, como create!, tiene el equivalente “seguro” save, que
devuelve nil en vez de lanzar una excepción si algo va mal).
Las líneas 23–25 (Delete) muestran cómo borrar un objeto. El método destroy (línea
24) borra el objeto de la base de datos de manera permanente. Todavía se puede inspec-
cionar la copia en memoria del objeto, pero si usted intenta modificarlo o llamar a cualquier
método sobre él para el que se necesite acceder a la base de datos, se lanzará una excep-
ción (después de hacer destroy, intente hacer requiem.update_attributes(...), o incluso
requiem.rating=’R’ para comprobar esto).
Las líneas 26–29 muestran que el resultado de una lectura sobre la base de datos suena
como una colección: podemos usar each para iterar sobre ella y borrar cada película una a
una.
Esta visión general tan rápida de Active Record apenas rasca la superficie, pero debería
clarificar la manera en la que los métodos que ofrece ActiveRecord::Base soportan las
acciones CRUD básicas.
Como último paso antes de continuar, debería inicializar (seed, sembrar) la base de datos
con algunas películas, para hacer el resto del capítulo más interesante, usando el código de la
figura 4.5. Copie el código en db/seeds.rb y ejecute rake db:seed para hacerlo correr.
4.3. MODELOS: FUNDAMENTOS DE ACTIVE RECORD 123
Resumen
• Active Record usa una convención sobre configuración para inferir los nombres de
la base de datos a partir de los nombres de las clases de los modelos, y para inferir
los nombres y tipos de las columnas (atributos) asociadas con un tipo de modelo
dado.
• El soporte básico de Active Record se centra en las acciones CRUD: crear (Create),
leer (Read), modificar (Update), borrar (Delete).
• Las instancias de los modelos se pueden crear (Create) llamando a new y después a
save, o llamando a create, que combina los dos.
• Las instancias de los modelos se pueden leer o consultar (Read) usando where para
expresar las condiciones de coincidencia o find para buscar la clave primaria (ID)
directamente, como ocurre si se procesa un URI REST que lleve embebido un ID de
objeto.
Autoevaluación 4.3.1. ¿Por qué where y find son métodos de clase y no de instancia?
⇧ Los métodos de instancia operan sobre una instancia de una clase, pero hasta que no
encontremos uno o más objetos, no tenemos instancia sobre la cual operar.
Autoevaluación 4.3.2. ¿Los modelos de Rails adquieren los métodos where y find a través
de (a) herencia o (b) el mecanismo mix-in? Pista: comprueba el fichero movie.rb.
⇧ (a) Los heredan de ActiveRecord::Base.
http://pastebin.com/ZLBvm1iN
1 # This file is app / controllers / m o v i e s _ c o n t r o l l e r . rb
2 class M o v i e s C o n t r o l l e r < A p p l i c a t i o n C o n t r o l l e r
3 def index
4 @movies = Movie . all
5 end
6 end
http://pastebin.com/dLwJ4ZvH
1 -# This file is app / views / movies / index . html . haml
2 % h1 All Movies
3
4 % table # movies
5 % thead
6 % tr
7 % th Movie Title
8 % th Rating
9 % th Release Date
10 % th More Info
11 % tbody
12 - @movies . each do | movie |
13 % tr
14 % td = movie . title
15 % td = movie . rating
16 % td = movie . release_date
17 % td = link_to " More about #{ movie . title } " , movie_path ( movie )
Figura 4.6. El código del controlador y la plantilla con el lenguaje de marcado para soportar la acción REST index.
se pluraliza para formar el nombre del fichero del controlador). Todos los controladores
de su aplicación heredan del controlador raíz de la aplicación llamado Application-
Controller (en app/controllers/application_controller.rb), que a su vez
hereda de ActionController::Base.
• Cada método de instancia del controlador se nombra usando snake_lower_case (e-
lementos en minúsculas, unidos por guión bajo) de acuerdo a la acción que maneja, así
que el método show manejaría la acción Show.
• La plantilla de la vista para Show está en app/views/movies/show.html.haml,
con la extensión .haml indicando el uso del intérprete de Haml. Se incluyen otras
extensiones como .xml para un fichero con código XML Builder (como vimos en la
sección 3.6), .erb (que veremos en breve) para el intérprete de código Ruby embebido
en Rails, y muchos más.
El módulo de Rails que programa cómo se van a manejar las vistas es Action-
View::Base. Como hemos estado usando el lenguaje de marcado Haml para nuestras
vistas (recuerde que añadimos la gema Haml a las dependencias de Gemfile), nuestros
ficheros de la vista tendrán los nombres terminados en .html.haml. Por tanto, para im-
plementar la acción REST del índice, index, debemos definir una acción index en app/
controllers/movies_controller.rb y una plantilla de vista en app/views/movies/
index.html.haml. Crea estos dos ficheros usando la figura 4.6 (necesitará crear el directo-
rio intermedio app/views/movies/).
El método controlador simplemente recupera todas las películas de la tabla Movies usan-
do el método all introducido en la sección anterior, y se lo asigna a la variable de instancia
@movies. Recuerde del tour sobre una aplicación Rails visto en el capítulo 2, que las va-
riables de instancia definidas en las acciones del controlador están disponibles en las vistas;
126 CAPÍTULO 4. ENTORNO SAAS: INTRODUCCIÓN A RAILS
la línea 12 de index.html.haml itera sobre la colección @movies usando each. Hay tres
cosas que observar en esta pequeña plantilla.
Primero, las columnas en la cabecera de la tabla (th) sólo tienen texto estático descri-
biendo las columnas de la tabla, pero las columnas en el cuerpo de la tabla, (td), usan la sin-
taxis Haml = para indicar que el contenido de las etiquetas debe ser evaluado como código
Inmunización Ruby, sustituyendo en el documento HTML el resultado . En este caso, estamos usando los
(Sanitization) La sintaxis
7 atributos accesores (getters)de los objetos Movie aportados por ActiveRecord.
= de Haml inmuniza el
resultado de evaluar el Segundo, le hemos dado a la tabla de películas el ID movies en HTML. Usaremos esto
código Ruby antes de más tarde para dar cierto estilo visual a la página usando CSS, como aprendimos en el capí-
insertarlo en la salida tulo 2.
HTML, para ayudar a
impedir la inyección de
Lo tercero es la llamada en la línea 17 a link_to, uno de los muchos métodos helper que
8
código con lenguaje script ofrece ActionView para crear las vistas. Como dice su documentación , el primer argumento
(cross-site scripting) y es una cadena de caracteres que aparecerá como un enlace (texto sobre el cual se puede hacer
ataques similares descritos clic) en la página y el segundo argumento se usa para crear el URI que se convertirá en la
en el capítulo 12.
dirección de destino. Este argumento puede tomar varias formas; la forma que hemos usado
se aprovecha del asistente para URI movie_path() (como se muestra en rake routes para
la acción show), que toma como su argumento una instancia de un recurso REST (en este
caso una instancia de Movie) y genera el URI REST para la ruta REST de Show para ese
objeto. Este comportamiento es un bonito ejemplo de la reflexión y la metaprogramación al
servicio de la concisión. Como le recuerda rake routes, la acción Show para una película
se expresa con el URI /movies/:id, donde :id es la clave primaria de la película en la
tabla Movies, así que esa apariencia tendrá el enlace de destino creado por link_to. Para
verificar esto, reinicie la aplicación (rails server en el directorio raíz de la aplicación) y
visite http://localhost:3000/movies/, el URI correspondiente a la acción index. Si
todo está bien, debería ver una lista de cualquier película de la base de datos. Si usa la opción
Ver código fuente (View Source) de su navegador para ver el código generado, podrá ver que
los enlaces generados por link_to tienen los URI correspondientes a la acción show de cada
una de las películas (proceda y seleccione uno, pero espere un error, porque todavía no hemos
creado el método show del controlador).
La línea resources :movies que añadimos en la sección 4.1 crea realmente una am-
plia variedad de métodos helper para los URI de tipo REST, resumidos en la figura 4.7.
Como habrá podido adivinar, la convención sobre configuración determina los nombres de
los métodos helper, y la metaprogramación se usa para definirlos al vuelo como resultado de
usar resources :movies. La creación y uso de esos métodos helper puede parecer gratuita
hasta que se de cuenta de que es posible definir rutas mucho más complejas e irregulares,
más allá de las rutas REST estándar que hemos usado hasta ahora, o si decide durante el
desarrollo de su aplicación que tiene más sentido usar un esquema de encaminamiento di-
ferente. Los métodos helper aíslan a las vistas de estos cambios y permite que se centren
en qué mostrar, en vez de tener que incluir código dedicado a cómo mostrarlo. De hecho,
si el segundo argumento de link_to es un recurso para el cual se han establecido rutas en
routes.rb, link_to generará automáticamente la ruta REST para mostrar (show) ese re-
curso, así que la línea 17 de la figura 4.6 podría haberse escrito como link_to "More about
#{movie.title}",movie.
Hay una última cosa a tener en cuenta sobre estas vistas. Si mira el código fuente en su
navegador, verá que incluye lenguaje HTML que no aparece en nuestra plantilla Haml, como
el elemento head con enlaces a la hoja de estilo assets/application.css y la etiqueta
<title>. Este marcado proviene de la plantilla application, que “envuelve” a todas las
4.4. CONTROLADORES Y VISTAS 127
Figura 4.7. Como se describe en la documentación para la clase ActionView::Helpers, Rails usa metaprogramación para
crear métodos helper de encaminamiento basados en el nombre de su clase ActiveRecord. Se asume que m es un objeto
ActiveRecord de tipo Movie. Las rutas REST son como las muestra la salida de rake routes; recuerde que rutas
diferentes pueden tener el mismo URI pero diferentes métodos HTTP, por ejemplo, create vs. index.
http://pastebin.com/a9TbxRmU
1 !!! 5
2 % html
3 % head
4 % title Rott enPota toes !
5 = s t y l e s h e e t _ l i n k _ t a g ' application '
6 = j a v a s c r i p t _ i n c l u d e _ t a g ' application '
7 = csrf_m eta_ta gs
8
9 % body
10 # main
11 = yield
Por su cuenta, intente crear la acción y la vista del controlador para show usando un
proceso similar:
1. Use rake routes para recordar qué nombre debería darle al método controlador y
qué parámetros se pasarán en el URI
2. En el método del controlador, use el método ActiveRecord más adecuado de la sec-
ción 4.3 para recuperar el objeto Movie apropiado de la base de datos, para después
asignarlo a una variable de instancia
128 CAPÍTULO 4. ENTORNO SAAS: INTRODUCCIÓN A RAILS
http://pastebin.com/5hfPskzM
1 # in app / controllers / m o v i e s _ c o n t r o l l e r . rb
2
3 def show
4 id = params [: id ] # retrieve movie ID from URI route
5 @movie = Movie . find ( id ) # look up movie by unique ID
6 # will render app / views / movies / show . html . haml by default
7 end
Figura 4.9. Un ejemplo de implementación del método controlador para la acción Show. Una implementación más robusta
capturaría y trataría la excepción ActiveRecord::RecordNotFound, como advertimos en la sección 4.3. Le mostraremos
cómo manejar estos casos en el capítulo 5.
http://pastebin.com/TbbGtpHn
1 -# in app / views / movies / show . html . haml
2
3 % h2 Details about # { @movie . title }
4
5 % ul # details
6 % li
7 Rating :
8 = @movie . rating
9 % li
10 Released on :
11 = @movie . release_date . strftime ( " % B %d , % Y " )
12
13 % h3 Description :
14
15 % p # description = @movie . description
16
17 = link_to ' Back to movie list ' , movies_path
Figura 4.10. Un ejemplo de vista relacionada con la figura 4.9. Para el estilo CSS posterior, dimos identificadores únicos a la
lista de elementos de detalles, (ul), y a la descripción de un solo párrafo, (p). Usamos la función de biblioteca strftime10
para formatear la fecha de una manera más atractiva, y el método link_to con el método helper REST movies_path
(figura 4.7) para ofrecer enlaces apropiados de vuelta a la página del listado. En general, puede añadir _path a cualquier
método asistente de recursos REST de la columna de la izquierda que aparece en la salida de rake routes para llamar a un
método que generará el correspondiente URI REST.
3. Cree una plantilla de la vista en el lugar correcto bajo la jerarquía de app/views, y use
el lenguaje de marcado Haml para mostrar los diferentes atributos del objeto Movie
que estableció en el método controlador
4. Compruebe su método y la vista haciendo clic en uno de los enlaces de películas de la
vista de index
Cuando haya acabado, puede cotejar lo que ha hecho por usted mismo con el ejemplo del
método del controlador de la figura 4.9 y el ejemplo de vista de la figura 4.10. Experimente
con otros valores para los argumentos de link_to y strftime para entender cómo funcionan.
Como las vistas actuales son muy básicas y toscas, mientras sigamos trabajando con la
aplicación, estaría bien que fuera algo atractivo para la vista. Copie la hoja de estilo CSS que
hay debajo en app/assets/stylesheets/application.css, que ya está incluida en la
línea 5 de la plantilla application.html.haml.
4.4. CONTROLADORES Y VISTAS 129
http://pastebin.com/28CD45Cm
1 /* Simple CSS styling for R ottenP otatoe s app */
2 /* Add these lines to app / assets / stylesheets / application . css */
3
4 html , body {
5 margin : 0;
6 padding : 0;
7 background : White ;
8 color : DarkSlateGrey ;
9 font - family : Tahoma , Verdana , sans - serif ;
10 font - size : 10 pt ;
11 }
12 div # main {
13 margin : 0;
14 padding : 0 20 px 20 px ;
15 }
16 a {
17 background : transparent ;
18 color : maroon ;
19 text - decoration : underline ;
20 font - weight : bold ;
21 }
22 h1 {
23 color : maroon ;
24 font - size : 150%;
25 font - style : italic ;
26 display : block ;
27 width : 100%;
28 border - bottom : 1 px solid DarkSlateGrey ;
29 }
30 h1 . title {
31 margin : 0 0 1 em ;
32 padding : 10 px ;
33 background - color : orange ;
34 color : white ;
35 border - bottom : 4 px solid gold ;
36 font - size : 2 em ;
37 font - style : normal ;
38 }
39 table # movies {
40 margin : 10 px ;
41 border - collapse : collapse ;
42 width : 100%;
43 border - bottom : 2 px solid black ;
44 }
45 table # movies th {
46 border : 2 px solid white ;
47 font - weight : bold ;
48 background - color : wheat ;
49 }
50 table # movies th , table # movies td {
51 padding : 4 px ;
52 text - align : left ;
53 }
54 # notice , # warning {
55 background : rosybrown ;
56 margin : 1 em 0;
57 padding : 4 px ;
58 }
59 form label {
60 display : block ;
61 line - height : 25 px ;
62 font - weight : bold ;
63 color : maroon ;
64 }
130 CAPÍTULO 4. ENTORNO SAAS: INTRODUCCIÓN A RAILS
Resumen
• El lenguaje Haml de plantillas le permite mezclar en sus vistas etiquetas HTML con
código Ruby. El resultado al evaluar ese código Ruby se puede o bien descartar o
bien añadirse a la página HTML.
Autoevaluación 4.4.1. En la figura 4.7, ¿por qué los métodos helper de las acciones New
(new_movie_path) y Create (movies_path) no cogen ningún argumento, como en el
caso de los métodos helper de Show y Update?
⇧ Show y Update operan sobre películas existentes, así que toman un argumento para identi-
ficar sobre qué película operan. New y Create, por definición, operan sobre películas que aún
no existen.
Autoevaluación 4.4.2. En la figura 4.7, ¿por qué el método asistente de la acción Index no
toma ningún argumento? (Pista: La razón es distinta a la de la respuesta a 4.4.1).
⇧ La acción Index simplemente muestra una lista de todas las películas, así que no se necesita
ningún argumento que distinga sobre qué película se opera.
Autoevaluación 4.4.3. En la figura 4.6, ¿porqué no hay un end asociado al do de la línea
12?
⇧ Al contrario que Ruby, Haml se apoya en la indentación para indicar anidamiento, así que
Haml ofrece el end cuando ejecuta el código Ruby del do.
4.5. DEPURACIÓN: CUANDO LAS COSAS VAN MAL 131
Aunque es impresionante que obtuviera su respuesta en poco más de una hora, esto sig-
nifica que también perdió una hora de tiempo para codificar, razón por la cual usted debería
escribir y enviar una pregunta sólo después de haber agotado las otras alternativas. ¿Cómo
puede avanzar a la hora de depurar problemas por su cuenta? Hay dos tipos de problemas.
En el primer tipo, un error o una excepción de alguna clase aborta la aplicación. Como Ruby
es un lenguaje interpretado, los errores de sintaxis pueden causar esto (al contrario que Java,
que no compilará si hay errores de sintaxis). Aquí hay algunas cosas que se pueden intentar
si la aplicación se para:
Resumen
• Use un editor que tenga en cuenta el lenguaje de programación, con resaltado de
sintaxis e indentación automática, para que le ayude a encontrar errores de sintaxis.
• Instrumente su aplicación insertando la salida de debug o inspect dentro de las
vistas, o convirtiéndolas en el argumento de raise, lo que provocará una excepción
en tiempo de ejecución que mostrará el mensaje como una página web.
• Para depurar usando el depurador interactivo, asegúrese de que el fichero Gemfile de Para depurar
su aplicación incluye debugger, arranque el servidor de su aplicación con rails aplicaciones que no
sean con Rails, inserte
server --debugger, y coloque la sentencia debugger en el punto del código
require ’debugger’ al
donde desee que se pare su aplicación. principio de su aplicación.
Autoevaluación 4.5.1. ¿Porqué no puede usar print o puts para desplegar mensajes que le
ayuden a depurar sus aplicaciónes SaaS?
⇧ Al contrario que las aplicaciones por línea de comandos, las aplicaciones SaaS no están
unidas a una ventana de terminal, por eso no hay un lugar claro por dónde pueda salir el
resultado de una sentencia print o puts.
134 CAPÍTULO 4. ENTORNO SAAS: INTRODUCCIÓN A RAILS
Autoevaluación 4.5.2. De los tres métodos de depuración descritos en esta sección, ¿cuales
son los apropiados para recoger un diagnóstico o información de instrumentación una vez
que su aplicación se haya desplegado en producción?
⇧ Sólo es apropiado el método logger, porque los otros dos métodos (detener la ejecución
en un controlador o insertar información diagnóstica en las vistas ) interferiría con el uso de
los clientes reales en la aplicación en producción.
Por supuesto, antes de ir más lejos, necesitamos ofrecer al usuario una manera de llegar
al formulario que vamos a crear. Como el formulario será para crear una nueva película, le
corresponderá a la acción REST new, y seguiremos la convención colocando el formulario
en app/views/movies/new.html.haml. Podemos así aprovecharnos del método asistente
para los URI REST new_movie_path, del que diponemos automáticamente, para crear un
enlace al formulario. Haga esto añadiendo una sola línea al final de index.html.haml:
http://pastebin.com/XUGTnere
1 -# add to end of index . html . haml
2
3 = link_to ' Add new movie ' , new_ movie_ path
¿Qué acción del controlador se disparará si el usuario hace clic en este enlace? Como
hemos usado el método helper para URI new_movie_path, se disparará la acción new
del controlador. No hemos definido esta acción aún pero, de momento, como el usuario está
creando una nueva película, la única cosa que necesita realizar la acción es hacer que se
muestre la vista correspondiente a la acción new. Recuerde que, por defecto y automática-
mente, cada método del controlador intenta mostrar una plantilla con el nombre correspon-
diente (en este caso new.html.haml), así que simplemente añada este sencillo método new
a movies_controller.rb:
http://pastebin.com/FeYh04c6
1 def new
2 # default : render ' new ' template
3 end
Rails facilita describir un formulario de envío usando los métodos helper de etiquetas
de formularios14 , disponibles en todas las vistas. Copie el código de la figura 4.11 en app/
views/movies/new.html.haml y vea el screencast 4.6.1 para obtener una descripción de
lo que está pasando.
4.6. ENVÍO DE FORMULARIOS: NEW Y CREATE 135
http://pastebin.com/RPPNrMfK
1 % h2 Create New Movie
2
3 = form_tag movies_path , : method = > : post do
4
5 = label : movie , : title , ' Title '
6 = text_field : movie , : title
7
8 = label : movie , : rating , ' Rating '
9 = select : movie , : rating , [ 'G ' , ' PG ' , 'PG -13 ' , 'R ' , 'NC -17 ']
10
11 = label : movie , : release_date , ' Released On '
12 = date_select : movie , : release_date
13
14 = submit_tag ' Save Changes '
Figura 4.11. El formulario que ve el usuario para crear y añadir una película nueva en RottenPotatoes.
Como menciona el screencast, no todos los tipos de campos de entrada están soportados
por los métodos helper de etiquetas de formulario (en este caso, los campos de fecha no están
soportados), y en algunos casos necesitará generar formularios cuyos campos no se van a
corresponder necesariamente con los atributos de un objeto ActiveRecord.
Para resumir dónde nos encontramos, hemos creado el método new del controlador, que
desplegará una vista ofreciendo al usuario un formulario a rellenar, hemos colocado esa vista
en new.html.haml, y hemos hecho que el formulario se envíe al método create del con-
trolador. Todo lo que queda es usar la información contenida en params (los valores de los
campos del formulario) para crear esa nueva película en la base de datos.
Resumen
• Rails ofrece helpers para generar un formulario cuyos campos se corresponden con
los atributos de un tipo particular de objeto ActiveRecord.
• Cuando creamos un formulario, hay que especificar la acción del controlador que
va a recibir ese formulario, pasando a form_tag el URI REST y método HTTP
apropiados (tal y como aparece con rake routes) .
una nueva película. La razón es que la ruta requiere tanto un URI como un método. Como
muestra la figura 4.7, el método asistente movies_path con el método GET dirigiría a la
acción index, mientras que movies_path con el método POST dirigiría a la acción create.
Esto nos lleva a la tercera pregunta que nos hicimos al principio de la sección 4.6:
¿qué vista deberíamos mostrar cuando se completa la acción create? Para ser consistentes
con otras acciones como show, podríamos crear una vista llamada app/views/movies/
create.html.haml, que contenga un bonito mensaje informando al usuario de que la ac-
ción se ha completado con éxito, pero parece gratuito tener una vista aparte para hacer esto.
Lo que se hace en muchas aplicaciones web es devolver al usuario una página más útil—
como la página de bienvenida, o la lista de todas las películas— pero con un mensaje de
éxito añadido en esa página para hacer saber al usuario que sus cambios se han almacenado
correctamente.
Rails facilita la implementación de este comportamiento. Para enviar al usuario a una
página diferente, redirect_to provoca que una acción del controlador no muestre una
vista, sino que realiza una nueva petición a otra acción diferente. De este modo, redi-
rect_to movies_path es justo como si el usuario de repente solicitara la acción REST
index GET movies (es decir, la acción correspondiente al método asistente movies_path):
la acción index se ejecutará por completo y desplegará su vista como siempre. En otras
palabras, la acción de un controlador debe terminar o bien desplegando una vista, o bien
redirigiendo a otra acción. Elimine el punto de ruptura de depuración de la acción de contro-
lador (la que insertó si modificó su código de acuerdo al screencast 4.7.1) y modifíquelo para
138 CAPÍTULO 4. ENTORNO SAAS: INTRODUCCIÓN A RAILS
Por supuesto, para ser amigable, nos gustaría mostrar un mensaje para hacer saber que la
creación de la película se ha realizado satisfactoriamente. (Pronto veremos qué hacer cuando
la acción ha fallado). El problema es que cuando llamamos a redirect_to, comienza una
nueva petición HTTP; y como HTTP es un protocolo sin estado, todas las variables asociadas
a la petición de create se pierden.
Para abordar este escenario tan habitual, flash[] es un mecanismo especial que actúa
como una hash, pero persiste desde la petición actual hasta la siguiente (dentro de un mo-
mento veremos cómo ataja esto Rails). En otras palabras, si ponemos algo en flash[] durante
la acción del controlador, podemos acceder a ello durante la acción posterior también. Toda
la hash persiste, pero, por convención, flash[:notice] se usa para mensajes informativos y
flash[:warning] se usa para mensajes sobre cosas que han ido mal. Modifique la acción del
controlador para almacenar un mensaje útil en flash, y compruébelo:
http://pastebin.com/6DuHAwbN
1 # in m o v i e s _ c o n t r o l l e r . rb
2 def create
3 @movie = Movie . create !( params [: movie ])
4 flash [: notice ] = " #{ @movie . title } was successfully created . "
5 redirect_to movies_path
6 end
http://pastebin.com/4rsZ5qyx
1 -# this goes just inside % body :
2 - if flash [: notice ]
3 # notice . message = flash [: notice ]
4 - elsif flash [: warning ]
5 # warning . message = flash [: warning ]
Figura 4.13. Fíjese en el uso de CSS para dar estilo a los mensajes flash: cada tipo de mensaje se muestra en un div cuyo
ID único es o notice o warning, dependiendo del tipo de mensaje, pero comparten una clase común, message. Esto nos da
la libertad en nuestro fichero CSS de poner el mismo estilo para los dos tipos de mensajes haciendo referencia a su clase, o
bien poner estilos diferentes haciendo referencia a sus IDs. La concisión de Haml permite de manera notable expresar en
una sola línea los atributos de ID y de clase de cada div, además del mensaje de texto.
Resumen
• Aunque la acción de un controlador suele finalizar mostrando la vista correspondi-
ente a dicha acción, para algunas acciones como create es más útil enviar al usuario
de vuelta a alguna otra vista. Con redirect_to puede sustituir ese comportamiento
por defecto de la acción (mostrar su vista) con una redirección a una acción distinta.
• Aunque la redirección hace que el navegador comience una petición HTTP nueva, la
hash flash puede usarse para almacenar una pequeña cantidad de información que
estará disponible para esa nueva petición, por ejemplo, para mostrar información
útil al usuario relacionada con la redirección.
• Puede hacer sus vistas más DRY haciendo que los mensajes de flash aparezcan en
una de las plantillas de la aplicación, en vez de tener que estar replicándolos en cada
vista que necesite mostrar esos mensajes.
Autoevaluación 4.7.1. ¿Por qué cada acción de un controlador tiene que o bien mostrar una
vista o bien realizar una redirección?
⇧ HTTP es un protocolo de petición-respuesta, así que cada acción debe generar una res-
puesta. Una forma de responder es con una vista (página web), pero otra forma de responder
es con una redirección, que ordena al navegador a realizar una nueva petición a un URI dife-
rente.
Autoevaluación 4.7.2. En la figura 4.13, y dado que vamos a sacar una etiqueta HTML,
¿por qué la línea 2 comienza con - en vez de con =?
⇧ = hace que Haml ejecute la expresión de Ruby y la sustituye por su resultado en la vista,
140 CAPÍTULO 4. ENTORNO SAAS: INTRODUCCIÓN A RAILS
Create Update
Parámetros pasados a la ninguno instancia existente de
vista Movie
Valores por defecto en los vacío atributos de la película exis-
campos del formulario tente
Etiqueta del botón de envío “Create Movie” (o “Save “Update Movie” (o “Save
Changes”) Changes”)
Acciones del controlador new muestra el formulario, edit muestra el formulario,
create recibe el formulario update recibe el formula-
y modifica la base de datos rio y modifica la base de
datos
params[] valores del atributo para la valores modificados del
nueva película atributo para la película ya
existente
Figura 4.14. La pareja de acciones edit/update es muy similar a la pareja new/create que acabamos de implementar.
pero no queremos que coloque el valor de la expresión if en la vista —lo que queremos
realmente es la etiqueta HTML, que va a generar Haml con #notice.message, además del
resultado de evaluar flash[:notice], que sí está precedido correctamente por =—.
http://pastebin.com/HpVcAmTw
1 % h2 Edit Movie
2
3 = form_tag movie_path ( @movie ) , : method = > : put do
4
5 = label : movie , : title , ' Title '
6 = text_field : movie , ' title '
7
8 = label : movie , : rating , ' Rating '
9 = select : movie , : rating , [ 'G ' , ' PG ' , 'PG -13 ' , 'R ' , 'NC -17 ']
10
11 = label : movie , : release_date , ' Released On '
12 = date_select : movie , : release_date
13
14 = submit_tag ' Save Changes '
Figura 4.15. El marcado Haml de la vista edit difiere de la de new sólo en la línea 3.
Pruebe a hacer clic en el enlace Edit que ha añadido para editar una película. Observe que
cuando se actualiza una película existente, los valores por defecto que aparecen en los campos
del formulario se corresponden a los valores que tienen actualmente los atributos. Esto es así
porque los métodos helper como text_field en la línea 6 de las plantillas new o edit buscan
por defecto una variable de instancia cuyo nombre coincida con su primer argumento—en
este caso, el primer argumento es :movie, así que el método asistente text_field buscará la
variable @movie—. Si existe y corresponde a un modelo ActiveRecord, el método asistente
asume que este formulario es para editar un objeto existente, y los valores actuales de los
atributos de @movie se usan para poblar los campos del formulario. Si no existe o no res-
ponde al método de atributo del segundo argumento (’title’), esos campos del formulario
se quedarán en blanco. Este comportamiento es una buena razón para llamar @movie a su
variable de instancia, en vez de (pongamos) @my_movie: con esto todavía podría conseguir
la funcionalidad extra que le ofrece los métodos helper, pero tendría que pasarle argumentos
adicionales.
La última acción CRUD es borrar (Delete) y, como muestra la figura 4.4, se puede realizar
142 CAPÍTULO 4. ENTORNO SAAS: INTRODUCCIÓN A RAILS
llamando al método destroy sobre un modelo ActiveRecord. Como pasaba con la acción de
actualizar, es muy común responder a la acción de borrar destruyendo el objeto y después
llevar al usuario a alguna otra página útil (como a la vista del índice), y desplegar además un
mensaje de confirmación explicando que se ha borrado el elemento, así que ya sabemos cómo
escribir el método del controlador—añada las siguientes líneas a movies_controller.rb:
http://pastebin.com/djpFThe2
1 def destroy
2 @movie = Movie . find ( params [: id ])
3 @movie . destroy
4 flash [: notice ] = " Movie '#{ @movie . title } ' deleted . "
5 redirect_to movies_path
6 end
Si examina el HTML generado por este código, verá que Rails genera un enlace que
incluye el atributo poco usual data-method="delete". Mucho antes de que los principos
REST fueran un concepto fundamental en SaaS, había ya una regla general por la que las
peticiones de aplicaciones SaaS que usaban GET debían ser “seguras” —no debían causar
efectos colaterales como el borrado de un elemento o la compra de algún producto, y se
debían poder repetir de manera segura—. De hecho, si intenta actualizar una página que
viene de una operación POST, la mayoría de navegadores mostrarán un aviso preguntándole
Los motores de si quiere realmente volver a mandar el formulario. Como borrar algo no es una operación
búsqueda utilizan “segura”, Rails maneja el borrado usando POST. Como indica la explicación al final de la
rastreadores
(crawlers) que siguen sección 6.5, el HTML poco común que se genera con link_to, combinado con JavaScript, da
enlaces GET para explorar como resultado un formulario que se POSTea cuando se hace clic en el enlace —permitiendo a
la web. ¡Imagínese a los navegadores con JavaScript manejar de forma segura la operación destructiva delete—.
Google realizando millones
Intente modificar la vista index (lista de todas las películas) para que cada fila de la tabla
de compras falsas cada vez
que rastrea un portal de muestre el título de una película incluyendo también un enlace Edit que traiga el formulario
comercio electrónico! de edición de esa película, además de un botón Destroy que borre la película con un diálogo
de confirmación.
(o dos métodos render, o dos métodos redirect) al final de una misma acción de un contro-
lador?
⇧ Mostrar (render) y redireccionar (redirect) son dos formas diferentes de responder a una
petición. Cada petición necesita sólo una respuesta.
Resumen
• Rails ofrece varios métodos helper para crear formularios HTML que se refieren
a modelos ActiveRecord. En el método del controlador que recibe el envío de un
formulario, las claves en la hash params se refieren a los atributos de name de los
campos del formulario, y los valores correspondientes son las elecciones que ha
escogido el usuario para esos campos.
• También para hacerlo más amigable, es muy típico modificar la vista gene-
ral de la aplicación para mostrar mensajes almacenados en flash[:notice] o
flash[:warning], que persisten hasta la próxima petición, por lo que pueden usarse
con redirect_to.
• Para especificar los URI que se necesitan tanto en los envíos de formularios como
en las redirecciones, podemos usar los métodos helper de los URI REST, como
movies_path y edit_movie_path, en vez de crear los URI de forma manual.
al modelo—. De igual forma, es fácil que haya código que salte a las vistas —algo muy
común es que una vista se encuentre llamando al método de un modelo como Movie.all,
en vez de hacer que el método del controlador configure una variable para ello mediante
@movies=Movie.all, para que la vista use @movies. Hacer que las vistas dependan del
modelo, además de violar el principio MVC, puede interferir con el proceso de caché, que
veremos en el capítulo 5. La vista debería centrarse en mostrar contenido y facilitar las
entradas del usuario, y el controlador debería centrarse en mediar entre la vista y el modelo,
y configurar cualquier variable necesaria para evitar que cierto código salga a las vistas.
http://pastebin.com/9ZvvznvJ
1 def update
2 @movie = Movie . find params [: id ]
3 @movie . u p d a t e _ a t t r i b u t e s !( params [: movie ])
4 respond_to do | client_wants |
5 client_wants . html { redirect_to movie_path ( @movie ) } # as before
6 client_wants . xml { render : xml = > @movie . to_xml }
7 end
8 end
Igualmente, la única razón por la que new necesita su propia acción del controlador es
porque el usuario humano requiere una forma de rellenar los valores que se usarán para la
acción create . Otro servicio nunca llamaría a la acción new en absoluto. Y tampoco tendría
sentido redirigir de vuelta a la lista de películas tras la acción create: el método create
podría simplemente devolver una representación XML del objeto creado, o incluso el ID del
objeto creado.
De este modo, como con muchas herramientas que veremos en este libro, la curva inicial
de aprendizaje para realizar una tarea sencilla puede parecer bastante empinada, pero usted
recibirá rápidamente la recompensa de usar esta base tan sólida cuando añada de manera
rápida y concisa nuevas funcionalidades y características.
O. Fernandez. Rails 3 Way, The (2nd Edition) (Addison-Wesley Professional Ruby Series).
Addison-Wesley Professional, 2010. ISBN 0321601661.
H. Fulton. The Ruby Way, Second Edition: Solutions and Techniques in Ruby Programming
(2nd Edition). Addison-Wesley Professional, 2006. ISBN 0672328844.
146 NOTAS
S. Ruby, D. Thomas, and D. H. Hansson. Agile Web Development with Rails 3.2 (Pragmatic
Programmers). Pragmatic Bookshelf, 2011. ISBN 1934356549.
Notas
1 http://ruby-doc.org/core-1.9.3/Time.html
2 http://ruby-doc.org/core-1.9.3/
3 http://guides.rubyonrails.org/v3.2.19/routing.html
4 http://www.sqlite.org/cli.html
5 http://api.rubyonrails.org/v3.2.19/
6 http://api.rubyonrails.org/v3.2.19/
7 http://en.wikipedia.org/wiki/HTML_sanitization
8 http://api.rubyonrails.org/v3.2.19/classes/ActionView/Helpers/UrlHelper.html#
method-i-link_to
9 http://ruby-doc.org/core-1.9.3/Time.html#method-i-strftime
10 http://ruby-doc.org/core-1.9.3/Time.html#method-i-strftime
11 http://catb.org/jargon/html/koans.html
12 http://stackoverflow.com
13 http://stackoverflow.com/questions/2945228/i-see-gem-in-gem-list-but-have-no-such-
file-to-load
14 http://api.rubyonrails.org/v3.2.19/classes/ActionView/Helpers/FormTagHelper.html
15 http://guides.rubyonrails.org/v3.2.19/security.html#mass-assignment
16 http://homakov.blogspot.com/2012/03/how-to.html
17 http://www.w3.org/Amaya
18 http://api.rubyonrails.org/v3.2.19/classes/ActionController/MimeResponds.html#
method-i-respond_to
19 http://api.rubyonrails.org/v3.2.19/
20 http://pragprog.com/book/rails32/agile-web-development-with-rails-3-2
21 http://pluralsight.com/
22 http://pluralsight.com/training/courses/TableOfContents/introduction-to-ruby-on-
rails-3
23 http://rubygems.org
24 http://rubyforge.org
1. Modifique la vista Index para incluir el número de fila de cada fila en la tabla de
películas. Pista: Busque la documentación de la función each_with_index usada en
la línea 11 de la vista.
2. Modifique la vista Index para que cuando se sitúe el ratón sobre una fila de la tabla,
dicha fila cambie temporalmente su color de fondo a amarillo. PISTA: busque infor-
mación sobre la pseudo-clase hover que le ofrece CSS.
3. Modifique la acción Index del controlador para que devuelva las películas ordenadas
alfabéticamente por título, en vez de por fecha de lanzamiento. Pista: No intente or-
denar el resultado de la llamada que hace el controlador a la base de datos. Los
gestores de bases de datos ofrecen formas para especificar el orden en que se quiere
una lista de resultados y, gracias al fuerte acoplamiento entre ActiveRecord y el sis-
tema gestor de bases de datos (RDBMS) que hay debajo, los métodos find y all de
la biblioteca de ActiveRecord en Rails ofrece una manera de pedirle al RDBMS que
haga esto.
4. Simule que no dispone de ese fuerte acoplamiento de ActiveRecord, y que no puede
asumir que el sistema de almacenamiento que hay por debajo pueda devolver la colec-
ción de ítems en un orden determinado. Modifique la acción Index del controlador
para que devuelva las películas ordenadas alfabéticamente por título. Pista: Utilice el
método sort del módulo Enumerable de Ruby.
Ejercicio 4.4. ¿Qué sucede si el usuario cambia de opinión antes de enviar un formulario
para crear o actualizar y decide no enviarlo? Añada un enlace “Cancel” al formulario que
lleve al usuario de vuelta a la lista de películas.
Ejercicio 4.5. Modifique el enlace “Cancel” para que, si se hace clic sobre él dentro del flujo
de la acción Create, se devuelva al usuario a la lista de películas, pero, si se hace clic sobre
él dentro del flujo de la acción Update, se devuelva al usuario a la vista Show de la película
que estaba editando. Pista: El método de instancia ActiveRecord::Base#new_record?
devuelve verdadero si el receptor en un nuevo objeto del modelo, es decir, un objeto que
nunca se ha almacenado en la base de datos. Ese tipo de objetos no tendrán IDs.
Ejercicio 4.6. Los menús desplegables para el campo Release Date no permiten añadir
películas que hayan aparecido antes del 2006. Modifíquelo para permitir películas lanzadas
desde el 1930. (Pista: Compruebe la documentación1 del método asistente date_select
usado en el formulario).
Ejercicio 4.7. El campo description de una película se creó como parte de la migración
original, pero no se muestra y tampoco se puede editar. Realice los cambios necesarios para
que el campo de la descripción se vea y se pueda editar en las vistas de New y Edit. Pista:
Debería cambiar sólo dos ficheros.
Ejercicio 4.8. Nuestros métodos actuales del controlador no son muy robustos: si el usuario
introduce de manera manual un URI para ver (Show) una película que no existe (por ejem-
plo /movies/99999), verá un mensaje de excepción horrible. Modifique el método show
del controlador para que, si se pide una película que no existe, el usuario sea redirigido
a la vista Index con un mensaje más amigable explicando que no existe ninguna película
con ese ID. (Pista: Use begin. . . rescue. . . end para recuperar el control en la excepción
ActiveRecord::RecordNotFound).
148 NOTAS
Ejercicio 4.9. Ejercicio que reúne todo: Escriba y despliegue una aplicación Rails que reúna
información de una página Web usando las características de XPath de Nokogiri, y que
devuelva esa información en un canal RSS usando Builder. Compruebe que puede suscribirse
al canal con su navegador o con un lector de noticias RSS.
4.12. EJERCICIOS PROPUESTOS 149
5 Entorno SaaS: Rails avanzado
Kristen Nygaard
(izquierda,
1926–2002) y Programar es comprender.
Ole-Johan Dahl Kristen Nygaard
(derecha, 1931–2002)
compartieron en 2001 el
Premio Turing por inventar
los conceptos 5.1 Vistas parciales, validaciones y filtros . . . . . . . . . . . . . . . . . . 152
fundamentales de la OO, 5.2 SSO y autenticación a través de terceros . . . . . . . . . . . . . . . . 159
incluyendo objetos, clases y
herencia, y por demostrarlos 5.3 Asociaciones y claves foráneas . . . . . . . . . . . . . . . . . . . . . . 165
en Simula, el antecesor de 5.4 Asociaciones through . . . . . . . . . . . . . . . . . . . . . . . . . . . 169
todo lenguaje OO. 5.5 Rutas REST para asociaciones . . . . . . . . . . . . . . . . . . . . . 172
5.6 Composición de consultas con ámbitos reutilizables . . . . . . . . . . 175
5.7 Falacias y errores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 178
5.8 Observaciones finales: lenguajes, productividad y elegancia . . . . . 178
5.9 Para saber más . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 179
5.10 Ejercicios propuestos . . . . . . . . . . . . . . . . . . . . . . . . . . . 180
151
Conceptos
Este capítulo trata sobre las características avanzadas de Rails que usted puede usar
para hacer su código más DRY y conciso, incluyendo cómo reutilizar servicios externos
completos, como Twitter, para integrarlos en sus aplicaciones.
• Algunos mecanismos de Rails, como los filtros para controladores, los métodos de
interrupción del ciclo de vida de un modelo y las validaciones de los modelos,
ofrecen una forma limitada de programación orientada a aspectos, que permite
que se pueda colocar todo el código interrelacionado entre sí en un sólo lugar, y
que le se pueda llamar automáticamente cuando se le necesite.
• Las asociaciones de ActiveRecord usan la metaprogramación y la reflexión para
mapear las relaciones entre los recursos de su aplicación, como las relaciones de
“pertenece a” (belongs to) o “tiene muchas” (has_many), a consultas que reflejan
esas relaciones en la base de datos de la aplicación.
• Los ámbitos de ActiveRecord son “filtros” de composición que usted puede definir
en los datos de su modelo, habilitando la reutilización DRY en la lógica del modelo.
152 CAPÍTULO 5. ENTORNO SAAS: RAILS AVANZADO
http://pastebin.com/AY6TjGrp
1 -# in _movie_form . html . haml ( the partial )
2
3 = label : movie , : title , ' Title '
4 = text_field : movie , ' title '
5
6 = label : movie , : rating , ' Rating '
7 = select : movie , : rating , Movie . all_ratings
8
9 = label : movie , : release_date , ' Released On '
10 = date_select : movie , : release_date
http://pastebin.com/F0NzXDqP
1 -# new . html . haml using partial
2
3 % h2 Create New Movie
4
5 = form_tag '/ movies ' , : method = > : post do
6 = render : partial = > ' movie_form '
7 = submit_tag ' Save Changes '
http://pastebin.com/J3dz3FjR
1 -# edit . html . haml using partial
2
3 % h2 Edit Existing Movie
4
5 = form_tag movie_path ( @movie ) , : method = > : put do
6 = render : partial = > ' movie_form '
7 = submit_tag ' Update Movie Info '
Figura 5.1. Una vista parcial (arriba) que recoge los elementos del formulario que son comunes tanto a la plantilla de new
como a la de edit. Las plantillas modificadas de new y edit usan la vista parcial (en la línea 6 de ambos trozos de código),
pero la línea 5 es diferente en ambas plantillas porque new y edit van a parar a diferentes acciones. (Use el servicio Pastebin
para copiar y pegar este código).
http://pastebin.com/tEALd9RT
1 -# A single row of the All Movies table
2 % tr
3 % td = movie . title
4 % td = movie . rating
5 % td = movie . release_date
6 % td = link_to " More about #{ movie . title } " , movie_path ( movie )
Figura 5.2. Si esta vista parcial se guarda como views/movies/_movie.html.haml, las líneas 12–17 de nuestra vista
original index.html.haml (figura 4.6) se puede sustituir por la línea = render :partial=>’movie’,
:collection=>@movies. Por la convención sobre configuración, el nombre de la vista parcial sin el guión bajo está
disponible como una variable local que identifica a cada elemento de @movies uno a uno.
hacer DRY los modelos y los controladores son más sofisticados y sutiles. En las aplica-
ciones SaaS es muy común querer forzar ciertas restricciones de validación en un objeto de
modelo dado, o restricciones de cuándo se pueden realizar ciertas acciones. Por ejemplo,
cuando se añade una nueva película en RottenPotatoes, querremos comprobar que el título
no esté vacío, que la fecha de lanzamiento sea una fecha válida, y que la valoración sea uno
de los valores permitidos. Otro ejemplo podría ser querer permiter que cualquier usuario
pueda añadir películas, pero sólo un tipo especial de usuarios “administradores” puedan bo-
rrar películas. Los dos ejemplos implican restricciones específicas sobre entidades o sobre ¿Pero el usuario no
acciones, y aunque podría haber muchos sitios donde se deberían considerar dichas restri- elegía la valoración
desde un menú? Sí,
cciones, la filosofía DRY nos insta a centralizarlas en un solo lugar. Rails dispone de dos pero un usuario malicioso o
medios análogos para hacer ésto: validaciones para modelos y filtros para controladores. un bot podrían haber
Las validaciones del modelo, como las migraciones, se expresan con un mini DSL em- construido la petición. Con
aplicaciones SaaS no
bebido en Ruby, como muestra la figura 5.3. Las validaciones se lanzan cuando usted llama
puedes confiar en nadie. El
al método de instancia valid? o cuando intenta guardar el modelo a la base de datos (lo que servidor debe siempre
invoca igualmente a valid? antes). comprobar sus entradas, en
Cualquier error de validación se guarda en el objeto ActiveModel::Errors4 asociado vez de simplemente confiar
en ellas; si no, se arriesga a
a cada modelo; este objeto se devuelve a través del método de instancia errors. Como ser atacado por métodos
muestra la figura 5.3, puede preguntar por errores en atributos individuales (líneas 18–20) como los que veremos en el
o usar full_messages para obtener todos los errores en un array de cadenas de caracteres. capítulo 12.
Cuando escriba sus propias validaciones personalizadas, como en las líneas 7–10, puede usar
errors.add para añadir un mensaje de error asociado o bien a un atributo específico no válido
del objeto, o al objeto en general (pasándole :base en vez de el nombre del atributo).
El ejemplo también demuestra que las validaciones pueden ser condicionales. Por ejem-
plo, la línea 6 de la figura 5.3 asegura que la valoración de la película es válida, a menos que
la película se lanzase antes de que entrara en funcionamiento el sistema de valoraciones (en
Estados Unidos entró el 1 de noviembre de 1968), en cuyo caso no necesitamos validar la
valoración.
Podemos hacer uso de las validaciones para sustituir los peligrosos save! y up-
date_attributes! en nuestras acciones del controlador por sus versiones más seguras, save
y update_attributes, que fallan si la validación falla. La figura 5.4 muestra cómo modi-
ficar nuestros métodos de controlador para que sean más idiomáticos, aprovechándonos de
esto. Modifique las acciones del controlador create y update para que coincidan con las de
la figura, modifique app/models/movie.rb para incluir las validaciones de la figura 5.3, y
luego intente añadir una película que viole una o más de las validaciones.
Por supuesto, sería deseable informar al usuario de lo que ha ido mal y qué debería hacer.
Es fácil: dentro de una vista, podemos obtener el nombre de la acción del controlador que
154 CAPÍTULO 5. ENTORNO SAAS: RAILS AVANZADO
http://pastebin.com/yctJ0riC
1 class Movie < ActiveRecord :: Base
2 def self . all_ratings ; % w [ G PG PG -13 R NC -17] ; end # shortcut : array of
strings
3 validates : title , : presence = > true
4 validates : release_date , : presence = > true
5 validate : r e l e a s e d _ 1 9 3 0 _ o r _ l a t e r # uses custom validator below
6 validates : rating , : inclusion = > {: in = > Movie . all_ratings } ,
7 : unless = > : grandfathered ?
8 def r e l e a s e d _ 1 9 3 0 _ o r _ l a t e r
9 errors . add (: release_date , ' must be 1930 or later ') if
10 release_date && release_date < Date . parse ( '1 Jan 1930 ')
11 end
12 @ @ g r a n d f a t h e r e d _ d a t e = Date . parse ( '1 Nov 1968 ')
13 def grandfathered ?
14 release_date && release_date < @ @ g r a n d f a t h e r e d _ d a t e
15 end
16 end
17 # try in console :
18 m = Movie . new (: title = > ' ' , : rating = > ' RG ' , : release_date = > ' 1929 -01 -01 ')
19 # force validation checks to be performed :
20 m . valid ? # = > false
21 m . errors [: title ] # = > [" can 't be blank "]
22 m . errors [: rating ] # = > [" is not included in the list "]
23 m . errors [: release_date ] # = > [" must be 1930 or later "]
24 m . errors . full_messages # = > [" Title can 't be blank " , " Rating is not
25 included in the list " , " Release date must be 1930 or later " ]
http://pastebin.com/fauUp1Xn
1 # replaces the ' create ' method in controller :
2 def create
3 @movie = Movie . new ( params [: movie ])
4 if @movie . save
5 flash [: notice ] = " #{ @movie . title } was successfully created . "
6 redirect_to movies_path
7 else
8 render ' new ' # note , ' new ' template can access @movie 's field values !
9 end
10 end
11 # replaces the ' update ' method in controller :
12 def update
13 @movie = Movie . find params [: id ]
14 if @movie . u p d a t e _ a t t r i b u t e s ( params [: movie ])
15 flash [: notice ] = " #{ @movie . title } was successfully updated . "
16 redirect_to movie_path ( @movie )
17 else
18 render ' edit ' # note , ' edit ' template can access @movie 's field values !
19 end
20 end
21 # note , you will also have to update the ' new ' method :
22 def new
23 @movie = Movie . new
24 end
Figura 5.4. Si create o update fallan, usamos un render explícito para volver a mostrar el formulario, para que el usuario
pueda rellenarlo de nuevo. Convenientemente, el objeto @movie estará disponible en la vista y tendrá los valores que el
usuario ha introducido la primera vez, así que el formulario se rellenará con esos valores porque (por convención sobre
configuración), los métodos helper de etiquetas de formulario que se han usado en _movie_form.html.haml usarán
@movie para poblar los campos del formulario mientras sea un objeto Movie válido.
5.1. VISTAS PARCIALES, VALIDACIONES Y FILTROS 155
llamó a esa vista, de forma que podemos incluirlo en el mensaje de error, como muestra la
línea 5 del siguiente código.
http://pastebin.com/fNRS4LB6
1 -# insert at top of _movie_form . html . haml
2
3 - unless @movie . errors . empty ?
4 # warning
5 Errors prevented this movie from being # { controller . action_name } d :
6 % ul
7 - @movie . errors . full_messages . each do | error |
8 % li = error
El screencast 5.1.1 va más allá y explica cómo aprovechan las vistas de Rails el hecho
de que puedan preguntar a los atributos de cada modelo por separado sobre su validez para
dar al usuario una respuesta más específica sobre qué campos concretamente han causado el
problema.
Screencast 5.1.1. Cómo interaccionan las validaciones de modelos con los controladores
y las vistas.
http://vimeo.com/34754932
Los métodos helper de formulario de nuestras vistas usan el objeto errors para des-
cubrir qué campos causaron errores de validación, y aplica una clase CSS especial,
field-with-errors, a cada uno de esos campos. Si incluimos los selectores para esa
clase en app/assets/stylesheets/application.css, podemos resaltar visualmente
los campos afectados, en beneficio del usuario.
De hecho, las validaciones son sólo un caso especial de un mecanismo más general, las
funciones callback del ciclo de vida de Active Record7 , que le permiten ofrecer métodos
que “interceptan” un objeto del modelo en cualquier punto relevante de su ciclo de vida. La
figura 5.5 muestra qué funciones callback están disponibles; la figura 5.6 ilustra cómo usar
este mecanismo para “canonicalizar” (estandarizar el formato de) ciertos campos del modelo
antes de que ese modelo se guarde. Veremos otro uso de las funciones callback en el ciclo
de vida cuando discutamos el patrón de diseño Observer (Observador) en el capítulo 11 y la
caché en el capítulo 12.
Análogo a una validación es el filtro de un controlador —un método que comprueba si
son verdaderas ciertas condiciones antes de que se ejecute una acción, o que comprueba una
serie de condiciones que son comunes a muchas acciones. Si las condiciones no se dan, el
filtro puede decidir “parar el espectáculo”, mostrando la plantilla de una vista o redirigiendo a
otra acción. Si el filtro permite que se ejecute la acción, será responsabilidad de dicha acción
ofrecer una respuesta, como siempre.
A modo de ejemplo, un uso bastante común de los filtros es forzar el requisito de que
un usuario inicie sesión antes de realizar ciertas acciones. Asuma por el momento que
hemos verificado la identidad de un usuario y hemos guardado su clave primaria (ID) en
session[:user_id] para recordar el hecho de que ha iniciado sesión. La figura 5.7 muestra
un filtro que fuerza que un usuario válido haya iniciado sesión. En la sección 5.2 mostraremos
cómo combinar el filtro previo (before-filter) con los otros “bloques” implicados en tratar con
usuarios dentro de la sesión.
Los filtros normalmente se aplican a todas las acciones del controlador, pero se puede
usar :only para especificar que el filtro sólo se aplica a ciertas acciones, mientras que se
puede usar :except para especificar que algunas acciones están exentas. Cada uno de éstos
toma un array de nombres de acciones. Usted puede definir múltiples filtros: se ejecutan en el
156 CAPÍTULO 5. ENTORNO SAAS: RAILS AVANZADO
movie.create movie.update_attributes
before_validation before_validation
before_validation_on_create before_validation_on_update
after_create after_update
after_save after_save
Figura 5.5. Los múltiples puntos en los que usted puede “intervenir” dentro del ciclo de vida de un objeto de modelos
ActiveRecord. Todas las operaciones de ActiveRecord que modifican la base de datos (update, create, etc.) al final llaman a
save, así que la función callback before_save puede interceptar cualquier cambio en la base de datos. Mire esta guía de
Rails6 para más detalles y ejemplos.
http://pastebin.com/2zQPLxAZ
1 class Movie < ActiveRecord :: Base
2 before_save : c a p i t a l i z e _ t i t l e
3 def c a p i t a l i z e _ t i t l e
4 self . title = self . title . split (/\ s +/) . map (&: downcase ) .
5 map (&: capitalize ) . join ( ' ')
6 end
7 end
8 # now try in console :
9 m = Movie . create !(: title = > ' STAR wars ' , : release_date = > ' 27 -5 -1977 ' , :
rating = > ' PG ')
10 m . title # = > " Star Wars "
Figura 5.6. Esta función callback before_save capitaliza cada palabra del título de una película, poniendo en mayúscula
la primera letra y dejando en minúscula el resto de la palabra, además de comprimir múltiples espacios entre palabras a
uno sólo, convirtiendo STAR wars en Star Wars (que no es necesariamente el comportamiento correcto que pudieran
tener los títulos de películas, pero es útil desde el punto de vista didáctico).
5.1. VISTAS PARCIALES, VALIDACIONES Y FILTROS 157
http://pastebin.com/3fzBknNQ
1 class A p p l i c a t i o n C o n t r o l l e r < A c t i o n C o n t r o l l e r :: Base
2 before_filter : s e t _ c u r r e n t _ u s e r
3 protected # prevents method from being invoked by a route
4 def s e t _ c u r r e n t _ u s e r
5 # we exploit the fact that find_by_id ( nil ) returns nil
6 @current_user ||= Moviegoer . find_by_id ( session [: user_id ])
7 redirect_to login_path and return unless @current_user
8 end
9 end
Figura 5.7. Si hay un usuario dentro de la sesión, no se llevará a cabo la redirección, y la variable de instancia
@current_user del controlador estára disponible en la acción y en las vistas. En caso contrario, se lleva a cabo una
redirección a login_path, que se asume que corresponde a una ruta que lleva al usuario a una página de inicio de sesión, de
la que se hablará más en la sección 5.2. (and es como && pero con menor precedencia, de ahí ((redirect_to login_path)
and (return)) unless. . . )
orden en los que los haya declarado. También puede definir filtros que se ejecuten después de
que ciertas acciones se hayan completado (after-filters), y filtros que especifican acciones que
se van a ejecutar antes y después (around-filters), comunes en acciones como inspeccionar o
medir tiempos. Estos últimos filtros usan yield para realizar la acción del controlador:
http://pastebin.com/LLjiWBK7
1 # somewhat contrived example of an around - filter
2 around_filter : only = > [ ' with draw_m oney ' , ' tran sfer_m oney '] do
3 # log who is trying to move money around
4 start = Time . now
5 yield # do the action
6 # note how long it took
7 logger . info params
8 logger . info ( Time . now - start )
9 end
158 CAPÍTULO 5. ENTORNO SAAS: RAILS AVANZADO
Autoevaluación 5.1.1. ¿Por qué los diseñadores de Rails no eligieron lanzar las valida-
ciones cuando se instancia por primera vez con Movie#new, en vez de esperar a hacer el
objeto persistente?
⇧ Mientras usted está rellenando los atributos de un objeto nuevo, éste puede estar en un
estado temporal inválido, con lo que lanzar una validación en ese momento puede dificultar
el manejo del objeto. Cuando se persiste el objeto se le dice a Rails “Creo que este objeto
está ya preparado para ser almacenado”.
Autoevaluación 5.1.2. ¿Por qué no podemos escribir validate released_1930_or_later,
es decir, por qué el argumento de validate tiene que ser o un símbolo o una cadena de ca-
racteres?
⇧ Si el argumento es sólo el nombre del método sin parámetros, Ruby intentará evaluarlo
en el momento que se ejecuta validate, que no es lo que se desea —queremos que re-
leased_1930_or_later se invoque cada vez que tiene que realizarse una validación—.
1. “Login with
Twitter” 7. “Welcome, 4. Yes, OK to
3. “OK to authenticate
@armandofox” authenticate
to this app?”
2. Redirect to
Twitter login 5. Redirect to RP
callback page with
page
access token
6. Here’s a token that
proves I’m allowed to
know this user’s name
Figura 5.8. La autenticación a través de terceros habilita SSO dejando que una aplicación SaaS pida al usuario que se
autentique a través de otro proveedor. Una vez que se ha autenticado, el proveedor envía un token a esa aplicación
demostrando que el usuario se ha acreditado correctamente, y quizás le envié también privilegios codificados que el usuario
le concede a dicha aplicación. Este flujo es una versión simplificada de OAuth, un protocolo abierto de autenticación y
autorización (acompañado de alguna controversia) usado por Twitter, Facebook, Microsoft, Google, Netflix, y muchos otros.
El logo de Twitter y su imagen son propiedad de Twitter Inc. (copyright 2012), y se muestran sólo con fines educativos.
2. Puede usar el token para solicitar al proveedor más información acerca del usuario, de-
pendiendo de qué privilegios exactamente le han sido concedidos tras la autenticación.
Por ejemplo, un token de Facebook puede indicar que el usuario dio permiso a la apli-
cación para conocer quiénes son sus amigos, pero que no dio permiso para realizar
publicaciones en su muro.
http://pastebin.com/V4tw3Ld9
1 rails generate model Moviegoer name : string provider : string uid : string
http://pastebin.com/1JaAMKKD
1 # Edit app / models / moviegoer . rb to look like this :
2 class Moviegoer < ActiveRecord :: Base
3 a tt r_ a cc e ss ib l e : uid , : provider , : name # see text for explanation
4 def self . c r e a t e _ w i t h _ o m n i a u t h ( auth )
5 Moviegoer . create !(
6 : provider = > auth [ " provider " ] ,
7 : uid = > auth [ " uid " ] ,
8 : name = > auth [ " info " ][ " name " ])
9 end
10 end
Figura 5.9. Arriba (a): Teclee este comando en un terminal para crear el modelo moviegoers y su migración, y ejecute
rake db:migrate para aplicar la migración. Abajo (b): Luego edite el fichero app/models/moviegoer.rb generado para
que coincida con este código, que se explica en el texto.
http://pastebin.com/GUz4rscD
1 get ' auth /: provider / callback ' = > ' sessions # create '
2 post ' logout ' = > ' sessions # destroy '
3 get ' auth / failure ' = > ' sessions # failure '
http://pastebin.com/eb50EvUx
1 class S e s s i o n s C o n t r o l l e r < A p p l i c a t i o n C o n t r o l l e r
2 # user shouldn 't have to be logged in before logging in !
3 skip_before_filter : set_current_user
4 def create
5 auth = request . env [ " omniauth . auth " ]
6 user = Moviegoer . f i n d _ b y _ p r o v i d e r _ a n d _ u i d ( auth [ " provider " ] , auth [ " uid " ]) ||
7 Moviegoer . c r e a t e _ w i t h _ o m n i a u t h ( auth )
8 session [: user_id ] = user . id
9 redirect_to movies_path
10 end
11 def destroy
12 session . delete (: user_id )
13 flash [: notice ] = ' Logged out successfully . '
14 redirect_to movies_path
15 end
16 end
http://pastebin.com/xbMdTJYJ
1 # login
2 - if @current_user
3 % p . welcome Welcome , # { @current_user . name }!
4 = link_to ' Log Out ' , logout_path
5 - else
6 % p . login = link_to ' Log in with your Twitter account ' , '/ auth / twitter '
Figura 5.10. Los bloques del flujo de autenticación en una aplicación Rails típica. Arriba (a): Tres rutas que siguen la
convención de la gema OmniAuth para mapear las acciones create y destroy en un controlador SessionsController
separado, además de una ruta que se pueda usar en un futuro para manejar autenticaciones fallidas (por ejemplo, que el
usuario introduzca una contraseña incorrecta en Twitter o que no permita el acceso a nuestra aplicación). Medio (b): La
línea 3 se salta el filtro before_filter de la figura 5.7 que añadimos en ApplicationController. Fíjese que tenemos que
borrar la línea 7 de la figura 5.7 porque en este ejemplo no tenemos una ruta de inicio de sesión a la cual redirigir. Si el inicio
de sesión de un usuario dado es correcto, la acción create recuerda la clave primaria (ID) del usuario en la sesión hasta que
se llame a la acción destroy para olvidarla. Abajo (c): La variable @current_user (que se estableció en la línea 6 de
ApplicationController, figura 5.7) puede usarse por una vista parcial relacionada con el inicio de sesión para mostrar un
mensaje apropiado. La vista parcial se podría incluir desde application.html.haml con render
:partial=>’sessions/login’.
162 CAPÍTULO 5. ENTORNO SAAS: RAILS AVANZADO
gema OmniAuth9 , que abstrae completamente el proceso global mostrado en la figura 5.8.
Esta gema permite a los desarrolladores crear una estrategia para cada proveedor de au-
tenticación. Una estrategia gestiona todas las interacciones con el proveedor de autenti-
cación (pasos 2–4 de la figura 5.8) y finalmente ejecuta una acción POST HTTP al URI
/auth/proveedor/callback de su aplicación (la de usted). Los datos incluidos en la ac-
ción POST indican el éxito o fracaso del proceso de autenticación y, si todo ha ido bien,
el(los) token(s) que su aplicación puede usar para conseguir información adicional sobre
el usuario autenticado. En el momento en el que se escribe este libro, existen estrategias
para Facebook, Twitter, Google Apps y muchos otros, cada una disponible en forma de
gema llamada omniauth-proveedor. Usaremos Twitter como ejemplo, así que añada tanto
gem ’omniauth’ como gem ’omniauth-twitter’ a su fichero Gemfile y ejecute bundle
install --without production como siempre. Después tendrá que crear una aplicación
de desarrollo Twitter y configurar la gema OmniAuth con un proveedor de Twitter en con-
fig/initializers/omniauth.rb. Los detalles se encuentran en las instrucciones de configu-
ración de OmniAuth para Twitter10 en GitHub. Una vez se haya completado este paso, añada
el código de la figura 5.10(a) a su fichero config/routes.rb, que especifica algunas de las
rutas que usará la estrategia de OmniAuth cuando se complete la autenticación en Twitter.
El segundo aspecto a tratar es el de seguir la pista de si el usuario actual ha sido autenti-
cado. Habrá podido adivinar que esta información se puede almacenar en el objeto session[].
Sin embargo, deberíamos mantener la gestión de la sesión separada de los demás aspectos de
la aplicación, ya que la sesión puede no ser relevante si su aplicación está siendo usada en
el marco de una arquitectura orientada a servicios. Para este caso, la figura 5.10(b) muestra
cómo podemos “crear” una sesión donde un usuario se autentica correctamente (líneas 3–9)
y “destruir” esa autenticación cuando abandona la sesión (líneas 11–15). Las comillas están
ahí porque lo único que se está creando o destruyendo es el valor de session[:user_id], que
durante la sesión es la clave primaria del usuario que ha iniciado sesión, y que vale nil el
resto del tiempo. La figura 5.10(c) muestra cómo se abstrae esta comprobación por medio
de un filtro before_filter en ApplicationController (que será heredado por todos los con-
troladores), que da valor a @current_user de acuerdo a lo anterior. Así, los métodos del
controlador o las vistas sólo tienen que mirar a @current_user sin tener que estar al tanto
de los detalles de cómo ha ocurrido la autenticación.
El tercer aspecto es enlazar nuestra representación de la identidad de un usuario —esto
es, su clave primaria en la tabla moviegoers— con la representación del proveedor de au-
tenticación, por ejemplo el uid de Twitter. Como igual queremos extender en un futuro
qué proveedores de autenticación van a poder usar nuestros clientes, la migración de la
figura 5.9(a) que crea el modelo Moviegoer especifica tanto un campo uid como un campo
provider. ¿Qué ocurre la primera vez que Alice inicia sesión en RottenPotatoes con su
ID de Twitter? La consulta find_by_provider_and_uid de la línea 6 del controlador de
sesiones (figura 5.10(b)) devolverá nil, así que se llamará a Moviegoer.create_with_-
omniauth (figura 5.9(b), líneas 5–10) para crear un nuevo registro de este usuario. Fíjese
que “Alicia autenticada a través de Twitter” será un usuario diferente, bajo nuestro punto de
vista, que el usuario “Alicia autenticada a través de Facebook,” porque no tenemos forma
de saber que son la misma persona. Esa es la razón por la que algunos sitios que soportan
muchos proveedores de autenticación a través de terceros dan al usuario la posibilidad de
“enlazar” dos cuentas para indicar que identifican a la misma persona.
Todas estas cosas pueden parecer que son un montón de bloques, pero comparado con
hacer lo mismo pero sin una abstracción como la que ofrece OmniAuth, esto es código limpio:
5.2. SSO Y AUTENTICACIÓN A TRAVÉS DE TERCEROS 163
hemos añadido menos de dos docenas de líneas, e incorporando más estrategias OmniAuth
podemos añadir más proveedores de autenticación a través de terceros sin apenas trabajo. El
screencast 5.2.1 muestra la experiencia del usuario relacionada con este código.
Screencast 5.2.1. Inicio de sesión en RottenPotatoes con Twitter.
http://vimeo.com/41300070
Esta versión de RottenPotatoes, modificada para usar la gema OmniAuth como se describe
en el texto, permite a los usuarios entrar en la aplicación usando sus ID de Twitter.
Resumen
• La autenticación centralizada (SSO, Single Sign-On) se refiere a la experiencia de
un usuario final en la que un solo conjunto de credenciales (como pueden ser su
usuario y contraseña de Google o Facebook) le va a permitir iniciar sesión en una
variedad de servicios distintos.
1 0..* 0..* 1
Moviegoer Review Movie
Figura 5.11. Cada extremo de una asociación se etiqueta con su cardinalidad, o el número de entidades que participan en ese
“lado” de la asociación, donde un asterisco significa “cero o más”. En la figura, cada Review pertenece a un único Moviegoer
y a una sola Movie, y no es posible tener una Review sin un Moviegoer o sin una Movie. (La notación de cardinalidad “0..1”,
en contraposición a “1”, permitiría tener reseñas “huérfanas”).
Figura 5.12. En esta figura, Alice ha dado 5 patatas a Star Wars y 4 patatas a Inception, Bob ha dado 3 patatas a Inception,
Carol no ha escrito ninguna crítica, y nadie ha dicho nada sobre It’s Complicated. Por brevedad y claridad, no mostramos
los otros campos de las tablas movies y reviews.
http://pastebin.com/W0xJTuc4
1 # it would be nice if we could do this :
2 inception = Movie . find_by_title ( ' Inception ')
3 alice , bob = Moviegoer . find ( alice_id , bob_id )
4 # alice likes Inception , bob hates it
5 alice_review = Review . new (: potatoes = > 5)
6 bob_review = Review . new (: potatoes = > 2)
7 # a movie has many reviews :
8 inception . reviews = [ alice_review , bob_review ]
9 inception . save !
10 # a moviegoer has many reviews :
11 alice . reviews << alice_review
12 bob . reviews << bob_review
13 # can we find out who wrote each review ?
14 inception . reviews . map { | r | r . moviegoer . name } # = > [ ' alice ',' bob ']
Figura 5.13. Una implementación de asociaciones sencilla y directa nos permitiría referirnos directamente a los objetos
asociados, incluso aunque estuvieran almacenados en tablas de bases de datos diferentes.
cada fila de la tabla movies con cada posible fila de la tabla reviews. Esto nos daría una
nueva tabla con 9 filas (porque hay 3 películas y 3 críticas) y 7 columnas (3 de la tabla
movies y 4 de la tabla reviews). De esta tabla enorme, seleccionaríamos sólo aquellas
filas donde el id de la tabla movies fuera igual que el movie_id de la tabla reviews,
es decir, sólo aquellos pares película-crítica en los que la crítica fuera sobre esa película.
Por último, seleccionaríamos sólo aquellas filas para las cuales el id de la película (y por
tanto el movie_id de las críticas) fuera igual a 41, el ID de la clave primaria de Star Wars.
Este pequeño ejemplo (llamado unión natural ó join en el lenguaje de las bases de datos
relacionales) muestra cómo se pueden representar y manipular relaciones complejas mediante
un conjunto pequeño de operaciones (álgebra relacional) en una colección de tablas con un
esquema de datos uniforme. En el lenguaje estructurado de consultas (Structured Query
Language, SQL) usado por casi todas las bases de datos relacionales, la consulta sería algo
así:
http://pastebin.com/qCTqmark
1 SELECT reviews .*
2 FROM movies JOIN reviews ON movies . id = reviews . movie_id
3 WHERE movies . id = 41;
http://pastebin.com/5bwBMzzM
1 # Run ' rails generate migration cr eate_r eviews ' and then
2 # edit db / migrate /* _ cr e at e _r ev i ew s . rb to look like this :
3 class CreateReviews < ActiveRecord :: Migration
4 def up
5 create_table ' reviews ' do | t |
6 t . integer ' potatoes '
7 t . text ' comments '
8 t . references ' moviegoer '
9 t . references ' movie '
10 end
11 end
12 def down ; drop_table ' reviews ' ; end
13 end
http://pastebin.com/0qJQgUwi
1 class Review < ActiveRecord :: Base
2 belongs_to : movie
3 belongs_to : moviegoer
4 att r_prot ected : moviegoer_id # see text
5 end
http://pastebin.com/NG88vs0V
1 # place a copy of the following line anywhere inside the Movie class
2 # AND inside the Moviegoer class ( idiomatically , it should go right
3 # after ' class Movie ' or ' class Moviegoer ') :
4 has_many : reviews
Figura 5.14. Arriba (a): Cree y aplique esta migración para crear la tabla reviews. Las claves foráneas del nuevo modelo
están relacionadas, por el principio de convención sobre configuración, con las tablas movies y moviegoers ya existentes.
Medio (b): Ponga este nuevo modelo Review en app/models/review.rb. Abajo (c): Realice este cambio de una línea en
cada uno de los ficheros movie.rb y moviegoer.rb.
Figura 5.15. Un subconjunto de los métodos de asociaciones creados por movie has_many :reviews y review belongs_to
:movie, asumiendo que m es un objeto Movie que ya existe y que r1,r2 son objetos Review. Consulte la documentación16 de
ActiveRecord::Associations para ver una lista más completa. Los nombres de los métodos de las asociaciones siguen el
principio de convención sobre configuración, basándose en el nombre del modelo asociado.
Resumen:
• Las asociaciones entre entidades de una aplicación pueden ser de una a una (one-to-
one), de una a muchas (one-to-many), o de muchas a muchas (many-to-many).
• Las bases de datos relacionales (Relational Databases Management Systems,
RDBMS) usan claves foráneas para representar estas asociaciones.
• El módulo de asociaciones de ActiveRecord usa la metaprogramación de Ruby para
crear métodos nuevos con los que “recorrer” asociaciones, construyendo las consul-
tas apropiadas a la base de datos. Aún así, tendrá que crear explícitamente con una
migración los campos necesarios para representar esas claves foráneas.
5.4. ASOCIACIONES THROUGH 169
Autoevaluación 5.3.1. En la figura 5.14(a), ¿por qué hemos añadido clave foránea
(references) sólo a la tabla reviews, y no a las tablas moviegoers o movies?
⇧ Como necesitamos asociar muchas críticas a una sola película o usuario, las claves foráneas
deben formar parte del modelo del lado que es “poseído” en la asociación, en este caso, Re-
views.
Autoevaluación 5.3.2. En la figura 5.15, ¿los métodos accesores y modificadores de la aso-
ciación (como m.reviews y r.movie) son métodos de instancia o de clase?
⇧ Métodos de instancia, porque una colección de críticas está asociada con una determinada
película, no con películas en general.
!"#$%&'%"()*& +,-,&.,//%)&
Moviegoer Moviegoer-
firstname Mapper
Moviegoer
lastname create()
age firstname
read()
lastname
create() update()
age
read() delete()
update()
delete()
%'()*
Movie !"#$%& +,-&
title
rating
created_on Movie MovieMapper
description title create()
create() rating read()
read() created_on update()
update() description delete()
delete()
Figura 5.16. En el patrón de diseño Active Record (izquierda), usado por Rails, el objeto del modelo sabe cómo se guarda a
sí mismo en la capa de persistencia, y cómo se representan sus relaciones con otros tipos de modelos allí guardados. El el
patrón Data Mapper (derecha), usado por Google AppEngine, PHP y Sinatra, una clase aísla los objetos de los modelos de la
capa de persistencia. Cada enfoque tiene sus pros y sus contras. Este diagrama de clases es una forma de diagrama de
Lenguaje Unificado de Modelado(Unified Modeling Language, UML), que veremos en más detalle en el capítulo 11.
http://pastebin.com/3UMDrq1N
1 # in moviegoer . rb :
2 class Moviegoer
3 has_many : reviews
4 has_many : movies , : through = > : reviews
5 # ... other moviegoer model code
6 end
7 alice = Moviegoer . find_by_name ( ' Alice ')
8 alice_movies = alice . movies
9 # MAY work , but a bad idea - see caption :
10 alice . movies << Movie . find_by_name ( ' Inception ') # Don 't do this !
Figura 5.17. Uso de las asociaciones through en Rails. Como se ha visto antes, el objeto que devuelve alice.movies en la
línea 8 suena como una colección. Sin embargo, fíjese que como la asociación entre una Movie y un Moviegoer ocurre a
través (through) de una Review entre ambos, la sintaxis de las líneas 9 y 10 hará que se cree un objeto Review que “una” la
asociación, y por defecto todos sus atributos valdrán nil. Esto no es lo que se desea, y si usted tiene validaciones en el objeto
Review (por ejemplo, el número de patatas debe ser un entero), el objeto Review recien creado fallará la validación y se
abortará toda la operación.
5.4. ASOCIACIONES THROUGH 171
http://pastebin.com/BmTg4Fs2
1 class Review < ActiveRecord :: Base
2 # review is valid only if it 's associated with a movie :
3 validates : movie_id , : presence = > true
4 # can ALSO require that the referenced movie itself be valid
5 # in order for the review to be valid :
6 v a l i d a t e s _ a s s o c i a t e d : movie
7 end
Figura 5.18. Este ejemplo muestra una validación en una asociación, que asegura que una crítica se almacene sólo si se ha
asociado con alguna película.
dando como resultado una tabla que, conceptualmente en nuestro ejemplo, tiene 27 filas y 9
columnas. De esta tabla seleccionamos las filas para las cuales el ID de la película coincida
con el movie_id de la crítica y que el ID del espectador coincida con el moviegoer_id de
la crítica. Ampliando la explicación de la sección 5.3, la consulta SQL sería como ésta:
http://pastebin.com/uupUEC58
1 SELECT movies .*
2 FROM movies JOIN reviews ON movies . id = reviews . movie_id
3 JOIN moviegoers ON moviegoers . id = reviews . moviegoer_id
4 WHERE moviegoers . id = 1;
• Existen opciones adicionales en los métodos de asociaciones que controlan lo que pasa
a los objetos que son “tenidos” cuando el objeto “poseedor” se destruye. Por ejem-
plo, has_many :reviews,:dependent=>:destroy especifica que las críticas que
pertenezcan a una determina película se deben borrar de la base de datos si se borra esa
película.
• Cuando dos modelos A y B tienen cada uno de ellos una asociación has-one o has-
many sobre un modelo común C, se puede establecer una asociación de muchos a
muchos (many-to-many) entre A y B a través (through) de C.
• La opción :through de has_many le permite manipular cada lado de una aso-
ciación through como si fuera una asociación directa. Sin embargo, si modifica
directamente una asociación through, el objeto del modelo intermedio se tiene que
crear automáticamente, algo que posiblemente usted no desee.
Autoevaluación 5.4.1. Describa en lenguaje natural los pasos que se necesitan para deter-
minar todos los espectadores que han realizado una crítica de una película con un id (clave
primaria) dado.
⇧ Encontrar todas las reseñas cuyo campo movie_id contenga el id de la película dada. Por
cada crítica, encontrar el usuario cuyo id coincida con el campo moviegoer_id de la crítica.
Figura 5.19. Especificar rutas anidadas en routes.rb también ofrece métodos helper para URI anidados, similares a los
básicos que se ofrecen para recursos normales. Compare esta tabla con la de la figura 4.7 en el capítulo 4.
http://pastebin.com/J5UR6ftj
1 class R e v i e w s C o n t r o l l e r < A p p l i c a t i o n C o n t r o l l e r
2 before_filter : has_moviegoer_and_movie , : only = > [: new , : create ]
3 protected
4 def h a s _ m o v i e g o e r _ a n d _ m o v i e
5 unless @current_user
6 flash [: warning ] = ' You must be logged in to create a review . '
7 redirect_to '/ auth / twitter '
8 end
9 unless ( @movie = Movie . find_by_id ( params [: movie_id ]) )
10 flash [: warning ] = ' Review must be for an existing movie . '
11 redirect_to movies_path
12 end
13 end
14 public
15 def new
16 @review = @movie . reviews . build
17 end
18 def create
19 # since moviegoer_id is a protected attribute that won 't get
20 # assigned by the mass - assignment from params [: review ] , we set it
21 # by using the << method on the association . We could also
22 # set it manually with review . moviegoer = @current_user .
23 @current_user . reviews << @movie . reviews . build ( params [: review ])
24 redirect_to movie_path ( @movie )
25 end
26 end
http://pastebin.com/5VuxXT4z
1 % h1 New Review for # { @movie . title }
2
3 = form_tag m o v i e _ r e v i e w s _ p a t h ( @movie ) do
4 % label How many potatoes :
5 = select_tag ' review [ potatoes ] ' , o p t i o n s _ f o r _ s e l e c t (1..5)
6 = submit_tag ' Create Review '
Figura 5.20. Arriba (a): un controlador que manipula Reviews que “pertenecen” tanto a una Movie como a un Moviegoer,
usando filtros previos (before-filters) para asegurar que los recursos “poseedores” se identifican propiamente en el URI de la
ruta. Abajo (b): Una posible plantilla Haml de la vista para crear una nueva crítica, es decir,
app/views/reviews/new.html.haml.
5.6. COMPOSICIÓN DE CONSULTAS CON ÁMBITOS REUTILIZABLES 175
antes de llamar a la acción create. Finalmente, como en la figura 5.14(b) declaramos movie-
goer_id como un atributo protegido, no puede ser asignado en masa con params (línea 23
en la acción create) a la nueva crítica, así que le asignamos un valor construyendo la crítica
desde su otro “poseedor” @current_user.
Podríamos enlazar a la página para crear una nueva crítica usando algo parecido a lo
siguiente, en la página de detalles de la película:
http://pastebin.com/rpJ02W6A
1 = link_to ' Add Review ' , n e w _ m o v i e _ r e v i e w _ p a t h ( @movie )
• Cuando se manipulan los recursos “poseídos” que tienen un padre, como las Reviews
que “pertenecen” a una Movie, los filtros previos (before-filters) se pueden usar para
interceptar y verificar la validez de los ID embebidos en la ruta REST anidada.
Autoevaluación 5.5.1. ¿Por qué tenemos que dar valores a los campos movie_id y movie-
goer_id de una crítica en las acciones new y create de ReviewsController, pero no en
las acciones edit o update?
⇧ Una vez que se crea la crítica, los valores almacenados de sus campos movie_id y movie-
goer_id nos dicen su película y su usuario asociados.
http://pastebin.com/JyHTtgT5
1 # BAD : details of computing review goodness is exposed to controller
2 class M o v i e s C o n t r o l l e r < A p p l i c a t i o n C o n t r o l l e r
3 def m o v i e s _ w i t h _ g o o d _ r e v i e w s
4 @movies = Movie . joins (: reviews ) . group (: movie_id ) .
5 having ( ' AVG ( reviews . potatoes ) > 3 ')
6 end
7 def m ov i es _f o r_ ki d s
8 @movies = Movie . where ( ' rating in ? ' , % w ( G PG ) )
9 end
10 end
Figura 5.21. Determinar qué es lo que hace “buena” a una película o si una película es apropiada para niños debería ser
tarea del modelo Movie, pero en este mal ejemplo, esos detalles aparecen codificados en dos métodos diferentes del
controlador. (Usamos el método group de ActiveRecord para agrupar las críticas por ID de película, y luego aplicamos el
agregador AVERAGE de SQL, para obtener sólo esos ID de películas cuyas críticas tienen de media más de 3 patatas.)
y Movie.for_kids pero, ¿qué ocurre si quiere que el usuario filtre por ambos atributos —
películas para niños que tengan buenas reseñas?
Los ámbitos que permiten composición (composable scopes) son una característica
muy potente de ActiveRelation (el “álgebra relacional” que hay detrás de ActiveRecord)
que le ayuda a hacer esto. Como muestra la figura 5.22, un ámbito con nombre es
una expresión lambda que se evalúa en tiempo de ejecución. Pero los ámbitos tienen
dos características prácticas que les hacen mejores para definir métodos explícitos como
Movie.with_good_reviews. Primero, permiten composición: como muestran las líneas
15–17 de la figura 5.22, el valor que se devuelve al llamar a un ámbito es a su vez un ob-
jeto ActiveRelation al que se le puede aplicar más ámbitos. Esto permite que se puedan
reutilizar trozos de código en distintos sitios y de manera limpia, como estos filtros.
Segundo, los ámbitos se evalúan de forma perezosa (lazy evaluation): una cadena
de ámbitos conforman una relación que puede crear y ejecutar la correspondiente consulta
SQL, pero la ejecución no se realiza hasta que se solicita el primer resultado. Esto sucede
en la vista de nuestro ejemplo, en el bucle each de las líneas 28–29 de la figura 5.22. La
evaluación perezosa es una técnica muy potente de la programación funcional que veremos
de nuevo en el capítulo 12.
Resumen de ámbitos:
Los ámbitos posibilitan especificar la lógica del modelo de manera declarativa y permiten
composición, habilitando la reutilización limpia de trozos de código de la lógica del mo-
delo. Como los ámbitos se evalúan de manera perezosa (lazy evaluation) — no tiene lugar
ninguna consulta a la base de datos hasta que no se solicita el primer resultado— se pueden
componer en cualquier orden sin incurrir a penalizaciones de rendimiento.
Autoevaluación 5.6.1. Escribe una expresión con ámbitos para películas para las que se ha
escrito una crítica en los últimos n días, donde n es un parámetro del ámbito.
http://pastebin.com/EG3hcHXi
1 class Movie < ActiveRecord :: Base
2 scope : recently_reviewed , lambda { | n |
⇧ 3 Movie . joins (: reviews ) . where ([ ' reviews . created_at >= ? ' , n . days . ago ]) . uniq
4 }
5 end
5.6. COMPOSICIÓN DE CONSULTAS CON ÁMBITOS REUTILIZABLES 177
http://pastebin.com/JCwE7cNx
1 # BETTER : move filter logic into Movie class using composable scopes
2 class Movie < ActiveRecord :: Base
3 scope : with_good_reviews , lambda { | threshold |
4 Movie . joins (: reviews ) . group (: movie_id ) .
5 having ([ ' AVG ( reviews . potatoes ) > ? ' , threshold . to_i ])
6 }
7 scope : for_kids , lambda {
8 Movie . where ( ' rating in (?) ' , % w ( G PG ) )
9 }
10 end
11 # in the controller , a single method can now dispatch :
12 class M o v i e s C o n t r o l l e r < A p p l i c a t i o n C o n t r o l l e r
13 def m o v i e s _ w i t h _ f i l t e r s
14 @movies = Movie . w i t h _ g o o d _ r e v i e w s ( params [: threshold ])
15 @movies = @movies . for_kids if params [: for_kids ]
16 @movies = @movies . wit h_many _fans if params [: with_ many_f ans ]
17 @movies = @movies . r e c e n t l y _ r e v i e w e d if params [: r e c e n t l y _ r e v i e w e d ]
18 end
19 # or even DRYer :
20 def m o v i e s _ w i t h _ f i l t e r s _ 2
21 @movies = Movie . w i t h _ g o o d _ r e v i e w s ( params [: threshold ])
22 % w ( for_kids wi th_man y_fans r e c e n t l y _ r e v i e w e d ) . each do | filter |
23 @movies = @movies . send ( filter ) if params [ filter ]
24 end
25 end
26 end
27 # in the view :
28 - @movies . each do | movie |
29 -# ... code to display the movie here ...
Figura 5.22. Encapsulamos los criterios de filtrado usando ámbitos, que pueden tomar uno o más argumentos opcionales.
Los ámbitos se pueden componer de manera flexible en tiempo de ejecución (líneas 14–17), por ejemplo, en respuesta a la
presencia de checkboxes de nombre for_kids, with_many_fans, etc. La implementación alternativa,
movies_with_filters_2, realiza lo mismo pero con menos código, usando metaprogramación y pudiéndose extender a más
ámbitos.
178 CAPÍTULO 5. ENTORNO SAAS: RAILS AVANZADO
Autoevaluación 5.6.2. ¿Por qué la lógica del ámbito tiene que ser parte de un bloque o de
una expresión lambda? Por ejemplo, por qué los diseñadores de Rails no han usado esta otra
sintaxis:
http://pastebin.com/ErKmDCYL
1 class Movie < ActiveRecord :: Base
2 scope : for_kids , Movie . where ( ' rating in ? ' , % w ( G PG ) )
3 end
Error. Demasiados filtros o funciones callback del ciclo de vida del modelo, o
! lógica muy compleja en los filtros y funciones callback.
Los filtros y funciones callback ofrecen lugares convenientes y bien definidos para hacer
más DRY código duplicado, pero si se utilizan demasiados puede ser difícil seguir el flujo de
la lógica de la aplicación. Por ejemplo, cuando hay tantos filtros previos (before-filters) o pos-
teriores (after-filters) a un conjunto de acciones de controladores, o ambos (around-filters),
puede ser complicado adivinar por qué la acción de un controlador ha fallado en ejecutar
como debía o cuál es el filtro que ha detenido la ejecución. Las cosas pueden empeorar aún
más si algunos de los filtros no están declarados en el controlador sino en un controlador del
que hereda, como ApplicationController. Los filtros y las funciones callback sólo se de-
berían usar cuando realmente quiere centralizar código que de otra manera estaría duplicado.
aunque sea llamado desde diferentes sitios del código. Las validaciones y los filtros son un
AOP se ha comparado con
ejemplo de la programación orientada a aspectos (Aspect-Oriented Programming, AOP),
la estructura ficticia de
una metodología que ha sido criticada porque ofusca el flujo de control pero cuyo buen uso control de flujo COME
puede mejorar el estilo DRY. FROM, que empezó
El segundo ejemplo corresponde a las decisiones de diseño reflejadas en los métodos como una broma en
respuesta a la carta de
helper para asociaciones. Por ejemplo, habrá notado que aunque el campo de clave foránea Edsger Dijkstra,
de un objeto Movie asociado con una crítica se llama movie_id, los métodos helper de la Instrucción GOTO
asociación nos permiten hacer referencia a review.movie, haciendo que nuestro código se considerada dañina
centre en la asociación arquitectónica entre Movies y Reviews, en vez de en los detalles de (Dijkstra 1968) que
promovía la programación
implementación de los nombres de las claves foráneas. Usted podría manipular los cam- estructurada.
pos movie_id o review_id directamente en la base de datos, como le obligan a hacer en
aplicaciones web basadas en entornos menos avanzados, o hacerlo en su aplicación Rails: re-
view.movie_id=some_movie.id. Pero además de ser más difícil de leer, este código im-
plementa la premisa de que el campo de la clave foránea se llama movie_id, que puede no ser
cierto si sus modelos están usando características avanzadas de Rails como las asociaciones
polimórficas, o si ActiveRecord se ha configurado para operar con una base de datos heredada
que no sigue las convenciones de nombrado habituales. En estos casos, review.movie y re-
view.movie= funcionarán, pero review.movie_id dará error. Como algún día su código
también será código heredado, ayude a sus sucesores a ser productivos —mantenga la estruc-
tura lógica de sus entidades lo más separada posible de su representación en la base de datos.
También podríamos preguntarnos, ahora que sabemos cómo se almacenan las relaciones
en el RDBMS, por qué movie.save también modifica la tabla reviews cuando guardamos
una película después de añadirle una crítica. De hecho, llamar a save en el nuevo objeto
de la crítica también funcionaría, pero como Movie tiene muchas Reviews, tiene más sentido
pensar en guardar la película cuando actualizamos las críticas que tiene. En otras palabras,
está diseñado de esta manera para que tenga más sentido de cara a los programadores y para
que el código quede más elegante.
Resumiendo, merece la pena estudiar las validaciones, los filtros y los métodos helper
para las asociaciones, porque son ejemplos exitosos de cómo se pueden explotar las carac-
terísticas de un lenguaje de programación para hacer código más elegante y para mejorar la
productividad.
• La sección Guides23 de la web de Rails incluye guías útiles de una gran variedad de
aspectos de Rails, incluyendo depuración, gestión de la configuración de la aplicación,
y más.
• En la sección Guides también se encuentra un repaso de los conceptos básicos de las
asociaciones24 .
180 NOTAS
• The Rails 3 Way (Fernandez 2010) es una referencia enciclopédica para todos los as-
pectos de Rails 3, incluyendo los mecanismos tan potentes que dan soporte a las aso-
ciaciones.
Notas
1 http://api.rubyonrails.org
2 http://api.rubyonrails.org/v3.2.19/classes/ActiveModel/Validations/ClassMethods.
html#method-i-validates
3 http://api.rubyonrails.org/v3.2.19/classes/ActiveModel/Validations/ClassMethods.
html#method-i-validates
4 http://api.rubyonrails.org/v3.2.19/classes/ActiveModel/Errors.html
5 http://guides.rubyonrails.org/v3.2.19/active_record_validations_callbacks.html
6 http://guides.rubyonrails.org/v3.2.19/active_record_validations_callbacks.html
7 http://api.rubyonrails.org/v3.2.19/classes/ActiveRecord/Callbacks.html
8 http://en.wikipedia.org/wiki/Iframe#Frames
9 http://www.omniauth.org
10 https://github.com/arunagw/omniauth-twitter
11 http://api.rubyonrails.org/v3.2.19/classes/ActiveModel/MassAssignmentSecurity/
ClassMethods.html#method-i-attr_protected
12 http://api.rubyonrails.org/v3.2.19/classes/ActiveModel/MassAssignmentSecurity/
ClassMethods.html#method-i-attr_accessible
13 http://www.omniauth.org
14 http://api.rubyonrails.org/v3.2.19/classes/ActiveRecord/Associations/ClassMethods.
html
15 http://api.rubyonrails.org/v3.2.19/classes/ActiveRecord/Associations/ClassMethods.
html
16 http://api.rubyonrails.org/v3.2.19/classes/ActiveRecord/Associations/ClassMethods.
html
17 http://cassandra.apache.org
18 http://mongodb.org
19 http://couchdb.org
20 http://appspot.com
21 http://api.rubyonrails.org/v3.2.19/classes/ActiveRecord/Associations/ClassMethods.
html
22 http://guides.rubyonrails.org/v3.2.19/active_record_querying.html
23 http://guides.rubyonrails.org/v3.2.19/
24 http://guides.rubyonrails.org/v3.2.19/association_basics.html
Ejercicio 5.1. Extienda el ejemplo de la sección 5.2 para permitir la autenticación vía Face-
book Connect.
5.10. EJERCICIOS PROPUESTOS 181
Ejercicio 5.2. Extienda su solución al ejercicio 5.1 para permitir que un usuario autenticado
pueda “enlazar” dos cuentas. Es decir, si Alice ha iniciado sesión previamente con Twitter,
y acto seguido la inicia con Facebook, debería ser capaz de “enlazar” las dos cuentas para
que, en un futuro, iniciar sesión con cualquiera de ellas la identifique como la misma usuaria.
Pista: considere crear un modelo adicional, Identity, que tenga una relación de muchos a
uno (many-to-one) a Moviegoer.
Ejercicio 5.3. En el fichero README del plugin OmniAuth, el autor da el siguiente ejemplo
de código que muestra cómo se integra OmniAuth en una aplicación Rails:
http://pastebin.com/3shQFuZm
1 class S e s s i o n s C o n t r o l l e r < A p p l i c a t i o n C o n t r o l l e r
2 def create
3 @user = User . f i n d _ o r _ c r e a t e _ f r o m _ a u t h _ h a s h ( auth_hash )
4 self . current_user = @user
5 redirect_to '/ '
6 end
7 protected
8 def auth_hash
9 request . env [ ' omniauth . auth ']
10 end
11 end
El método auth_hash (líneas 8–10) tiene la sencilla tarea de devolver lo que devuelva
OmniAuth como resultado de intentar autenticar a un usuario. ¿Por qué piensa que el au-
tor colocó esta funcionalidad en su propio método en vez de simplemente referenciar re-
quest.env[’omniauth.auth’] directamente en la línea 3?
Asociaciones y arquitectura REST:
Ejercicio 5.4. Extienda el código del controlador de la figura 5.20 con los métodos edit y
update para las críticas. Use un filtro de controlador para asegurarse de que un usuario
sólo puede editar o actualizar sus propias críticas.
6 Entorno de cliente SaaS:
introducción a JavaScript
Alan Perlis (1922–1990) Un lenguaje que no afecta a la forma de pensar sobre la programación no merece la pena
fue el primer galardonado aprenderlo.
con el Premio Turing (1966),
otorgado por su influencia Alan Perlis
en lenguajes de
programación y
compiladores avanzados. 6.1 JavaScript: visión general . . . . . . . . . . . . . . . . . . . . . . . . 184
En 1958 ayudó en el diseño
6.2 JavaScript en el lado cliente . . . . . . . . . . . . . . . . . . . . . . . 187
de ALGOL, el cual ha
influido en virtualmente 6.3 Funciones y constructores . . . . . . . . . . . . . . . . . . . . . . . . 193
todos los lenguajes de 6.4 DOM y jQuery . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 196
programación imperativos,
incluidos C y Java. Para
6.5 Eventos y funciones callback . . . . . . . . . . . . . . . . . . . . . . . 199
evitar los problemas 6.6 AJAX: JavaScript asíncrono y XML . . . . . . . . . . . . . . . . . . 206
sintácticos y semánticos de 6.7 Pruebas de JavaScript y AJAX . . . . . . . . . . . . . . . . . . . . . 212
FORTRAN, ALGOL fue el
primer leguaje descrito en 6.8 Aplicaciones de página única y API JSON . . . . . . . . . . . . . . . 221
términos de una gramática 6.9 Falacias y errores comunes . . . . . . . . . . . . . . . . . . . . . . . 225
formal, la notación 6.10 Pasado, presente y futuro de JavaScript . . . . . . . . . . . . . . . . 229
Backus-Naur (llamada
así por el ganador del 6.11 Para saber más . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 232
premio Turing Jim Backus y 6.12 Ejercicios propuestos . . . . . . . . . . . . . . . . . . . . . . . . . . . 233
su colega Peter Naur).
183
Conceptos
JavaScript es un lenguaje de scripting dinámico e interpretado, integrado en los nave-
gadores actuales. En este capítulo se describen sus características principales, incluyendo
algunas que recomendamos evitar ya que representan decisiones de diseño cuestiona-
bles, y cómo amplía los tipos de contenido y aplicaciones que se pueden ofrecer como
SaaS.
• Un navegador representa una página web como una estructura de datos denomi-
nada DOM (Document Object Model , modelo de objetos del documento). El
código JavaScript que se ejecuta en el navegador puede inspeccionar y modificar
esta estructura de datos, obligando al navegador a volver a presentar los elementos
modificados de la página.
• Cuando un usuario interactúa con el navegador (por ejemplo, tecleando, haciendo
clic o moviendo el ratón), o cuando el navegador hace algún progreso en una
interacción con un servidor, el navegador genera un evento indicando qué ha
ocurrido. El código JavaScript puede realizar acciones específicas de la aplicación
para modificar el DOM cuando sucede dicho evento.
• Usando AJAX (Asynchronous JavaScript And XML, JavaScript asíncrono y
XML), el código JavaScript puede realizar peticiones HTTP a un servidor web sin
que se necesite recargar la página. La información contenida en la respuesta se
puede usar después para modificar los elementos de la página in situ, proporcio-
nando una experiencia de usuario más rica y a menudo con una respuesta más
ágil que las páginas web tradicionales. Las acciones de controlador y las vistas
parciales (partials) de Rails se pueden utilizar para manejar interacciones AJAX.
• Al igual que usamos el entorno Rails y la herramienta TDD RSpec para el código
SaaS de la parte servidor, aquí utilizaremos el entorno jQuery y la herramienta
TDD Jasmine1 para desarrollar el código del lado cliente.
• Seguiremos la práctica recomendada de “degradación elegante”, también cono-
cida como “mejora progresiva”: los navegadores antiguos sin soporte para
JavaScript aún podrán proporcionar una buena experiencia de usuario, mientras
que los navegadores que soportan JavaScript proporcionarán una experiencia de
usuario aún mejor.
184 CAPÍTULO 6. ENTORNO DE CLIENTE SAAS: INTRODUCCIÓN A JAVASCRIPT
Servidor Cliente
Lenguaje Ruby JavaScript
Entorno Rails jQuery
Arquitectura El controlador recibe una peti- El controlador recibe una petición, interactúa
cliente- ción, interactúa con el mode- con el modelo, y muestra una vista parcial o
servidor lo, muestra una nueva página un objeto codificado en XML o JSON, que el
sobre HTTP (vista) código JavaScript ejecutándose en el navega-
dor utilizará para modificar la página actual
en el sitio adecuado
Depuración Depurador Ruby, consola Firebug, consola JavaScript del navegador
rails
Pruebas RSpec con rspec-rails; Jasmine, jasmine-jquery; aislar las prue-
aislar las pruebas de la base bas del servidor utilizando fixtures HTML y
de datos utilizando fixtures y JSON
factorías de modelo Active
Record
Figura 6.1. La correspondencia entre nuestra explicación de la programación del lado servidor con Ruby y Rails, y de la
programación del lado cliente con JavaScript continúa con nuestro enfoque en crear, de forma productiva, código conciso y
no repetitivo (DRY) con una buena cobertura de pruebas.
En este capítulo nos centraremos en los casos 1 y 2, teniendo en cuenta las buenas prác-
ticas que se citan a continuación:
La sección 6.2 presenta una introducción al lenguaje y a cómo se conecta el código con las
páginas web, mientras que en la sección 6.3 se describen las funciones, cuya comprensión es
la base para escribir código JavaScript limpio y no intrusivo. La sección 6.4 presenta jQuery4 ,
que recubre las JSAPI de los distintos navegadores, incompatibles entre sí, con una API única
jQuery se puede ver como
que funciona en todos los navegadores, y la sección 6.5 describe cómo las características de
un adaptador (Adapter)
mejorado (sección 11.6) jQuery facilitan la programación de interacciones entre los elementos de la página y el código
para las JSAPI de los JavaScript.
distintos navegadores. La sección 6.6 hace una introducción a la programación en AJAX. En 1998, Internet Ex-
plorer 5 introdujo un nuevo mecanismo que permitía que el código JavaScript se comunicara
con un servidor SaaS después de que se hubiera cargado la página, así como utilizar infor-
mación que provenía del servidor para actualizar la página “in situ” sin que el usuario tuviera
6.2. JAVASCRIPT EN EL LADO CLIENTE 187
que recargar la página. Otros navegadores copiaron rápidamente esta tecnología. El desarro-
Irónicamente, la
llador Jesse James Garrett acuñó5 el término AJAX , de Asynchronous JavaScript And
programación AJAX actual
XML (JavaScript asíncrono y XML), para describir la combinación de estas tecnologías que involucra mucho menos
impulsa aplicaciones “Web 2.0” impactantes como Google Maps. XML del que utilizaba
Probar JavaScript del lado cliente supone un reto ya que los navegadores fallan silencio- originalmente, tal y como
veremos más adelante.
samente cuando ocurre un error en lugar de mostrar mensajes JavaScript de error a usuarios
desprevenidos. Afortunadamente, el entorno TDD Jasmine le ayudará a probar el código, tal
y como se describe en la sección 6.7.
Finalmente, la sección 6.8 describe los mecanismos utilizados tanto para desarrollar como
para probar aplicaciones de página única (SPA) basadas en el navegador, que gozan de una
creciente popularidad.
• Nos centraremos en JavaScript del lado cliente, es decir, en utilizar el lenguaje para
mejorar la experiencia de usuario de las páginas proporcionadas por aplicaciones
SaaS centradas en servidor. Nos esforzaremos en conseguir degradación elegante
(la experiencia de usuario sin JavaScript debe ser usable, aunque empobrecida) y
el principio de código no intrusivo (el código JavaScript debe estar completamente
separado del marcado de la página).
Autoevaluación 6.1.1. Verdadero o falso: una de las ventajas iniciales de JavaScript para la
validación de formularios (prevenir que el usuario envíe un formulario con datos no válidos)
fue la habilidad de eliminar código de validación del servidor y trasladarlo al cliente.
⇧ Falso; no hay garantía de que el envío provenga realmente de esa página (cualquiera con
una herramienta de línea de comandos puede construir una petición HTTP), e incluso si
proviene de la página, el usuario podría estar utilizando un navegador antiguo. Tal y como
hemos indicado repetidamente en SaaS, el servidor no puede confiar en nadie y siempre debe
validar sus entradas.
A pesar de su nombre y sintaxis, JavaScript tiene más puntos en común con Ruby que
con Java:
• Las clases y tipos importan aún menos que en Ruby —de hecho, a pesar de la apa-
riencia sintáctica de mucho código JavaScript en el mundo real, JavaScript no tiene
clases, aunque existen convenciones que se usan para conseguir algunos de los efectos
derivados de tener clases—.
• Las funciones son clausuras que llevan su entorno siempre consigo, permitiendo que
se ejecuten correctamente en sitios y momentos diferentes de donde fueron definidas
inicialmente. Al igual que los bloques anónimos (do. . . end) están en todas partes en
Ruby, las funciones anónimas (function() {. . . }) son omnipresentes en JavaScript.
• JavaScript es un lenguaje interpretado e incluye características de metaprogramación e
introspección.
La figura 6.2 muestra la sintaxis y construcciones básicas de JavaScript, que deberían
resultarles familiares a los programadores de Java y Ruby. La sección de “Falacias y errores
comunes” describe varios errores comunes que se cometen en JavaScript asociados a esta
figura; léalas cuidadosamente una vez termine el capítulo para no tropezar con alguno de los
desafortunados defectos de JavaScript o con algún mecanismo de JavaScript que se parece a
su equivalente en Ruby y funciona de forma muy similar, pero no igual. Por ejemplo, mientras
que Ruby utiliza nil para referirse tanto al concepto “indefinido” (una variable a la que nunca
se le ha asignado un valor) y “vacío” (una variable que siempre se evalúa como falso), null
en JavaScript es diferente a undefined, “valor” que se obtiene cuando una variable no se ha
inicializado nunca.
Tal y como muestra la primera fila de la figura 6.2, el tipo fundamental en JavaScript es
el objeto (object), una colección no ordenada de pares clave/valor o, como se conocen en
JavaScript, propiedades (properties) o ranuras (slots). El nombre de una propiedad puede
ser cualquier cadena de caracteres, incluyendo la cadena vacía. El valor de una propiedad
puede ser cualquier expresión JavaScript, incluyendo otro objeto; no puede ser undefined.
JavaScript permite expresar objetos literales especificando sus propiedades y valores di-
rectamente, como se muestra en la figura 6.3. Esta sencilla sintaxis de objeto literal es la base
de JSON (JavaScript Object Notation, notación de objetos JavaScript), el cual, a pesar
de su nombre, es una forma independiente del lenguaje de representar los datos que se pueden
intercambiar entre servicios SaaS o entre cliente y servidor SaaS. De hecho, las líneas 2–11 de
la figura (excepto el punto y coma al final de la línea 11) son una representación JSON legal.
Oficialmente, cada valor de propiedad en un objeto JSON puede ser de tipo Number, String
Unicode, Boolean (true o false son los únicos valores posibles), null (valor vacío) o un
Object anidado definido recursivamente. Sin embargo, a diferencia del lenguaje JavaScript,
en la representación JSON de un objeto, todas las cadenas de texto deben entrecomillarse, por
JSON.org define la
lo que el ejemplo en la fila superior de la figura 6.2 necesitaría comillas encerrando la palabra
sintaxis precisa de JSON y title para ajustarse a la sintaxis JSON. La figura 6.4 resume un conjunto de herramientas para
muestra un listado de comprobar la sintaxis y estilo tanto del código JavaScript como de las estructuras de datos y
librerías de análisis protocolos relacionados con JavaScript que veremos en el resto del capítulo.
sintáctico (parseo)
disponibles para otros El hecho de que un objeto JavaScript pueda tener propiedades cuyo valor sea una función
lenguajes. se usa en librerías bien diseñadas para recopilar todas sus funciones y variables en un único
espacio de nombres. Por ejemplo, como veremos en la sección 6.4, jQuery define un
única variable global jQuery a través de la cual se puede acceder a toda la funcionalidad de
la librería jQuery, en lugar de contaminar el espacio de nombres global con los numerosos
objetos de la librería. Seguiremos una práctica similar definiendo un pequeño número de
variables globales para encapsular todo nuestro código JavaScript.
6.2. JAVASCRIPT EN EL LADO CLIENTE 189
Figura 6.2. Análogamente a la figura 3.1, esta tabla resume las construcciones básicas de JavaScript. El texto comenta
errores comunes importantes. Mientras que Ruby utiliza nil tanto como valor null explícito como para el valor devuelto por
variables de instancia no existentes, JavaScript distingue undefined, que se devuelve cuando las variables no se han
declarado o no tienen un valor asignado, del valor especial null y del valor booleano false. Sin embargo, los tres casos son
“falsos” —se evalúan como falso en una sentencia condicional—.
190 CAPÍTULO 6. ENTORNO DE CLIENTE SAAS: INTRODUCCIÓN A JAVASCRIPT
http://pastebin.com/gaR9tA4k
1 var potatoReview =
2 {
3 " potatoes " : 5 ,
4 " reviewer " : " armandofox " ,
5 " movie " : {
6 " title " : " Casablanca " ,
7 " description " : " Casablanca is a classic and iconic film starring ... " ,
8 " rating " : " PG " ,
9 " release_date " : " 1942 -11 -26 T07 :00:00 Z "
10 }
11 };
12 potatoReview [ ' potatoes '] // = > 5
13 potatoReview [ ' movie ' ]. title // = > " Casablanca "
14 potatoReview . movie . title // = > " Casablanca "
15 potatoReview [ ' movie ' ][ ' title '] // = > " Casablanca "
16 potatoReview [ ' blah '] // = > undefined
Figura 6.3. Notación JavaScript para objetos literales, es decir, objetos que se especifican enumerando sus propiedades y
valores explícitamente. Si el nombre de la propiedad es un nombre de variable legal en JavaScript, se pueden omitir las
comillas o utilizar el atajo idiomático de la notación de punto (líneas 13–14), aunque hay que usar siempre las comillas para
delimitar todas las cadenas de caracteres cuando un objeto se expresa en formato JSON. Puesto que los objetos pueden
contener otros objetos, se pueden construir (línea 5) y recorrer (líneas 13–15) estructuras de datos jerárquicas.
Figura 6.4. Diversas herramientas de depuración de código JavaScript y estructuras de datos e interacciones del servidor
asociados. Un reto es que, como ocurre con el lenguaje C, existen numerosas directrices de código conflictivas para
JavaScript —de Google, Yahoo, el proyecto Node.js, entre otros— y distintas herramientas comprueban y refuerzan
diferentes estilos de código.
6.2. JAVASCRIPT EN EL LADO CLIENTE 191
http://pastebin.com/7SztJxcj
1 < script src = " / public / javascripts / application . js " > </ script >
http://pastebin.com/KBnYjPhc
1 < html >
2 < head > < title > Update Address </ title > </ head >
3 < body >
4 <! - - BAD : embedding scripts directly in page , esp . in body -->
5 < script >
6 <! - - // BAD : " hide " script body in HTML comment
7 // ( modern browsers may not see script at all )
8 function checkValid () { // BAD : checkValid is global
9 if !( fieldsValid ( getEl ementB yId ( ' addr ') ) ) {
10 // BAD : > and < may confuse browser 's HTML parser
11 alert ( ' >>> Please fix errors & resubmit . <<< ') ;
12 }
13 // BAD : " hide " end of HTML comment ( l .3) in JS comment : -->
14 </ script >
15 <! - - BAD : using HTML attributes for JS event handlers -->
16 < form onsubmit =" return checkValid () " id =" addr " action ="/ update " >
17 < input onchange =" RP . filter_adult " type =" checkbox "/ >
18 <! - - BAD : URL using ' javascript : ' -->
19 <a href =" javascript : back () " > Go Back </ a >
20 </ form >
21 </ body >
22 </ html >
Figura 6.5. Arriba: la forma no intrusiva y recomendada de cargar código JavaScript en la(s) vista(s) HTML. Abajo: tres
formas intrusivas de embeber JavaScript en las páginas HTML, todas obsoletas porque mezclan código JavaScript con
marcado HTML. Por desgracia, todas son habituales en el “JavaScript callejero” que encontramos en sitios con un diseño
precario, aunque todas se pueden evitar fácilmente con script src= y usando las técnicas no intrusivas para conectar código
JavaScript con elementos HTML descritas en el resto de este capítulo.
El término JavaScript del lado cliente se refiere de forma específica al código JavaScript
asociado a las páginas HTML y que, por tanto, se ejecuta en el navegador. Cada página
de la aplicación que quiera usar las funciones o variables JavaScript debe incluir el pro-
pio código JavaScript necesario. La forma recomendada y no intrusiva de hacerlo es me-
diante una etiqueta script que referencie al fichero que contenga el código, como se mues-
tra en la figura 6.5. El método helper de vista de Rails javascript_include_tag ’ap-
plication’, que genera la etiqueta anterior, se puede colocar en app/views/layouts/
application.html.haml u otra plantilla de diseño que sea parte de cada página servida
por la aplicación. Si luego se coloca el código en uno o más ficheros .js distintos en
app/assets/javascripts, entonces Rails realizará los siguientes pasos automáticamente
al hacer el despliegue en producción:
Este comportamiento automático, apoyado por los entornos de producción actuales como
Heroku, se conoce como tubería de activos (asset pipeline). Descrito en mayor detalle en
esta guía9 , la tubería de activos permite también utilizar lenguajes como CoffeeScript, como
veremos más adelante. Puede parecer ineficiente que el navegador del usuario cargue un
Minificar (Minifying),
en español también único archivo JavaScript enorme, sobre todo si sólo utilizan JavaScript unas pocas páginas
minimizar, es un término de la aplicación y cualquiera de ellas usa sólo un pequeño subconjunto del código JavaScript
usado para describir las desarrollado. Sin embargo, el navegador sólo carga dicho enorme fichero una única vez y lo
transformaciones de
compresión, que reducen el
guarda en memoria caché hasta que vuelva a desplegarse la aplicación con cambios en los
tamaño de la librería ficheros .js. Además, en el modo de desarrollo, la tubería de activos se salta el proceso de
jQuery 1.7.2 de 247 KiB a “precompilación” y carga cada uno de los ficheros JavaScript de forma independiente, dado
32 KiB. que es probable que cambien frecuentemente durante el desarrollo.
• Para asociar JavaScript a una página HTML de forma no intrusiva se recomienda in-
cluir en el elemento head del documento HTML una etiqueta script cuyo atributo
src indique la URL del script, de modo que el código JavaScript se pueda mantener
separado del marcado HTML. El método helper de Rails javascript_include_tag
genera la URL correcta que aprovecha la tubería de activos (asset pipeline) de Rails.
nombre de una función sin paréntesis podría ser una expresión cuyo valor es la función en sí
misma, en lugar de una llamada a la función.
http://pastebin.com/4nBsjb0t
1 var Movie = function ( title , year , rating ) {
2 this . title = title ;
3 this . year = year ;
4 this . rating = rating ;
5 this . full_title = function () { // " instance method "
6 return ( this . title + ' ( ' + this . year + ') ') ;
7 };
8 };
9 function Movie ( title , year , rating ) { // this syntax may look familiar ...
10 // ...
11 }
12 // using ' new ' makes Movie the new objects ' prototype :
13 pianist = new Movie ( ' The Pianist ' , 2002 , 'R ') ;
14 pianist . full_title ; // = > function () {...}
15 pianist . full_title () ; // = > " The Pianist (2002) "
16 // BAD : without ' new ' , ' this ' is bound to global object in Movie call !!
17 juno = Movie ( ' Juno ' , 2007 , 'PG -13 ') ; // DON 'T DO THIS !!
18 juno ; // undefined
19 juno . title ; // error : ' undefined ' has no properties
20 juno . full_title () ; // error : ' undefined ' has no properties
Figura 6.6. Dado que las funciones son objetos de primera clase, es correcto que un objeto tenga una propiedad cuyo valor
sea una función, como ocurre con full_title. Utilizaremos mucho esta característica. Preste atención al error común de las
líneas 14–18.
desgracia) totalmente legal llamar a Movie como una función simple y llana sin utilizar la
palabra reservada new, como en la línea 17. Si hace esto, el comportamiento de JavaScript es
totalmente distinto en dos formas verdaderamente horribles. Primero, en el cuerpo de Movie,
this no se referirá a un objeto nuevo, sino al objeto global, el cual define varias constantes es-
peciales, como Infinity, NaN y null, y proporciona varias otras partes del entorno JavaScript.
Cuando se ejecuta JavaScript en un navegador, el objeto global es una estructura de datos que
representa la ventana del navegador. Por lo tanto, las líneas 2–5 crearán y asignarán valores
a nuevas propiedades de este objeto —lo cual, claramente, no es lo que se pretendía, pero
lamentablemente, cuando se usa this en un entorno donde no se ha definido de otra manera,
se refiere al objeto global, un serio defecto de diseño del lenguaje—. (Ver “Falacias y errores
comunes” y “Para saber más” si tiene interés en las razones de este extraño comportamiento,
cuya explicación queda fuera del alcance de esta introducción al lenguaje).
Segundo, dado que Movie no devuelve nada específicamente, su valor de retorno (y por
tanto el valor de juno) será undefined. Mientras que una función Ruby devuelve por defecto
el valor de la última expresión en una función, una función JavaScript devuelve undefined
a menos que incluya un sentencia return explícita. (El return de la línea 6 pertenece a la
función full_title, no a la propia Movie). Por tanto, las líneas 19–20 dan errores porque
tratan de referenciar una propiedad (title) sobre algo que no es ni siquiera un objeto.
Puede evitar este error común siguiendo rigurosamente la convención JavaScript genera-
lizada de escribir el nombre de una función con la inicial en mayúscula sólo si dicha función
está pensada para ser llamada como un constructor utilizando new. Las funciones que no son
van a ser usadas como constructores deben tener nombres que empiecen por minúscula.
6.3. FUNCIONES Y CONSTRUCTORES 195
• En JavaScript las funciones son objetos de primera clase: se pueden asignar a varia-
bles, pasar a otra funciones o devolverlas como resultado de otra funciones.
• Aunque JavaScript no tiene clases, una forma de gestionar el espacio de nombres
de forma ordenada es almacenar las funciones como propiedades de objetos, permi-
tiendo que un único objeto (hash) aúne un conjunto de funciones relacionadas como
haría una clase.
<html>
html window.document <head>
<title>...</title>
<script>...</script>
head body
</head>
<body>
h1 div <h1 class='title'>Rotten Potatoes!</h1>
class: 'title' id: 'main' <div id='main'>
<h1>All Movies</h1>
<table id='movies'>
h1 table a ...
id: 'movies' href: '/movies/new' </table>
</div>
</body>
</html>
Figura 6.7. Vista simplificada del árbol DOM correspondiente a la página “lista de películas” de RottenPotatoes con
marcado HTML esquemático. Un triángulo abierto indica los lugares donde hemos suprimido el resto del subárbol por
brevedad. this.document está configurado para apuntar a la raíz del árbol DOM cuando se carga una página.
⇧ square.area() es una llamada a una función que en este caso devolverá 9, mien-
tras que square.area es un objeto función sin aplicar.
Autoevaluación 6.3.2. Dado el código de la autoevaluación 6.3.1, explique por qué es in-
correcto escribir s=new square.
⇧ square es sólo un objeto, no una función, por lo que no se puede invocar como si fuera un
constructor (ni de ninguna manera).
mentaciones de la JSAPI de los navegadores, evitaremos por completo estas funciones nativas
JSAPI en favor de los “envoltorios” (wrappers) de las mismas que proporciona jQuery, y que
son mucho más potentes. jQuery añade, además, características adicionales y comportamien-
tos no disponibles en las JSAPI nativas, tales como animaciones y soporte de CSS y AJAX
mejorado (sección 6.6). JQuery define una función global jQuery() (cuyo alias es $()) que, La documentación12 de la
cuando se le pasa un selector CSS (de los cuales vimos algunos ejemplos en la figura 2.5), gema jquery-rails
explica cómo añadir jQuery
devuelve todos los elementos DOM de la página actual que coincidan con el selector. Por manualmente a su
ejemplo, jQuery(’#movies’) o $(’#movies’) devolverían el único elemento cuyo identi- aplicación si usa una versión
ficador (id) es movies, si existiera alguno en la página; $(’h1.title’) devolvería todos los de Rails anterior a la 3.1.
elementos h1 cuya clase CSS fuera title. Una versión más general de esta funcionalidad
es .find(selector), que sólo busca en el subárbol DOM cuya raíz es el elemento sobre el que
La llamada
se invoca la función. Para ilustrar esta distinción, $(’p span’) busca cualquier elemento
jQuery.noConflict()
span contenido en un elemento p, mientras que si elt apunta a un elemento p concreto, anula la definición del alias
elt.find(’span’) sólo buscará los elementos span descendientes de elt. $, en caso de que su
Tanto si usa $() como find, el valor de retorno es un conjunto de nodos (colección de aplicación utilice la variable
$ integrada en el navegador
uno o más elementos) que coinciden con el selector, o null si no existen coincidencias. Cada (habitualmente un alias para
elemento se “envuelve” con la representación de elemento DOM de jQuery, proveyéndole de document.-
habilidades más allá de las correspondientes a la JSAPI integrada en el navegador. De ahora getElementById), o
en adelante nos referiremos a dichos elementos como elementos “envueltos por jQuery”, carga otra librería JavaScript
como Prototype13 , que
para distinguirlos de la representación que devolverían las JSAPI nativas del navegador. En también intenta definir $.
particular, puede hacer diversas cosas con los elementos envueltos por jQuery en el conjunto
de nodos, como muestra la figura 6.8:
• Para cambiar el aspecto visual de un elemento, defina clases CSS que creen la aparien-
cia deseada y utilice jQuery para añadir o eliminar la(s) clase(s) CSS del elemento en
tiempo de ejecución.
• Para cambiar el contenido de un elemento, utilice funciones jQuery que modifiquen el
contenido HTML o de texto plano del elemento.
• Para animar un elemento (mostrar/ocultar, aparecer/desaparecer gradualmente, etc.),
invoque una función jQuery sobre dicho elemento que manipule el DOM para conse-
guir el efecto deseado.
Observe, sin embargo, que incluso cuando un conjunto de nodos incluye múltiples ele-
mentos que coinciden, no es un array JavaScript y, por tanto, no se le puede tratar como
tal: no se puede escribir $(’tr’)[0] para seleccionar la primera fila de una tabla, ni siquiera
aunque se invoque primero la función toArray() de jQuery sobre el conjunto de nodos. En su
lugar, siguiendo el patrón de diseño Iterator (iterador), jQuery proporciona el iterador each,
definido sobre la colección, que devuelve un elemento cada vez que se invoca, ocultando
los detalles de cómo se almacenan los elementos en la colección, igual que Array#each en
Ruby.
El screencast 6.4.1 muestra algunos ejemplos sencillos de estos comportamientos desde
la consola JavaScript del navegador. Los utilizaremos para implementar las funcionalidades
del screencast 6.1.1.
198 CAPÍTULO 6. ENTORNO DE CLIENTE SAAS: INTRODUCCIÓN A JAVASCRIPT
Figura 6.8. Algunos atributos y funciones definidos en los objetos de elementos DOM mejorados de jQuery; deberían
invocarse sobre el elemento o colección de elementos apropiado (target, equivalente al receptor en Ruby). Las funciones que
sólo tienen sentido aplicadas sobre un único elemento, como attr, se aplican al primero cuando se usan sobre una colección.
Las funciones que pueden tanto leer como modificar las propiedades de los elementos devuelven el valor de la propiedad
cuando el argumento final (o único) está ausente, y lo modifican cuando está presente. Excepto que se indique lo contrario,
todas las funciones devuelven el elemento sobre el que se invocan, de modo que las llamadas se pueden encadenar, como en
elt.insertBefore(. . . ).hide(). Consulte la documentación de jQuery15 para funcionalidades adicionales a este subconjunto.
6.5. EVENTOS Y FUNCIONES CALLBACK 199
• El modelo de objetos del documento del W3C —World Wide Web Consortium
Document Object Model (W3C DOM)— es una representación independiente del
lenguaje de la jerarquía de elementos que constituyen un documento HTML.
• Todos los navegadores que soportan JavaScript proporcionan mecanismos para acce-
der y recorrer el DOM. Esta funcionalidad, junto con el acceso a otras características
del navegador desde JavaScript, se conoce en conjunto como la interfaz de progra-
mación de aplicaciones JavaScript (JavaScript Application Programming Interface,
JSAPI).
• La potente librería jQuery proporciona un adaptador uniforme para las diferentes
JSAPI de los navegadores y añade numerosas funciones mejoradas, como recorridos
del DOM basados en CSS, animaciones y otros efectos especiales.
Figura 6.9. Algunos de los eventos JavaScript definidos por la API de jQuery. Se asocia un manejador a un evento con
element.on(’evt’, func) o con el atajo element.evt(func). De esta forma, $(’h1’).on(’click’, function() {. . . }) es
equivalente a $(’h1’).click(function() {. . . }). La función callback func recibirá un argumento (que puede ignorar) cuyo
valor es el objeto jQuery Event que describe el evento que ha ocurrido. Recuerde que on y sus atajos ligarán la función
callback a todos los elementos que coincidan con el selector, por lo que debe asegurarse de que el selector no sea ambiguo,
por ejemplo identificando el elemento mediante su ID.
en la interfaz de usuario, como hacer clic en un botón o mover el ratón dentro o fuera de un
El término HTML dinámico,
elemento HTML en particular, se puede designar una función JavaScript que será llamada
menos preciso, se usaba a
veces en el pasado para cuando ocurra dicha acción y tendrá la oportunidad de reaccionar. Esta capacidad hace que
referirse a los efectos de la página se comporte de forma más parecida a una interfaz de usuario de escritorio, en la
combinar la manipulación que cada elemento individual responde visualmente a las interacciones del usuario, y menos
del DOM basada en
JavaScript con CSS.
como una página estática, en la que cualquier interacción hace que toda la página tenga que
ser cargada y mostrada de nuevo.
La figura 6.9 resume los eventos más importantes definidos por la JSAPI nativa del na-
vegador y mejorados por jQuery. Mientras que algunos se activan por acciones del usuario
sobre elementos del DOM, otros están relacionados con el funcionamiento del navegador en
sí o con eventos “pseudo-UI” tales como el envío de un formulario, que pueden ocurrir como
consecuencia de pulsar un botón “Enviar”, pulsar la tecla Enter (en algunos navegadores) u
otras funciones callback JavaScript que provocan el envío del formulario. Para asociar un
comportamiento a un evento, tan sólo tiene que proporcionar una función JavaScript que será
llamada cuando el evento se dispare. Decimos que esta función, llamada función callback o
Callback, devolución de
manejador de eventos, está ligada o asociada a ese evento en dicho elemento DOM. Aunque
llamada, retrollamada,
manejador de eventos o los eventos son activados automáticamente por el navegador, también los puede activar usted
gestor de eventos son mismo: por ejemplo, e.trigger(’click’) activa el manejador del evento click para el elemento
distintas expresiones que e. Como veremos en la sección 6.7, esta característica resulta útil para las pruebas: se puede
hacen referencia al mismo
concepto.
simular la interacción del usuario y comprobar que se aplican los cambios correctos al DOM
en respuesta a un evento en la interfaz de usuario.
Los navegadores definen comportamientos por defecto para algunos eventos y elementos:
por ejemplo, hacer clic en un enlace hace que se visite la página enlazada. Si dicho elemento
tiene además un gestor del evento click proporcionado por el programador, este gestor se
ejecuta primero; si el manejador devuelve “verdadero” (figura 6.2), el comportamiento por
defecto se ejecuta a continuación, pero si el manejador devuelve “falso”, se suprime el com-
portamiento por defecto. ¿Y si un elemento no tiene ningún manejador para un evento de
usuario, como en el caso de las imágenes? En este caso, se le da la oportunidad de responder
al gestor de eventos del elemento padre en el árbol DOM. Por ejemplo, si hace clic sobre
un elemento img dentro de un div, y el elemento img no tiene manejador para el evento de
clic, entonces el div recibirá dicho evento de clic. Este proceso continúa hasta que algún
elemento gestione el evento o sigue ascendiendo hasta el nivel superior window, que puede
6.5. EVENTOS Y FUNCIONES CALLBACK 201
http://pastebin.com/s9tPrqjZ
1 var M ov ie L is t Fi lt e r = {
2 filter_adult : function () {
3 // ' this ' is * unwrapped * element that received event ( checkbox )
4 if ( $ ( this ) . is ( ': checked ') ) {
5 $ ( ' tr . adult ') . hide () ;
6 } else {
7 $ ( ' tr . adult ') . show () ;
8 };
9 },
10 setup : function () {
11 // construct checkbox with label
12 var l a b e l A n d C h e c k b o x =
13 $ ( ' < label for =" filter " > Only movies suitable for children </ label > ' +
14 ' < input type =" checkbox " id =" filter "/ > ' ) ;
15 l a b e l A n d C h e c k b o x . insertBefore ( '# movies ') ;
16 $ ( '# filter ') . change ( Mo v ie Li s tF i lt er . filter_adult ) ;
17 }
18 }
19 $ ( M ov ie L is tF i lt e r . setup ) ; // run setup function when document ready
Figura 6.10. Uso de jQuery para añadir una casilla para filtrar películas en la página que lista las películas en
RottenPotatoes; ponga este código en app/assets/javascripts/movie_list_filter.js. El texto le guiará en detalle a través
del ejemplo, y las figuras adicionales del resto del capítulo generalizarán las técnicas mostradas aquí. Nuestros ejemplos
utilizan las funcionalidades de jQuery para manipular el DOM en lugar de las que vienen integradas en el navegador, ya que
la API de jQuery es más coherente en diferentes navegadores que la especificación oficial de DOM del W3C.
(el elemento que gestionó el evento). Para utilizar las potentes funcionalidades de jQuery,
como is(’:checked’), tenemos que “envolver” el elemento nativo para convertirlo en un ele-
mento jQuery, lo que hacemos llamando a $ sobre el elemento para dotarle de estos poderes
especiales. La primera fila de la figura 6.12 muestra este uso de $.
Para el paso 3, proporcionamos la función setup, la cual realiza dos funciones. Primero,
¿<input> fuera de
crea una etiqueta y una casilla de verificación (líneas 12–14), utilizando el mecanismo $
<form>? Sí —es legal a
partir de HTML 4, siempre mostrado en la segunda fila de la figura 6.12, y las inserta justo antes de la tabla movies (línea
que el programador 15). De nuevo, al crear un elemento jQuery podemos llamar a la función insertBefore, que
gestione todos los no forma parte de la JSAPI integrada en el navegador, sobre él. La mayoría de las funciones
comportamientos del
elemento input, tal y como
jQuery como insertBefore devuelven el propio objeto sobre el que se llama a la función
estamos haciendo—. (target), lo que permite encadenar llamadas a funciones como hemos visto en Ruby.
En segundo lugar, la función setup asocia la función filter_adult como manejador del
evento change de la casilla de verificación. Puede que esperara que la función se asociara
al manejador de click de la casilla, pero change es más robusto porque es un ejemplo de
un evento “pseudo-UI”: se activa tanto si la casilla cambia por un clic del ratón, por teclado
(para los navegadores que tienen la navegación por teclado activada, como en el caso de
usuarios con alguna discapacidad que les impida utilizar el ratón), o incluso por efecto de
otro código JavaScript. Algo similar ocurre con el evento submit de los formularios: es
preferible asociar la función al manejador de dicho evento que asociarlo al al manejador de
click, por si el usuario envía el formulario pulsando la tecla Enter.
¿Por qué no hemos añadido sencillamente la etiqueta y la casilla a la plantilla de la vista de
Rails? La razón se deriva de nuestra directriz de diseño de degradación elegante: utilizando
JavaScript para crear la casilla de verificación, los navegadores antiguos no la mostrarán. Si
la casilla formara parte de la plantilla de vista, los usuarios de navegadores antiguos podrían
verla, pero no ocurriría nada cuando pulsaran sobre ella.
¿Por qué aparece MovieListFilter.filter_adult en la línea 16? ¿No podría poner sen-
cillamente filter_adult? La respuesta es no, ya que esto implicaría que filter_adult es un
6.5. EVENTOS Y FUNCIONES CALLBACK 203
var m = new(Figura 6.6, línea 13) En el cuerpo de la función Movie, this está asociado a un nuevo objeto que
Movie(); será devuelto por la función, de forma que se pueda usar this.title (por ejemplo) para asignar
sus propiedades. El prototipo del nuevo objeto será el mismo que el prototipo de la función.
pianist.full_title(); (Figura 6.6, línea 15) Cuando se ejecuta full_title, this estará asociado al objeto que “posee” la
función, en este caso pianist.
$(’#filter’).change( (Figura 6.10, línea 16) Cuando se llama a filter_adult para manejar el evento change, this
MovieListFil- se referirá al elemento al que está asociado el gestor, en este caso uno de los elementos que
ter.filter_adult); coincidan con el selector CSS #filter.
Figura 6.11. Los tres usos más comunes de this introducidos en las secciones 6.3 y 6.5. Consulte la sección de “Falacias y
errores comunes” para más información sobre el uso correcto e incorrecto de this.
Usos de $() o jQuery() con ejemplo Valor/efectos secundarios, número de línea en la figura 6.10
$(sel) Devuelve una colección de elementos envueltos por jQuery seleccionados
$(’.mov span’) por el selector CSS3 sel (línea 16)
$(elt) Cuando una llamada JSAPI, como getElementById, devuelve un elemento
$(this), $(document), $(docu- o éste se pasa a una función callback de un gestor de eventos, se usa esta
ment.getElementById(’main’)) función para crear una versión del elemento con envoltorio jQuery, sobre la
cual se pueden aplicar las operaciones de la figura 6.8 (línea 4)
$(HTML[, attribs]) Devuelve un nuevo elemento HTML envuelto por jQuery correspondiente al
$(’<p><b>bold</b>words</p>’), texto que se pasa como argumento, que debe contener al menos una etiqueta
$(’<img/>’, { HTML entre corchetes angulares “< >” (de otra forma jQuery interpretaría
src: ’/rp.gif’, que está recibiendo un selector CSS y que se está llamando como en la fila
click: handleImgClick }) anterior de esta tabla). Si se pasa un objeto JavaScript como attribs, se usará
para construir los atributos del elemento (líneas 13–14). El nuevo elemento
no se inserta automáticamente en el documento; la figura 6.8 muestra al-
gunos métodos para hacerlo, uno de los cuales se usa en la línea 15.
$(func) Ejecuta la función proporcionada una vez que el documento ha terminado de
$(function () {. . . }); cargarse y el DOM está preparado para ser manipulado. Esto es un atajo de
$(document).ready(func), que es en sí un envoltorio jQuery del manejador
onLoad() de la JSAPI integrada en el navegador (línea 19).
Figura 6.12. Las cuatro formas de invocar la función sobrecargada jQuery() o $ y los efectos de cada una. La figura 6.10
incluye demostraciones de todas ellas.
• Dentro de un manejador de eventos, jQuery hace que this esté asociado a la re-
presentación DOM proporcionada por el navegador del elemento que gestionó el
evento. Solemos “envolver” el elemento para disponer de $(this), un elemen-
to “envuelto por jQuery” que soporta operaciones de jQuery mejoradas, como
$(this).is(’:checked’).
http://pastebin.com/AqHkMHRk
1 // from file jquery_ujs . js in jquery - rails 3.0.4 gem
2 // ( Line numbers may differ if you have a different gem version )
3 // line 23:
4 $ . rails = rails = {
5 // Link elements bound by jquery - ujs
6 l i n k C l i c k S e l e c t o r : 'a [ data - confirm ] , a [ data - method ] , a [ data - remote ] , a [
data - disable - with ] ' ,
7 // line 160:
8 handleMethod : function ( link ) {
9 // ... code elided ...
10 form = $ ( ' < form method =" post " action =" ' + href + '" > </ form > ') ,
11 meta data_i nput = ' < input name =" _method " value =" ' + method + '" type ="
hidden " / > ';
12 // ... code elided ...
13 form . hide () . append ( meta data_ input ) . appendTo ( ' body ') ;
14 form . submit () ;
15 }
Figura 6.13. Cuando se carga jquery_ujs, los elementos <a> que tengan cualquiera de los atributos data-confirm,
data-method, data-remote o data-disable-with se asocian al manejador handleMethod que se ejecuta cuando se hace
clic en el enlace. Si el enlace tiene un atributo data-method, el manejador construye un <form> efímero pasando el valor de
data-method como el atributo oculto _method, oculta el formulario (de modo que no aparezca en la página cuando se
construya) y lo envía. En Rails 2 y anteriores, el helper link_to generaba código JavaScript en línea (intrusivo); Rails 3
cambió el comportamiento para generar código JavaScript más limpio y no intrusivo.
1. Crear una acción del controlador o modificar una existente (sección 4.4) para ges-
tionar las peticiones AJAX hechas por el código JavaScript. En lugar de procesar una
vista completa, la acción procesará una parcial (sección 5.1) para generar un fragmento
HTML que se insertará en la página.
2. Construir un URI REST en JavaScript y utilizar XHR para enviar la petición HTTP
al servidor. Como habrá supuesto, jQuery dispone de atajos útiles para muchos casos
habituales, por lo que utilizaremos las funciones de más alto nivel y más potentes que
ofrece jQuery en lugar de llamar a XHR directamente.
3. Dado que JavaScript, por definición, se ejecuta en un hilo único (single-threaded )
—sólo puede trabajar en una tarea cada vez hasta que dicha tarea se completa— la
interfaz de usuario del navegador se quedaría “congelada” mientras JavaScript espe-
rara la respuesta del servidor. Por ello, XHR en cambio vuelve inmediatamente de
la llamada a la función y permite proporcionar una función callback para manejar el
evento (tal y como hicimos para la programación para navegadores en la sección 6.5)
que se activará cuando responda el servidor o si se produce un error.
6.6. AJAX: JAVASCRIPT ASÍNCRONO Y XML 207
http://pastebin.com/mcmdUnqA
1 % p = movie . description
2
3 = link_to ' Edit Movie ' , e d it _m o vi e_ p at h ( movie )
4 = link_to ' Close ' , ' ' , {: id = > ' closeLink '}
http://pastebin.com/ck2q1ZxJ
1 class M o v i e s C o n t r o l l e r < A p p l i c a t i o n C o n t r o l l e r
2 def show
3 id = params [: id ] # retrieve movie ID from URI route
4 @movie = Movie . find ( id ) # look up movie by unique ID
5 render (: partial = > ' movie ' , : object = > @movie ) if request . xhr ?
6 # will render app / views / movies / show . < extension > by default
7 end
8 end
Figura 6.14. (a) Arriba: una sencilla vista parcial que se renderizará y devolverá como respuesta a la petición AJAX. Damos
un ID de elemento único al enlace “Close” de forma que podamos asociarlo cómodamente a un manejador que ocultará la
ventana emergente. (b) Abajo: la acción del controlador que renderiza la vista parcial, obtenida haciendo un pequeño
cambio en la figura 4.9: si la petición es una petición AJAX, la línea 5 realiza el renderizado y vuelve inmediatamente. La
opción :object hace que @movie esté disponible para la vista parcial como variable local cuyo nombre coincide con el
nombre de la vista parcial, en este caso movie. Si xhr? no es verdadero, el método del controlador ejecutará la acción de
renderizado por defecto, que es procesar la vista show.html.haml como habitualmente.
Vamos a ilustrar cómo funciona cada paso de la funcionalidad AJAX que se muestra en el
screencast 6.1.1, mediante la cual los detalles de la película aparecen en una ventana flotante
en lugar de cargar una página independiente. El paso 1 necesita que identifiquemos o creemos
una nueva acción de controlador que será la encargada de gestionar la petición. Usaremos
la acción ya existente MoviesController#show, por lo que no necesitaremos definir una
nueva ruta. Esta decisión de diseño es justificable, dado que la versión AJAX de la acción rea-
liza la misma función que la versión original, es decir, la acción REST de mostrar (“show”).
Modificaremos la acción show de forma que si está respondiendo a una petición AJAX,
procesará la sencilla vista parcial de la figura 6.14(a) en lugar de la vista completa. También
se podrían definir acciones de controlador independientes para uso exclusivo con AJAX, pero
esto podría no seguir la filosofía DRY si duplican el trabajo de acciones existentes.
¿Cómo sabe nuestra acción de controlador si show fue llamada desde código JavaScript
o mediante una petición HTTP normal iniciada por el usuario? Por suerte, todas las librerías
JavaScript más importantes y la mayoría de los navegadores configuran una cabecera HTTP
X-Requested-With: XMLHttpRequest en todas las peticiones HTTP AJAX. El método
helper de Rails xhr?, definido en el objeto request de la instancia del controlador que repre-
senta la petición HTTP entrante, comprueba la presencia de dicha cabecera. La figura 6.14(b)
muestra la acción del controlador que renderizará la vista parcial.
Continuando con el paso 2, ¿cómo debería construir y lanzar la petición XHR nuestro
código JavaScript? Tal como se mostraba en el screencast, queremos que la ventana flotante
208 CAPÍTULO 6. ENTORNO DE CLIENTE SAAS: INTRODUCCIÓN A JAVASCRIPT
http://pastebin.com/zZPKvmVW
1 var MoviePopup = {
2 setup : function () {
3 // add hidden ' div ' to end of page to display popup :
4 var popupDiv = $ ( ' < div id =" movieInfo " > </ div > ') ;
5 popupDiv . hide () . appendTo ( $ ( ' body ') ) ;
6 $ ( document ) . on ( ' click ' , '# movies a ' , MoviePopup . getMovieInfo ) ;
7 }
8 , getMovieInfo : function () {
9 $ . ajax ({ type : ' GET ' ,
10 url : $ ( this ) . attr ( ' href ') ,
11 timeout : 5000 ,
12 success : MoviePopup . showMovieInfo ,
13 error : function ( xhrObj , textStatus , exception ) { alert ( ' Error ! ') ;
}
14 // ' success ' and ' error ' functions will be passed 3 args
15 }) ;
16 return ( false ) ;
17 }
18 , showMovieInfo : function ( data , requestStatus , xhrObject ) {
19 // center a floater 1/2 as wide and 1/4 as tall as screen
20 var oneFourth = Math . ceil ( $ ( window ) . width () / 4) ;
21 $ ( '# movieInfo ') .
22 css ({ ' left ': oneFourth , ' width ': 2* oneFourth , ' top ': 250}) .
23 html ( data ) .
24 show () ;
25 // make the Close link in the hidden element work
26 $ ( '# closeLink ') . click ( MoviePopup . hideMovieInfo ) ;
27 return ( false ) ; // prevent default link action
28 }
29 , hideMovieInfo : function () {
30 $ ( '# movieInfo ') . hide () ;
31 return ( false ) ;
32 }
33 };
34 $ ( MoviePopup . setup ) ;
Figura 6.15. La función ajax construye y envía una petición XHR con las características especificadas. type especifica el
verbo HTTP a usar, url es la URL o URI donde se dirige la petición, timeout es el número de milisegundos que se debe
esperar una respuesta antes de declararla fallida, success especifica la función a llamar con los datos devueltos y error
indica la función a llamar si se agota el tiempo de espera sin recibir una respuesta o se produce cualquier otro error. Existen
muchas otras opciones de la función ajax disponibles, en concreto para una gestión de errores más robusta.
6.6. AJAX: JAVASCRIPT ASÍNCRONO Y XML 209
http://pastebin.com/vWwDrYEc
1 # movieInfo {
2 padding : 2 ex ;
3 position : absolute ;
4 border : 2 px double grey ;
5 background : wheat ;
6 }
Figura 6.16. Añadiendo este código a app/assets/stylesheets/application.css especificamos que la ventana flotante
debe colocarse según coordenadas absolutas en lugar de relativas al elemento que la contiene, pero como explica el texto,
hasta el momento de ejecución no sabemos cuáles serán dichas coordenadas, por lo que usamos jQuery para modificar
dinámicamente las propiedades de estilo CSS de #movieInfo cuando estamos listos para mostrar la ventana flotante.
aparezca cuando pinchamos en el enlace que tiene el nombre de la película. Como se explicó
en la sección 6.5, podemos “interceptar” el comportamiento por defecto de un elemento aso-
ciándole un manejador JavaScript del evento click explícito. Por supuesto, para respetar el HiJax se usa a veces para
principio de degradación elegante, debemos modificar el comportamiento del enlace sólo si describir en clave de humor
esta técnica, haciendo un
JavaScript está disponible. Siguiendo la misma estrategia que en el ejemplo de la sección 6.5, juego de palabras con el
nuestra función setup (líneas 2–8 de la figura 6.15) registra el manejador y crea un elemento término inglés “hijack”
div oculto para mostrar la ventana flotante. Los navegadores antiguos no ejecutarán esta (interceptar, secuestrar) y
“AJAX”.
función y seguirán el comportamiento por defecto al pinchar en el enlace.
El manejador del evento clic getMovieInfo debe lanzar la petición XHR y proporcionar
una función callback que se llamará con la información devuelta. Para esto utilizamos la
función ajax de jQuery, que recibe un objeto cuyas propiedades especifican las caracterís-
ticas de la petición AJAX, como muestran las líneas 10–15 de la figura 6.15. Nuestro Por supuesto, $.ajax es
ejemplo muestra un subconjunto de propiedades que se pueden especificar en este objeto; sólo un alias para
jQuery.ajax.
una propiedad importante que no mostramos es data, que puede ser tanto una cadena de
argumentos a añadir al final del URI (como en la figura 2.3) o un objeto JavaScript, en cuyo
caso las propiedades del objeto y sus valores serán serializados para obtener una cadena de
caracteres que se añadirá al URI. Como siempre, dichos argumentos aparecerían en el hash
params[] disponible para nuestras acciones del controlador de Rails.
El screencast 6.6.1 utiliza el depurador interactivo Firebug así como el depurador de Rails
para avanzar paso a paso por el resto del código de la figura 6.15. Obtener el URI destino
de la petición XHR es sencillo: puesto que el enlace que estamos interceptando ya enlaza de
por sí al URI REST para mostrar los detalles de la película, podemos consultar su atributo
href, como muestra la línea 11. Las líneas 13–14 nos recuerdan que las propiedades que
toman valores de funciones pueden especificar bien una función con nombre, como ocurre
con success, o bien funciones anónimas, como ocurre con error. Para mantener el ejemplo
sencillo, nuestro comportamiento de error es rudimentario: no importa qué tipo de error haya
ocurrido, incluyendo un timeout de 5000 ms (5 segundos), sencillamente mostraremos un
cuadro de alerta. En caso de éxito, designamos showMovieInfo como función callback.
210 CAPÍTULO 6. ENTORNO DE CLIENTE SAAS: INTRODUCCIÓN A JAVASCRIPT
En las líneas 20 y 23 de la figura 6.15 ocurren algunos trucos interesantes de CSS. Puesto
que nuestro objetivo es que la ventana emergente “flote”, podemos utilizar CSS para especi-
ficar la posición como absolute añadiendo el marcado de la figura 6.16. Pero sin saber el
tamaño de la ventana del navegador, no sabemos el tamaño que debe tener la ventana flotante
ni dónde colocarla. showMovieInfo calcula las dimensiones y coordenadas de un elemento
div flotante la mitad de ancho y un cuarto de alto que la ventana del navegador (línea 20).
Después reemplaza el contenido HTML del div con los datos devueltos por el servidor (línea
22), centra el elemento horizontalmente sobre la ventana principal y lo coloca a 250 píxeles
del borde superior (línea 23) y, finalmente, muestra el div, que hasta ahora permanecía oculto
(línea 24).
Queda una cosa más que hacer: el elemento div flotante tiene un enlace “Close” que
debería hacerlo desaparecer, así que la línea 26 le asocia un sencillo manejador de click.
Finalmente, showMovieInfo devuelve false (línea 27). ¿Por qué? Como el manejador se
invoca como resultado de hacer clic en un enlace (elemento <a>), tenemos que devolver false
para suprimir el comportamiento por defecto asociado con dicha acción, es decir, seguir el
enlace (por esta misma razón, el manejador de click asociado al enlace “Close” devuelve
false en la línea 31).
Con tantas funciones distintas que se llaman incluso en un ejemplo sencillo, puede re-
sultar difícil trazar el flujo de control cuando se depura. Aunque siempre se puede utilizar
console.log(string) para escribir mensajes en la consola JavaScript del navegador, es fácil
olvidar eliminarlos en producción y, como se describe en el capítulo 8, dicha “depuración
con printf” pueden resultar lenta, ineficiente y frustrante. En la sección 6.7 comentaremos
un procedimiento mejor creando pruebas con Jasmine.
Por último, conviene mencionar una advertencia a considerar cuando se usa JavaScript
para crear nuevos elementos dinámicamente en tiempo de ejecución, aunque no surgió en
este ejemplo en concreto. Sabemos que $(’.myClass’).on(’click’,func) registra func como
el manejador de eventos de clic para todos los elementos actuales que coincidan con la clase
CSS myClass. Pero si se utiliza JavaScript para crear nuevos elementos que coincidan con
myClass después de la carga inicial de la página y de la llamada inicial a on, dichos elemen-
tos no tendrán el manejador asociado, ya que on sólo puede asociar manejadores a elementos
existentes.
Una solución habitual a este problema es aprovechar el mecanismo jQuery que permite
a un elemento padre delegar en un descendiente la gestión de eventos, utilizando el polimor-
fismo de on: $(’body’).on(’click’,’.myClass’,func) asocia el elemento HTML body (que
siempre existe) al evento click, pero delega el evento a cualquier descendiente que coincida
6.6. AJAX: JAVASCRIPT ASÍNCRONO Y XML 211
con el selector .myClass. Puesto que la comprobación de delegación se hace cada vez que
se procesa un evento, nuevos elementos que coincidan con .myClass tendrán asociada la
función func como su manejador de eventos clic “automágicamente” al ser creados.
Resumen de AJAX:
• Para crear una interacción AJAX, identifique qué elementos van a adquirir nuevos
comportamientos, qué nuevos elementos se va a necesitar construir para posibilitar
la interacción o mostrar respuestas, etc.
• Una interacción AJAX suele involucrar tres fragmentos de código: el manejador
que inicia la petición, la función callback que recibe la respuesta y el código en
la función document.ready (función de inicialización) para asociar el manejador.
Resulta más legible definir funciones con nombres independientes para cada una de
estas tareas que proporcionar funciones anónimas.
• Tal y como hicimos en el ejemplo de la sección 6.5, para respetar la degradación
elegante, cualquier elemento de la página que se use sólo en interacciones AJAX
debe construirse en la función o funciones de inicialización, en lugar de incluirse en
la propia página HTML.
• Tanto los depuradores interactivos, como Firebug o las consolas JavaScript de
Google Chrome y Safari, como la “depuración con printf” utilizando con-
sole.log() pueden ayudar a encontrar problemas de JavaScript, pero existe una
aproximación mejor mediante pruebas, que explicaremos en la sección 6.7.
Figura 6.17. Comparación de la instalación, configuración y uso de Jasmine y RSpec. Todas las rutas son relativas a la raíz
de la aplicación y todos los comandos deben ejecutarse desde la raíz de la aplicación. Como se puede ver, la principal
diferencia es el uso de lower_snake_case (elementos unidos por guion bajo) para los nombres de ficheros y métodos en
Ruby, frente al uso de lowerCamelCase (elementos capitalizados) en JavaScript.
http://pastebin.com/YPssaaXU
1 rails generate jasmine : install
2 mkdir spec / javascripts / fixtures
3 curl https :// raw . g i t h u b u s e r c o n t e n t . com / velesin / jasmine - jquery / master / lib /
jasmine - jquery . js > spec / javascripts / helpers / jasmine - jquery . js
4 git add spec / javascripts
Figura 6.18. Cómo crear directorios de Jasmine en su aplicación. La línea 1 crea un directorio spec/javascripts en el
que se almacenarán nuestras pruebas, con subdirectorios support y helper análogos a la configuración de RSpec
(sección 8.2). La línea 2 añade un subdirectorio para datos de prueba (fixtures) (sección 8.5). La línea 3 instala un
complemento en Jasmine que proporciona soporte adicional para probar código basado en jQuery y utilizar fixtures. La
línea 4 añade estos nuevos ficheros JavaScript TDD al proyecto.
214 CAPÍTULO 6. ENTORNO DE CLIENTE SAAS: INTRODUCCIÓN A JAVASCRIPT
ciertos elementos DOM en la página, tal y como los usamos en la sección 8.5 para probar el
código de la aplicación Rails que depende de la presencia de ciertos elemento en la base de
datos.
La figura 6.19 da una visión general de Jasmine para usuarios de RSpec. Recorreremos
cinco especificaciones (specs) Jasmine de “caminos felices” (happy-paths) para la funciona-
lidad de la ventana emergente desarrollada en la sección 6.6. Aunque estas pruebas distan
de ser exhaustivas ni siquiera para el camino feliz, nuestro objetivo es ilustrar las técnicas
de prueba de Jasmine de forma general y el uso de stubs y fixtures en Jasmine para realizar
pruebas de código AJAX en particular.
La estructura básica de los casos de prueba de Jasmine se hace evidente en la figura 6.21:
como en RSpec, Jasmine utiliza it para especificar un único ejemplo y bloques describe
anidados para agrupar conjuntos de ejemplos relacionados. Tal y como ocurre en RSpec,
describe e it reciben un bloque de código como argumento, pero mientras que en Ruby los
bloques de código están delimitados por do. . . end, en JavaScript son funciones anónimas
(funciones sin nombre) sin argumentos. La secuencia de puntuación }); prevalece porque
describe e it son funciones JavaScript de dos argumentos, el segundo de los cuales es una
función sin argumentos.
Los ejemplos describe(’setup’) comprueban que la función MoviePopup.setup cree
el contenedor #movieInfo correctamente y lo mantenga oculto. toExist y toBeHidden
son mecanismos para comprobar expectativas proporcionados por el complemento Jasmine-
jQuery. Dado que Jasmine carga todos los ficheros JavaScript antes de ejecutar ningún ejem-
plo, la llamada a setup (línea 34 de la figura 6.15) ocurre antes de que se ejecuten nuestras
pruebas; por tanto, parece razonable comprobar que dicha función hizo su trabajo.
Los ejemplos describe(’AJAX call to server’) son más interesantes ya que utilizan
stubs y fixtures para aislar el código AJAX del lado cliente del servidor con el que se comu-
nica. La figura 6.20 resume los stubs y fixtures disponibles en Jasmine y Jasmine-jQuery.
Como en RSpec, Jasmine permite ejecutar código de inicialización y desmantelamiento de
pruebas utilizando beforeEach y afterEach. En este conjunto de ejemplos, nuestro código
de inicialización carga el fixture HTML mostrado en la figura 6.22, para imitar el entorno que
el manejador getMovieInfo vería si fuera llamado después de mostrar la lista de películas.
La funcionalidad de fixtures la proporciona Jasmine-jQuery; cada fixture se carga dentro de
div#jasmine-fixtures, que está dentro de div#jasmine_content en la página princi-
pal de Jasmine, y todos los fixtures se eliminan después de cada especificación (spec) para
preservar la independencia de las pruebas.
El primer ejemplo (línea 12 de la figura 6.21) comprueba que la llamada AJAX utiliza
la URL de película correcta derivada de la tabla. Para ello, utiliza spyOn de Jasmine para
realizar un stub de la función $.ajax. Al igual que el método stub de RSpec, esta llamada
reemplaza cualquier función del mismo nombre, de forma que al disparar manualmente la
acción clic sobre el (único) elemento a de la tabla #movies, si todo funciona correctamente,
deberíamos esperar que se haya llamado a nuestra función espía. Puesto que en JavaScript es
habitual tener funciones como valores de las propiedades de los objetos, spyOn recibe dos
argumentos, un objeto ($) y el nombre de la propiedad de tipo función del objeto a espiar
(’ajax’).
La línea 15 parece compleja, pero en realidad es sencilla. Cada espía Jasmine recuerda
los argumentos que se le han pasado en cada una de las llamadas que recibe, por ejemplo
calls.mostRecent(), y como recordará de la explicación en la sección 6.6, una llamada real
a la función AJAX recibe un único objeto (líneas 9–15 de la figura 6.15) cuya propiedad url es
6.7. PRUEBAS DE JAVASCRIPT Y AJAX 215
Expectativas
Una expectativa en una spec tiene la forma expect(object).expectation o
expect(object).not.expectation
Algunas expectativas de uso común integradas en Jasmine:
• toEqual(val), toBeTruthy(), toBeFalsy()
Prueba de igualdad usando ==, o si una expresión se evalúa al valor booleano true o false.
Expectativas de uso común proporcionadas por el complemento Jasmine jQuery —en este caso, el
argumento de expect debe ser un elemento o conjunto de elementos con envoltura jQuery:
• toBeSelected(), toBeChecked(), toBeDisabled(), toHaveValue(stringValue)
Expectativas sobre elementos de entrada de datos en formularios.
• toBeVisible(), toBeHidden()
Hidden (oculto) es verdadero si el elemento tienen anchura y altura cero, si es un campo de
formulario con type="hidden" o si el elemento o uno de sus antecesores tienen la propiedad
CSS display: none.
• toExist(), toHaveClass(class), toHaveId(id), toHaveAttr(attrName,attrValue)
Comprueba diversos atributos y características de un elemento.
• toHaveText(stringOrRegexp), toContainText(string)
Comprueba si el texto del elemento coincide exactamente con la cadena o expresión regular
dados, o si contiene la subcadena indicada.
Figura 6.19. Resumen parcial de un pequeño subconjunto de funcionalidades de uso común en Jasmine y Jasmine-jQuery,
siguiendo la estructura de las figuras 8.17 y 8.18 y extraídas de la completa documentación de Jasmine22 y de la
documentación del complemento Jasmine jQuery23 .
216 CAPÍTULO 6. ENTORNO DE CLIENTE SAAS: INTRODUCCIÓN A JAVASCRIPT
Stubs (Espías)
• spyOn(obj, ’func’)
Crea y devuelve un espía (mock) de una función existente, que debe ser una propiedad-función
de obj denominada func. El espía reemplaza a la función existente.
• calls es una propiedad de un espía que hace un seguimiento de las llamadas que que se le han
hecho y del array args[] de argumentos de cada llamada.
Los siguientes modificadores se pueden llamar sobre un espía para controlar su comportamiento:
• and.returnValue(value)
• and.throwError(exception)
• and.callThrough()
• and.callFake(func)
func debe ser una función sin argumentos, aunque tiene acceso a los argumentos con los que se
llamó al espía mediante spy.calls.mostRecent().args[], y puede llamar a otras funciones usando
dichos argumentos.
Fixtures y factorías (requiere jasmine-jquery)
• sandbox({class: ’myClass’, id: ’myId’})
Crea un elemento div vacío con los atributos HTML indicados, si los hay; por defecto es un div
sin clase CSS y con ID sandbox. Una forma alternativa de crear el argumento para setFixtures
que evita tener que poner cadenas de caracteres HTML literales en el código de pruebas.
• loadFixtures("file.html")
Carga contenido HTML desde spec/javascripts/fixtures/file.html y lo coloca dentro de
un elemento div con ID jasmine-fixtures, que se reinicia entre casos de prueba.
• setFixtures(HTMLcontent)
Crea una fixture directamente en lugar de cargarla desde archivo. HTMLcontent puede ser una
cadena HTML literal como <p class="foo">text</p> o un elemento con envoltura jQuery
como $(’<p class="foo">text</p>’).
• getJSONFixture("file.json")
Devuelve el objeto JSON en spec/javascripts/fixtures/file.json. Puede ser útil para
almacenar datos que simulen el resultado de una llamada AJAX sin tener que poner objetos
JSON literales en el código de prueba.
Figura 6.20. Continuación de la figura 6.19 describiendo los stubs (espías (spies) en Jasmine) y fixtures.
6.7. PRUEBAS DE JAVASCRIPT Y AJAX 217
http://pastebin.com/zhQw7uUd
1 describe ( ' MoviePopup ' , function () {
2 describe ( ' setup ' , function () {
3 it ( ' adds popup Div to main page ' , function () {
4 expect ( $ ( '# movieInfo ') ) . toExist () ;
5 }) ;
6 it ( ' hides the popup Div ' , function () {
7 expect ( $ ( '# movieInfo ') ) . toBeHidden () ;
8 }) ;
9 }) ;
10 describe ( ' clicking on movie link ' , function () {
11 beforeEach ( function () { loadFixtures ( ' movie_row . html ') ; }) ;
12 it ( ' calls correct URL ' , function () {
13 spyOn ($ , ' ajax ') ;
14 $ ( '# movies a ') . trigger ( ' click ') ;
15 expect ( $ . ajax . calls . mostRecent () . args [0][ ' url ' ]) . toEqual ( '/ movies /1 ') ;
16 }) ;
17 describe ( ' when successful server call ' , function () {
18 beforeEach ( function () {
19 var htmlResponse = readFixtures ( ' movie_info . html ') ;
20 spyOn ($ , ' ajax ') . and . callFake ( function ( ajaxArgs ) {
21 ajaxArgs . success ( htmlResponse , ' 200 ') ;
22 }) ;
23 $ ( '# movies a ') . trigger ( ' click ') ;
24 }) ;
25 it ( ' makes # movieInfo visible ' , function () {
26 expect ( $ ( '# movieInfo ') ) . toBeVisible () ;
27 }) ;
28 it ( ' places movie title in # movieInfo ' , function () {
29 expect ( $ ( '# movieInfo ') . text () ) . toContain ( ' Casablanca ') ;
30 }) ;
31 }) ;
32 }) ;
33 }) ;
Figura 6.21. Cinco especificaciones (specs) Jasmine del camino feliz del código AJAX desarrollado en la sección 6.6. Las
líneas 2–9 comprueban si la función MoviePopup.setup inicializa correctamente el elemento div flotante destinado a
mostrar la información de la película. Las líneas 10–32 comprueban el comportamiento del código AJAX sin llamar al
servidor RottenPotatoes, sino simulando (con stubs) la llamada AJAX.
http://pastebin.com/1PdEwxnQ
1 < table id = " movies " >
2 < tbody >
3 < tr class = " adult " >
4 <td > Casablanca </ td >
5 <td > PG </ td >
6 <td > < a href = " / movies /1 " > More about Casablanca </ a > </ td >
7 </ tr >
8 </ tbody >
9 </ table >
Figura 6.22. Este fixture HTML imita una fila de la tabla #movies generada por la vista que muestra la lista de películas de
RottenPotatoes (figura 4.6); aparece en spec/javascripts/fixtures/movie_row.html. Se pueden generar estos fixtures
copiando y pegando código HTML desde “Ver código fuente” del navegador, o en el caso de código generado dinámicamente
por JavaScript (como la casilla de verificación “Hide adult movies”), inspeccionando $(’#movieInfo’).html() en la consola
JavaScript. En la sección de “Falacias y errores comunes” se describe un procedimiento con el que prevenir que dichos
fixtures dejen de estar sincronizados si las vistas de la aplicación cambian.
218 CAPÍTULO 6. ENTORNO DE CLIENTE SAAS: INTRODUCCIÓN A JAVASCRIPT
Figura 6.23. Arriba: normalmente, nuestra función getMovieInfo llama a ajax de jQuery, la cual llama a xhr de la JSAPI
del navegador, que envía una petición al servidor. La respuesta del servidor dispara la lógica de devolución de llamada
(callback) de la JSAPI del navegador, que llama a un método interno jQuery que acabará llamando a nuestra función
callback showMovieInfo. Si simulamos con un stub la función ajax, podemos hacer que showMovieInfo sea llamada
inmediatamente; también podemos colocar el stub “más allá”, sobre xhr (utilizando el complemento Jamsine-Ajax),
haciendo que el despachador interno de jQuery sea llamado inmediatamente. Abajo: representación gráfica de la
explicación que acompaña a la figura 8.16 en la sección 8.6.
http://pastebin.com/pnTj5S5c
1 <p > Casablanca is a classic and iconic film starring
2 Humphrey Bogart and Ingrid Bergman . </p >
3 <a href = " " id = " closeLink " > Close </ a >
Figura 6.24. Este fixture HTML imita la respuesta AJAX de la acción mostrar del controlador de películas; se encuentra en
spec/javascripts/fixtures/movie_info.html.
la URL a la cual debe dirigirse la llamada AJAX. La línea 15 de la especificación (spec) sim-
plemente comprueba el valor de dicha URL. De hecho, comprueba si $(this).attr(’href’)
es el código JavaScript correcto para extraer la URL AJAX de la tabla.
La figura 6.23 muestra la similitud entre los retos de “simular Internet” para probar
AJAX y “simular Internet” para probar código en una arquitectura orientada a servicios (sec-
ción 8.6). Como puede observar, en ambos escenarios, la decisión de dónde situar el stub
depende de cuánta parte de la pila queremos verificar con nuestras pruebas.
La línea 19 lee un fixture que reemplazará a la respuesta AJAX de la acción mostrar
del controlador de películas (ver figura 6.24). En las líneas 20-22 vemos el uso de la función
callFake no sólo para interceptar una llamada AJAX, sino también para simular una respuesta
correcta con fixture. Esto y la activación de la llamada AJAX (línea 23) se repite por cada
una de las siguientes dos pruebas que verifican tanto que el popup #movieInfo está visible
(línea 26) como que contiene el texto de la descripción de la película (línea 29).
Esta concisa introducción, junto con las tablas resumen de esta sección, deberían ayudarle
a empezar a usar BDD para su código JavaScript. Las mejores fuentes de documentación
completa para estas herramientas son la documentación de Jasmine24 y la documentación del
complemento Jasmine jQuery25 .
6.7. PRUEBAS DE JAVASCRIPT Y AJAX 219
http://pastebin.com/9rsFCnwE
1 describe ( ' element sanitizer ' , function () {
2 it ( ' removes IMG tags from evil HTML ' , function () {
3 setFixtures ( sandbox ({ class : ' myTestClass ' }) ) ;
4 $ ( '. myTestClass ') . text ( " Evil HTML ! < img src = ' http :// evil . com / xss '>" ) ;
5 $ ( '. myTestClass ') . sanitize () ;
6 expect ( $ ( '. myTestClass ') . text () ) . not . toContain ( ' < img ') ;
7 }) ;
8 }) ;
Figura 6.25. El método sandbox de Jasmine-jQuery crea un nuevo elemento HTML div con los atributos indicados; su
atributo id toma como valor por defecto sandbox si no se le asigna otro. Las líneas 4–5 utilizan el elemento creado con
sandbox, que se puede utilizar para contener temporalmente elementos construidos de forma parecida a las factorías, pero
sin “contaminar” el código de pruebas con marcado HTML.
• Como ocurría en RSpec, las especificaciones (specs) en Jasmine son funciones anó-
nimas acompañadas por una cadena de caracteres descriptiva. Se introducen me-
diante la función it de Jasmine, se pueden agrupar con bloques describe (anidados)
que tienen asociadas llamadas beforeEach y afterEach (inicialización y desman-
telamiento de pruebas).
• Se puede utilizar spyOn para simular mediante stubs un método existente, reempla-
zándolo con un espía. El comportamiento del espía se puede controlar con funciones
como and.callThrough, and.returnValue, etc., como muestra la figura 6.20.
• Los fixtures HTML de Jasmine-jQuery pueden proporcionar tanto el contenido que
debe aparecer “antes” para disparar una petición AJAX como el contenido que debe
aparecer “después” para verificar los resultados de una petición AJAX exitosa o
fallida.
Explicación. ¿Por qué no hay especificaciones Jasmine para código sólo del lado cliente?
No hemos incluido especificaciones (specs) para el ejemplo de sólo lado cliente en la sec-
ción 6.5 por la misma razón que no escribimos pruebas de vistas en el capítulo 8: una prácti-
ca ampliamente extendida consiste en probar los comportamientos de visualización del lado
cliente con pruebas del nivel de integración o aceptación, tales como escenarios Cucumber
utilizando Webdriver (sección 7.6).
220 CAPÍTULO 6. ENTORNO DE CLIENTE SAAS: INTRODUCCIÓN A JAVASCRIPT
http://pastebin.com/H16DAvwY
1 Review . first . to_json
2 # = > "{\" created_at \":\"2012 -10 -01 T20 :44:42 Z \" , \" id \":1 , \" movie_id \":1 ,
3 \ " moviegoer_id \":2 ,\" potatoes \":3 ,\" updated_at \":\"2013 -07 -28 T18 :01:35 Z \"} "
Figura 6.26. La función to_json integrada en Rails puede serializar objetos ActiveRecord sencillos llamándose
recursivamente en cada atributo del modelo. Como puede ver, no recorre asociaciones —los identificadores movie_id y
moviegoer_id de las opiniones se serializan como enteros, no como los objetos Movie y Moviegoer a los que la claves
foráneas representadas por dichos enteros se refieren—. Es posible efectuar serializaciones más sofisticadas
sobreescribiendo to_json en sus modelos de ActiveRecord.
Para usar JSON en nuestro código del lado cliente, debemos considerar tres cuestiones:
3. Cuando se prueban peticiones AJAX que esperan respuestas JSON, ¿cómo podemos
usar fixtures para “simular el servidor” y probar estos comportamientos de forma ais-
lada, tal y como hicimos en la sección 6.7?
La primera pregunta es sencilla. Si tiene control sobre el código del servidor, sus acciones
de controlador Rails pueden devolver JSON en lugar de XML o una plantilla Haml usando
render :json=>object, que envía al cliente una representación JSON de un objeto como
única respuesta de la acción del controlador. Al igual que al renderizar una plantilla, sólo se
permite una única llamada render por acción, por lo que todos los datos de respuesta para
una acción del controlador dada deben estar empaquetados en un único objeto JSON.
render :json funciona llamando a to_json sobre el objeto para crear la cadena de carac-
teres que se enviará de vuelta al cliente. La implementación por defecto de to_json puede
serializar objetos ActiveRecord sencillos, como muestra la figura 6.26.
Para hacer una llamada AJAX que espere una respuesta codificada en JSON, sólo tenemos
que asegurarnos de que el objeto que se pasa como argumento a $.ajax incluye una propiedad
dataType cuyo valor es la cadena json, como muestra la figura 6.27. La presencia de esta
propiedad hace que jQuery llame automáticamente a JSON.parse sobre los datos devueltos,
Por supuesto, también de modo que no es necesario que realice este paso usted mismo.
debemos hacer que el ¿Cómo podemos probar este código sin llamar al servidor cada vez? Por suerte, el me-
servidor devuelva un objeto
JSON, como se comentó canismo de fixtures de Jasmine-jQuery permite especificar fixtures JSON además de fixtures
anteriormente. HTML, como muestra la figura 6.28.
6.8. APLICACIONES DE PÁGINA ÚNICA Y API JSON 223
http://pastebin.com/6cUbpbfY
1 var MovieP opupJs on = {
2 // ' setup ' function omitted for brevity
3 getMovieInfo : function () {
4 $ . ajax ({ type : ' GET ' ,
5 dataType : ' json ' ,
6 url : $ ( this ) . attr ( ' href ') ,
7 success : MovieP opupJ son . showMovieInfo
8 // ' timeout ' and ' error ' functions omitted for brevity
9 }) ;
10 return ( false ) ;
11 }
12 , showMovieInfo : function ( jsonData , requestStatus , xhrObject ) {
13 // center a floater 1/2 as wide and 1/4 as tall as screen
14 var oneFourth = Math . ceil ( $ ( window ) . width () / 4) ;
15 $ ( '# movieInfo ') .
16 css ({ ' left ': oneFourth , ' width ': 2* oneFourth , ' top ': 250}) .
17 html ( $ ( ' <p > ' + jsonData . description + ' </p > ') ,
18 $ ( ' <a id =" closeLink " href ="#" > </ a > ') ) .
19 show () ;
20 // make the Close link in the hidden element work
21 $ ( '# closeLink ') . click ( Mov iePopu pJson . hideMovieInfo ) ;
22 return ( false ) ; // prevent default link action
23 }
24 // hideMovieInfo omitted for brevity
25 };
Figura 6.27. Esta versión de MoviePopup espera una respuesta JSON en lugar de HTML (línea 5), así que la función
success utiliza la estructura de datos JSON devuelta para crear nuevos elementos HTML dentro del div emergente (líneas
17–19; observe que las funciones jQuery de manipulación del DOM como append pueden recibir múltiples argumentos de
distintos fragmentos de HTML a crear). Las funciones omitidas por concisión son las mismas que en la figura 6.15.
http://pastebin.com/sq6FASzh
1 describe ( ' Mov iePopu pJson ' , function () {
2 describe ( ' successful AJAX call ' , function () {
3 beforeEach ( function () {
4 loadFixtures ( ' movie_row . html ') ;
5 var jsonResponse = getJSO NFixtu re ( ' movie_info . json ') ;
6 spyOn ($ , ' ajax ') . and . callFake ( function ( ajaxArgs ) {
7 ajaxArgs . success ( jsonResponse , ' 200 ') ;
8 }) ;
9 $ ( '# movies a ') . trigger ( ' click ') ;
10 }) ;
11 // ' it ' clauses are same as in m o v i e _ p o p u p _ s p e c . js
12 }) ;
13 }) ;
Figura 6.28. Jasmine-jQuery espera encontrar ficheros de fixtures que contengan datos .json en
spec/javascripts/fixtures/json. Tras ejecutar la línea 5, jsonResponse contendrá el objeto JavaScript real (¡no la
cadena JSON en bruto!) que se pasará al manejador success.
224 CAPÍTULO 6. ENTORNO DE CLIENTE SAAS: INTRODUCCIÓN A JAVASCRIPT
• Mientras que las aplicaciones SaaS tradicionales mejoradas con JavaScript renderi-
zarían generalmente fragmentos completos de HTML (usando, por ejemplo, vistas
parciales) que el cliente sencillamente “insertaría” en la página HTML actual, las
SPA recibirían datos estructurados de uno o más servicios y utilizarían dichos datos
para sintetizar nuevo contenido o modificar el que ya existe en la página.
• La simplicidad de JSON y su correspondencia natural con JavaScript lo están con-
virtiendo rápidamente en el formato preferido para el intercambio de datos estruc-
turados en SPA. Rails puede serializar modelos ActiveRecord sencillos con ren-
der :json=> object, pero se puede sobreescribir el método to_json de ActiveRe-
cord para serializar estructuras de datos arbitrariamente complejas.
• Asignar el valor "json" a la propiedad dataType en una llamada a $.ajax hace que
jQuery deserialice automáticamente la respuesta del servidor para crear un objeto
JSON.
• Se puede usar un espía (stub) que devuelva un fixture JSON para simular una res-
puesta del servidor cuando se prueba una SPA, permitiendo que las pruebas de Jas-
mine estén aisladas del servidor(es) remoto(s) en los que se apoya la SPA.
Autoevaluación 6.8.1. En la figura 6.28, donde se muestra el uso de un fixture JSON, ¿por
qué seguimos necesitando que se cargue también el fixture HTML en la línea 4?
⇧ La línea 9 intenta disparar el manejador de clic para un elemento que coincida con
#movies a y, si no cargamos el fixture HTML que representa una fila de la tabla de películas,
no existirá tal elemento (de hecho, la función MoviePopupJson.setup trata de asociar un
manejador de clic en este elemento, por lo que fallaría). Esto es un ejemplo de uso tanto de
un fixture HTML para simular que el usuario hace clic en un elemento de la página como de
un fixture JSON para simular una respuesta exitosa del servidor en respuesta a dicho clic.
6.9. FALACIAS Y ERRORES COMUNES 225
Figura 6.29. Arquitectura de SPA en el navegador que obtienen recursos desde diferentes servicios. Izquierda: si el código
JavaScript se sirvió desde RottenPotatoes.com, la política del mismo origen, aplicada por defecto, que implementan los
navegadores para JavaScript prohibirá que el código realice llamadas AJAX a servidores en otros dominios. La
especificación de compartición de recursos entre dominios (cross-origin resource sharing, CORS) relaja esta restricción,
pero sólo la soportan algunos navegadores muy recientes. Derecha: en la arquitectura SPA tradicional, un único servidor
sirve el código JavaScript e interactúa con otros servicios remotos. Este esquema respeta la política del mismo origen y
también permite que el servidor principal realice trabajo adicional en nombre del cliente si es necesario.
! Error. Crear un sitio que falla sin JavaScript en vez de que mejore con él.
Por razones de accesibilidad de personas con discapacidad, seguridad y compatibilidad
226 CAPÍTULO 6. ENTORNO DE CLIENTE SAAS: INTRODUCCIÓN A JAVASCRIPT
entre navegadores, un sitio bien diseñado debería funcionar mejor si se dispone de JavaScript
y aceptablemente bien en caso contrario. Por ejemplo, las páginas de GitHub para nave-
gar por los repositorios de código funcionan bien sin JavaScript, pero funcionan de forma
más fluida y rápida con JavaScript. Navegue por el sitio de ambas formas para observar un
magnífico ejemplo de mejora progresiva. Las pruebas también se ejecutan más rápido sin
JavaScript: tener un sitio para el que JavaScript es opcional significa que se puede realizar la
mayor parte de las pruebas de integración en el modo, más rápido, de “navegador sin interfaz”
de Cucumber y Capybara.
aunque tenemos el control casi completo del rendimiento (y, por tanto, de la experiencia de
usuario) en el lado servidor, apenas tenemos control en el lado cliente. Debido a las enormes
variaciones en el tipo de navegador, la velocidad de la conexión a Internet y otros factores
más allá de nuestro control, el rendimiento de JavaScript en el navegador de cada usuario
está en buena medida fuera de nuestro control, haciendo difícil proporcionar un rendimiento
constante para la experiencia de usuario.
Error. Permitir que los fixtures HTML o JavaScript pierdan la sincronización
! con el código de la aplicación o entre ellas.
Uno de los riesgos de utilizar fixtures HTML para probar la funcionalidad AJAX es que
los fixtures están basados en el código HTML generado por la aplicación y, si las plantillas
de las vistas de la aplicación cambian sin modificar también los fixtures, puede ocurrir que
las pruebas se ejecuten contra un código HTML que no se corresponde con la salida real de
la aplicación.
Una solución es la automatización: este flujo de trabajo de Pivotal Labs29 utiliza RSpec
(capítulo 8) para crear automáticamente fixtures desde las vistas de la aplicación para utili-
zarlos desde las pruebas de Jasmine. Esta solución también evita otro problema sutil: las
pruebas que funcionan con fixtures pequeños pero fallan con el DOM de la página com-
pleta. Por ejemplo, dos manejadores de eventos que tratan de responder al mismo evento
probablemente causen un problema en producción, pero si sólo se prueban por separado,
probando sólo uno cada vez con fixtures independientes, las pruebas unitarias no detectarán
este problema. Ejecutar specs utilizando “fixtures” de página completa en lugar de fixtures
para diferentes fragmentos de la página resolvería este problema, y es precisamente lo que
consigue de forma elegante el flujo de trabajo automatizado de Pivotal.
El operador ++ fue inventado por [Ken] Thompson para aritmética de punteros. Ahora
sabemos que la aritmética de punteros es mala y hemos dejado de usarla; se ha visto
implicada en ataques de desbordamiento de buffer y otras maldades. El último lenguaje
popular que incluye el operador ++ es C++, un lenguaje tan malo que tomó el nombre
de este operador.
Douglas Crockford, Programming and Your Brain, discurso de apertura en la
conferencia USENIX WebApps’12
228 CAPÍTULO 6. ENTORNO DE CLIENTE SAAS: INTRODUCCIÓN A JAVASCRIPT
El boom empresarial durante el que nació JavaScript fue una época de presiones de
agenda ridículas: LiveScript se diseñó, implementó y lanzó como producto en 10 días. Como
resultado, el lenguaje tiene algunos defectos y errores ampliamente reconocidos que algunos
han comparado con los “gotchas” del lenguaje C, por lo que instamos a utilizar la herramienta
JSLint30 de Doug Crokcford para que le avise tanto de potenciales errores como de oportuni-
dades de embellecer su código JavaScript. Esta herramienta viene instalada en la imagen de
la máquina virtual proporcionada con la biblioteca de recursos del libro; puede ejecutarla es-
cribiendo jsl -process filename en la línea de comandos. Tiene una miríada de opciones,
sobre las que puede informarse en el sitio web de JSLint31 .
Algunos errores comunes específicos a evitar incluyen los siguientes:
1. El intérprete intenta ayudar insertando punto y comas que cree que el programador
ha olvidado, pero a veces sus suposiciones son erróneas y da como resultado cambios
drásticos e inesperados en el comportamiento del código, como en el siguiente ejemplo:
http://pastebin.com/AZk8Q4uK
1 // good : returns new object
2 return {
3 ok : true ;
4 };
5 // bad : returns undefined , because JavaScript
6 // inserts " missing semicolon " after return
7 return
8 {
9 ok : true ;
10 };
Ruby también tiene un operador === (“triple-igual”, en inglés “threequal”) que hace
algo muy distinto.
5. La igualdad para arrays y hashes se basa en identidad y no en valor, por lo que
[1,2,3]==[1,2,3] es falso. A diferencia de Ruby, donde la clase Array puede definir
su propio operador ==, en JavaScript hay que lidiar con estos comportamientos pre-
definidos, ya que == es parte del lenguaje.
6. Las cadenas de caracteres (strings) son inmutables, por lo que métodos como
toUpperCase() devuelven siempre un nuevo objeto. Por tanto, escriba
s=s.toUpperCase() si quiere reemplazar el valor de una variable existente.
7. Si se llama a una función con más argumentos de los que especifica su definición, los
argumentos extra se ignoran; si se invoca con menos, los argumentos sin asignar se
consideran undefined. En cualquier caso, el array arguments[] (dentro del ámbito
de la función) da acceso a todos los argumentos que se pasaron realmente.
8. Las cadenas de texto (string) literales se comportan de forma distinta a las creadas con
new String si se intenta crear nuevas propiedades sobre ellas, tal y como muestra el
siguiente extracto de código. La razón es que JavaScript crea un “objeto envoltorio”
temporal en torno a fake para responder a fake.newprop=1, realiza la asignación y
destruye inmediatamente después el objeto envoltorio, dejando el objeto fake real sin
ninguna propiedad newprop. Es posible asignar propiedades adicionales sobre strings
si se crean explícitamente con new. Pero, aún mejor, no asigne propiedades sobre los
tipos predefinidos de JavaScript: defina su propio objeto prototipo y use composición
en lugar de herencia (capítulo 11) para que el string sea una de sus propiedades, des-
pués asigne el resto de propiedades que estime adecuadas. (Esta restricción aplica
igualmente a números y booleanos por las mismas razones, pero no aplica a los arrays
porque, como mencionamos anteriormente, sólo son un caso especial de hashes).
http://pastebin.com/LWxdsn3F
1 real = new String ( " foo " ) ;
2 fake = " foo " ;
3 real . newprop = 1;
4 real . newprop // = > 1
5 fake . newprop = 1; // BAD : silently fails since ' fake ' isn 't true object
6 fake . newprop // = > undefined
de JavaScript tanto para los sitios “Web 2.0” como para SPAs complejas como Google Docs,
los desarrolladores se han centrado en mejorar tanto el rendimiento como la productividad de
JavaScript.
Rendimiento. La compilación en tiempo de ejecución o compilación dinámica (Just-in-
time compilation, JIT), y otras técnicas avanzadas de ingeniería de software se están inclu-
yendo en el lenguaje, cerrando así la brecha de rendimiento con otros lenguajes interpreta-
dos e incluso compilados. Más de media docena de implementaciones del intérprete de
JavaScript y un compilador (Closure de Google) están disponibles a fecha de la escritura
de este texto, siendo la mayoría de ellos de código abierto, y empresas como Microsoft,
Apple o Google, entre otras, compiten en el rendimiento de los intérpretes JavaScript de sus
navegadores. Evaluar el rendimiento de los lenguajes interpretados es delicado, dado que los
resultados dependen de la implementación del intérprete así como de la aplicación específi-
ca, pero los bancos de pruebas del intérprete de física Box2D35 descubrieron que la versión
JavaScript era 5 veces más lenta que la de Java, y entre 10 y 12 veces más lenta que la versión
de C, y que las diferencias de rendimiento variaban hasta en un factor de tres utilizando dife-
rentes intérpretes JavaScript. Aún así, JavaScript es hoy en día lo bastante rápido como para
que en mayo de 2011 Hewlett-Packard lo usara para reescribir grandes partes de su sistema
operativo Palm webOS. Podemos esperar que esta tendencia continúe, ya que JavaScript es
uno de los primeros lenguajes en recibir atención cuando hay nuevo hardware disponible que
puede resultar útil para aplicaciones de cara al usuario: por ejemplo, WebCL propone utilizar
las asociaciones de JavaScript para el lenguaje OpenCL, utilizado para programar unidades
de procesado gráfico (Graphics Processing Units, GPUs).
Productividad. Estudiando Ruby y Rails, hemos visto una y otra vez que la productivi-
dad va de la mano de la concisión. La sintaxis de JavaScript pocas veces es concisa y muchas
extraña —en parte porque JavaScript siempre fue funcional en su núcleo (recuerde que su
creador quería usar Scheme originalmente como lenguaje de scripting para el navegador)
pero se le encargó el requisito, dictado por el marketing, de parecerse a un lenguaje impera-
tivo como Java—. CoffeeScript36 , lanzado por primera vez en 2010, trata de devolver parte
de la concisión sintáctica y de la belleza propia de la mejor parte de JavaScript. Un traductor
de código fuente a código fuente compila los ficheros de CoffeeScript (.coffee) a ficheros
.js que contienen código JavaScript normal, que utiliza el navegador. Las tuberías de Rails,
de las que se hablará en mayor profundidad en la sección A.8, automatizan esta compilación
de forma que no sea necesario generar manualmente los ficheros .js o incluirlos en el ár-
bol de código fuente. Dado que CoffeeScript compila generando JavaScript, no puede hacer
nada que JavaScript no haga ya de por sí, pero proporciona una notación sintáctica más con-
cisa para muchas construcciones comunes. Por ejemplo, la figura 6.30 muestra una versión
obtenida por CoffeeScript mucho menos ruidosa del spec de Jasmine en la figura 6.21.
Por desgracia, tras unos pocos años “en el mundo real”, el diseño de CoffeeScript ha
sido criticado por problemas fundamentales de diseño que limitan su utilidad en proyectos
grandes. Dos objeciones importantes son que todas sus variables externas tienen ámbito
global37 y una sensibilidad a los espacios en blanco que da lugar a interpretaciones ambiguas
del código fuente38 , violando el “principio de mínima sorpresa”, una de las piedras angulares
del diseño de Ruby. El tiempo dirá si CoffeeScript reemplazará a JavaScript o seguirá siendo
un “lenguaje de nicho” utilizado sólo en proyectos pequeños.
Las herramientas para programadores de SPAs que crean aplicaciones centradas en JSON
están mejorando también. Por ejemplo, el entorno de desarrollo de código abierto de Yahoo,
Mojito39 , permite que un mismo código JavaScript muestre HTML a partir de JSON tanto en
6.10. PASADO, PRESENTE Y FUTURO DE JAVASCRIPT 231
http://pastebin.com/gEyt3RUd
1 describe ' MoviePopup ' , ->
2 describe ' setup ' , ->
3 it ' adds popup Div to main page ' , -> expect $ ( '# movieInfo ') . toExist
4 it ' hides the popup Div ' , -> expect $ ( '# movieInfo ') . toBeHidden
5 describe ' AJAX call to server ' , ->
6 beforeEach -> loadFixtures ( ' movie_row . html ')
7 it ' calls correct URL ' , ->
8 spyOn $ , ' ajax '
9 $ ( '# movies a ') . trigger ' click '
10 expect ( $ . ajax . mostRe centCa ll . args [0][ ' url ' ]) . toEqual '/ movies /1 '
11 describe ' when successful ' , ->
12 beforeEach ->
13 @htmlResponse = readFixtures ' movie_info . html '
14 spyOn ($ , ' ajax ') . andCallFake ( ajaxArgs ) ->
15 ajaxArgs . success ( htmlResponse , ' 200 ')
16 $ ( '# movies a ') . trigger ' click '
17 it ' makes # movieInfo visible ' , -> expect $ ( '# movieInfo ') . toBeVisible
18 it ' places movie title in # movieInfo ' , ->
19 expect ( $ ( '# movieInfo ') . text ) . toContain ' Casablanca '
Figura 6.30. La versión CoffeeScript de la figura 6.21. Entre otras diferencias, CoffeeScript proporciona la sintaxis concisa,
->, del estilo Haskell para las funciones, utiliza indentación como la de Haml en lugar de llaves para indicar la estructura, y
permite la omisión de la mayoría de los paréntesis como en Ruby, además de tomar prestada la notación de variable de
instancia @ para referirse a las propiedades de this. Algunos consideran que el código resultante es más fácil de leer, ya que
tiene 1/3 de líneas menos y mucha menos puntuación que la versión plana de JavaScript.
el cliente como en un servidor basado en Node. Sin embargo, hay un enorme inconveniente
potencial para las aplicaciones que tomen este camino: su contenido no será ni indexado ni
podrá ser buscado mediante motores de búsqueda, sin lo que la Web pierde una buena parte
de su utilidad. Hay soluciones tecnológicas para este problema, pero de momento existe poco
debate sobre ellas.
Además de esta desventaja, el modelo de ejecución de hilo único de JavaScript, del cual
algunos opinan que obstaculiza la productividad porque requiere una programación dirigida
a eventos, parece que no va a cambiar próximamente. Algunos se lamentan de la adopción
en el lado servidor de entornos de desarrollo basados en JavaScript como Node, una libre-
ría JavaScript que proporciona versiones orientadas a eventos de las mismas capacidades del
sistema operativo POSIX (Unix-like) utilizadas en código de tareas paralelas. El principal
contribuidor de Rails, Yehuda Katz, resumió las opiniones de muchos programadores ex-
perimentados: cuando las cosas ocurren en un orden determinista, como cuando el código
del lado servidor gestiona una acción del controlador en una aplicación SaaS, un modelo
secuencial y bloqueante es más fácil de programar; cuando las cosas suceden en un orden
impredecible, como cuando se reacciona a estímulos externos como los eventos de interfaz
de usuario iniciados por el usuario, el modelo asíncrono tiene más sentido. Nosotros creemos
firmemente que el futuro del software serán las aplicaciones “nuble+cliente”, y nuestra pers-
pectiva es que es más importante elegir el lenguaje o el entorno de desarrollo apropiado para
cada tarea, que obsesionarse sobre si un único lenguaje o entorno de desarrollo se alzará
dominante para ambas partes, cliente y nube, de la aplicación.
Por último, mientras que en los primeros días de la Web era común que las páginas
tuvieran una autoría manual basada en HTML y CSS (tal vez usando herramientas de autoría
WYSIWYG), hoy en día la vasta mayoría del código HTML se genera utilizando entornos
de desarrollo como Rails. De forma similar, desarrollos como CoffeeScript sugieren que,
aunque JavaScript permanecerá como la lengua franca de la programación en el navegador,
puede que se convierta cada vez más en un lenguaje intermedio en lugar de ser en el que
232 REFERENCIAS
• Una excelente presentación por el gurú de JavaScript de Google, Miško Hevery: How
JavaScript works: introduction to JavaScript and Browser DOM 42 .
• Yehuda Katz43 es uno de los contribuidores principales tanto de Rails como de jQuery,
entre otros proyectos de perfil alto. Las entradas de su blog orientadas a programación
debaten consejos y técnicas, desde lo práctico a lo esotérico, tanto para Ruby como
para JavaScript. En concreto, tiene una interesante entrada sobre diferencias sutiles
entre los bloques de Ruby y las funciones anónimas de JavaScript44 y otra sobre por
qué this funciona como funciona en las funciones JavaScript45 .
• jQuery es una librería extremadamente potente cuyo potencial apenas hemos explota-
do. jQuery: Novice to Ninja (Castledine and Sharkie 2012) es una referencia excelente
con muchos ejemplos que van mucho más allá de nuestra introducción.
• JavaScript: The Good Parts (Crockford 2008), escrito por el creador de la herramienta
JSLint46 , es una exposición sobre JavaScript muy sesgada pero intelectualmente ri-
gurosa, que se centra firmemente en el uso disciplinado de sus buenas características
mientras que expone de forma sincera los errores comunes derivados de sus defectos de
diseño. Este libro es de lectura “obligada” si quiere escribir una aplicación JavaScript
completa comparable a Google Docs.
• El sitio web ProgrammableWeb47 lista cientos de API de servicios, tanto REST como
no REST que proporcionan datos XML y JSON, que puede encontrar útiles para sus
SPA y mashups. Algunas son de código abierto por completo y no requieren autenti-
cación; otras requieren una clave de programador que puede o no ser gratis.
E. Castledine and C. Sharkie. jQuery: Novice to Ninja, 2nd Edition - New Kicks and Tricks.
SitePoint Books, 2012.
D. Crockford. JavaScript: The Good Parts. O’Reilly Media, 2008.
P. Seibel. Coders at Work: Reflections on the Craft of Programming. Apress, 2009. ISBN
1430219483.
NOTAS 233
Notas
1 http://jasmine.github.io/
2 http://angularjs.org
3 http://www.ie6countdown.com
4 http://jquery.org
5 http://www.adaptivepath.com/ideas/ajax-new-approach-web-applications
6 http://developers.google.com/closure
7 http://yui.github.io/yuicompressor
8 http://jsonlint.com
9 http://guides.rubyonrails.org/v3.2.19/asset_pipeline.html
10 http://getfirebug.org
11 http://www.w3.org/DOM
12 https://github.com/rails/jquery-rails
13 http://prototypejs.org
14 http://api.jquery.com
15 http://api.jquery.com
16 http://en.wikipedia.org/wiki/Web_Workers
17 http://johnbintz.github.com/jasmine-headless-webkit/
18 http://johnbintz.github.com/jasmine-headless-webkit/
19 https://github.com/searls/jasmine-rails
20 http://pivotal.github.com/jasmine
21 http://github.com/velesin/jasmine-jquery
22 http://pivotal.github.com/jasmine
23 http://github.com/velesin/jasmine-jquery
24 http://pivotal.github.com/jasmine
25 http://github.com/velesin/jasmine-jquery
26 http://github.com/isaacs/sax-js
27 https://github.com/pivotal/jasmine-ajax
28 http://jslint.com
29 http://pivotallabs.com/javascriptspecs-bind-reality/
30 http://jslint.com
31 http://jslint.com
32 https://npmjs.org/doc/coding-style.html
33 http://phonegap.com
34 http://jquerymobile.org
35 http://blog.j15r.com/2011/12/for-those-unfamiliar-with-it-box2d-is.html
36 http://coffeescript.org
37 https://donatstudios.com/CoffeeScript-Madness
38 http://ruoyusun.com/2013/03/17/my-take-on-coffeescript.html
39 http://developer.yahoo.com/blogs/ydn/posts/2012/04/
40 http://dom4j.sourceforge.net
41 http://nokogiri.org
42 http://misko.hevery.com/2010/07/14/how-javascript-works/
43 http://yehudakatz.com
44 http://yehudakatz.com/2012/01/10/javascript-needs-blocks/
45 http://yehudakatz.com/2011/08/11/understanding-javascript-function-invocation-and-
this
46 http://jslint.com
47 http://programmableweb.com
aprovechar las clausuras para obtener atributos privados. Cree un sencillo constructor para
los objetos User que acepte un nombre de usuario y una contraseña, y proporcione un método
checkPassword que indique si la contraseña proporcionada es correcta, pero que deniegue
la inspección de la contraseña en sí. Esta expresión de “sólo métodos de acceso” se usa
ampliamente en jQuery. (Pista: El constructor debe devolver un objeto en el que una de sus
propiedades es una función que aprovecha las clausuras de JavaScript para “recordar” la
contraseña proporcionada inicialmente al constructor. El objeto devuelto no debería tener
ninguna propiedad que contenga la contraseña).
Ejercicio 6.2. En el ejemplo usado en la sección 6.5, suponga que no puede modificar el
código del servidor para añadir la clase CSS adult a las filas de la tabla movies. ¿Cómo
identificaría las filas que están ocultas utilizando sólo código JavaScript del lado cliente?
Ejercicio 6.3. Escriba el código JavaScript necesario para crear una cascada de menús para
el día, mes y año que sólo permita introducir fechas válidas. Por ejemplo, si se selecciona
febrero como mes, el menú para seleccionar los días debe contener los días 1–28, excepto
cuando el menú del año indique un año bisiesto, en cuyo caso el menú de los días debería
recoger los días 1–29, etcétera.
Adicionalmente, envuelva su código JavaScript en un helper de Rails que de lugar a
menús de fechas con los mismos nombres de menú y etiquetas de opciones que los helpers
integrados en Rails, haciendo que sus menús de JavaScript sean un reemplazo de aquellos.
Nota: Es importante que los menús funcionen también en navegadores que no soporten
JavaScript; en dicho caso, los menús deben mostrar de forma estática los días 1–31 para
cada mes.
Ejercicio 6.4. Escriba el código AJAX necesario para crear menús en cascada basados en
una asociación has_many. Esto es, dados los modelos de Rails A y B, donde A has_many
(tiene muchos) B, el primer menú de la pareja tiene que listar las opciones de A, y cuando se
selecciona una, devolver las opciones de B correspondientes y rellenar el menú B.
Ejercicio 6.5. Extienda la función de validación en ActiveModel (que vimos en el capí-
tulo 5) para generar automáticamente código JavaScript que valide las entradas del formu-
lario antes de que sea enviado. Por ejemplo, puesto que el modelo Movie de RottenPotatoes
requiere que el título de cada película sea distinto de la cadena vacía, el código JavaScript
debería evitar que el formulario “Add New Movie” se enviara si no se cumplen los criterios
de validación, mostrar un mensaje de ayuda al usuario, y resaltar el(los) campo(s) del for-
mulario que ocasionaron los problemas de validación. Gestione, al menos, las validaciones
integradas, como que los títulos sean distintos de cadena vacía, que las longitudes máxima
y mínima de la cadena de caracteres sean correctas, que los valores numéricos estén dentro
de los límites de los rangos, y para puntos adicionales, realice las validaciones basándose
en expresiones regulares.
Ejercicio 6.6. Siguiendo la estrategia del ejemplo de jQuery en la sección 6.5, utilice
JavaScript para implementar un conjunto de casillas de verificación (checkboxes) para la
página que muestra la lista de películas, una por cada calificación (G, PG, etcétera), que
permitan que las películas correspondientes permanezcan en la lista cuando están marcadas.
Cuando se carga la página por primera vez, deben estar marcadas todas; desmarcar alguna
de ellas debe esconder las películas con la clasificación a la que haga referencia la casilla
desactivada.
6.12. EJERCICIOS PROPUESTOS 235
Ejercicio 6.7. Extienda la funcionalidad del ejemplo de la sección 6.6 de forma que si el
usuario expande u oculta repetidamente la misma fila de la tabla de películas, sólo se haga
una única petición al servidor para la película en cuestión la primera vez. En otras palabras,
implemente una memoria caché con JavaScript en el lado cliente para la información de la
película devuelta en cada llamada AJAX.
Ejercicio 6.8. Si visita twitter.com y la página tarda más de unos pocos segundos en car-
garse, aparece una ventana emergente pidiendo disculpas por el retraso y sugiriéndole que
trate de recargar la página. Explique cómo implementaría este comportamiento utilizando
JavaScript. Pista: Recuerde que el código JavaScript puede empezar a ejecutarse tan pronto
como se carga, mientras que la función document.ready no se ejecutará hasta que el do-
cumento se haya cargado e interpretado completamente.
Ejercicio 6.9. Utilice las técnicas JSON y jQuery descritas en este capítulo para aplicar
BDD en el desarrollo de la siguiente aplicación de página única (SPA) homóloga a Rot-
tenPotatoes, a la que llamaremos LocalPotatoes. Cuando un usuario introduzca su código
postal, LocalPotatoes utilizará el feed RSS (Really Simple Syndication) proporcionado gra-
tuitamente por el sitio web de aficionados a las películas Fandango1 para obtener los nom-
bres y localizaciones de los cines cercanos, así como los títulos de las películas que se
proyectan en ellos. Estos datos se devuelven en formato XML, por lo que tendrá que proce-
sar el XML en su código JavaScript para extraer los nombres de los cines y de las películas.
Se mostrará una lista con los cines en la página cliente; cuando el usuario haga clic en el
nombre de un cine, se utilizará la API JavaScript de Google Maps2 para centrar el mapa
en la localización del cine, y se listarán las películas proyectadas en dicho cine en la caja
Movies. Al hacer clic en el nombre de una película se buscará la información sobre la misma
usando la API gratuita Open Movie Database3 , que puede devolver resultados básicos tanto
en JSON como en XML, y se mostrará el cartel promocional de la película y la calificación
global de los usuarios de Internet Movie Database4 .
Ejercicio 6.10. Considere un sitio web que vende un pequeño número fijo de artículos, y un
usuario que sólo indica cuántas unidades de cada producto quiere comprar eligiendo una
cantidad de un menú desplegable que se muestra al lado del nombre de cada artículo. Es-
criba código JavaScript no intrusivo que vigile estos menús desplegables y, cada vez que
cualquiera de ellos cambie, actualice el campo “Total” con el precio total del pedido, mul-
tiplicando cada cantidad por el precio correspondiente a cada artículo y sumando todos los
resultados. El campo “Total” debe ser de sólo lectura (es decir, no puede ser modificado por
los usuarios).
Ejercicio 6.11. La figura 6.21 realiza sólo pruebas de la spec que conduce al camino feliz
(describe(’when successful’) utilizando stubs de AJAX. Añada specs para los caminos de
error when server error y when timeout.
236 NOTAS
Figura 6.31. Un sencillo carrito de compra con menús desplegables para seleccionar cuántas unidades de cada artículo se
quieren comprar.
Parte II
Conceptos
Los principales conceptos de este capítulo son: obtención de requisitos, estimación de
costes, planificación de proyectos y monitorización de progresos.
El equivalente de estos conceptos en el ciclo de vida ágil, y que se ajustan al desarrollo
guiado por comportamiento (BDD), son:
En los ciclos de vida clásicos, los mismos conceptos aparecen de forma ligeramente
distinta:
• Estimación de costes según la experiencia del jefe de proyecto o fórmulas como CO-
COMO, planificación y monitorización del progreso utilizando gráficos PERT , y
gestión de cambios usando Revision control sistemas de control de versiones para
documentación y planificación, así como para el código fuente.
Ambos tipos de ciclo de vida ilustran las diferencias entre requisitos funcionales y
requisitos no funcionales, así como entre requisitos explícitos e implícitos.
240 CAPÍTULO 7. REQUISITOS: BDD E HISTORIAS DE USUARIO
Legacy (Ch. 9)
Measure Velocity(Ch.
Measure Velocity (Ch.
10)7)
Figura 7.1. Iteración dentro el ciclo de vida de desarrollo ágil y sus relaciones con los capítulos de este libro. Este capítulo se
centra en hablar con los clientes como parte del diseño guiado por comportamiento.
Los proyectos de desarrollo software fallan debido a que no hacen lo que los usuarios
necesitan, o porque se retrasan, o porque se salen de presupuesto, o por ser difíciles de man-
tener y evolucionar. O por todo lo anterior.
El ciclo de vida de desarrollo de software ágil se creó para atajar estos problemas en
muchos tipos de software. La figura 7.1 muestra una iteración del ciclo de vida ágil del
capítulo 1, destacando el conjunto que cubre este último. Como ya vimos en el capitulo 1, el
Como colaboradores ciclo de vida de desarrollo ágil conlleva:
ágiles se incluye a
usuarios, clientes,
• Trabajar continuamente y muy cerca de los participantes del proyecto para desarrollar
desarrolladores, soporte,
operadores, jefes de requisitos y pruebas.
proyecto. . .
• Mantener un prototipo funcional mientras se implementan nuevas características, nor-
malmente cada dos semanas —lo que se conoce como iteración— y reunirse con los
7.1. DISEÑO GUIADO POR COMPORTAMIENTO E HISTORIAS DE USUARIO 241
participantes del proyecto para decidir qué añadir a continuación y validar que el sis-
tema actual cumple con sus necesidades. Tener un prototipo y priorizar las funcionali-
dades reduce las probabilidades de que el proyecto se retrase o se salga de presupuesto,
y puede incrementar las posibilidades de que los socios estén satisfechos con el sistema
actual una vez finalice el mismo.
A diferencia de los ciclos de vida clásicos (ver el capítulo 1), el desarrollo ágil no cambia
de fase ni a las personas según avanza en el tiempo desde el modo desarrollo al modo man-
tenimiento. Con la metodología ágil, usted se encuentra en modo mantenimiento tan pronto
como se ha implementado el primer conjunto de características. Esta estrategia hace más
sencillo el mantenimiento y evolución del proyecto.
Comenzamos el ciclo de vida ágil con el diseño guiado por comportamiento (BDD).
BDD realiza preguntas acerca del comportamiento de una aplicación antes y durante el de-
sarrollo de forma que reduce el riesgo de malentendidos y problemas de comunicación entre
los integrantes del proyecto. Los requisitos se ponen por escrito como en los ciclos de vida
tradicionales, pero a diferencia de éstos, los requisitos van siendo refinados continuamente
para asegurar que el software resultante cumple con los deseos del cliente. Es decir, usando
los términos del capítulo 1, el fin último de los requisitos BDD es la validación (desarrollar
la aplicación correcta), y no simplemente verificación (desarrollar correctamente la apli-
cación).
El equivalente en BDD de los requisitos son las historias de usuario, que describen
cómo se espera que sea utilizada la aplicación. Son versiones ligeras de los requisitos, siendo
más adecuadas para la metodología ágil. Las historias de usuario facilitan a los participantes
el planificar y priorizar el desarrollo. Así, al igual que sucede en las metodologías clásicas,
se comienza por los requisitos, pero en BDD, las historias de usuario ocupan el lugar de los
documentos de diseño de las metodologías tradicionales.
Concentrándose en el comportamiento de la aplicación en vez de en la implementación
de la misma, es más fácil reducir los malentendidos entre los integrantes del proyecto. Como
veremos en el siguiente capítulo, BDD está estrechamente ligado al desarrollo guiado por
pruebas (TDD), que realiza la implementación de las pruebas. En la práctica ambos funcio-
nan codo con codo, pero por razones pedagógicas los presentaremos de forma secuencial.
Las historias de usuario provienen de la comunidad HCI (Human Computer Interface,
Interacción persona-ordenador). Se popularizaron a través del uso de fichas de 3 x 5 pul-
gadas, o tarjetas tamaño A7 (74mm x 105mm) en aquellos países que utilizan el formato
DIN de tamaños de papel (próximamente veremos otros ejemplos de la comunidad HCI de
“tecnología de lápiz y papel”). Estas fichas contienen de una a tres frases escritas en lenguaje
no técnico, consensuadas entre clientes y desarrolladores. El motivo es que estas fichas de
papel se ven como inofensivas y fáciles de reorganizar, facilitando así la lluvia de ideas y
la priorización de unas historias de usuario sobre otras. Como directrices generales para las
historias de usuario, deben ser verificables, ser lo suficientemente asequibles para ser imple-
mentadas en una iteración, y tener valor de negocio. La sección 7.3 aporta una orientación
más detallada para crear buenas historias de usuario.
Nótese que desarrolladores individuales trabajando para sí mismos sin interacción con
ningún cliente no necesitan ninguna de estas fichas, pero estos “lobos solitarios” no encajan
dentro de la filosofía ágil de desarrollo cercano y continuo junto al cliente.
Utilizaremos la aplicación RottenPotatoes del capítulo 2 y 4 como ejemplo en este capí-
tulo y el siguiente. Los integrantes o actores de esta sencilla aplicación son:
242 CAPÍTULO 7. REQUISITOS: BDD E HISTORIAS DE USUARIO
En la sección 7.8 presentaremos una nueva funcionalidad, pero para facilitar la compren-
sión de todas las partes, empezaremos con una historia de usuario de una característica de
RottenPotatoes ya implementada, de forma que se puedan entender las relaciones existentes
entre todos los componentes en un escenario más simple. La historia de usuario seleccionada
se refiere a la introducción de películas en la base de datos de RottenPotatoes:
Pastebin es un servicio
para copiar y pegar código http://pastebin.com/BpmHu0Nq
del libro. (Si está leyendo el 1 Feature : Add a movie to Rotte nPotat oes
libro impreso es necesario 2 As a movie fan
teclear el URL en un 3 So that I can share a movie with other movie fans
navegador; en el libro 4 I want to add a movie to Rot tenPot atoes database
electrónico es un enlace).
Este formato de historia de usuario fue creado por la compañia Connextra y tomó el nom-
bre de “formato Connextra” (desgraciadamente, esta startup ya no existe en la actualidad). El
formato es:
http://pastebin.com/We7vY0eg
1 Feature name
2 As a [ kind of stakeholder ] ,
3 So that [ I can achieve some goal ] ,
4 I want to [ do some task ]
Este formato identifica al actor, ya que distintos actores pueden describir el compor-
tamiento deseado de forma diferente. Por ejemplo, los usuarios pueden querer enlazar fuentes
de información para facilitar la obtención de la misma, mientras que los operadores pueden
querer enlaces a los trailers de forma que se puedan transmitir con publicidad. El formato
Connextra obliga a que las tres frases estén presentes, pero no necesariamente en este orden.
Autoevaluación 7.1.1. Verdadero o Falso: las historias de usuario en fichas de 3x5 pulgadas
en BDD son equivalentes a la especificación de diseño de las metodologías de desarrollo
tradicionales.
⇧ Verdadero.
7.2. PUNTOS, VELOCIDAD Y PIVOTAL TRACKER 243
Introducción a
Tracker En la web se mente, y cuando el equipo está listo para comenzar a trabajar en ellas, arrastrarlas hacia el
encuentra disponible un panel actual o el backlog.
excelente video de 3
Tracker permite además la definición de marcadores de puntos de liberación de software
minutos de introducción5 al
uso de Tracker, producido en la lista de historias de usuario, y realiza la estimación de cuándo se liberará realmente el
por Pivotal Labs. software, basándose en la velocidad del equipo calculada a partir de los puntos completados.
Este enfoque es completamente diferente al de gestión por planificación, donde un gestor
o jefe de proyecto toma un día como fecha de lanzamiento del software y el equipo debe
trabajar duro para cumplir el plazo.
Otra de las características de Pivotal Tracker que no representa realmente una historia de
usuario es un spike. Un spike6 es una pequeña investigación de alguna técnica o problema
que el equipo prefiere explorar antes de sentarse a escribir código en serio. Un ejemplo
sería un spike para analizar algoritmos disponibles para resolver una tarea. Una vez se da
por terminado el spike, el código del mismo es desechado. El spike le permite decidir qué
estrategia seguir, y después implementarlo correctamente.
Tracker ha incorporado recientemente un nuevo concepto que permite combinar un con-
junto de historias de usuario en un grupo llamado historia épica7 . Las historias épicas tienen
su propio panel y barra de progreso en Tracker, y se pueden reordenar de forma independiente
a las historias de usuario del backlog. La idea es proporcionar a los ingenieros de software
la imagen de dónde se encuentra la aplicación en el proceso de desarrollo en relación a las
funcionalidades principales.
7.3. SMART: HISTORIAS DE USUARIO EFECTIVAS 245
• Todo repositorio GitHub (ver sección A.7) ofrece una Wiki, que permite a los inte-
grantes del equipo editar conjuntamente un documento y añadir archivos.
• Google Drive8 permite la creación conjunta y visualización de esquemas, presenta-
ciones, hojas de cálculo y documentos de texto.
• Campfire9 es un servicio web que ofrece salas de chat online protegidas por contraseña.
Resumen: A fin de ayudar al equipo a gestionar cada iteración y a predecir cuánto tiempo
llevará implementar nuevas funcionalidades, el equipo asigna puntos para evaluar la di-
ficultad de cada historia de usuario y monitoriza la velocidad del equipo, o media de
puntos por iteración. Pivotal Tracker proporciona un servicio que permite priorizar y rea-
lizar seguimiento sobre las historias de usuario y su estado, realizar cálculos de velocidad,
y hacer pronósticos sobre el tiempo de desarrollo en función del historial del equipo.
Autoevaluación 7.2.1. Verdadero o Falso: al comparar dos equipos, aquel que tiene una
velocidad más alta es el más productivo.
⇧ Falso: Al ser cada equipo el que asigna los puntos a las historias de usuario, no puede
usarse la velocidad para comparar distintos equipos. Sin embargo, se puede ver un equipo
conforme avance el tiempo para comprobar si hay iteraciones que sean significativamente
más o menos productivas.
Autoevaluación 7.2.2. Verdadero o Falso: Cuando no se sabe cómo afrontar una determi-
nada historia de usuario, simplemente se le debe otorgar 3 puntos.
⇧ Falso: Una historia de usuario no debería ser nunca tan compleja como para no tener una
estrategia para su implementación. Si lo es, se debe evaluar con los partícipes del proyecto,
para dividir dicha historia en un conjunto de tareas más sencillas que sepa acometer.
• Específica.
A continuación pueden verse ejemplos de una funcionalidad definida de forma vaga
junto con su versión definida de forma específica:
http://pastebin.com/vnUt6KLF
1 Feature : User can search for a movie ( vague )
2 Feature : User can search for a movie by title ( specific )
• Medible.
Uniendo Específico con Medible significa que cada historia debe poder ser comproba-
ble, lo que implica que se esperan unos determinados resultados para ciertas entradas.
Un ejemplo de funcionalidad no Medible junto con otra que sí lo es sería:
http://pastebin.com/rbLcwD2f
1 Feature : Rott enPota toes should have good response time ( unmeasurable )
2 Feature : When adding a movie , 99% of Add Movie pages
3 should appear within 3 seconds ( measurable )
1. ¿Para qué añadir esta nueva funcionalidad? Como encargado de la taquilla, creo
que vendrá más gente con sus amigos y disfrutarán más de las funciones.
2. ¿Qué importancia tiene que disfruten más de las sesiones? Creo que venderemos
más entradas.
3. ¿Para qué queremos vender más entradas? Para que el teatro gane más dinero.
4. ¿Para qué quiere el teatro ganar más dinero? Queremos ganar más dinero para
que no tengamos que cerrar el negocio.
5. ¿Qué importancia tiene que el teatro siga funcionando el año que viene? Si ce-
rramos, no tendré trabajo.
(¡Es más que probable que al menos uno de los integrantes del proyecto vea como
evidente el valor de negocio de la propuesta!)
7.3. SMART: HISTORIAS DE USUARIO EFECTIVAS 247
• La técnica de los Cinco por qués ayuda a examinar a fondo una historia de usuario
para descubrir su relevancia real.
Una vez presentadas las historias de usuario como el resultado del trabajo de obtención de
requisitos de los clientes, podemos dar a conocer una métrica y una herramienta para medir
la productividad.
La figura 7.4 muestra una secuencia de bocetos Lo-Fi con indicaciones de qué clics rea-
liza el usuario para provocar las transiciones entre los diferentes bocetos. Una vez dibujados
los bocetos y los storyboards, está en disposición de codificar HTML. El capítulo 2 mostró
cómo el lenguaje de marcado Haml se convierte en HTML, y cómo los atributos class e id
de los elementos HTML se pueden usar para adjuntar información de estilos a través de CSS
7.4. BOCETOS DE UI LO-FI Y STORYBOARDS 249
Figura 7.3. Ventana que corresponde a la acción de añadir una película a RottenPotatoes.
250 CAPÍTULO 7. REQUISITOS: BDD E HISTORIAS DE USUARIO
Figura 7.4. Storyboard en imágenes para introducir una nueva película en RottenPotatoes.
7.5. ESTIMACIÓN ÁGIL DE COSTES 251
(Cascading Style Sheets, plantillas de estilos en cascada). La clave del enfoque Lo-Fi es tener
una buena estructura global de los bocetos, y realizar un CSS simple (si fuese necesario) para
que se visualice de forma parecida al boceto.
Comience analizando los bocetos Lo-Fi de UI y dividiéndolos en “bloques” del diseño.
Utilice etiquetas divs CSS para las secciones obvias. No hay ninguna necesidad de hacerlo
bonito hasta tenerlo todo funcionando. El añadir estilos CSS, imágenes y demás es la parte
divertida, pero debe dejarlo para cuando todo funciona.
Como el ejemplo de la sección 7.6 implica funcionalidad ya existente, no hay necesidad
de modificar el código Haml o CSS. La sección siguiente añade una nueva funcionalidad a
RottenPotatoes y por tanto sí requiere cambios en el código Haml.
Resumen Poniendo el énfasis en la colaboración con el cliente por encima de los con-
tratos, como predica el Manifiesto por el Desarrollo Ágil de Software, la noción de un
equipo que utiliza la metodología ágil sobre la “estimación de costes” está más cerca de
asesorar al cliente sobre cuál es el tamaño del equipo que puede proporcionar la máxima
eficiencia, siguiendo así la Ley de Brooks, que indica que hay un punto en el que disminu-
yen los resultados según el tamaño del equipo (ver sección 7.11). El propósito del equipo
ágil en el proceso de estudio de alcance es identificar dicho punto y reforzar el equipo hasta
ese tamaño durante el ciclo de desarrollo. Las compañías ágiles ofertan precios por tiempo
y materiales basándose en reuniones de corta duración con los clientes. Como veremos en
la sección 7.10, este enfoque contrasta enormemente con el de aquellas compañías que
siguen ciclos de vida clásicos, que se comprometen a entregar en una fecha acordada un
conjunto dado de funcionalidades por un coste acordado inicialmente.
Autoevaluación 7.5.1. Verdadero o Falso: Como profesionales del desarrollo ágil, Pivotal
Labs no proporciona contratos.
⇧ Falso. Pivotal sí ofrece a sus clientes un contrato que ellos deben firmar, pero es principal-
mente una promesa de pago a Pivotal por su esfuerzo más que por satisfacer al cliente por un
período limitado de tiempo.
Dejando atrás el papel útil de las historias de usuario para medir progresos, vamos a
presentar una herramienta que da a las historias de usuario otro importante rol.
7.6. INTRODUCCIÓN A CUCUMBER Y CAPYBARA 253
http://pastebin.com/CSCVp9M3
1 Feature : User can manually add movie
2
3 Scenario : Add a movie
4 Given I am on the Rotten Potat oes home page
5 When I follow " Add new movie "
6 Then I should be on the Create New Movie page
7 When I fill in " Title " with " Men In Black "
8 And I select " PG -13 " from " Rating "
9 And I press " Save Changes "
10 Then I should be on the Rott enPota toes home page
11 And I should see " Men In Black "
Figura 7.5. Ejemplo de escenario en Cucumber asociado a la acción de añadir una característica una película en
RottenPotatoes.
Por ejemplo, a continuación se muestra una cadena de caracteres tomada de una definición
de paso del escenario de RottenPotatoes:
http://pastebin.com/hwkkP8Mr
1 Given /^(?:| I ) am on (.+) $ /
Esta expresión regular puede coincidir con el texto “I am on the RottenPotatoes home
page” en la línea 4 de la figura 7.5. La expresión regular captura también la cadena de ca-
racteres que aparece detrás de “am on ” hasta el final de la línea (“the RottenPotatoes home
page”). El cuerpo de la definición del paso contiene código Ruby que comprueba el paso,
utilizando strings obtenidos como el del ejemplo anterior.
Por tanto, una forma de pensar en la asociación que existe entre las definiciones del paso
y los pasos es tal que las definiciones son a las definiciones de un método como os pasos son
a las llamadas al método.
Necesitamos por tanto una herramienta que pueda actuar como un usuario y que trate de
utilizar la funcionalidad bajo diferentes escenarios. En el mundo Rails, esta herramienta se
denomina Capybara, que se integra perfectamente con Cucumber. Capybara “pretende ser
un usuario” realizando acciones en un navegador web simulado, por ejemplo, haciendo clic
sobre un enlace o un botón. Capybara puede interactuar con la aplicación recibiendo páginas,
analizando HTML, y enviando formularios tal y como lo haría un usuario normal.
Una vez que haya modificado Gemfile, ejecute bundle install --without
production. Si todo va bien, aparecerá el mensaje “Your bundle is complete”.
Ahora debemos configurar los directorios y archivos con código ya generado que
necesitan Cucumber y Capybara. Como el propio Rails, Cucumber contiene un generador
que realiza este trabajo por usted. A continuación, en el directorio raíz de la aplicación,
ejecute estos dos comandos (a la pregunta sobre si desea sobreescribir ciertos archivos como
cucumber.rake, responda afirmativamente):
rails generate cucumber:install capybara
rails generate cucumber_rails_training_wheels:install
256 CAPÍTULO 7. REQUISITOS: BDD E HISTORIAS DE USUARIO
http://pastebin.com/RbPqfg1g
1 # add to paths . rb , just after " when /^ the home \ s ? page$ /
2 # '/ '"
3
4 when /^ the Rot tenPot atoes home page /
5 '/ movies '
6 when /^ the Create New Movie page /
7 '/ movies / new '
Figura 7.6. El código que necesita añadir a features/support/paths.rb para que el escenario AddMovie funcione
correctamente. Vea que la primera línea en ambos ficheros paths.rb y websteps.rb es una instrucción para “BORRAR
ESTE FICHERO”, algo que debe hacer una vez se ha familiarizado con los conceptos básicos de cucumber y capybara. Los
ficheros paths.rb y websteps.rb son parte de la gema de cucumber y rails training wheels, muy útiles cuando se está
empenzando a usar cucumber, pero que deberá eliminar en última instancia en modo avanzado. Por favor siga utilizándolos
de momento.
Al ejecutar estos dos generadores, se obtienen como puntos de partida varias defini-
ciones de pasos usadas comúnmente, como interacciones con un navegador web. Para
esta aplicación, se encuentran en myrottenpotatoes/features/step_definitions/
web_steps.rb. Aparte de estos pasos predefinidos, será necesario crear nuevas definiciones
de pasos que correspondan con la funcionalidad única de su aplicación. Es aconsejable cono-
cer las definiciones de pasos predefinidas más comunes para utilizarlas al escribir las fun-
cionalidades, de forma que tenga que escribir menos definiciones de pasos.
Antes de arrancar Cucumber, debe realizar un paso previo: inicializar la base de datos
de pruebas ejecutando rake db:test:prepare. Es necesario hacer esto antes de ejecutar
las pruebas o si el esquema de la base de datos cambia. La sección 4.2 proporciona una
descripción más detallada.
En este punto, está preparado para comenzar a utilizar Cucumber. Las funcionalidades
se añadirán en el directorio features como ficheros con extensión .feature. Copie la
historia de usuario de la figura 7.5 y péguela en un archivo con nombre AddMovie.feature
en el directorio features. Para ver cómo interactúan los escenarios con las definiciones de
pasos y cómo cambian de color como las hojas de arce en Nueva Inglaterra con el cambio de
estaciones, escriba
cucumber features/AddMovie.feature
Vea el screencast para continuar.
Screencast 7.7.1. Cucumber Parte I.
http://vimeo.com/34754747
El screencast muestra cómo Cucumber comprueba si las pruebas funcionan, coloreando
las definiciones de pasos. Los pasos que fallan aparecen en rojo, en amarillo los pasos no
implementados, y los pasos que se superan correctamente se marcan en verde. (Los pasos
que van después de un paso fallado en rojo aparecen en azul, indicando que no han sido
probados nuevamente). El primer paso en la línea 4 es rojo, por lo que Cucumber salta el
resto. Falla debido a que no hay ninguna ruta en paths.rb que cumpla “the RottenPotatoes
home page”, tal y como explica el mensaje de error. Este mensaje incluso sugiere cómo
arreglar el problema añadiendo dicha ruta a paths.rb (ver figura 7.6). Con esta ruta el
primer paso se vuelve verde, pero entonces el tercer paso en la línea 6 queda en rojo. Como
explica el mensaje de error en este caso, el paso falla debido a que ninguna ruta encaja con
“Create New Movie page”, problema que solucionamos nuevamente añadiendo la ruta a
paths.rb. Ahora todos los pasos quedan en verde como un pepino, y el escenario AddMovie
supera la prueba.
7.8. MEJORANDO ROTTENPOTATOES 257
Resumen Para añadir funcionalidades como parte de BDD, necesitamos definir en primer
lugar los criterios de aceptación. Cucumber permite realizar ambos capturando los requi-
sitos como historias de usuario, separando la integración y las pruebas de aceptación de
esa historia. Además, dispondremos de pruebas ejecutables de forma automática de forma
que tenemos pruebas de regresión que ayudan al mantenimiento del código según con-
tinúa evolucionando (ahondaremos en este enfoque nuevamente en el capítulo 9 con una
aplicación de mucha mayor envergadura que RottenPotatoes).
Autoevaluación 7.7.1. Cucumber colorea en verde los pasos que superan las pruebas. ¿Cuál
es la diferencia entre los pasos coloreados en amarillo y rojo?
⇧ Los pasos amarillos no han sido implementados aún, mientras que los pasos en rojo han
sido ya implementados pero han fallado la prueba.
http://pastebin.com/qTYS5tLs
1 Feature : User can add movie by searching for it in The Movie Database ( TMDb )
2
3 As a movie fan
4 So that I can add new movies without manual tedium
5 I want to add movies by looking up their details in TMDb
6
7 Scenario : Try to add nonexistent movie ( sad path )
8
9 Given I am on the Rotten Potat oes home page
10 Then I should see " Search TMDb for a movie "
11 When I fill in " Search Terms " with " Movie That Does Not Exist "
12 And I press " Search TMDb "
13 Then I should be on the Rott enPota toes home page
14 And I should see " ' Movie That Does Not Exist ' was not found in TMDb . "
Figura 7.8. Camino de error del escenario asociado a la funcionalidad de búsqueda en la base de datos de películas TMDb.
http://pastebin.com/QtUf0qsB
1 -# add to end of app / views / movies / index . html . haml :
2
3 % h1 Search TMDb for a movie
4
5 = form_tag : action = > ' search_tmdb ' do
6
7 % label {: for = > ' search_terms '} Search Terms
8 = text_f ield_t ag ' search_terms '
9 = submit_tag ' Search TMDb '
Figura 7.9. El código Haml para la página de búsqueda en la base de datos TMDb.
http://pastebin.com/tdxgK77Z
1 # add to routes . rb , just before or just after ' resources : movies ' :
2
3 # Route that posts ' Search TMDb ' form
4 post '/ movies / search_tmdb '
http://pastebin.com/cGmgFyEZ
1 # add to m o v i e s _ c o n t r o l l e r . rb , anywhere inside
2 # ' class M o v i e s C o n t r o l l e r < A p p l i c a t i o n C o n t r o l l e r ':
3
4 def search_tmdb
5 # hardwire to simulate failure
6 flash [: warning ] = " '#{ params [: search_terms ]} ' was not found in TMDb . "
7 redirect_to movies_path
8 end
Figura 7.10. (Arriba) La ruta que dispara este mecanismo cuando se envía un formulario por POST. (Abajo) Este “falso”
controlador se comporta como si nunca se encontrara ningún resultado. Obtiene las palabras tecleadas por el usuario a
partir de params (tal y como vimos en el capítulo 4), almacena el mensaje en flash[], y redirige al usuario de nuevo a la lista
de películas. Recuerde del capítulo 4 que añadimos código a app/views/layouts/application.html.haml para mostrar
el contenido de flash en todas las vistas.
¿Haciendo lo mismo
una y otra vez? rake http://pastebin.com/itVarUq5
cucumber ejecuta todas
su funcionalidades o, de 1 < label for = ' search_terms ' > Search Terms </ label >
2 < input id = " search_terms " name = " search_terms " type = " text " / >
forma más precisa, aquellas
seleccionadas por el perfil
por defecto en el fichero de
La clave está en que el atributo for de la etiqueta label coincide con el atributo id de la
configuración de Cucumber etiqueta input, que viene determinado por el primer parámetro del método text_field_tag
cucumber.yml.11 En el invocado en la línea 8 de la figura 7.9. Esta correspondencia es la que permite a Cucumber
siguiente capítulo daremos determinar a qué campo del formulario se hará referencia por el nombre “Search Terms” en
a conocer una herramienta
llamada autotest que la línea 11 de la figura 7.8: When I fill in “Search Terms”. . .
automatiza la re-ejecución Como recordará de la sección 4.1, debemos asegurarnos de que existe una ruta para esta
de las pruebas cuando se nueva acción de controlador. La parte superior de la figura 7.10 muestra la línea que debe
realizan cambios sobre los añadir a config/routes.rb para incluir una ruta para envío (POST) de un formulario para
archivos.
esta acción.
Sin embargo, incluso con la nueva ruta, este paso seguirá fallando con una excepción:
a pesar de que tenemos un botón con el nombre “Search TMDb”, el form_tag especifica
que MoviesController#search_tmdb es la acción del controlador que debería recibir el
formulario, y dicho método no existe aún en movies_controller.rb. La figura 7.1 afirma
que en este momento deberíamos utilizar técnicas del desarrollo guiado por pruebas (TDD)
para crear dicho método. Pero como TDD es el tema del próximo capítulo, haremos una pe-
queña trampa para tener el escenario en verde. Como éste es el camino de error del escenario
en el que no se encuentra ninguna película, crearemos de forma temporal un controlador que
siempre se comporte como si no se hubiera obtenido ningún resultado, de forma que podamos
dar por concluidas las pruebas del camino de error. Además, la parte inferior de la figura 7.10
muestra el código que debemos añadir a app/controllers/movies_controller.rb para
implementar la acción “falsa” y cableada search_tmdb.
Si es usted nuevo con BDD, este paso podría sorprenderle. ¿Para qué íbamos a crear
deliberadamente un controlador falso que en realidad no accede a la base de datos TMDb
y que simula que la búsqueda falla? La respuesta es que eso nos permite finalizar el resto
del escenario, asegurándonos de que las vistas HTML coinciden con los bocetos Lo-Fi y que
la secuencia de vistas coinciden con los storyboards. De hecho, una vez que incluímos los
cambios de la figura 7.10, el camino de error del escenario completo debe funcionar. El
7.8. MEJORANDO ROTTENPOTATOES 261
http://pastebin.com/7nQQ6zwg
1 Feature : User can add movie by searching for it in The Movie Database ( TMDb )
2
3 As a movie fan
4 So that I can add new movies without manual tedium
5 I want to add movies by looking up their details in TMDb
6
7 Background : Start from the Search form on the home page
8
9 Given I am on the Rotten Potat oes home page
10 Then I should see " Search TMDb for a movie "
11
12 Scenario : Try to add nonexistent movie ( sad path )
13
14 When I fill in " Search Terms " with " Movie That Does Not Exist "
15 And I press " Search TMDb "
16 Then I should be on the Rott enPota toes home page
17 And I should see " ' Movie That Does Not Exist ' was not found in TMDb . "
18
19 Scenario : Try to add existing movie ( happy path )
20
21 When I fill in " Search Terms " with " Inception "
22 And I press " Search TMDb "
23 Then I should be on the " Search Results " page
24 And I should not see " not found "
25 And I should see " Inception "
Figura 7.11. La palabra clave Background permite evitar la repetición en los pasos comunes de los caminos satisfactorio y
de error. Agrupa los pasos que deben realizarse antes de cada escenario para una funcionalidad.
¿Y qué ocurre con el camino satisfactorio, cuando buscamos una película que sí existe?
Observe que las dos primeras acciones de este flujo —ir a la página de inicio de Rotten
Potatoes y asegurarse de que hay un formulario de búsqueda, correspondiente a las líneas 9
y 10 de la figura 7.8— son las mismas que para el camino erróneo. Esto debería hacer sonar
una campanita de Pavlov en su cabeza preguntándose cómo puede evitar la repetición (DRY).
La figura 7.11 muestra la respuesta. El comando Background en Cucumber muestra los
pasos que se deben ejecutar con anterioridad a otros escenarios de una funcionalidad, lo
que permite aplicar el criterio DRY a los caminos feliz y de error. Modifique features/
262 CAPÍTULO 7. REQUISITOS: BDD E HISTORIAS DE USUARIO
Resumen
• Añadir una nueva funcionalidad a una aplicación SaaS normalmente implica especi-
ficar una UI para dicha funcionalidad, escribir nuevas definiciones de pasos, y quizás
incluso escribir nuevos métodos antes de que Cucumber pueda colorear satisfacto-
riamente los pasos en verde.
historias de usuario desarrolladas por los integrantes del proyecto. Utilizando la terminología
del capítulo 1, suelen corresponderse con las pruebas de aceptación. Los requisitos implí-
citos son la consecuencia lógica de los explícitos, y normalmente corresponden a lo que en
el capítulo 1 se conoce como pruebas de integración. Un ejemplo de requisito implícito en
RottenPotatoes podría ser que por defecto las películas deben mostrarse en orden cronológico
según su fecha de estreno.
La buena noticia es que se puede utilizar Cucumber para matar dos pájaros de un tiro—
crear las pruebas de aceptación y las de integración—si se escriben historias de usuario tanto
para los requisitos explícitos como para los implícitos (el siguiente capítulo muestra cómo
utilizar otra herramienta para las pruebas unitarias).
La otra perspectiva confrontada sones la de escenarios imperativos frente a declarativos.
El escenario ejemplo de la figura 7.5 de arriba es imperativo, ya que especifica una secuencia
lógica de acciones de usuario: rellenar un formulario, hacer clic en algún botón, etc. Los
escenarios imperativos tienden a llevar sentencias complicadas con When con muchos pasos
con And. Aunque estos escenarios son útiles para asegurar que los detalles de la UI cumplen
las expectativas del cliente, escribir escenarios de esta forma se vuelve tedioso rápidamente
y no siguen la filosofía DRY.
Para ver el porqué, suponga que queremos escribir una funcionalidad que especifique que
las películas deben aparecer en order alfabético en la página de lista de películas. Por ejem-
plo, “Zorro” debería aparecer después de “Apocalypse Now”, aunque “Zorro” hubiera sido
añadida en primer lugar. Ingenuamente, puede ser extremadamente aburrido el expresar este
escenario, ya que repite la mayoría de las líneas del escenario “añadir película” ya existente
—lo cual no favorece la no repetición—:
http://pastebin.com/qR9UTSsP
1 Feature : movies should appear in alphabetical order , not added order
2
3 Scenario : view movie list after adding 2 movies ( imperative and non - DRY )
4
5 Given I am on the Rotten Potat oes home page
6 When I follow " Add new movie "
7 Then I should be on the Create New Movie page
8 When I fill in " Title " with " Zorro "
9 And I select " PG " from " Rating "
10 And I press " Save Changes "
11 Then I should be on the Rott enPota toes home page
12 When I follow " Add new movie "
13 Then I should be on the Create New Movie page
14 When I fill in " Title " with " Apocalypse Now "
15 And I select " R " from " Rating "
16 And I press " Save Changes "
17 Then I should be on the Rott enPota toes home page
18 Then I should see " Apocalypse Now " before " Zorro " on the Rotten Potato es home
page sorted by title
Se supone que Cucumber está pensado para estar focalizado en el comportamiento más
que en la implementación —centrándose en qué se está haciendo— pero en este escenario
pobremente escrito, ¡únicamente la línea 18 hace referencia al comportamiento de interés!
Un enfoque alternativo es pensar en utilizar las definiciones de pasos para crear un
lenguaje de dominio de la aplicación (que no es lo mismo que un Domain Specific Lan-
guaje (DSL), o lenguaje específico del dominio formal). Un lenguaje de dominio es
informal pero utiliza términos y conceptos específicos de la aplicación desarrollada, más que
términos genéricos y conceptos relacionados con la implementación de la interfaz de usuario.
Los pasos descritos con un lenguaje de dominio suelen ser más declarativos que imperativos,
en tanto en cuanto describen más el estado de la aplicación que la secuencia de pasos a dar
264 CAPÍTULO 7. REQUISITOS: BDD E HISTORIAS DE USUARIO
http://pastebin.com/h7e2xtZu
1 Given / I have added " (.*) " with rating " (.*) " / do | title , rating |
2 steps % Q {
3 Given I am on the Create New Movie page
4 When I fill in " Title " with "#{ title } "
5 And I select " # { rating }" from " Rating "
6 And I press " Save Changes "
7 }
8 end
9
10 Then / I should see " (.*) " before " (.*) " on (.*) / do | string1 , string2 , path |
11 step " I am on #{ path } "
12 regexp = / # { string1 }.*#{ string2 }/ m # / m means match across newlines
13 page . body . should =~ regexp
14 end
Figura 7.12. Si se añade este código a movie_steps.rb se crean nuevas definiciones de pasos que corresponden con las
líneas 5 y 6 del escenario declarativo reutilizando los pasos existentes. steps (línea 2) reutiliza una secuencia de pasos y step
(línea 11) reutiliza un único paso. Recordará de la figura 3.1 que %Q es una sintaxis alternativa para rodear una cadena de
caracteres con comillas dobles, y que Given, When, Then son sinónimos disponibles para mejorar la legilibilidad (en el
capítulo siguiente se hablará acerca de la palabra clave should, que aparece en la línea 13).
para llegar a dicho estado, y son menos dependientes de los detalles de la interfaz de usuario.
Una versión declarativa del escenario anterior podría ser ésta:
http://pastebin.com/355SUaaT
1 Feature : movies should appear in alphabetical order , not added order
2
3 Scenario : view movie list after adding movie ( declarative and DRY )
4
5 Given I have added " Zorro " with rating " PG -13 "
6 And I have added " Apocalypse Now " with rating " R "
7 Then I should see " Apocalypse Now " before " Zorro " on the Rott enPota toes
home page sorted by title
Resumen
• Podemos usar Cucumber tanto para pruebas de aceptación como de integración si
escribimos historias de usuario para requisitos tanto explícitos como implícitos. Los
escenarios declarativos son más simples, concisos y más fácilmente mantenibles que
los escenarios imperativos.
• Una vez que vaya adquiriendo mayor experiencia, la gran mayoría de las historias
de usuario deberá expresarlas en un lenguaje de dominio que usted habrá creado
para la aplicación a través de las definiciones de pasos, de forma que las historias de
usuario se preocupen menos de los detalles de la interfaz de usuario. La excepción
la conforman aquellas historias específicas donde hay valor de negocio (requerido
por el cliente) en expresar los detalles de la interfaz de usuario.
Daniel Jackson, Martyn Thomas, and Lynette Millett (Editores), Software for
Dependable Systems: Sufficient Evidence?, 2007
Hay que recordar que el objetivo de los métodos de los ciclos de vida clásicos es hacer la
ingeniería del software tan predecible en presupuesto y planificación como lo es la ingeniería
civil. Sorprendentemente, las historias de usuario, los puntos y la velocidad se corresponden
266 CAPÍTULO 7. REQUISITOS: BDD E HISTORIAS DE USUARIO
con siete tareas principales en las metodologías clásicas de desarrollo de software. Estas
tareas incluyen:
1. Obtención de requisitos
2. Documentación de requisitos
3. Estimación de costes
4. Planificación y monitorización de progreso
predefinidas o simplemente se someten a discusiones de tipo informal. Fíjese que uno de los
objetivos es entender el entorno social y organizacional para ver cómo se realizan realmente
las tareas frente a la versión oficial. Otra técnica es crear escenarios de forma colabora-
tiva, que pueden comenzar con una asunción inicial del estado del sistema, mostrar el flujo
del sistema para los casos satisfactorio y no satisfactorio (“happy” y “sad”), enunciar qué
otras cosas suceden en la aplicación, y después el estado del sistema al final del escenario.
Relacionado con escenarios y con historias de usuario, una tercera técnica es crear casos de
uso, que son listas de pasos entre una persona y el sistema para alcanzar una meta (ver la
explicación de la sección 7.1).
Además de los requisitos funcionales como los listados arriba, los requisitos no fun-
cionales incluyen objetivos de rendimiento, de dependencia, etc.
2. Documentación de requisitos. Una vez obtenidos, el siguiente paso es documentar
los requisitos en un documento de especificación de requisitos software (SRS).
La figura 7.13 presenta un esquema para un SRS basado en el estándar 830-1998. Un
SRS para un sistema de gestión de pacientes14 tiene 14 páginas de extensión, pero a menudo
lo conforman cientos de páginas.
Parte del proceso consiste en comprobar en el SRS:
• Validez–¿son necesarios todos estos requisitos?
• Consistencia–¿hay conflictos entre los requisitos?
Índice de contenidos
1. Introducción
1.1 Propósito
1.2 Alcance
1.3 Definiciones, acrónimos y abreviaturas
1.4 Referencias
1.5 Resumen
2. Descripción general
2.1 Perspectiva del producto
2.2 Funciones del producto
2.3 Características
2.4 Limitaciones
2.5 Suposiciones y dependencias
3. Requisitos específicos
3.1 Requisitos externos de interfaz
3.1.1 Interfaces de usuario
3.1.2 Interfaces hardware
3.1.3 Interfaces software
3.1.4 Interfaces de comunicación
3.2 Funcionalidades del sistema
3.2.1 Funcionalidad de sistema 1
3.2.1.1 Introducción/propósito de la funcionalidad 1
3.2.1.2 Secuencia de estímulos/resultados
3.2.1.3 Requisitos funcionales asociados
3.2.1.3.1 Requisito funcional 1
...
3.2.1.3.n Requisito funcional n
3.2.2 Funcionalidad de sistema 2
...
3.2.m Funcionalidad de sistema m
3.3 Requisitos de rendimiento
3.4 Limitaciones de diseño
3.5 Atributos de sistema
3.6 Otros requisitos
Figura 7.13. Tabla de contenidos para el estándar 830-1998 de la IEEE, prácticas recomendadas para las especificaciones de
requisitos software. En la imagen se muestra la sección 3 organizada por funcionalidad, aunque el estándar ofrece muchas
otras formas de organizar esta sección: por modo, clases de usuario, objetos, estímulos, jerarquía funcional, o incluso
mezclando varias formas de organización.
7.10. LA PERSPECTIVA CLÁSICA 269
!
2! 4! 6! 9!
20! 20! 15!
10! Test!Plan! Testing! Release!
1!
1! 9! 10! 5!
11!
40!
10!
15!
3! 5!
30!
8!
Figura 7.14. Los nodos numerados representan hitos y las líneas etiquetadas representan tareas, con las flechas indicando
dependencias. Las líneas que divergen a partir de un nodo representan tareas concurrentes. Los números al otro lado de las
líneas representan el tiempo dispuesto para la tarea. Las líneas de puntos muestran dependencias que no necesitan recursos,
por lo que no tienen tiempo asociado para la tarea.
la arquitectura final detallada. Esta última fórmula extiende la penalización por tamaño aña-
diendo un producto normalizado de 5 factores y sustituye los factores asociados al producto
por la multiplicación de 17 factores independientes.
Una encuesta realizada por la British Computer Society mencionada en el capítulo 1 sobre
más de 1000 proyectos arrojó como resultado que el 92% de los jefes de proyecto realizaban
sus estimaciones apoyándose en la experiencia en vez de utilizar fórmulas (Taylor 2000).
Dado que sólo el 20% ó 30% de los proyectos cumplen su presupuesto y planificación,
¿qué le ocurre al resto? De hecho entre un 20% y un 30% de los proyectos son cancelados
o abandonados, pero el 40% ó 50% restante siguen siendo de valor para el cliente aunque se
entreguen con retraso. Normalmente los clientes y los proveedores negocian en este caso un
nuevo contrato para la entrega del producto en una fecha próxima y con un conjunto limitado
de funcionalidades no presentes.
planificación que muestre qué tareas pueden ser paralelizadas y cuáles de ellas tienen depen-
PERT significa técnica de
dencias entre ellas, de forma que deben ser ejecutadas de forma secuencial. Normalmente, el
evaluación y revisión de
formato usado es un diagrama de cajas y flechas, como un diagrama PERT . La figura 7.14 programas (Program
muestra un ejemplo. Este tipo de herramientas permite identificar el camino crítico, que Evaluation and Review
determina el tiempo mínimo de ejecución del proyecto. El jefe de proyecto dispone el dia- Technique), fue creada por
la marina estadounidense
grama en una tabla con filas asociadas al equipo del proyecto, para así asignar personas a las para su programa de
tareas. submarino nuclear.
Una vez más, este proceso se realiza normalmente dos veces, una al ofertar el contrato, y
una segunda vez una vez se ha firmado el contrato y se ha completado el diseño detallado de
la arquitectura. De nuevo, se utilizan los márgenes de seguridad para asegurar que la primera
planificación, que es la que el cliente espera que se cumpla para la entrega del proyecto, no
sea mayor que la segunda.
De forma similar a los cálculos de la velocidad, el jefe de proyecto puede ver si el proyecto
va retrasado simplemente comparando los costes y el tiempo presupuestado para las tareas
con el progreso y costes reales a fecha actual. Una manera de clarificar el estado del proyecto
a todos sus integrantes es el añadir hitos intermedios a la planificación, lo que permite que
cualquiera pueda ver si el proyecto está en presupuesto y/o en tiempo.
5. Gestión del cambio para los requisitos, costes y planificación. Como ya se ha
descrito varias veces en este libro, los clientes suelen pedir cambios en los requisitos según Arrastramiento de
va evolucionando el proyecto por muchas razones, incluyendo el comprender mejor lo que requisitos es el término
se quiere una vez se ha desarrollado un prototipo, cambios en las condiciones de mercado que utilizan los
desarrolladores para
relacionadas con el proyecto, etc. Por esto, tanto para la evolución de los documentos como describir el temido
para las aplicaciones se necesita un sistema de control de versiones, por lo que la norma debe incremento de requisitos a
ser subir la documentación revisada junto con el código revisado. lo largo del tiempo.
6. Asegurar que la implementación corresponde con las funcionalidades requeridas.
Las metodologías ágiles unifican todas estas tareas en tres muy relacionadas entre sí: historias
de usuario, pruebas de aceptación en Cucumber, y el código que se obtiene como resultado
del proceso de BDD/TDD. Por consiguiente, hay muy poca posibilidad de confusión en la
relación entre historias, pruebas y código.
Sin embargo, las metodologías clásicas implican bastantes más mecanismos sin esta in-
tegración tan ceñida. Así, necesitamos herramientas que permitan al jefe de proyecto el
comprobar si lo implementado corresponde con los requisitos. A la relación existente en-
tre las funcionalidades en los requisitos y lo implementado se le denomina trazabilidad de
los requisitos. Las herramientas que implementan esta característica ofrecen esencialmente
referencias cruzadas entre la parte del diseño, la parte del código que implementa una fun-
cionalidad dada, las revisiones de código que lo comprobaron y las pruebas que lo validaron.
Si existen los dos documentos SRS (de alto nivel y detallado), la trazabilidad hacia ade-
lante se refiere al camino tradicional desde los requisitos hasta la implementación, mientras
que la trazabilidad hacia atrás es la asociación de un requisito detallado con un requisito de
más alto nivel.
7. Análisis y gestión de riesgos. En un intento por mejorar la exactitud en la estimación
de costes y la planificación, las metodologías clásicas han tomado prestado el análisis de
riesgo de las escuelas de negocios. La filosofía es que invirtiendo tiempo al comienzo para
identificar los riesgos potenciales en el presupuesto y la planificación, se puede o realizar
trabajo extra para reducir las probabilidades de estos riesgos o cambiar el plan para evitarlos.
Idealmente, la identificación y gestión de riesgos ocurre en el primer tercio de un proyecto.
Que los riesgos se identifiquen más tarde en el ciclo de desarrollo no suele favorecer un buen
272 CAPÍTULO 7. REQUISITOS: BDD E HISTORIAS DE USUARIO
resultado.
Los riesgos se clasifican como técnicos, organizacionales, o de negocio. Un ejemplo de
un riesgo técnico podría ser que la base de datos relacional elegida no escale para la carga que
el proyecto necesita. Un riesgo organizacional sería que varios de los miembros del equipo
no estén familiarizados con J2EE, del que depende el proyecto. Un riesgo de negocio podría
ser que para la fecha en la que se complete el proyecto, el producto ya no sea competitivo en
el mercado.
Ejemplos de acciones que se pueden tomar para superar los riesgos enumerados arriba
pueden ser el adquirir una base de datos que goce de mayor escalabilidad, el enviar a los
integrantes del equipo a un workshop sobre J2EE, y el realizar encuestas de competitividad
sobre productos existentes, incluyendo sus funcionalidades actuales y planes de mejora.
La estrategia para identificar riesgos es preguntar a todos sobre sus casos más desfavo-
rables. El jefe de proyecto los colocará en una “tabla de riesgos”, asignará una probabilidad
de materializarse a cada uno entre 0 y 100, y el impacto en una escala numérica de 1 a 4,
que representarán despreciable, menor, crítico y catastrófico. La tabla podrá ordenarse por el
producto de la probabilidad y el impacto de cada riesgo.
Existen muchos más riesgos potenciales de los que se pueden abordar, por lo que el con-
sejo es afrontar los riesgos que conforman el 20% superior, con la esperanza de que represen-
tarán el 80% de los riesgos potenciales para el presupuesto y la planificación. Intentar abordar
todos los riesgos potenciales puede llevar a realizar un esfuerzo ¡mayor que el proyecto soft-
ware original! La reducción de los riesgos es una de las razones más fuertes para la iteración
en los modelos en espiral y RUP. Tanto las iteraciones como los prototipos deberían reducir
los riesgos asociados a un proyecto.
La sección 7.5 menciona el preguntar a los clientes acerca de los riesgos del proyecto
como una parte de la estimación de costes en la metodología ágil, pero la diferencia es que
suele decidir el rango de la estimación de coste más que convertirse en una parte significativa
del proyecto.
Resumen Los esfuerzos originales en la ingeniería del software aspiraban a hacer del de-
sarrollo de software algo tan predecible en calidad, coste y planificación como el construir
un puente. Quizás debido a que menos de la sexta parte de los proyectos software son com-
pletados en fecha y cumpliendo el presupuesto con toda la funcionalidad, el proceso en las
metodologías clásicas posee demasiados pasos para tratar de alcanzar este díficil objetivo.
Las metodologías ágiles no tratan de predecir el coste y planificar al inicio del proyecto,
y en su lugar confían en el trabajo con los clientes con iteraciones frecuentes y el acuerdo
sobre los rangos de fechas en los que los esfuerzos alcancen las metas del cliente. Puntuar
en dificultad las historias de usuario y registrar los puntos realmente completados por ite-
ración incrementa las posibilidades de estimaciones más realistas. La figura 7.15 muestra
las diferentes tareas resultantes dadas las distintas perspectivas de estas dos filosofías.
Autoevaluación 7.10.1. Nombre tres técnicas de las metodologías clásicas que pueden ayu-
dar en la obtención de requisitos.
⇧ Entrevistas, escenarios y casos de uso.
Figura 7.15. Relaciones entre las tareas asociadas a los requisitos en las metodologías clásicas frente a las metodologías
ágiles.
clientes. Añadir una funcionalidad que parece ser muy buena es una tentación muy fuerte,
pero también puede ser decepcionante ver descartado ese trabajo. Las historias de usuario
ayudan a que todos los participantes del proyecto prioricen desarrollos y reducen las posibi-
lidades de que haya esfuerzos desperdiciados en funcionalidades que únicamente atraen a los
desarrolladores.
La figura 7.16 muestra las relaciones que existen entre las herramientas para pruebas pre-
sentadas en este capítulo y las presentadas en los siguientes capítulos. Cucumber permite
escribir historias de usuario como funcionalidades, escenarios y pasos, y realiza la corres-
pondencia entre estos pasos y las definiciones de pasos utilizando expresiones regulares. Las
definiciones de pasos invocan métodos en Cucumber y Capybara. Necesitamos Capybara
porque estamos desarrollando una aplicación SaaS, y las pruebas requieren una herramienta
que actúe como un usuario y un navegador web. Si la aplicación no es SaaS, entonces po-
dríamos invocar directamente en Cucumber los métodos que realizan las pruebas de la apli-
cación.
La gran ventaja de las historias de usuario y de BDD es crear un lenguaje común com-
partido por todos los participantes del proyecto, especialmente por los clientes no técnicos.
BDD es perfecto para aquellos proyectos en los que los requisitos no se entienden bien o
276 CAPÍTULO 7. REQUISITOS: BDD E HISTORIAS DE USUARIO
Autotest
(Ch. 6)
SimpleCov
Cucumber RSpec (Ch. 6)
(Ch. 5) (Ch. 6)
reek
(Ch. 8)
Capybara
(Ch. 5) flog/flay
(Ch. 8)
Figura 7.16. Relaciones entre Cucumber, RSpec, Capybara y el resto de herramientas para pruebas y servicios descritos en
este libro. Este capítulo utiliza Rack::Test ya que nuestra aplicación no hace uso aún de JavaScript. Si lo hiciese, deberíamos
utilizar Webdriver, más lento pero más completo. El capítulo 12 muestra cómo podemos reemplazar Webdriver por el
servicio de SauceLabs para realizar pruebas de la aplicación con varios navegadores web en vez de sólo con uno.
cambian muy rápidamente, lo que es muy común. Además, las historias de usuario permiten
descomponer fácilmente los proyectos en pequeños incrementos o iteraciones, lo que facilita
la estimación del trabajo restante. El uso de fichas de 3x5 y de bocetos en papel de la interfaz
de usuario consigue mantener involucrados a los clientes no técnicos en el diseño y priori-
zación de funcionalidades, lo que incrementa las posibilidades de que el software resultante
cumpla con las necesidades del cliente. Las iteraciones guían el proceso de refinamiento del
Google coloca estos
desarrollo del software. Aún más, BDD y Cucumber dan paso de forma natural a la gene-
posters en salas de
descanso para recordar a ración de pruebas antes de implementar el código, trasladando los esfuerzos de validación e
los desarrolladores la implementación desde la depuración a las pruebas.
importancia de las pruebas Comparando historias de usuario, Cucumber, puntos y velocidad con los procesos de los
(utilizado con permiso).
ciclos de vida clásicos, se hace evidente que BDD juega un papel importante en el proceso
ágil:
1. Obtención de requisitos
2. Documentación de requisitos
3. Pruebas de aceptación
4. Trazabilidad entre funcionalidades e implementación
7.13. PARA SABER MÁS 277
ACM IEEE-Computer Society Joint Task Force. Computer science curricula 2013, Ironman
Draft (version 1.0). Technical report, February 2013. URL http://ai.stanford.edu/
users/sahami/CS2013/.
B. W. Boehm and R. Valerdi. Achievements and challenges in COCOMO-based software
resource estimation. IEEE Software, 25(5):74–83, Sept 2008.
278 NOTAS
J. Johnson. The CHAOS report. Technical report, The Standish Group, Boston, Mas-
sachusetts, 1995. URL http://blog.standishgroup.com/.
J. Johnson. The CHAOS report. Technical report, The Standish Group, Boston, Mas-
sachusetts, 2009. URL http://blog.standishgroup.com/.
A. Taylor. IT projects sink or swim. BCS Review, Jan. 2000. URL http://archive.bcs.
org/bulletin/jan00/article1.htm.
M. Wynne and A. Hellesøy. The Cucumber Book: Behaviour-Driven Development for
Testers and Developers. Pragmatic Bookshelf, 2012. ISBN 1934356808.
Notas
1 http://http://www.fandango.com/rss/moviefeed
2 https://developers.google.com/maps/documentation/javascript/tutorial
3 http://www.omdbapi.com
4 http://imdb.com
5 http://www.youtube.com/watch?v=mTYcHg51sWY
6 N. de T.: Literalmente, punta o clavo, o también se traduce como frustrar
7 N. de T.: También puede encontrarse como “Tema” o “Superhistoria de usuario”
8 http://drive.google.com
9 http://campfirenow.com
10 N.de.T.: del inglés, inteligente o listo referido a una persona; en Estados Unidos se utiliza también con el
MHCPMSCaseStudy.pdf
15 http://rubydoc.info/github/jnicklas/capybara/
16 http://cukes.info
17 http://cukes.info
18 http://benmabey.com/2008/05/19/imperative-vs-declarative-scenarios-in-user-
stories.html
19 http://elabs.se/blog/15-you-re-cuking-it-wrong
20 http://aslakhellesoy.com/post/11055981222/the-training-wheels-came-off
Pista: las variables de instancia en las definiciones de pasos en Cucumber están asocia-
das al escenario, no al paso.
7.14. EJERCICIOS PROPUESTOS 279
Ejercicio 7.15. Diferencie entre trazabilidad hacia delante y hacia atrás y explique qué roles
juegan en el proceso de validación de requisitos.
Ejercicio 7.17. Describa las diferentes categorías de riesgos en los sistemas software.
Ejercicio 7.18. Describa el impacto del riesgo en los ciclos de vida clásicos.
7.14. EJERCICIOS PROPUESTOS 281
8 Pruebas de software: desarrollo
orientado a pruebas
Donald Knuth (1938–), Quizás una de las lecciones más importantes es el hecho de que EL SOFTWARE ES
uno de los más ilustres DIFÍCIL1 . [. . . ] TEX y METAFONT demostraron ser mucho más difíciles que el resto
expertos en ciencias de la de tareas que había realizado (como demostrar teoremas o escribir libros). Crear buen
computación, recibió el software requiere un nivel de precisión significativamente más alto, así como más tiempo
Premio Turing en 1974 por
sus importantes
de concentración, que otras actividades intelectuales.
contribuciones al análisis de Donald Knuth, discurso de apertura del 11º World Computer Congress, 1989
algoritmos y al diseño de
lenguajes de programación,
y en particular por sus
contribuciones a El arte de
8.1 Antecedentes: API REST y gemas Ruby . . . . . . . . . . . . . . . . 284
programar ordenadores 8.2 FIRST, TDD y Rojo–Verde–Refactorizar . . . . . . . . . . . . . . . . 286
(The Art of Computer 8.3 Costuras y dobles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 290
Programming). Dicha serie
es considerada por muchos 8.4 Expectativas, mocks, stubs, configuración . . . . . . . . . . . . . . . . 294
la referencia definitiva sobre 8.5 Fixtures y factorías . . . . . . . . . . . . . . . . . . . . . . . . . . . . 298
análisis de algoritmos; los 8.6 Requisitos implícitos y simulación de Internet . . . . . . . . . . . . . 302
“cheques recompensa” de
Knuth por descubrir errores 8.7 Cobertura y pruebas unitarias vs. de integración . . . . . . . . . . . 307
en sus libros están entre los 8.8 Otros enfoques de pruebas y terminología . . . . . . . . . . . . . . . 312
trofeos más preciados para
8.9 La perspectiva clásica . . . . . . . . . . . . . . . . . . . . . . . . . . 314
los expertos en
computación. Knuth inventó 8.10 Falacias y errores comunes . . . . . . . . . . . . . . . . . . . . . . . 318
también el sistema de 8.11 Observaciones finales: TDD vs. depuración convencional . . . . . . . 320
composición de textos TEX,
ampliamente utilizado y con 8.12 Para saber más . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 321
el cual se ha editado este 8.13 Ejercicios propuestos . . . . . . . . . . . . . . . . . . . . . . . . . . . 322
libro.
1 Knuth hace un juego de palabras que se pierde con la traducción: su expresión original, “software is hard”,
Conceptos
Los principales conceptos de este capítulo son creación de pruebas, cobertura de pruebas y niveles
de prueba.
Las cinco propiedades FIRST que caracterizan las buenas pruebas son: rápidas (Fast), in-
dependientes (Independent), repetibles (Repeatables), autoevaluables (Self-checking) y oportunas
(Timely). Para ayudar a mantener las pruebas rápidas e independientes del comportamiento de
otras clases, se utilizan objetos mock o simulados y stubs. Son ejemplos de costuras o hil-
vanes (seams), que cambian el comportamiento del programa durante las pruebas sin cambiar
el código fuente.
En el ciclo de vida ágil, que sigue el desarrollo orientado a pruebas (TDD), las pruebas siguen
las siguientes fases:
• Escribir pruebas unitarias que prueben el código que se desea tener –aún inexistente, por
tanto fallarán–, comenzando por las pruebas de aceptación e integración derivadas de las
historias de usuario. Para ello, utilizaremos la herramienta RSpec.
• Escribir sólo el código necesario para pasar una prueba y buscar posibilidades de refac-
torizar el código antes de continuar con la siguiente. Puesto que las pruebas fallidas se
muestran en rojo y las superadas en verde, esta secuencia se denomina Rojo–Verde–
Refactorizar (Red–Green–Refactor).
• Usar mocks y stubs en las pruebas para aislar el comportamiento del código a probar del
de otras clases o métodos de los que dependa.
• Diversas métricas de cobertura de código ayudan a determinar qué partes del código
requieren más pruebas.
En el ciclo de vida clásico, se aplican algunos de estos mismos conceptos pero en un orden
muy diferente e incluso involucrando a distintas personas:
• El jefe de proyecto asigna tareas de programación basadas en los SRS, de modo que las
pruebas unitarias empiezan después de codificar. Los desarrolladores pasan el control a
los probadores de Control de calidad (Quality Assurance, QA) para realizar las pruebas
de más alto nivel.
• Descendente (Top-down), ascendente (Bottom-up) y mixto (Sandwich) son alternati-
vas de cómo combinar el código resultante para ejecutar las pruebas de integración. El
plan de pruebas y los resultados se documentan, por ejemplo siguiendo el estándar IEEE
829-2008.
• Tras las pruebas de integración, el equipo de control de calidad realiza la prueba de
sistemas antes de su entrega al cliente. Las pruebas finalizan cuando se alcanza el nivel de
cobertura especificado, por ejemplo “95% de cobertura de sentencia”.
• Una alternativa a las pruebas, para software crítico de pequeño tamaño, son los métodos
formales. Utilizan especificaciones formales del comportamiento correcto del programa que
se verifican automáticamente mediante demostradores de teoremas o búsqueda de estados
exhaustiva, métodos de mayor alcance que las pruebas convencionales.
La estrategia de prueba es una de las diferencias más acusadas entre los ciclos de vida ágil
y clásico: cuándo se comienzan a escribir las pruebas, en qué orden se escriben los niveles de
pruebas e incluso quién hace las pruebas.
284 CAPÍTULO 8. PRUEBAS: DESARROLLO ORIENTADO A PRUEBAS
Legacy (Ch. 9)
Figura 8.1. El ciclo de vida ágil del software y su relación con los capítulos de este libro. Este capítulo enfatiza las pruebas
unitarias como parte del desarrollo orientado a pruebas.
¿Método o función?
Siguiendo la terminología de 8.1 Antecedentes: API REST y gemas Ruby
programación orientada a
objetos (Object-Oriented
Programming, OOP),
En el capítulo 1 se introdujo el ciclo de vida ágil y se diferenciaron dos aspectos de control
utilizamos método para de calidad del software: validación (“¿Ha desarrollado el producto correcto?”) y verificación
refererirnos a un fragmento (“¿Ha desarrollado el producto correctamente?”). En este capítulo, nos centramos en la veri-
de código, con un ficación —desarrollar el producto correctamente— mediante pruebas de software como parte
determinado nombre, que
implementa un
del ciclo de vida ágil. La figura 8.1 resalta la parte del ciclo de vida ágil cubierta en este
comportamiento asociado a capítulo.
una clase, tanto si es tipo Aunque las pruebas son sólo una técnica usada para verificación, nos centramos en ellas
función que devuelve un porque a menudo se malinterpreta su papel y, como resultado, no reciben tanta atención
valor, como si es tipo
procedimiento que causa como otras partes del ciclo de vida software. Además, como veremos, abordar el desarrollo
efectos colaterales. software desde una perspectiva centrada en las pruebas mejora a menudo la legibilidad y el
Históricamente, para mantenimiento del software. En otras palabras, el código que puede probarse tiende a ser
referirse a dichos buen código, y viceversa.
fragmentos de código se
han utilizado, entre otros, En el capítulo 7 empezamos a trabajar en una nueva funcionalidad de RottenPotatoes,
los términos función, rutina, para permitir importar automáticamente información sobre una película de The Open Movie
subrutina y subprograma. Database2 o, abreviado, TMDb. En este capítulo, desarrollaremos los métodos necesarios
8.1. ANTECEDENTES: API REST Y GEMAS RUBY 285
Generalmente, invocar dicha API desde RottenPotatoes requeriría utilizar la clase URI
en la biblioteca estándar de Ruby para construir el URI de la petición con nuestra clave de
API, usar la clase Net::HTTP para enviar la petición a api.themoviedb.org, y analizar
el objeto JSON resultante (quizás utilizando la gema json). Pero a veces podemos ser más
productivos subiéndonos a hombros de otros. La gema themoviedb, mencionada en la do-
cumentación de la API TMDb, es un “wrapper” (envoltorio) de la API REST de TMDb
proporcionado por usuarios. El screencast 8.1.2 muestra cómo utilizarla.
Screencast 8.1.2. Uso simplificado de la API TMDb con la gema themoviedb.
http://vimeo.com/84683958
No todas las API REST tienen su correspondiente biblioteca Ruby, pero si existe, como para
IMDb, puede ocultar los detalles de la API tras unos pocos métodos Ruby sencillos. Conve-
nientemente, la gema themoviedb de Ahmet Abdi construye los URI REST correctamente,
realiza las llamadas a servicio remoto, y convierte los resultados JSON en objetos Ruby que
representan películas, listas de reproducción, etc. Pero, tal como muestra este screencast,
hay que ser cuidadoso con la detección y manejo de errores al interactuar con el servicio
remoto.
Resumen: TMDb (The Open Movie Database) tiene una API orientada a servicios que
puede invocarse enviando peticiones HTTP con los URI HTTP apropiados y devuelve una
respuesta HTTP cuyo cuerpo es un objeto JSON que contiene los datos de la respuesta.
Convenientemente, puede utilizarse una gema Ruby de código abierto para crear las peti-
ciones apropiadas y analizar las respuestas JSON, en vez de trabajar directamente con los
URI y JSON.
Autoevaluación 8.1.1. Verdadero o falso: para usar la API TMDb desde otro lenguaje como
Java, necesitaríamos una biblioteca Java equivalente a la gema themoviedb.
⇧ Falso: la API consiste en un conjunto de peticiones HTTP y respuestas JSON, de modo que
mientras podamos transmitir y recibir bytes sobre TCP/IP y analizar cadenas de caracteres
(las respuestas JSON), podemos usar las API sin una biblioteca especial.
http://pastebin.com/2BXbVMN8
1 require ' spec_helper '
2
3 describe M o v i e s C o n t r o l l e r do
4 describe ' searching TMDb ' do
5 it ' should call the model method that performs TMDb search '
6 it ' should select the Search Results template for rendering '
7 it ' should make the TMDb search results available to that template '
8 end
9 end
Figura 8.2. Esqueleto de ejemplos RSpec para MoviesController#search_tmdb. Por convención sobre configuración, las
especificaciones para app/controllers/movies_controller.rb se espera que estén en
spec/controllers/movies_controller_spec.rb, y así sucesivamente. (Use Pastebin para copiar y pegar este código).
• Debería llamar a un método del modelo para realizar la búsqueda en TMDb, pasándole
los términos de búsqueda tecleados por el usuario.
• Debería seleccionar la vista HTML resultados de búsqueda —en lenguaje Rails, la
plantilla (template) Search Results— para presentarla.
• Debería pasar los resultados de la búsqueda TMDb a dicha plantilla.
Observe que, en realidad, ¡ninguno de los métodos o plantillas de esta lista de desiderata
existe aún! Ésa es la esencia de TDD: escribir una lista concreta y concisa de comportamien-
tos deseados (la especificación –spec–) y utilizarla para dirigir la creación de los métodos y
plantillas.
La figura 8.2 muestra cómo se expresarían estas expectativas en RSpec. Como en el
capítulo 3, le animamos a aprender practicando. Antes de crear este fichero, necesita preparar
RottenPotatoes para utilizar RSpec para las pruebas, lo que requiere cuatro pasos:
Puede ver qué son en
spec/spec_helper.rb. 1. En el bloque group :test del gemfile, añada gem ’rspec-rails’
de especificaciones para las aplicaciones Rails. La línea 3 indica que las siguientes especi-
ficaciones describen (describe) el comportamiento de la clase MoviesController. Puesto
que esta clase tiene varios métodos, la línea 4 indica que este primer conjunto de especi-
ficaciones describe el comportamiento del método que busca en TMDb. Como puede ver,
describe puede ir seguido de o bien el nombre de una clase o bien una cadena descriptiva de
documentación.
Las tres siguientes líneas son espacios para ejemplos (examples), el término RSpec
para un fragmento corto de código que prueba un comportamiento específico del método
search_tmdb. Aún no hemos escrito nada de código de pruebas, pero el siguiente screen-
cast muestra que podemos no sólo ejecutar estos esqueletos con el comando rspec, sino, y
lo que es más importante, automatizar su ejecución con la herramienta autotest. Aunque el
comando rake spec es una forma de ejecutar el conjunto completo de pruebas, la automati-
zación de autotest mejora la productividad, puesto que evita tener que alternar la atención
entre escribir código y ejecutar pruebas. También agiliza la ejecución de pruebas (Faster),
puesto que se centra de forma inteligente sólo en las pruebas que aún fallan o para las cuales
ha cambiado el código recientemente, en vez de reejecutar el banco completo de pruebas cada
vez. Para utilizar autotest, añada la línea gem autotest-rails dentro de la sección group
:test de su Gemfile, y ejecute bundle como siempre para asegurar que la gema está insta-
lada. A continuación, en el directorio raíz de la aplicación, simplemente teclee autotest.
En el resto del capítulo se asume que autotest se está ejecutando y que según se añadan
pruebas o código de aplicación se obtendrá inmediatamente realimentación de RSpec. En la
siguiente sección crearemos nuestras primeras pruebas usando TDD.
Screencast 8.2.1. Ejecutar los esqueletos de pruebas vacíos y automatizar la ejecución
Depurar y autotest
con autotest. Para utilizar el depurador
http://vimeo.com/34754856 interactivo presentado en el
Cuando se ejecuta el comando RSpec, los ejemplos (cláusulas it) que no contienen código capítulo 4 con autotest,
se muestran en amarillo como “pendientes”. También puede marcarse explícitamente un añada require
ejemplo mediante pending y proporcionar una descripción de por qué está pendiente. En vez ’debugger’ a
de ejecutar manualmente spec cada vez que se añade o modifica código, se puede utilizar el spec/spec_helper.rb
e inserte llamadas de
comando autotest, que automáticamente vuelve a ejecutar las specs adecuadas cada vez depuración (debugger)
que se modifica un fichero de especificaciones o de código. donde quiera que se
detenga la acción.
Resumen
• Las pruebas deberían ser rápidas (Fast), independientes (Independent), repetibles
(Repeatable), autoevaluables (Self-checking) y oportunas (Timely) (FIRST).
• RSpec es un lenguaje específico del dominio embebido en Ruby para escribir prue-
bas. La convención sobre configuración determina dónde debería almacenarse el
fichero de especificaciones (specfile) correspondiente a una determinada clase.
• En un fichero de especificaciones (specfile), cada ejemplo (example) introducido
por el método it prueba un único comportamiento de un método. describe agrupa
ejemplos jerárquicamente de acuerdo al conjunto de comportamientos que prueban.
1. Antes de escribir nada de código, escriba una prueba para un aspecto del comportamiento
que debería tener. Puesto que el código a probar aún no existe, escribir la prueba le fuerza a
pensar cómo desearía que el código se comportara e interaccionara con sus colaboradores si
existiera. Lo denominamos “ejecutar el código que se querría tener”.
2. Fase roja: Ejecute la prueba y compruebe que falla porque aún no ha implementado el
código necesario para pasarla.
3. Fase verde: Escriba el código más simple posible que permita pasar esta prueba sin
provocar fallos en ninguna de las anteriores.
4. Fase refactorización: Busque posibilidades de refactorizar tanto en el código como en
las pruebas —modificando la estructura del código para eliminar redundancia, repeticiones
u otros desaguisados que hayan aparecido al añadir código nuevo—. Las pruebas garantizan
que la refactorización no introduce fallos.
5. Repetir hasta completar todos los comportamientos necesarios para superar un escenario.
Figura 8.3. El ciclo de desarrollo orientado a pruebas (TDD) también se conoce como Rojo–Verde–Refactorizar
(Red–Green–Refactor) debido a su esquema de pasos 2–4. El último paso asume que se está desarrollando código para
completar un escenario, como el que se empezó en el capítulo 7.
clave ____. Un grupo de ejemplos relacionados se introduce mediante la palabra clave ____,
que puede anidarse para organizar ejemplos jerárquicamente.
⇧ it; describe
Autoevaluación 8.2.2. Puesto que RSpec asocia pruebas con clases usando convención so-
bre configuración, las pruebas para app/models/movie.rb se pondrían en el fichero ____.
⇧ spec/models/movie_spec.rb
http://pastebin.com/6tJvd0hx
1 require ' spec_helper '
2
3 describe M o v i e s C o n t r o l l e r do
4 describe ' searching TMDb ' do
5 it ' should call the model method that performs TMDb search ' do
6 post : search_tmdb , {: search_terms = > ' hardware '}
7 end
8 it ' should select the Search Results template for rendering '
9 it ' should make the TMDb search results available to that template '
10 end
11 end
Figura 8.4. Rellenando la primera spec. Mientras un it “básico” (línea 8) sirve como contenedor para un ejemplo pendiente
de escribir, un it acompañado de un bloque do. . . end (líneas 5–7) es un caso de prueba real.
En este punto, RSpec da verde como resultado para nuestro primer ejemplo, pero en
realidad no es preciso puesto que el ejemplo en sí está incompleto: aún no hemos comprobado
realmente si search_tmdb invoca un método de modelo para buscar en TMDb, tal como la
especificación requiere. (Lo hicimos así de forma deliberada para ilustrar algunos de los
mecanismos necesarios para poner en funcionamiento la primera spec. Normalmente, puesto
que cada spec tiende a ser corta, se completa antes de volver a ejecutar las pruebas).
¿Cómo podemos comprobar que search_tmdb invoca un método de modelo, si aún
no existe ninguno? De nuevo, escribimos la prueba para el comportamiento del código que El código que querríamos
tener será un método de
querríamos tener, como se indica en el paso 1 de la figura 8.3. Simulemos que tenemos un
clase, puesto que encontrar
método de modelo que hace justo lo que queremos. En este caso, probablemente querríamos películas en TMDb es un
pasar al método una cadena de texto y obtener como resultado una colección de películas comportamiento relativo a
(objetos Movie) que coincidan con la cadena buscada. Si dicho método existiera, nuestro las películas en general y no
a una instancia particular de
método controlador podría invocarlo así: la clase (Movie).
http://pastebin.com/ACNefdqY
1 @movies = Movie . find_in_tmdb ( params [: search_terms ])
La figura 8.5 muestra el código de un caso de prueba que fuerza dicha llamada. En
este caso, el código a probar —código sujeto (subject code)— es search_tmdb. Sin em-
bargo, parte del comportamiento que se prueba depende de find_in_tmdb. Puesto que
find_in_tmdb aún no existe, el objetivo de las líneas 6–8 es “simular” el comportamiento
que exhibiría si existiera. La línea 6 utiliza el método mock de RSpec para crear un array
de dos “dobles de prueba” de objetos Movie. En particular, mientras un objeto Movie real
respondería a métodos como title (título) y rating (clasificación), el doble de prueba lanzaría
una excepción si se invoca cualquiera de sus métodos. ¿Por qué usar dobles entonces? Para
292 CAPÍTULO 8. PRUEBAS: DESARROLLO ORIENTADO A PRUEBAS
http://pastebin.com/fyyXrYJD
1 require ' spec_helper '
2
3 describe M o v i e s C o n t r o l l e r do
4 describe ' searching TMDb ' do
5 it ' should call the model method that performs TMDb search ' do
6 fake_results = [ mock ( ' movie1 ') , mock ( ' movie2 ') ]
7 Movie . sh ould_r eceive (: find_in_tmdb ) . with ( ' hardware ') .
8 and_return ( fake_results )
9 post : search_tmdb , {: search_terms = > ' hardware '}
10 end
11 it ' should select the Search Results template for rendering '
12 it ' should make the TMDb search results available to that template '
13 end
14 end
Figura 8.5. Ejemplo completo, indicando que el método controlador invocará el código que querríamos tener en el modelo
Movie. Las líneas 5–10 de este listado sustituyen a las líneas 5–7 en la figura 8.4.
aislar estas specs del comportamiento de la clase Movie, que podría tener sus propios erro-
res. Los objetos mock son como marionetas con un comportamiento totalmente controlado,
permitiendo aislar las pruebas unitarias de sus clases colaboradoras y mantener las pruebas
Independientes (la I de FIRST).
De hecho, un alias de
Volviendo a la figura 8.5, las líneas 6–7 expresan la expectativa de que la clase Movie
mock es double (doble).
Por claridad, use mock debería recibir una llamada al método find_in_tmdb y que dicho método debería recibir
cuando se pida al objeto como único argumento ’hardware’. RSpec abrirá la clase Movie y definirá un método de
falso que haga algo y clase find_in_tmdb cuyo único propósito es monitorizar si se le llama y, en ese caso, si
double cuando
simplemente se necesite un
se pasan los argumentos apropiados. Si ya existiera un método con el mismo nombre en la
suplente. clase Movie, sería “sobreescrito” temporalmente por este stub. Por eso no importa que no
hayamos escrito el método find_in_tmdb “real”: ¡no se invocaría en cualquier caso!
El uso de should_receive para reemplazar temporalmente el método “real” para las
pruebas es un ejemplo de uso de una costura (seam): “un lugar donde se puede alterar el
comportamiento del programa sin editar en esa posición” (Feathers 2004). En este caso,
should_receive crea una costura sobrecargando un método, sin tener que editar el fichero
que contiene el método original (aunque en este caso el método original ni siquiera existe
aún). Las costuras también son importantes cuando se trata de añadir nuevo código a la
aplicación, pero en el resto del capítulo se verán muchos más ejemplos en pruebas. Las
costuras son útiles para las pruebas porque permiten romper dependencias entre el fragmento
de código a probar y sus colaboradores, permitiendo que los colaboradores se comporten de
forma diferente en condiciones de prueba respecto a como lo harían en la vida real.
La línea 8 (que es la continuación de la 7) especifica que find_in_tmdb debería de-
volver la colección de dobles que configuramos en la línea 6. Esto completa la ilusión del
“código que querríamos tener”: estamos invocando un método que aún no existe ¡y pro-
porcionando el resultado que esperaríamos que diera si existiese! Si omitimos with, RSpec
seguirá comprobando que se llama a find_in_tmdb, pero no comprobará si los argumentos
Técnicamente, en este caso son los esperados. Si se omite and_return, la llamada al método “de mentira” devolverá
sería correcto omitir nil en vez de un valor predefinido. En cualquier caso, después de ejecutar cada ejemplo,
and_return, puesto que RSpec realiza un reseteo para restaurar las clases a su condición original, de modo que si se
este ejemplo no comprueba
el valor de retorno, pero se deseara realizar esas mismas falsificaciones en otros ejemplos, se necesitaría especificarlas
incluye con fines ilustrativos. en cada uno de ellos (aunque pronto se verá una forma de eliminar dicha repetición). Este
desmantelamiento automático es otra parte importante de mantener los tests Independientes.
8.3. COSTURAS Y DOBLES 293
Esta nueva versión de la prueba falla porque impone la expectativa de que search_tmdb
llame a find_in_tmdb, pero search_tmdb aún no está ni siquiera escrito. Por tanto,
la última etapa es pasar de rojo a verde añadiendo el código imprescindible para que
search_tmdb supere esta prueba. Se dice que la prueba orienta, dirige o guía (drives) la
creación de código, porque ampliar la prueba conlleva un error que debe solucionarse agre-
gando código al modelo. Puesto que lo único que prueba este ejemplo concreto es la llamada
al método find_in_tmdb, es suficiente añadir a search_tmdb la única línea de código
que teníamos en mente como “el código que querríamos tener”:
http://pastebin.com/vWt9uxGQ
1 @movies = Movie . find_in_tmdb ( params [: search_terms ])
Si TDD es nuevo para usted, todo esto es mucho material que absorber, especialmente
al usar un entorno potente como Rails. No se preocupe: ahora que ha visto los conceptos
principales, la siguiente serie de specs será más rápida. Requiere un poco de fe aventurarse
con este sistema, pero la recompensa merece la pena. Lea el siguiente resumen y plantéese
tomar un aperitivo y repasar los conceptos de esta sección antes de continuar.
Resumen
• Después de cada prueba, un reseteo automático destruye los mocks y stubs y elimina
las expectativas, de modo que las pruebas sean Independientes.
http://pastebin.com/T5rakACv
1 require ' spec_helper '
2
3 describe M o v i e s C o n t r o l l e r do
4 describe ' searching TMDb ' do
5 it ' should call the model method that performs TMDb search ' do
6 fake_results = [ mock ( ' movie1 ') , mock ( ' movie2 ') ]
7 Movie . sho uld_re ceive (: find_in_tmdb ) . with ( ' hardware ') .
8 and_return ( fake_results )
9 post : search_tmdb , {: search_terms = > ' hardware '}
10 end
11 it ' should select the Search Results template for rendering ' do
12 fake_results = [ mock ( ' Movie ') , mock ( ' Movie ') ]
13 Movie . stub (: find_in_tmdb ) . and_return ( fake_results )
14 post : search_tmdb , {: search_terms = > ' hardware '}
15 response . should r e nd er _ te mp l at e ( ' search_tmdb ')
16 end
17 it ' should make the TMDb search results available to that template '
18 end
19 end
Figura 8.6. Segundo ejemplo. Las líneas 11–16 reemplazan la línea 11 de la figura 8.5.
error si nunca se invoca. Cambie el fichero specfile de acuerdo a la figura 8.6; autotest
debería continuar ejecutándose e informar de que se pasa este segundo ejemplo.
En este sencillo ejemplo, podría argumentarse que estamos buscando tres pies al gato
usando should_receive en un ejemplo y stub en otro, pero el objetivo es ilustrar que
cada ejemplo debería probar un único comportamiento. Este segundo ejemplo sólo prueba
que se selecciona la vista correcta. No comprueba que se llama al método de modelo
apropiado —de eso se encarga el primer ejemplo—. De hecho, incluso aunque el método
Movie.find_in_tmdb estuviera ya implementado, aún se crearía un stub en estos ejemplos,
porque los ejemplos deberían aislar los comportamientos bajo prueba de los comportamientos
de otras clases con las que el código sujeto colabore.
Antes de pasar a escribir otro ejemplo, debe refactorizarse de acuerdo al ciclo Rojo–
Verde–Refactorizar. Puesto que las líneas 6 y 12 son idénticas, la figura 8.7 muestra una
forma de evitar repeticiones (DRY) refactorizando para extraer el código de configuración
común a un bloque before(:each). Como el propio nombre implica, este código se ejecuta
antes de cada uno de los ejemplos dentro del grupo de ejemplos describe, de forma similar
a la sección Background de una funcionalidad de Cucumber, cuyos pasos se ejecutan antes
de cada escenario. También existe before(:all), que ejecuta el código de configuración una
única vez para todo un grupo de pruebas; pero a costa de hacer las pruebas que lo usan depen-
dientes unas de otras, puesto que es muy fácil que se introduzcan a hurtadillas dependencias
difíciles de depurar y que sólo se descubren al ejecutar las pruebas en distinto orden o cuando
sólo se ejecuta un subconjunto de las mismas.
Aunque el concepto de extraer la configuración común a un bloque before es directo, ¿Variable de
requiere un cambio sintáctico para que funcione, por cómo está implementado RSpec. En instancia de qué?
concreto, fake_results tiene que convertirse en una variable de instancia @fake_results @fake_results no es
una variable de instancia de
porque las variables locales de un bloque do. . . end de un caso de prueba desaparecen una la clase puesta a prueba
vez finaliza dicho caso de prueba. Por el contrario, las variables de instancia de un grupo de (MoviesController),
ejemplos son visibles para todos los ejemplos del grupo. Puesto que se establece el valor en sino del objeto Test::-
Spec::ExampleGroup
el bloque before :each, cada caso de prueba verá el mismo valor inicial de @fake_results.
que representa un grupo de
casos de prueba.
Queda sólo un ejemplo por escribir, para comprobar que los resultados de la búsqueda
296 CAPÍTULO 8. PRUEBAS: DESARROLLO ORIENTADO A PRUEBAS
http://pastebin.com/eWvBdJR7
1 require ' spec_helper '
2
3 describe M o v i e s C o n t r o l l e r do
4 describe ' searching TMDb ' do
5 before : each do
6 @fake_results = [ mock ( ' movie1 ') , mock ( ' movie2 ') ]
7 end
8 it ' should call the model method that performs TMDb search ' do
9 Movie . sh ould_r eceive (: find_in_tmdb ) . with ( ' hardware ') .
10 and_return ( @fake_results )
11 post : search_tmdb , {: search_terms = > ' hardware '}
12 end
13 it ' should select the Search Results template for rendering ' do
14 Movie . stub (: find_in_tmdb ) . and_return ( @fake_results )
15 post : search_tmdb , {: search_terms = > ' hardware '}
16 response . should r e nd er _ te mp l at e ( ' search_tmdb ')
17 end
18 it ' should make the TMDb search results available to that template '
19 end
20 end
Figura 8.7. Evitar repeticiones (DRY) en los ejemplos del controlador mediante el bloque before (líneas 5–7).
sobre TMDb estarán disponibles para la vista de respuesta. Recuerde que en el capítulo 7,
se creó views/movies/search_tmdb.html.haml bajo la hipótesis de que la acción del
controlador configuraría @movies con la lista de películas resultantes de TMDb. Por eso se
asigna a la variable de instancia @movies el resultado de la llamada a find_in_tmdb en
MoviesController#search_tmdb. (Recuerde que las variables de instancia asignadas en
una acción de controlador son accesibles desde la vista).
El método assigns() de RSpec mantiene un registro de qué variables de instancia se
asignan en el método controlador. Por tanto, assigns(:movies) devuelve el valor que se
haya asignado a @movies en search_tmdb (si se asignó alguno) y nuestra spec simple-
mente tiene que verificar si la acción del controlador asigna correctamente dicha variable. En
nuestro caso, ya hemos acordado devolver los dobles como resultado de la llamada simulada
al método, de modo que el comportamiento correcto de search_tmdb sería asignar este
valor a @movies como se indica en la línea 21 de la figura 8.8.
http://pastebin.com/LJiz2q2q
1 require ' spec_helper '
2
3 describe M o v i e s C o n t r o l l e r do
4 describe ' searching TMDb ' do
5 before : each do
6 @fake_results = [ mock ( ' movie1 ') , mock ( ' movie2 ') ]
7 end
8 it ' should call the model method that performs TMDb search ' do
9 Movie . sho uld_re ceive (: find_in_tmdb ) . with ( ' hardware ') .
10 and_return ( @fake_results )
11 post : search_tmdb , {: search_terms = > ' hardware '}
12 end
13 it ' should select the Search Results template for rendering ' do
14 Movie . stub (: find_in_tmdb ) . and_return ( @fake_results )
15 post : search_tmdb , {: search_terms = > ' hardware '}
16 response . should r e nd er _ te mp l at e ( ' search_tmdb ')
17 end
18 it ' should make the TMDb search results available to that template ' do
19 Movie . stub (: find_in_tmdb ) . and_return ( @fake_results )
20 post : search_tmdb , {: search_terms = > ' hardware '}
21 assigns (: movies ) . should == @fake_results
22 end
23 end
24 end
Figura 8.8. Aserción de que search_tmdb asigna correctamente @movie. Las líneas 18–22 en este listado reemplazan la
línea 18 en la figura 8.7.
http://pastebin.com/xcGUCCFb
1 require ' spec_helper '
2
3 describe M o v i e s C o n t r o l l e r do
4 describe ' searching TMDb ' do
5 before : each do
6 @fake_results = [ mock ( ' movie1 ') , mock ( ' movie2 ') ]
7 end
8 it ' should call the model method that performs TMDb search ' do
9 Movie . sho uld_re ceive (: find_in_tmdb ) . with ( ' hardware ') .
10 and_return ( @fake_results )
11 post : search_tmdb , {: search_terms = > ' hardware '}
12 end
13 describe ' after valid search ' do
14 before : each do
15 Movie . stub (: find_in_tmdb ) . and_return ( @fake_results )
16 post : search_tmdb , {: search_terms = > ' hardware '}
17 end
18 it ' should select the Search Results template for rendering ' do
19 response . should r en d er _ te mp l at e ( ' search_tmdb ')
20 end
21 it ' should make the TMDb search results available to that template ' do
22 assigns (: movies ) . should == @fake_results
23 end
24 end
25 end
26 end
Figura 8.9. Spec completa y refactorizada para search_tmdb. El grupo anidado que comienza en la línea 13 permite evitar
código duplicado (DRY) en las líneas 14–15 y 19–20 de la figura 8.8.
298 CAPÍTULO 8. PRUEBAS: DESARROLLO ORIENTADO A PRUEBAS
Cuando se anidan grupos de ejemplos, cualquier bloque before asociado al grupo más
externo se ejecuta antes que los asociados con los niveles interiores. Por tanto, por ejemplo,
teniendo en cuenta el caso de prueba en las líneas 18–20 de la figura 8.9, el código de con-
figuración de las líneas 5–7 se ejecuta primero, seguido por el código de iniciación de las
líneas 14–17, y finalmente el propio ejemplo (líneas 18–20).
La próxima tarea será utilizar TDD para crear el método de modelo find_in_tmdb que
se ha estado simulando. Puesto que este método se supone que llama al servicio TMDb real,
hay que usar de nuevo stubs, esta vez para evitar que los ejemplos dependan del compor-
tamiento de un servicio de Internet remoto.
Resumen
• Un ejemplo de refactorización, en el ciclo Rojo–Verde–Refactorizar, es mover el
código de inicialización común a un bloque before block, evitando repeticiones
(DRY) en las specs.
• Al igual que should_receive, stub crea un método “doble de pruebas” para usar
en las pruebas, pero a diferencia de should_receive, stub no requiere que se llame
realmente al método.
• assigns() permite a una prueba de controlador inspeccionar los valores de las varia-
bles de instancia asignadas por una acción de controlador.
Autoevaluación 8.4.1. Especifique cuál de las siguientes construcciones RSpec se usa para
(a) crear una costura (seam), (b) determinar el comportamiento de una costura (seam), (c)
ninguna: (1) assigns(); (2) should_receive; (3) stub; (4) and_return.
⇧ (1) c, (2) a, (3) a, (4) b
Autoevaluación 8.4.2. ¿Por qué normalmente es preferible usar before(:each) mejor que
before(:all)?
⇧ El código de un bloque before(:each) se ejecuta antes de cada spec en ese bloque, ini-
cializando precondiciones idénticas para dichas specs y, en consecuencia, manteniéndolas
independientes
Pero hay dos razones para no utilizar un mock en este caso. Primero, este objeto mock
necesita casi tanta funcionalidad como el objeto Movie real, de modo que probablemente
sea mejor usar el objeto real. Segundo, puesto que el método de instancia que se prueba es
8.5. FIXTURES Y FACTORÍAS 299
http://pastebin.com/LViW2uA8
1 # spec / fixtures / movies . yml
2 milk_movie :
3 id : 1
4 title : Milk
5 rating : R
6 release_date : 2008 -11 -26
7
8 documentary_movie :
9 id : 2
10 title : Food , Inc .
11 release_date : 2008 -09 -07
http://pastebin.com/n6hkM1Cw
1 # spec / models / movie_spec . rb :
2
3 require ' spec_helper . rb '
4
5 describe Movie do
6 fixtures : movies
7 it ' should include rating and year in full name ' do
8 movie = movies (: milk_movie )
9 movie . n a m e _ w i t h _ r a t i n g . should == ' Milk ( R ) '
10 end
11 end
Figura 8.10. Los fixtures declarados en ficheros YAML (arriba) se cargan automáticamente en la base de datos de pruebas
antes de que se ejecute cada spec (abajo).
parte de la propia clase Movie, tiene sentido usar un objeto real ya que no se trata de aislar
el código de prueba de las clases colaboradoras.
Hay dos opciones para obtener un objeto Movie real para este tipo de pruebas. Una
opción es configurar uno o más fixtures —un estado fijo que se usa como referencia para
una o varias pruebas—. El término fixture viene del mundo de la industria: un test fixture
(banco de ensayo) es un instrumento que sostiene o sujeta el producto bajo evaluación. Puesto
que todo el estado de las aplicaciones SasS en Rails se mantiene en la base de datos, un
fichero fixture define un conjunto de objetos que se carga automáticamente en la base de datos
de pruebas antes de que se ejecuten los tests, de modo que puedan usarse dichos objetos
en las pruebas sin inicializarlos antes. Al igual que se inicializan y resetean los mocks y
stubs, la base de datos de pruebas se borra y recarga con los fixtures antes de cada spec, Estrictamente hablando, no
manteniendo las pruebas independientes. Rails busca fixtures en un fichero de objetos YAML se borra, pero cada spec se
(Yet Another Markup Language). Como se muestra en la figura 8.10, YAML es una forma ejecuta en una
muy simple de representar jerarquías de objetos con atributos, similar a XML, que vimos al transacción de la base
de datos que se revierte
principio del capítulo. Los fixtures para el modelo Movie se cargan de spec/fixtures/ cuando finaliza la spec.
movies.yml y están disponibles para las pruebas mediante sus nombres simbólicos, como
muestra la figura 8.10.
Sin embargo, a menos que se usen cuidadosamente, los fixtures pueden interferir con
la independencia de las pruebas, puesto que cada test depende ahora implícitamente del es-
tado del fixture, de modo que modificar los fixtures podría cambiar el comportamiento de
las pruebas. Además, aunque cada prueba individual probablemente sólo depende de uno o
dos fixtures, el conjunto de fixtures requeridos por todas las pruebas puede terminar siendo
inmanejable. Por esta razón, muchos programadores prefieren usar una factoría —una es-
tructura diseñada para facilitar la creación ágil de objetos completamente funcionales (en
vez de mocks) en tiempo de prueba. Por ejemplo, la popular herramienta para Rails Fac-
300 CAPÍTULO 8. PRUEBAS: DESARROLLO ORIENTADO A PRUEBAS
http://pastebin.com/60Th29d1
1 # spec / factories / movie . rb
2
3 FactoryGirl . define do
4 factory : movie do
5 title 'A Fake Title ' # default values
6 rating ' PG '
7 release_date { 10. years . ago }
8 end
9 end
http://pastebin.com/DVpJAWgr
1 # in spec / models / movie_spec . rb
2 describe Movie do
3 it ' should include rating and year in full name ' do
4 # ' build ' creates but doesn 't save object ; ' create ' also saves it
5 movie = FactoryGirl . build (: movie , : title = > ' Milk ' , : rating = > 'R ')
6 movie . n a m e _ w i t h _ r a t i n g . should == ' Milk ( R ) '
7 end
8 end
9 # More concise : uses Alternative RSpec2 ' subject ' syntax ', and mixes in
10 # FactoryGirl methods in spec_helper . rb ( see FactoryGirl README )
11 describe Movie do
12 subject { build : movie , : title = > ' Milk ' , : rating = > 'R ' }
13 its (: n a m e _ w i t h _ r a t i n g ) { should == ' Milk ( R ) ' }
14 end
Figura 8.11. Usar factorías en vez de fixtures preserva la independencia entre pruebas. Entornos como FactoryGirl
(gem ’factory_girl_rails’ en Gemfile) lo facilitan, racionalizando la creación de objetos reales (no mocks).
toryGirl6 permite definir una factoría de objetos Movie y crear rápidamente únicamente los
objetos que se necesitan para cada prueba, sobrecargando selectivamente sólo determinados
atributos, como muestra la figura 8.11. (FactoryGirl es parte de la biblioteca de recursos del
libro –bookware–). En nuestra sencilla aplicación, utilizar una factoría no confiere muchas
ventajas respecto a crear directamente un nuevo objeto Movie invocando Movie.new. Pero
en aplicaciones más complejas, en las que la creación e inicialización de objetos conlleva
muchas etapas —por ejemplo, objetos que tienen muchos atributos que deben inicializarse
al crearlos— una factoría ayuda a evitar repeticiones (DRY) en las precondiciones de prueba
(bloques before) y a mantener fluido el código de las pruebas.
Antes de agregar más funcionalidad, profundicemos un poco más en cómo funciona
RSpec. La función should de RSpec constituye un magnífico ejemplo de uso de las carac-
terísticas del lenguaje Ruby para mejorar la legibilidad y difuminar la frontera entre pruebas
y documentación. El siguiente screencast explica con más detalle cómo se maneja realmente
una expresión como value.should == 5.
8.5. FIXTURES Y FACTORÍAS 301
Screencast 8.5.1. Características del lenguaje dinámico Ruby que hacen más legibles
las specs.
http://vimeo.com/34754890
RSpec amplía la clase Object añadiendo el método should. should recibe como parámetro
un comparador (matcher) que representa la condición que será evaluada por should. Se
pueden utilizar métodos de RSpec como be para generar dicho comparador. Gracias a la fle-
xibilidad de la sintaxis de Ruby y los paréntesis opcionales, una aserción como value.should
be < 5 puede expresarse con paréntesis simplificándola como value.should(be.<(5)).
Además, RSpec permite la funcionalidad method_missing de Ruby (descrita en el
capítulo 3) para detectar comparadores (matchers) que comiencen con be_ o be_a_, de
forma que posibilita crear aserciones del tipo cheater.should be_disqualified. (Nota:
la spec que aparece al principio de esta edición beta del screencast no corresponde al
ejemplo desarrollado en esta sección. Sin embargo, esto no afecta a la cuestión principal
del screencast, que es ilustrar en detalle cómo funciona should en RSpec). (Nota adicional:
puede necesitar el comando require ’rspec/expectations’ para hacer funcionar los
ejemplos de este screencast.)
Resumen
• Cuando una prueba necesita manejar un objeto real mejor que un mock, el objeto
real puede crearse sobre la marcha mediante una factoría o precargarse como fix-
ture. Sin embargo, hay que tener cuidado, porque los fixtures pueden introducir
interdependencias sutiles entre pruebas, perdiendo la independencia.
• Las pruebas son una forma de documentación interna. RSpec explota las caracterís-
ticas de Ruby para permitir escribir código de pruebas extraordinariamente legible.
Al igual que el código de aplicación, el código de pruebas es para las personas, no
para el ordenador, de modo que tomarse tiempo para hacer los tests legibles no sólo
profundiza su comprensión por parte del programador sino que también documenta
las ideas más eficazmente para quienes trabajen después con el código.
Autoevaluación 8.5.1. Suponga que un juego de pruebas contiene un test que añade un
objeto del modelo a una tabla y a continuación espera encontrar como resultado un de-
terminado número de objetos del modelo en la tabla. Explique cómo puede afectar a la
independencia de las pruebas el uso de fixtures en este caso y cómo puede solucionarse el
problema con factorías.
⇧ Si el fichero de fixtures se modifica de modo que el número de ítems que contiene ini-
302 CAPÍTULO 8. PRUEBAS: DESARROLLO ORIENTADO A PRUEBAS
http://pastebin.com/TVmi7Zxu
1 require ' spec_helper '
2
3 describe Movie do
4 describe ' searching Tmdb by keyword ' do
5 it ' should call Tmdb with title keywords ' do
6 Tmdb :: Movie . sho uld_re ceive (: find ) . with ( ' Inception ')
7 Movie . find_in_tmdb ( ' Inception ')
8 end
9 end
10 end
http://pastebin.com/XvaAGUUQ
1 class Movie < ActiveRecord :: Base
2
3 def self . find_in_tmdb ( string )
4 Tmdb :: Movie . find ( string )
5 end
6
7 # rest of file elided for brevity
8 end
Figura 8.12. (Arriba) Spec del camino “feliz” para usar la gema TMDb; (abajo) implementación inicial del camino “feliz”
dirigida por la spec del camino “feliz” (happy path).
cialmente la tabla cambia, esta prueba puede empezar a fallar de repente porque dejan de
cumplirse las condiciones que asume sobre el estado inicial de la tabla. Por el contrario,
puede usarse una factoría para crear rápidamente, bajo demanda, únicamente los objetos ne-
cesarios para cada prueba o grupo de ejemplos, de modo que ningún test depende de ningún
“estado inicial” de la base de datos.
http://pastebin.com/cPXrpyMT
1 require ' spec_helper '
2
3 describe Movie do
4 describe ' searching Tmdb by keyword ' do
5 it ' should call Tmdb with title keywords given valid API key ' do
6 Tmdb :: Movie . sh ould_r eceive (: find ) . with ( ' Inception ')
7 Movie . find_in_tmdb ( ' Inception ')
8 end
9 it ' should raise an In v al i dK ey E rr or with invalid API key ' do
10 lambda { Movie . find_in_tmdb ( ' Inception ') }.
11 should raise_error ( Movie :: I nv a li dK e yE r ro r )
12 end
13 end
14 end
Figura 8.13. El código que nos gustaría tener lanzaría una excepción muy específica para señalizar que falta la clave de API
(línea 11), pero esta spec falla porque find_in_tmdb no implementa la lógica necesaria para comprobar un error en la
llamada al servicio y lanzar esta excepción.
¿Dónde está la
quizás para ajustarse a un cambio en la propia API del servicio TMDb, podemos aislar
gema? ¿No hay que
el controlador de dichos cambios, porque todo el conocimiento sobre cómo usar la gema incluir
para comunicarse con el servicio está encapsulado dentro de la clase del modelo Movie. require ’themoviedb’
Esta indirección es un ejemplo de separación entre los elementos que cambian y aquellos en alguna parte en la
definición del modelo o en
que permanecen igual, una idea clave en el uso de patrones de diseño, que se introdujo las especificaciones
brevemente en la sección 2.1 y se explica en detalle en el capítulo 11. La segunda razón, (specs)? En aplicaciones no
más importante, es que esta spec está sutilmente incompleta: find_in_tmdb tiene más basadas en Rails sí. Pero
tareas que hacer. Nuestros casos de prueba se han basado en el requisito explícito descrito en Rails automáticamente
requiere (require) las
la historia de usuario del capítulo 7: cuando el usuario introduce el nombre de una película gemas que se especifican
y pulsa Search TMDb, debería ver una página que muestre los resultados. Sin embargo, en el fichero Gemfile.
el screencast 8.1.2 mostraba que si una petición no va acompañada de una clave de API
válida, se lanza una excepción que no resulta muy informativa para el programador sobre
el origen real del error. Nuestra estrategia, denominada a veces envolver (wrapping) la
excepción, consistirá en capturar dicha excepción y lanzar una propia, InvalidKeyError,
cuando se produzca un problema de clave no válida. De este modo, si el comportamiento
En ediciones anteriores de
de error de la gema cambia en el futuro, podemos hacer los cambios aquí en el modelo, y
este libro, la API de la
quien realiza la llamada (search_tmdb en este caso) sólo tiene que preocuparse de manejar gema, del servicio y el
InvalidKeyError. comportamiento de error en
Esto lleva a un nuevo requisito implícito que descubrimos experimentando con la gema: caso de clave de API no
válida eran todos diferentes.
Aún así, los únicos cambios
• Debería lanzar una excepción de “clave no válida” (“invalid key”) si se proporciona necesarios para este
una clave no válida. ejemplo estaban
encapsulados en el modelo.
La especificación revisada en la figura 8.13 expresa este requisito implícito como una
nueva spec.
Fíjese que hemos renombrado nuestra primera spec para indicar que aplica al caso en que
la clave de API es válida y añadido una nueva spec para cubrir el caso en que la clave de API
no es válida.
Pero ahora tenemos dos dilemas. El primero es que esta spec llamaría al servicio TMDb
real cada vez que se ejecutara, con lo cual no sería ni rápida (Fast) —cada llamada requiere
unos pocos segundos— ni repetible (Repeatable) —la prueba se comportará de forma dife-
rente si TMDb está caída o si su ordenador no tiene conexión a Internet—. Incluso aunque
304 CAPÍTULO 8. PRUEBAS: DESARROLLO ORIENTADO A PRUEBAS
http://pastebin.com/cjcEZd4Y
1 require ' spec_helper '
2
3 describe Movie do
4 describe ' searching Tmdb by keyword ' do
5 it ' should call Tmdb with title keywords given valid API key ' do
6 Tmdb :: Movie . sho uld_re ceive (: find ) . with ( ' Inception ')
7 Movie . find_in_tmdb ( ' Inception ')
8 end
9 it ' should raise an I nv al i dK ey E rr o r with no API key ' do
10 Tmdb :: Movie . stub (: find ) . and_raise ( NoMethodError )
11 Tmdb :: Api . stub (: response ) . and_return ({ ' code ' = > 401})
12 lambda { Movie . find_in_tmdb ( ' Inception ') }.
13 should raise_error ( Movie :: I nv a li dK e yE rr o r )
14 end
15 end
16 end
Figura 8.14. Los stubs de las líneas 10–11 simulan el comportamiento que observamos en el screencast 8.1.2 cuando se
proporciona una clave de API no válida.
sólo se ejecutaran las pruebas con conexión, está muy mal visto (se considera de mala edu-
cación) que las pruebas accedan continuamente a un servicio en producción.
Podemos solucionarlo introduciendo una costura que aísle el método que realiza la lla-
mada del que la recibe. Sabemos, por el screencast 8.1.2, que Tmdb::Movie.find lanza
una excepción NoMethodError cuando se usa una clave no válida. En ese caso, podemos
analizar Tmdb::Api.response para verificar que el código de respuesta HTTP (code) es
401, que significa “No autorizado”. Podemos replicar ese comportamiento con un stub que
“simule” lo que ocurre cuando la gema realiza una llamada de servicio con una clave de API
errónea. La figura 8.14 muestra dicha spec. Fíjese que tuvimos que encapsular la llamada a
find_in_tmdb en la línea 12 en un bloque lambda. Esperamos que la llamada lance una
excepción, pero si una spec lanzara realmente una excepción, ¡detendría las pruebas! Por
tanto, para hacer la spec autoevaluable (Self-checking), se llama a should sobre el objeto
lambda, lo que hace que lambda se ejecute en un “entorno controlado” donde RSpec puede
capturar cualquier excepción y compararla con las expectativas.
Esta spec falla por la razón correcta, esto es, porque no hemos añadido código a
find_in_tmdb para comprobar si se produce una excepción en la gema. La figura 8.15
muestra el nuevo código añadido a find_in_tmdb para pasar la prueba. Fíjese que si se
produce una excepción NoMethodError pero no puede verificarse que el código de res-
puesta de la API sea 401 (líneas 9–13 de la figura 8.15), simplemente relanzamos la excep-
ción original, puesto que en este caso no sabemos cuál es el problema (y no hay nada en la
documentación de la gema themoviedb para averiguarlo). De igual modo, si se produce otra
excepción distinta de NoMethodError, no la capturaremos y tendrá que ocuparse de ella el
método que haya realizado la llamada.
Ahora podemos ver el segundo dilema en la figura 8.14: pasamos dos specs que clara-
mente prueban el comportamiento bajo condiciones diferentes —clave de API válida versus
no válida— Aun así, ¡no hay nada en el código de las pruebas que nos lo indique! Este error
es un “antipatrón”, al escribir pruebas que implican el uso de otra API, ya sea de un servicio
remoto o de otra clase. Puesto que nuestras pruebas nunca llaman al servicio remoto TMDb
“real”, lo que queremos en realidad es agrupar nuestras pruebas en dos conjuntos distintos, en
función de si estamos simulando llamadas con éxito con una clave de API válida o llamadas
fallidas debido a una clave de API no válida.
8.6. REQUISITOS IMPLÍCITOS Y SIMULACIÓN DE INTERNET 305
http://pastebin.com/1GRqdr91
1 class Movie < ActiveRecord :: Base
2
3 class Movie :: I n va li d Ke y Er ro r < StandardError ; end
4
5 def self . find_in_tmdb ( string )
6 begin
7 Tmdb :: Movie . find ( string )
8 rescue NoMethodError = > t m d b _ g e m _ e x c e p t i o n
9 if Tmdb :: Api . response [ ' code '] == 401
10 raise Movie :: InvalidKeyError , ' Invalid API key '
11 else
12 raise t m d b _ g e m _ e x c e p t i o n
13 end
14 end
15 end
16
17 # rest of file elided for brevity
18 end
Figura 8.15. Código añadido a find_in_tmdb para capturar la excepción, incluyendo la definición de un nuevo tipo de
excepción propio (línea 3). Si el código de respuesta de la API es 401, sabemos que el problema fue una clave no válida. Pero
si es otro distinto, no sabemos cuál es el problema, de modo que por seguridad simplemente relanzamos la excepción
original
La figura 8.16 muestra cómo hacer esto en RSpec. context es un simple sinónimo de
describe y, además de dejar agrupar las specs de acuerdo a su propósito, también pueden
usarse bloques before para inicializar los stubs que simularán las llamadas con claves no
válidas. Cualquier spec futura para probar otros casos que involucren una clave de API no
válida pueden simplemente ir en dicho bloque de contexto (context).
La figura 8.16 plantea una cuestión más general: ¿dónde deberíamos crear los stubs
para métodos externos cuando se usa un servicio externo? Aquí elegimos crear un stub
de find_in_tmdb y simular los resultados de las llamadas a TMDb de la gema, pero un
enfoque de pruebas de integración más robusto crearía el stub “más próximo al servicio re-
moto”. En particular, podríamos crear fixtures —ficheros con el contenido devuelto por
llamadas reales al servicio, como los objetos JSON del screencast 8.1.1— e interceptar las
llamadas al servicio remoto y devolver en cambio los contenidos de dichos fixtures. La gema
FakeWeb9 hace exactamente eso: simula la Web completa, excepto URI concretos que de-
vuelven una respuesta predefinida cuando un programa Ruby accede a ellos. (Se puede pensar
que FakeWeb es como una cláusula stub. . . with. . . and_return para toda la Web). Existe VCR (del inglés
incluso una gema complementaria VCR10 que automatiza el proceso de obtener una respuesta Videocassette
del servicio real, almacenarla en un fichero fixture y “reproducir” el fixture cuando se invoca Recorder
–videograbador–) era un
el servicio remoto desde las pruebas, interceptando las llamadas de bajo nivel en la biblioteca dispositivo de grabación de
HTTP de Ruby. vídeo en cinta analógica
Desde el punto de vista de pruebas de integración, FakeWeb es la forma más realista de popular en los 80 que quedó
probar las interacciones con un servicio remoto, porque el comportamiento simulado está obsoleto con la adopción del
DVD a principios de los
“más alejado” —introducimos el stub lo más tarde posible en el flujo de la petición—. Por 2000. La gema vcr usa
tanto, normalmente FakeWeb es la opción apropiada cuando se crean escenarios Cucumber incluso el término “cassette”
para probar la integración con servicios externos. Desde el punto de vista de pruebas unitarias para referirse a las
respuestas del servidor
(que es el que hemos adoptado en este capítulo) es menos convincente, puesto que lo que nos
almacenadas que se
preocupa es el comportamiento correcto de ciertos métodos de clase específicos y no nos reproducen durante las
importa crear los stubs “más cerca” para observar dichos comportamientos en un entorno pruebas.
controlado.
306 CAPÍTULO 8. PRUEBAS: DESARROLLO ORIENTADO A PRUEBAS
http://pastebin.com/CT0XWNrH
1 require ' spec_helper '
2
3 describe Movie do
4 describe ' searching Tmdb by keyword ' do
5 context ' with valid API key ' do
6 it ' should call Tmdb with title keywords ' do
7 Tmdb :: Movie . sh ould_r eceive (: find ) . with ( ' Inception ')
8 Movie . find_in_tmdb ( ' Inception ')
9 end
10 end
11 context ' with invalid API key ' do
12 before : each do
13 Tmdb :: Movie . stub (: find ) . and_raise ( NoMethodError )
14 Tmdb :: Api . stub (: response ) . and_return ({ ' code ' = > 401})
15 end
16 it ' should raise an In v al i dK ey E rr or with no API key ' do
17 lambda { Movie . find_in_tmdb ( ' Inception ') }.
18 should raise_error ( Movie :: I nv a li dK e yE r ro r )
19 end
20 end
21 end
22 end
Figura 8.16. Ahora las specs están agrupadas claramente de acuerdo a las condiciones de prueba (clave de API válida o no)
de find_in_tmdb. Una ventaja adicional de este agrupamiento es que podemos evitar repeticiones (DRY) en la
inicialización de los stubs que simulan el escenario de clave no válida, poniéndolos en un bloque before que aplique a todas
las specs de dicho grupo.
Resumen
• A veces, los requisitos explícitos conllevan requisitos implícitos adicionales —res-
tricciones adicionales que no son “visibles” como los requisitos explícitos, pero que
deben satisfacerse igualmente para cumplir los requisitos explícitos—. Los requi-
sitos implícitos son tan importantes como los explícitos y deberían probarse con el
mismo rigor.
Autoevaluación 8.6.1. Si no inicializar una clave de API válida provoca que la gema
themoviedb lance una excepción, ¿por qué la línea 7 de la figura 8.13 no lanza una ex-
cepción?
⇧ La línea 6 sustituye la llamada Tmdb::Movie.find por un stub, evitando que se ejecute el
método “real” y se lance la excepción.
Autoevaluación 8.6.2. Teniendo en cuenta la línea 10 de la figura 8.13, suponga que no
encapsulamos la llamada a find_in_tmdb en una expresión lambda. ¿Qué ocurriría y por
qué?
⇧ Si find_in_tmdb lanza correctamente la excepción, la spec falla porque la excepción
interrumpe la ejecución. Si find_in_tmdb no lanza la excepción por error, la spec falla
porque la aserción should raise_error espera que se produzca. Por tanto, la prueba fallaría
siempre, tanto si find_in_tmdb es correcto como si no.
Autoevaluación 8.6.3. Nombre dos infracciones probables de los principios FIRST que
aparecen cuando las pruebas unitarias invocan realmente un servicio externo.
⇧ La prueba puede dejar de ser rápida (Fast), puesto que es mucho más lento realizar una
llamada a un servicio remoto que operaciones locales. La prueba puede dejar de ser repetible
(Repeatable) porque circunstancias ajenas a nuestro control pueden afectar a su resultado,
como que el servicio externo no esté disponible temporalmente.
Mocks y stubs:
• m=mock(’movie’)
Crea un objeto mock sin métodos predefinidos
• m.stub(:rating).and_return(’R’)
Sustituye al método rating de m, si ya existe, o define un nuevo método rating, si no existía,
que devuelve la respuesta predefinida ’R’
• m=mock(’movie’, :rating=>’R’)
Atajo que combina los 2 ejemplos anteriores
• Movie.stub(:find).and_return(@fake_movie)
Fuerza que se devuelva @fake_movie si se llama a Movie.find, pero no requiere que se le
invoque
Métodos y objetos útiles para las especificaciones de controlador: Las specs que defina deben estar
en el subdirectorio spec/controllers para que estos métodos estén disponibles.
• post ’/movies/create’,
{:title=>’Milk’, :rating=>’R’}
Origina una petición POST a /movies/create y pasa la hash como valor de params. También
disponibles get, put, delete.
• response.should render_template(’show’)
Comprueba que la acción del controlador muestra la plantilla show para este modelo del contro-
lador.
• response.should redirect_to(:controller => ’movies’, :action => ’new’)
Comprueba que la acción del controlador redirige a MoviesController#new en vez de mostrar
una vista
Figura 8.17. Algunos de los métodos de RSpec más útiles presentados en este capítulo. Consulte la documentación completa
de RSpec12 para más información y otros métodos no incluidos en este listado.
8.7. COBERTURA Y PRUEBAS UNITARIAS VS. DE INTEGRACIÓN 309
Comparadores (matchers)
• greeting.should == ’buenos días’
Comparador de igualdad, compara si su argumento y el receptor de la aserción son iguales
• value.should be >= 7
Compara su argumento con el valor dado; simplificación sintáctica de value.should(be.>=(7))
• result.should be_remarkable
Llama al método remarkable? (fíjese en el signo de interrogación) de result
Figura 8.18. Continuación del resumen de métodos de RSpec útiles presentados en este capítulo.
Figura 8.19. Correspondencia (parcial) entre las sintaxis “clásica” y nueva de las expectativas RSpec. En los ejemplos de
expectativas negativas, pueden inferirse las correspondientes expectativas positivas eliminando la palabra not.
310 CAPÍTULO 8. PRUEBAS: DESARROLLO ORIENTADO A PRUEBAS
http://pastebin.com/QzMnndtu
1 class MyClass
2 def foo (x ,y , z )
3 if x
4 if ( y && z ) then bar (0) end
5 else
6 bar (1)
7 end
8 end
9 def bar ( x ) ; @w = x ; end
10 end
Figura 8.20. Ejemplo simple de código para ilustrar conceptos de cobertura básicos.
A veces se escribe con un • S0 o cobertura de método: ¿se ejecuta al menos una vez cada método en las pruebas?
subíndice, S0 . Para satisfacer S0 es necesario llamar al menos una vez a cada método, foo y bar.
• S1 o cobertura de llamada o cobertura de entrada/salida: ¿Se llama a cada método
desde todos los puntos desde donde podría invocarse? Para satisfacer S1, es necesario
llamar al método bar desde las líneas 4 y 6.
• C0 o cobertura de sentencia: ¿se ejecuta al menos una vez cada sentencia del código
fuente en las pruebas, contando ambas ramas de un condicional como una única sen-
tencia? Además de llamar a bar, para satisfacer C0 se necesita llamar a foo al menos
una vez con x verdadero (de otro modo, la sentencia de la línea 4 nunca se ejecuta), y
al menos otra con y falso.
• C1 o cobertura de rama: ¿Se entra en cada rama al menos una vez? Para satisfacer C1
habría que llamar a foo con valores de x verdadero y falso y con valores de y y z tales
que la condición y && z en la línea 4 se evalúe una vez a verdadero y otra a falso.
La cobertura de decisión, más estricta, requiere que cada subexpresión que afecta de
forma independiente a una expresión condicional se evalúe como verdadera y falsa. En
este ejemplo, una prueba tendría además que asignar de forma separada y y z, de modo
que la condición y && z falle una vez por ser y falso y otra por ser z falso.
• C2 o cobertura de camino: ¿Se ejecuta cada posible ruta del código? En este sencillo
ejemplo, donde x,y,z son variables booleanas, hay 8 posibles caminos.
• La cobertura de condición/decisión modificada (Modified Condition/Decision Cover-
age, MCDC) combina un subconjunto de los niveles anteriores: cada punto de entrada
y de salida del programa se invoca al menos una vez, cada decisión del programa toma
todos los posibles resultados al menos una vez y cada condición dentro de una decisión
se demuestra que afecta de forma independiente el resultado de dicha decisión.
Este capítulo, así como la discusión anterior sobre cobertura, se centra en las pruebas
unitarias. El capítulo 7 explicaba cómo pueden convertirse las historias de usuario en pruebas
de aceptación automatizadas; son pruebas de integración o pruebas de sistema porque
cada prueba (es decir, cada escenario) ejecuta gran cantidad de código de distintas partes de
la aplicación, en vez de servirse de objetos “de mentira” como mocks y stubs para aislar las
clases de sus colaboradores. Las pruebas de integración son importantes, pero insuficientes.
Su resolución es baja: si falla una prueba de integración, es difícil localizar la causa porque el
test toca muchas partes del código. Su cobertura tiende a ser pobre, porque incluso aunque un
único escenario toca muchas clases, sólo ejecuta unos pocos caminos de código en cada una
de ellas. Por la misma razón, la duración de la ejecución de las pruebas de integración tiende
a ser superior. Por otra parte, aunque las pruebas unitarias se ejecutan rápidamente y aíslan
el código sujeto a pruebas con gran precisión (mejorando tanto la resolución de la cobertura
como la localización de errores), al depender de objetos “de mentira” para aislar el código
sujeto, pueden enmascarar problemas que sólo aparecerían con las pruebas de integración.
En un nivel intermedio están las pruebas funcionales, que prueban un subconjunto bien
definido del código. Usan mocks y stubs para aislar un conjunto de clases colaboradoras
en vez de una única clase o método. Por ejemplo, las specs de controlador como la de la
figura 8.9 usan métodos get y post para enviar los URI a la aplicación, lo que significa
que dependen del subsistema de encaminamiento para dirigir correctamente dichas llamadas
a los métodos de controlador apropiados. (Puede comprobarlo por sí mismo eliminando
temporalmente la línea resources :movies de config/routes.rb e intentando ejecutar las
specs de controlador). Sin embargo, las specs de controlador aún están aisladas de la base
de datos, al simular el método de modelo find_in_tmdb que normalmente se comunicaría
con ella (la base de datos).
En otras palabras, garantizar un alto grado de calidad requiere tanto buena cobertura
como una combinación de los tres tipos de pruebas. La figura 8.21 resume las fortalezas y
debilidades de los distintos tipos de pruebas.
312 CAPÍTULO 8. PRUEBAS: DESARROLLO ORIENTADO A PRUEBAS
Figura 8.21. Resumen de las diferencias entre pruebas unitarias, pruebas funcionales y pruebas de integración o de sistema.
Resumen
• Las medidas de cobertura estáticas y dinámicas, incluidas la ratio código/pruebas
(code-to-test ratio calculada por rake stats), la cobertura C0 (calculada por Sim-
pleCov) y las coberturas C1–C2, evalúan en qué medida el banco de tests prueba
diferentes caminos del código.
Autoevaluación 8.7.1. ¿Por qué una cobertura de pruebas alta no implica necesariamente
que la aplicación esté bien probada?
⇧ La cobertura no dice nada sobre la calidad de las pruebas. Sin embargo, una cobertura baja
implica claramente una aplicación mal probada.
aplicación y ver qué falla. Puede hacerse fallar aproximadamente 1/4 de las utilidades co-
munes de Unix mediante fuzzing y Microsoft estima que encuentra el 20–25% de sus bugs
así. La técnica del fuzzing tonto (dumb fuzzing) genera datos completamente aleatorios, mien-
tras que el fuzzing inteligente (smart fuzzing) incorpora información sobre la estructura de la
aplicación. Por ejemplo, en el caso de una aplicación Rails, la técnica de fuzzing inteligente
podría incluir variables y valores al azar en envíos de formularios o en URI embebidos en
las vistas de páginas, generando URI sintácticamente válidos pero que podrían revelar un
error. En SaaS, el fuzzing inteligente también puede incluir ataques como cross-site scripting
o inyección SQL, que veremos en el capítulo 12. Tarantula14 (una araña –spider– fuzzy que
rastrea un sitio web) es una gema Ruby para probar aplicaciones Rails mediante fuzzing.
Resumen de otros enfoques de pruebas: Podemos pensar en las pruebas como “cubrir
un grafo” de posibles comportamientos de software. El grafo puede representar el flujo
de control (cobertura de bloques básicos), asignación y uso de variables (cobertura DU),
un espacio de datos de entrada aleatorios (fuzzing) o un espacio de posibles pruebas con
respecto a errores específicos en el código (pruebas de mutación). Las diferentes aproxi-
maciones son complementarias y tienden a detectar distintos tipos de errores
3. General
3.1. Glosario
3.2. Procedimiento de modificaciones e historia del documento
Figura 8.22. Esquema de la documentación del plan maestro de pruebas de acuerdo a la norma IEEE 829-2008.
8.9. LA PERSPECTIVA CLÁSICA 317
Figura 8.23. Relación entre las tareas de prueba de las metodologías clásica y ágil
fecha, el software más grande verificado es el núcleo de un sistema operativo con menos de Para poner en
10.000 líneas de código y su coste de verificación supuso aproximadamente $500 por línea perspectiva el coste
de código (Klein et al. 2010). de los métodos
formales, la NASA
Por tanto, los métodos formales no son buenos partidos para programas avanzados que gastó $35 millones de
cambian frecuentemente, como suele ser el caso en SaaS. dólares al año para
mantener 420.000 líneas de
código15 para el
Resumen: Las pruebas y los métodos formales reducen el riesgo de errores en el proyecto. transbordador espacial,
aproximadamente $80 por
• A diferencia de BDD/TDD, el proceso clásico empieza escribiendo el código antes línea de código al año.
que las pruebas.
• Luego, los desarrolladores realizan las pruebas unitarias.
• Las pruebas de más alto nivel las realizan personas distintas, especialmente en
proyectos grandes. Las pruebas de integración pueden seguir un planteamiento des-
cendente, ascendente o mixto.
• Los responsables de las pruebas realizan una prueba de sistema de forma indepen-
diente para asegurar que el producto cumple los requisitos funcionales y no fun-
cionales antes de entregarlo al cliente para las pruebas de aceptación finales.
• Los métodos formales se basan en especificaciones formales y demostraciones
automáticas o búsqueda exhaustiva de estados para llegar más allá de lo que per-
miten verificar las pruebas. Pero son tan costosos que hoy en día sólo son aplicables
a partes pequeñas, estables y críticas de software o hardware.
• La figura 8.23 compara las tareas de prueba para procesos clásicos y ágiles.
Autoevaluación 8.9.1. Compare y señale las diferencias entre las estrategias de integración
descendente, ascendente y mixta (sándwich).
⇧ La estrategia de integración descendente requiere stubs para las pruebas, pero permite ha-
318 CAPÍTULO 8. PRUEBAS: DESARROLLO ORIENTADO A PRUEBAS
cerse una idea de cómo funciona la aplicación. La estrategia ascendente no requiere stubs,
pero potencialmente sí podría necesitar tener todo escrito para poder verlo funcionar. La es-
trategia mixta (o sándwich) se mueve entre ambos extremos, para beneficiarse de las ventajas
de ambos.
Falacia. Pasar todas las pruebas con un 100% de cobertura significa que no
STOP
hay errores.
Esta afirmación puede ser falsa por muchos motivos. Tener una cobertura de pruebas
total no dice nada sobre la calidad de las pruebas en sí. Asimismo, algunos errores requieren
pasar como argumento un determinado valor (por ejemplo, para provocar un error de división
por cero) y las pruebas del flujo de control a menudo no pueden detectar este tipo de fallos.
Puede haber errores en la interacción entre la aplicación y un servicio externo como TMDb;
simular el servicio con stubs para poder ejecutar las pruebas localmente puede enmascarar
esos problemas.
Error. Insistir dogmáticamente en lograr cobertura 100% y superar todas las
! pruebas (verde) antes de entregar.
Como vimos antes, un 100% de cobertura de pruebas no sólo es difícil de conseguir a
niveles mayores que C1, sino que tampoco garantiza la ausencia de errores incluso aunque
se alcance. La cobertura de pruebas es una herramienta útil para estimar hasta qué punto
es exhaustivo el banco de pruebas. Pero para conseguir un alto nivel de confianza se nece-
sitan diversos métodos de prueba —de integración además de unitarias, fuzzing además de
casos de prueba preparados a mano, cobertura de definición y uso (DU) además de flujo de
control, pruebas de mutación para detectar lagunas adicionales en la estrategia de pruebas,
etc. De hecho, en el capítulo 12 comentaremos aspectos operativos, como la seguridad y
el rendimiento, que requieren estrategias de prueba adicionales más allá de las orientadas a
comprobar la corrección descritas en este capítulo.
Falacia. No necesita mucho código de pruebas para tener confianza en la
STOP
aplicación.
Aunque insistir en una cobertura del 100% puede ser contraproducente, también lo es
irse al otro extremo. En sistemas de producción, la ratio código-pruebas (code-to-test ratio)
(líneas de código no comentadas divididas por líneas de pruebas de cualquier tipo) suele ser
menor que 1. Como caso extremo, la base de datos SQLite incluida en Rails contiene ¡más de
1200 veces más código de pruebas que de aplicación16 , por su gran variedad de formas de uso
y por la gran variedad de sistemas sobre los que debe funcionar! Pese a la controversia sobre
la utilidad de esta medida, dada la alta productividad de Ruby y su capacidad de eliminar
repeticiones (DRY) del código de pruebas, una ratio rake stats entre 0,2 y 0,5 supone un
objetivo razonable.
Error. Depender demasiado de un sólo tipo de pruebas (unitarias, fun-
! cionales, de integración).
Incluso un 100% de cobertura de pruebas unitarias no dice nada sobre las interacciones
entre las clases. Aún hay que crear tests para probar las interacciones entre las clases (pruebas
8.10. FALACIAS Y ERRORES COMUNES 319
funcionales o de módulo) y para probar caminos completos a través de la aplicación que tocan
muchas clases y cambian el estado en muchos puntos (pruebas de integración). Por otra parte,
las pruebas de integración sólo comprueban una minúscula fracción de todos los posibles
caminos de la aplicación. Por tanto, prueban sólo unos pocos comportamientos en cada
método, de modo que no sustituyen a una buena cobertura de pruebas unitarias para garantizar
que el código de bajo nivel funciona correctamente. Una regla común usada en Google y en
otras organizaciones (Whittaker et al. 2012) es “70–20–10”: 70% pruebas unitarias cortas y
específicas, 20% pruebas funcionales que tocan múltiples clases, 10% pruebas de integración.
Error. Puntos de integración insuficientemente probados debido al empleo
! abusivo de stubs.
El uso de mocks y stubs conlleva muchos beneficios, pero también puede ocultar poten-
ciales problemas en puntos de integración —puntos donde una clase o módulo interactúa con
otra—. Suponga que Movie interacciona con otra clase Moviegoer, pero para las pruebas
unitarias de Movie se simulan todas las llamadas a métodos de Moviegoer, y viceversa.
Puesto que los stubs están escritos para “simular” el comportamiento de la(s) clase(s) colabo-
radora(s), es imposible saber si Movie “sabe cómo hablar con” Moviegoer correctamente.
Una buena cobertura con pruebas funcionales y de integración, que no simulan todas las
llamadas entre clases, evita este problema.
En otras palabras: si escribimos primero el código y tenemos que arreglar los errores,
acabamos aplicando las mismas técnicas que con TDD pero de forma manual y menos efi-
ciente, por tanto, menos productiva.
Pero si usamos TDD, podemos detectar los errores inmediatamente según escribimos el
código. Si nuestro código funciona a la primera, usar TDD aún nos proporciona pruebas de
regresión para cazar los errores que podrían colarse en esta parte del código en el futuro.
Notas
1 http://www.cs.st-andrews.ac.uk/~ifs/Books/SE9/CaseStudies/MHCPMS/SupportingDocs/
MHCPMSCaseStudy.pdf
2 http://themoviedb.org
3 https://code.google.com/apis/console
4 http://en.wikipedia.org/wiki/Y2k
5 http://jmock.org/getting-started.html
6 https://github.com/thoughtbot/factory_girl_rails
7 http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
8 http://rubydoc.info/gems/rspec-expectations/frames
9 http://fakeweb.rubyforge.org
322 NOTAS
10 http://github.com/vcr
11 http://rspec.info
12 http://rspec.info
13 https://github.com/colszowka/simplecov
14 https://github.com/relevance/tarantula
15 http://www.fastcompany.com/magazine/06/writestuff.html
16 http://www.sqlite.org/testing.html
17 http://rspec.info
Ejercicio 8.2. Compare y señale las diferencias entre las estrategias de integración descen-
dente, ascendente y sándwich.
Ejercicio 8.3. Complete el “camino feliz”(happy path) del escenario Cucumber iniciado
en el capítulo 7 para recuperar la información de una película de TMDb. Para mantener
el escenario Independiente del servicio real TMDb, tendrá que descargar y usar la gema
FakeWeb para “simular” (con stubs) las llamadas al servicio TMDb.
Ejercicio 8.4. Escriba specs y código para probar el requisito implícito de que se devuelva
una colección vacía cuando se hace una petición con una clave de API válida pero no se
encuentran resultados en TMDb.
Ejercicio 8.5. En la sección 8.3, creamos un stub para el método find_in_tmdb, tanto
para aislar las pruebas del controlador de otras clases como porque el método aún no existía.
¿Cómo se podría manejar dicha simulación en Java?
Ejercicio 8.6. Basándose en el siguiente fichero de especificaciones (specfile), ¿a qué méto-
dos deberían responder las instancias de Foo para pasar las pruebas?
http://pastebin.com/CugB7gup
1 require ' foo '
2 describe Foo do
3 describe " a new foo " do
4 before : each do ; @foo = Foo . new ; end
5 it " should be a pain in the butt " do
6 @foo . should b e _ a _ p a i n _ i n _ t h e _ b u t t
7 end
8 it " should be awesome " do
9 @foo . should be_awesome
10 end
11 it " should not be nil " do
12 @foo . should_not be_nil
13 end
14 it " should not be the empty string " do
15 @foo . should_not == " "
16 end
17 end
18 end
especificación que verifique que el botón encamina a la acción correcta. Escriba dicha spec
en RSpec usando route_to y añádala a la especificación del controlador. Pista: Puesto que
esta ruta no se corresponde con una acción básica CRUD, no podrá usar los métodos helper
de URI tipo REST para especificar la ruta, pero puede usar los argumentos :controller y
:action de route_to para especificar la acción explícitamente.)
Ejercicio 8.8. Incremente la cobertura C0 de movies_controller.rb al 100% creando
specs adicionales en movies_controller_spec.rb.
Ejercicio 8.9. En 1999, la nave de 165 millones de dólares Mars Climate Orbiter se volatilizó
al entrar en la atmósfera de Marte porque uno de los equipos que trabajaba en el software
de los impulsores había utilizado unidades del sistema internacional (SI) mientras que otro
equipo, que trabajaba en otra parte distinta de dicho software, había utilizado unidades
imperiales. ¿Qué tipo de pruebas de corrección —unitarias, funcionales o de integración—
habrían sido necesarias para detectar este fallo?
Ejercicio 8.10. Ruby Rod acaba de introducir su nombre de usuario y contraseña y está
a punto de pulsar el botón “Iniciar sesión” de la aplicación Rails de Ben Bitdiddle. El
resultado esperado, si los datos de identificación son correctos, sería una página que muestre
“Bienvenido, Ruby Rod”.
Considere cada uno de los pasos que ocurren como resultado de esta interacción. Para
cada uno de ellos, determine si puede probarse mediante:
Ejercicio 8.11. En el área de la bahía de San Francisco, los usuarios del transporte público
pueden adquirir una tarjeta denominada Clipper que sirve como medio de pago para las
distintas empresas de transporte que actualmente tienen sus propios sistemas. Entre otras
cosas, se supone que calcula los descuentos al hacer transbordo, puesto que muchas empre-
sas tienen acuerdos de este tipo entre ellas. Sin embargo, cuando se desplegó por primera vez
había errores de software que a veces provocaban que dichos descuentos no se calcularan
correctamente. Aquí se presenta un escenario similar a uno que ocurrió en la realidad en
20111 . Dos de las reglas para calcular los descuentos entre empresas son:
3. Cuando sale de BART, menos de 90 minutos después, hace transbordo a otro autobús
Muni. No debería pagar nada, porque su billete Muni original es válido durante 90
minutos para cualquier autobús. Pero de hecho se le cobran $2.00 —la tarifa de un
billete Muni nuevo—.
Si el paso 3 hubiera ocurrido más de 90 minutos después del paso 1, sería correcto
cobrarle los $2.00. Use TDD y RSpec para desarrollar una estrategia de pruebas que hubiera
comprobado el comportamiento en ambos casos.
8.13. EJERCICIOS PROPUESTOS 325
Mantenimiento del software:
9 Mejora del software heredado
usando refactorización y métodos
ágiles
Butler Lampson Probablemente no hay una “mejor” manera de desarrollar un sistema, o incluso ninguna
(1943–) fue el líder parte importante del mismo; es mucho más importante evitar elegir una forma catastró-
intelectual del legendario fica, y tener una división clara de las responsabilidades entre los distintos componentes.
centro de investigación de
Xerox en Palo Alto (Xerox Butler Lampson, Hints for Computer System Design, 1983
PARC), que durante su
apogeo en los 70 inventó el
ordenador personal 9.1 Código heredado y metodología ágil . . . . . . . . . . . . . . . . . . 328
moderno, las interfaces de
usuario, la programación 9.2 Exploración de código heredado . . . . . . . . . . . . . . . . . . . . . 331
orientada a objetos, la 9.3 Realidad sobre el terreno y pruebas de caracterización . . . . . . . . 336
impresora láser, y Ethernet. 9.4 Comentarios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 339
Con el tiempo, tres
investigadores de PARC 9.5 Métricas, smells de código y SOFA . . . . . . . . . . . . . . . . . . . 340
ganaron premios Turing por 9.6 Refactorización a nivel de método . . . . . . . . . . . . . . . . . . . . 345
su trabajo allí. Lampson
9.7 La perspectiva clásica . . . . . . . . . . . . . . . . . . . . . . . . . . 351
recibió el Premio Turing en
1992 por su contribución al 9.8 Falacias y errores comunes . . . . . . . . . . . . . . . . . . . . . . . 356
desarrollo e implementación 9.9 Observaciones finales: refactorización continua . . . . . . . . . . . . 357
de entornos de computación
personal distribuida: 9.10 Para saber más . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 358
workstations, redes, 9.11 Ejercicios propuestos . . . . . . . . . . . . . . . . . . . . . . . . . . . 360
sistemas operativos,
sistemas de programación,
displays, seguridad y
publicación de información.
327
Conceptos
Al igual que un tiburón debe moverse para mantenerse con vida, el software debe ir
cambiando para mantenerse viable. Los principales conceptos de este capítulo son que
el desarrollo ágil es una buena estrategia para el mantenimiento del software y para
mejorar código heredado (legacy code), y que la refactorización es necesaria en todos
los procesos de desarrollo para hacer el código mantenible.
Al escribir código, las métricas software y los smells de código pueden identificar
código que es difícil de entender. Transformar el código a través de la refactorización
debería mejorar las métricas y eliminar el código propenso a errores. Los métodos
deben ser cortos (Short), hacer una única tarea (One thing), tener pocos argumentos
(Few arguments) y mantener un único nivel de abstracción (Abstraction) (SOFA).
Para mejorar código heredado utilizando el ciclo de vida ágil, se debe:
Legacy (Ch. 9)
Figura 9.1. El ciclo de vida ágil y sus relaciones con los capítulos de este libro. El capítulo presente se centra en cómo pueden
ser de ayuda las técnicas ágiles para mejorar aplicaciones heredadas.
Pruebas unitarias, funcionales y de integración muy Mensajes de registro de operaciones commit en Git
legibles (capítulo 8) (capítulo 10)
Mockups poco detallados e historias de usuario tipo Comentarios y documentación tipo RDoc embebida en
Cucumber (capítulo 7) el código (sección 9.4)
Fotografías de bocetos en pizarras sobre la arquitec- Correo electrónico archivado, notas en wiki/blog, no-
tura de la aplicación, relación entre clases, etc. (sec- tas o grabaciones en vídeo de revisiones de diseño y
ción 9.2) código, por ejemplo en Campfire2 o Basecamp3 (capí-
tulo 10)
Figura 9.2. A pesar del valor que representan los documentos de diseño actualizados, las metodologías ágiles sugieren que se
debe poner más el foco en documentación que sea “más cercana” al código.
Realizar estos tipos de mantenimiento en código heredado es una habilidad que se aprende
con la práctica: le proporcionaremos una gran variedad de técnicas que podrá utilizar, pero
nada sustituye la experiencia. Dicho esto, un componente fundamental de todas estas tareas
de mantenimiento es la refactorización, un proceso mediante el cual se modifica la estruc-
tura del código (con suerte, mejorándolo) sin cambiar la funcionalidad del mismo. El mensaje
principal de este capítulo es que la refactorización continua mejora la mantenibilidad. Es por
esto que gran parte de este capítulo se centrará en la refactorización.
Cualquier producto software, incluso bien diseñado, puede evolucionar finalmente más
allá de lo que su diseño original contemplaba. Este proceso conlleva desafíos de manteni-
bilidad, y uno de ellos es el reto de trabajar con código heredado. Algunos desarrolladores
utilizan el término “heredado” (legacy) cuando el código es poco legible debido a que los di-
señadores originales del mismo ya no están presentes y el software lleva acumulados muchos
parches sin reflejo en ningún documento de diseño. Un punto de vista que denota más hastío,
compartido por algunos profesionales experimentados, (Glass 2002), es que esos documentos
ni siquiera serían de utilidad. Una vez que comienza el desarrollo, los cambios necesarios en
el diseño provocan que el sistema se vaya apartando de los documentos originales de diseño,
que no se actualizan. En estos casos, los desarrolladores deben basarse en documentos de
diseño informales, como los que se enumeran en la figura 9.2.
¿Cómo podemos mejorar un software heredado sin una buena documentación? Tal y
como enuncia Michael Feathers en Working Effectively With Legacy Code (Feathers 2004),
hay dos formas de hacer cambios en un software ya existente: editar y rezar o cubrir y
modificar. Tristemente, el primero de los métodos es muy común: familiarizarse con una
pequeña parte del software que se debe modificar, editar el código, realizar comprobaciones
manuales para ver si se ha roto algo (aunque es complicado tener la certeza absoluta), para
finalmente desplegar el software y rezar para tener suerte.
En cambio, cubrir y modificar significa crear pruebas (si es que no existen aún) que
cubran el código que se va a modificar y utilizarlas como “red de seguridad” para detectar
330 CAPÍTULO 9. MANTENIMIENTO DE CÓDIGO HEREDADO
cambios no deseados de comportamiento que hayan sido provocados por las modificaciones
realizadas, del mismo modo que las pruebas de regresión detectan fallos en el código que
antes funcionaba correctamente. El punto de vista de “cubrir y modificar” conduce a la
definición más precisa de código heredado enunciada por Feathers, que es la que usaremos:
código que adolece de suficientes pruebas para ser modificado con fiabilidad, independiente-
mente de quién lo desarrolló y cuándo. En otras palabras, también puede considerarse código
heredado el código que fue escrito hace tres meses por uno mismo para otro proyecto y que
ahora se debe revisar y modificar.
Afortunadamente, las técnicas ágiles que ya hemos aprendido para desarrollar nuevo soft-
ware pueden ser de ayuda con código heredado. De hecho, la tarea de entender y evolucionar
código heredado puede verse como un ejemplo de “acoger el cambio” en plazos más amplios.
Si heredamos un software bien estructurado con pruebas exhaustivas, podremos usar BDD y
TDD para guiarnos al añadir funcionalidad en pasos pequeños pero fiables. Si heredamos un
código mal estructurado o incluso poco probado, necesitaremos ponernos en situación con
cuatro pasos:
1. Identificar los puntos de cambio, o sitios donde será necesario introducir modifica-
ciones en el sistema heredado. La sección 9.2 describe algunas técnicas de exploración
que pueden ser de utilidad, e introduce un tipo de diagrama UML (Unified Modeling
Language, Lenguaje Unificado de Modelado) que permite representar las relaciones
entre las principales clases de una aplicación.
2. Si es necesario, crear pruebas de caracterización que capturen el funcionamiento
del código, para establecer un punto de partida antes de hacer ningún cambio. La
sección 9.3 detalla cómo hacer esto utilizando herramientas que le serán familiares.
3. Determinar si los puntos de cambio exigen refactorizar para hacer más estable el
código existente o para incorporar los cambios requeridos, por ejemplo, rompiendo
dependencias que hacen que el código sea difícil de probar. La sección 9.6 introduce
algunas de las técnicas de los catálogos de refactorizaciones más utilizadas que han
evolucionado como parte del “movimiento” ágil.
4. Una vez que el código que rodea los puntos de cambio está refactorizado y cubierto por
sus correspondientes pruebas, realizar los cambios necesarios, utilizando las pruebas
creadas al efecto como pruebas de regresión, y añadir nuevas pruebas para el nuevo
código como se muestra en los capítulos 7 y 8.
9.2. EXPLORACIÓN DE CÓDIGO HEREDADO 331
• El trabajo con código heredado (legacy code) comienza con la exploración para
entender la estructura básica del código, y particularmente el código alrededor de
los puntos de cambio donde suponemos que haremos modificaciones.
• Sin unas buenas pruebas de cobertura, no podemos fiarnos de que la refactorización
o mejora del código preservará el comportamiento actual. Por tanto, es conveniente
adoptar la definición de Feathers —“código heredado es código sin pruebas”— y
generar pruebas de caracterización para reforzar la cobertura de código como paso
previo a la refactorización o mejora del código heredado.
Autoevaluación 9.1.1. ¿A qué se debe que muchos ingenieros de software crean que para
modificar código heredado, unas buenas pruebas de cobertura son más importantes que unos
documentos de diseño detallados o código bien estructurado?
⇧ Sin pruebas, no hay ninguna garantía de que los cambios introducidos en el código
heredado preserven los comportamientos existentes.
1. Crear una rama desde cero para ejecutar la aplicación en un entorno de desarrollo
2. Aprender y replicar las historias de usuario, trabajando con otros integrantes del
proyecto si es necesario
3. Examinar el esquema de la base de datos y las relaciones entre las clases más impor-
tantes
4. Echar una ojeada a todo el código para cuantificar su calidad y la cobertura de pruebas
existente.
• La aplicación puede tener relaciones del tipo tiene-muchos o pertenece-a, que estarán
reflejadas en las filas de las tablas. Sin conocer los detalles de dichas relaciones, podría
crearse un subconjunto de datos no válido. Poniendo RottenPotatoes como ejemplo,
podría terminar involuntariamente con un review cuyos movie_id y moviegoer_id
se refieran a películas o cinéfilos no existentes.
• Al clonar la base de datos se eliminan posibles diferencias de comportamiento entre
los entornos de producción y desarrollo por diferencias en las implementaciones de la
base de datos, diferencias en cómo se representan ciertos tipo de datos como las fechas
en las distintas bases de datos, etc.
• Al clonar los datos se dispone de datos válidos y reales para trabajar con ellos en
desarrollo.
En el caso de que no sea posible clonar la base de datos de producción, o que se hay
clonado satisfactoriamente pero sea demasiado grande para ser utilizada en el entorno de
desarrollo, se puede crear una base de datos de desarrollo extrayendo los fixtures de la base
de datos de producción6 mediante los pasos de la figura 9.3.
9.2. EXPLORACIÓN DE CÓDIGO HEREDADO 333
http://pastebin.com/gMFCF02W
1 # on production computer :
2 RAILS_ENV = production rake db : schema : dump
3 RAILS_ENV = production rake db : fixtures : extract
4 # copy db / schema . rb and test / fixtures /*. yml to development computer
5 # then , on development computer :
6 rake db : create # uses RAILS_ENV = development by default
7 rake db : schema : load
8 rake db : fixtures : load
Figura 9.3. Se puede crear una base de datos de desarrollo vacía que tenga el mismo esquema que la base de datos de
producción para a continuación introducir los fixtures. Aunque en el capítulo 8 se alerta contra el abuso en el uso de fixtures,
en este caso los usamos para replicar el comportamiento ya conocido desde el entorno de producción al entorno de
desarrollo.
Figura 9.4. El presente diagrama de clases simplificado en lenguaje UML, generado automáticamente con la gema
railroady, muestra el modelo de una aplicación Rails que gestiona venta de entradas, donaciones y asistencia a las
funciones de un pequeño teatro. Las flechas y líneas con círculos muestran relaciones entre clases: un Customer tiene varios
Visits y Vouchers (línea con círculo hueco y flecha), tiene una most_recent_visit (línea con círculo relleno y flecha), y
tiene y pertenece a varios Labels (línea con cabeza de flecha en ambas puntas). Las líneas simples muestran herencia:
Donation y Voucher son subclases de Item. (Todas las clases importantes aquí heredan de ActiveRecord::Base, pero
railroady sólo dibuja las clases de la aplicación). Veremos otros tipos de diagramas UML en el capítulo 11.
la compresión que inició con el diagrama de clases y el esquema de la base de datos. (Más
adelante en este capítulo le mostraremos cómo evaluar el código con algunas métricas de cali-
dad adicionales para proporcionarle una visión de dónde deberá hacer los mayores esfuerzos).
Si dispone de un conjunto de pruebas, ejecútelas; asumiendo que la mayoría de las pruebas
pasarán con éxito, lea las pruebas como ayuda para comprender las intenciones originales del
desarrollador. Después emplee una hora de tiempo (Nierstrasz et al. 2009) inspeccionando
las clases más importantes del código además de aquellas que crea que necesitará modificar
(los puntos de cambio), de los que ahora deberá tener una idea bastante certera.
• Una vez haya visto ejecutarse la aplicación en producción, los siguientes pasos son
tenerla en ejecución en desarrollo, clonando la base de datos o extrayendo los fix-
tures de la misma, y tener el conjunto de pruebas ejecutándose en desarrollo.
9.2. EXPLORACIÓN DE CÓDIGO HEREDADO 335
Figura 9.5. Una tarjeta de 3x5 pulgadas (o tamaño A7) Clase-Responsabilidad-Colaboración (CRC) que representa la clase
Voucher (recibo) de la figura 9.4. La columna izquierda muestra las responsabilidades de Voucher —cosas que “sabe”
(variables de instancia) o hace (métodos de instancia)—. Como en Ruby a las variables de instancia se accede siempre a
través de métodos de instancia, podemos determinar responsabilidades buscando métodos de instancia en el archivo de clase
voucher.rb y llamadas a attr_accessor. La columna derecha representa las clases colaboradoras con Voucher; en las
aplicaciones Rails se pueden determinar muchas de ellas buscando has_many y belongs_to en voucher.rb.
336 CAPÍTULO 9. MANTENIMIENTO DE CÓDIGO HEREDADO
Autoevaluación 9.2.1. ¿Cuáles son algunas de las razones por las que es importante tener
la aplicación corriendo en desarrollo incluso aunque no se planee hacer ningún cambio en
el código?
http://pastebin.com/fvDf8t31
1 # WARNING ! This code has a bug ! See text !
2 class TimeSetter
3 def self . convert ( d )
4 y = 1980
5 while ( d > 365) do
6 if ( y % 400 == 0 ||
7 ( y % 4 == 0 && y % 100 != 0) )
8 if ( d > 366)
9 d -= 366
10 y += 1
11 end
12 else
13 d -= 365
14 y += 1
15 end
16 end
17 return y
18 end
19 end
Figura 9.6. Este método es difícil de comprender, complicado de comprobar y, según la definición de código heredado de
Feathers, difícil de modificar. De hecho, contiene un fallo —este ejemplo es una versión simplificada de un error en el
reproductor de música de Microsoft Zune que provocó que cualquier reproductor iniciado el día 31 de diciembre de 2008 se
quedara colgado, y donde la única solución era esperar hasta el primer minuto del 1 de enero de 2009 antes de reiniciarlo—.
El screencast 9.3.1 muestra el bug y el parche.
De hecho, mientras que los buenos escenarios en definitiva hacen uso de un “lenguaje del
dominio” en vez de describir las interacciones del usuario en detalle en pasos imperativos
(ver la sección 7.9), llegados a este punto es correcto comenzar con escenarios imperativos,
ya que el objetivo es incrementar el porcentaje de código cubierto por pruebas y proporcionar
una “realidad sobre el terreno” a partir de la cual escribir más pruebas más detalladas. Una
vez se disponga de varias pruebas de integración en verde (satisfactorias), puede ponerse el
foco en pruebas unitarias o a nivel funcional, tal y como TDD sigue a BDD de fuera hacia
dentro en el ciclo de vida ágil.
Aunque las pruebas de caracterización a nivel de integración simplemente capturan com-
portamientos que observamos sin necesidad de entender cómo suceden dichos comportamien-
tos, una prueba de caracterización unitaria parece requerir que se comprenda su imple-
mentación. Por ejemplo, considérese el código de la figura 9.6. Como explicaremos en
detalle en la siguiente sección, adolece de varios problemas, entre otros el contener un fallo.
El método convert calcula el año actual dado un año inicial (en este caso 1980) y el número
de días que han pasado desde el 1 de enero de ese año. Si han pasado 0 días, entonces es 1
de enero de 1980; si han pasado 365 días, es 31 de diciembre de 1980; ya que 1980 fue un
año bisiesto; si han pasado 366 días, es 1 de enero de 1981; y así. ¿Cómo se crearían pruebas
unitarias para la función convert sin comprender la lógica del método en detalle?
Feathers describe una técnica muy útil para realizar “ingeniería inversa” sobre una parte
de código del que no comprendemos el funcionamiento y obtener así especificaciones (specs):
Se crea una especificación con una aserción que sabemos que probablemente falle, se ejecuta
la especificación, y se utiliza la información del mensaje de error para cambiar la especifi-
cación y ajustarse al comportamiento actual. El screencast 9.3.1 muestra cómo hacer esto
con el método convert, con lo que se obtiene la especificación de la figura 9.7, ¡y hasta se
consigue encontrar un bug en el proceso!
338 CAPÍTULO 9. MANTENIMIENTO DE CÓDIGO HEREDADO
http://pastebin.com/ZWb9QZRE
1 require ' simplecov '
2 SimpleCov . start
3 require ' ./ time_setter '
4 describe TimeSetter do
5 { 365 = > 1980 , 366 = > 1981 , 900 = > 1982 }. each_pair do | arg , result |
6 it " #{ arg } days puts us in #{ result } " do
7 TimeSetter . convert ( arg ) . should == result
8 end
9 end
10 end
Figura 9.7. Esta simple especificación, obtenida mediante la técnica de ingeniería inversa mostrada en el screencast 9.3.1,
alcanza una cobertura C0 del 100% y ayuda a encontrar un fallo en el código de la figura 9.6.
Autoevaluación 9.3.1. Indique si cada uno de los siguientes representan un objetivo de las
pruebas funcionales y unitarias, un objetivo de las pruebas de caracterización, o ambos:
i Mejorar la cobertura
ii Probar condiciones de valores límite
iii Documentar la intención y el comportamiento del código de la aplicación
iv Pruebas de regresión (reintroducción de fallos anteriores)
⇧ (i) y (iii) son objetivos de las pruebas unitarias, funcionales y de caracterización. (ii) y (iv)
9.4. COMENTARIOS 339
http://pastebin.com/c7FTpZxQ
1 # Add one to i .
2 i += 1
3
4 # Lock to protect against concurrent access .
5 mutex = SpinLock . new
6
7 # This method swaps the panels .
8 def swap_panels ( panel_1 , panel_2 )
9 # ...
10 end
Figura 9.8. Ejemplos de malos comentarios, que exponen obviedades. Sorprende la asiduidad de este tipo de comentarios
que imitan el código incluso en muchas aplicaciones (aplicaciones bien desarrolladas, por otra parte). (Estos ejemplos y los
consejos sobre los comentarios son de John Ousterhout).
son metas de las pruebas funcionales y unitarias, pero no de las pruebas de caracterización.
Explicación. ¿Qué hacer con las especificaciones (specs) que deberían pasar las pruebas,
pero no lo hacen?
Si el conjunto de pruebas está obsoleto, algunas pruebas podrían fallar (en rojo). En vez de
intentar corregir las pruebas antes de comprender el código, es preferible marcarlas como
“pendientes” (por ejemplo, utilizando el método pending de RSpec) con un comentario que
le recuerde volver a ellas más tarde para averiguar el porqué del fallo. Cíñase a la tarea
actual de preservar la funcionalidad existente mientras mejora la cobertura, y no se distraiga
intentando corregir errores por el camino.
9.4 Comentarios
Es muy común en código heredado que, además de adolecer de pruebas y buena docu-
mentación, los comentarios del código no existan o no sean consistentes con el código. Hasta
ahora no hemos explicado cómo escribir buenos comentarios, de la misma forma que asumi-
mos en el libro que usted ya sabe cómo escribir buen código. Ofrecemos aquí un breve apunte
sobre los comentarios, de forma que una vez escriba pruebas de caracterización satisfactorias,
pueda capturar lo aprendido añadiendo comentarios al código heredado.
Idealmente, se escriben los comentarios según se desarrolla el código; si se vuelve so-
bre el código más tarde se habrán olvidado las ideas de diseño, por lo que los comentarios
únicamente replicarán al código. Lamentablemente, este error es común en código heredado.
Los comentarios deben describir cosas que no son obvias a partir del código. Este consejo
es un arma de doble filo, ya que esto significa
• No repetir lo que ya es obvio a partir del código. La figura 9.8 muestra ejemplos de
malos comentarios.
• (Sí) pensar en lo que no es obvio tanto a bajo como a alto nivel. La figura 9.9 muestra
un ejemplo.
“Obvio” se refiere para alguien que lea el código en el futuro y que no sea el progra-
mador original. Ejemplos de cosas que no son obvias serían las unidades de las variables,
340 CAPÍTULO 9. MANTENIMIENTO DE CÓDIGO HEREDADO
http://pastebin.com/7PthRNCW
1 # Good Comment :
2 # Scan the array to see if the symbol exists
3
4 # Much better than :
5 # Loop through every array index , get the
6 # third value of the list in the content to
7 # determine if it has the symbol we are looking
8 # for . Set the result to the symbol if we
9 # find it .
Figura 9.9. Ejemplo de comentario que incrementa el nivel de abstracción comparado con comentarios que describen cómo
se ha implementado. (Estos ejemplos y los consejos sobre los comentarios son de John Ousterhout).
condiciones invariantes del código, y problemas sutiles que requieren de una implementación
concreta. Particularmente, es importante documentar las incidencias en el diseño que pasan
por la mente del programador al ir desarrollando su código, explicando por qué el código está
escrito de esa forma. En este caso se está intentando documentar lo que pasó por la mente de
otro programador; una vez que lo averigüe, ¡asegúrese de escribirlo antes de olvidarlo!
En general, los comentarios deben aumentar el nivel de abstracción respecto al código.
El objetivo de un programador es escribir clases y otro código que encapsule la complejidad;
es decir, hacer el código fácil de utilizar más que hacerlo fácil de escribir. La abstracción
puede no ser obvia a partir de la implementación; pero los comentarios deben capturarla. Por
ejemplo, ¿qué se necesita saber al invocar a un método determinado? No se debe tener que
leer el código de un método antes de poder llamarlo.
Una razón para encontrarnos entusiasmados como autores con el material de este libro
es que cualquier otro apunte sobre ingeniería del software viene acompañado con una herra-
mienta que facilita hacer las cosas correctamente y posibilita a otros el comprobar si se ha
apartado de ese camino. Lamentablemente, no es el caso para estas notas sobre los comen-
tarios. El único mecanismo de aplicación más allá de la autodisciplina es la revisión, descrita
en la sección 10.7.
Resumen sobre los comentarios:
Autoevaluación 9.4.1. Verdadero o falso: una razón por la que el código heredado es du-
radero es porque normalmente está bien comentado.
⇧ Falso. Ojalá fuera cierto. Los comentarios muchas veces faltan o son inconsistentes con el
propio código, lo que es un motivo para llamarlo heredado en vez de elegante.
13,14
y=1980 while… if (y%400) if d>366 end return
4 5 6,7 8 9,10 16 17
Figura 9.10. Los números que aparecen en los nodos de este grafo de control de flujo corresponden a números de línea de la
figura 9.6. La complejidad ciclomática es E N + 2P , donde E es el número de conectores, N el número de nodos y P
representa el número de componentes conectados. El método convert obtiene una puntuación de 4 en cuanto a complejidad
ciclomática según saikuro y 23 para la métrica ABC (Assignments —asignaciones—, Branches —ramificaciones—,
Conditionals —condicionales—) según flog. La figura 9.11 pone estos valores en contexto.
Uno de los conceptos clave de este libro es que la ingeniería del software trata sobre el
desarrollo no simplemente de código que funcione, sino de código elegante que funcione.
Este capítulo debe dejar claro por qué creemos esto: un código elegante es más fácil y más
barato de mantener. Dado que el software puede durar mucho más que el hardware, incluso
los ingenieros pueden apreciar la ventaja económica y práctica de reducir los costes de mante-
nimiento aunque la idea de código elegante no consiga despertar sus sensibilidades estéticas.
¿Cuándo se puede decir que el código no es tan elegante, y cómo se puede mejorar éste?
Todos hemos visto ejemplos de código no elegante, incluso aunque no seamos capaces de
precisar qué problemas específicos presenta. Podemos identificar problemas en dos sentidos:
cuantitativamente usando métricas del software y cualitativamente a través del smells de
código. Ambos son útiles y cuentan cosas diferentes sobre el código, y aplicamos ambos al
código desagradable de la figura 9.6.
Las métricas del software son medidas cuantitativas de la complejidad del código, lo
que habitualmente representa una estimación de lo difícil que es probar meticulosamente un
trozo de código. Existen docenas de métricas, y opiniones muy dispares sobre su utilidad,
efectividad y “rango normal” de valores. La mayoría de las métricas se sustentan en el grafo Los proyectos software de
de control de flujo del programa, en el que cada nodo del gráfico representa un bloque ciclo de vida clásico a veces
básico (un conjunto de sentencias que siempre se ejecutan juntas). Una línea desde el nodo incluyen requisitos
contractuales específicos
A al nodo B significa que hay algún flujo en el código en el que el bloque básico B se ejecuta basados en métricas
inmediatamente después del bloque A. software.
La figura 9.10 muestra el gráfico de control de flujo correspondiente a la figura 9.6, que
podemos utilizar para calcular dos indicadores muy usados de complejidad a nivel de método:
1. Complejidad ciclomática mide el número de caminos independientes dentro de un
El ingeniero de software
trozo de código. Frank McCabe Sr. inventó la
métrica de complejidad
2. Métrica ABC es una suma ponderada del número de asignaciones (Assignments), ciclomática en 1976.
ramificaciones (Branches) y condicionales (Conditionals) de un trozo de código.
Estos análisis suelen realizarse sobre el código fuente y fueron desarrollados para lengua-
jes de tipado estático en un principio. Con los lenguajes de tipado dinámico, los análisis
se complican debido a la metaprogramación y otros mecanismos que pueden provocar cam-
bios en el grafo de control de flujo en tiempo de ejecución. Sin embargo, son métricas de
primer orden muy útiles y, como era de esperar, la comunidad Ruby ha desarrollado herra-
mientas para poder calcularlas. saikuro calcula una versión simplificada de la complejidad
342 CAPÍTULO 9. MANTENIMIENTO DE CÓDIGO HEREDADO
Figura 9.11. Compendio de varias métricas útiles que ya hemos visto y que resaltan la conexión existente entre código
elegante y facilidad para probar el mismo, incluyendo las herramientas Ruby que las calculan y sus rangos “normales”
sugeridos. (El valor recomendado para la complejidad ciclomática ha sido definido por el NIST, el Instituto Nacional
americano de estándares y tecnologías). La gema metric_fu incluye flog, saikuro y herramientas adicionales para el
cálculo de métricas expuestas en el capítulo 11.
ciclomática y flog proporciona una variante de la métrica ABC ponderada de forma apro-
piada para las expresiones Ruby. La gema metric_fu (parte del software del curso) incluye
ambos y muchos otros más. Al ejecutar rake metrics en una aplicación Rails, se calculan
varias métricas, incluyendo éstas, y se resaltan las partes del código para las que varias métri-
cas se encuentran fuera de sus respectivos rangos recomendados. Además, la página web de
CodeClimate9 proporciona muchas de estas métricas como servicio: basta con crearse una
cuenta en el sitio y asociarle un repositorio de GitHub para poder obtener un informe de
sus métricas de código en cualquier momento. Dicho informe se actualiza automáticamente
al subir nuevo código al repositorio GitHub. La figura 9.11 resume las métricas útiles que
ya hemos visto relacionadas con la facilidad de las pruebas y por tanto con la elegancia del
código.
El segundo método para reconocer problemas en el código es buscar smells de código,
que son características estructurales del código fuente que no son detectadas fácilmente por
las métricas. Al igual que los malos olores de verdad (smells), los smells de código deben
Los smells de diseño
hacer prestar atención a los sitios que podrían ser problemáticos. El clásico de Martin Fowler
(ver capítulo 11) nos indican
que algo funciona mal en la sobre la refactorización (Fowler et al. 1999) enumera 22 smells de código, de los cuales
forma de interaccionar de cuatro se muestran en la figura 9.12, y el libro Clean Code (Martin 2008) de Robert C. Martin
las clases, en lugar de presenta uno de los catálogos más exhaustivos con unos sorprendentes 63 smells de código,
referirse a lo que ocurre
dentro de los métodos de
de los que tres son específicos de Java, nueve están relacionados con las pruebas y el resto
una clase en particular. son más genéricos.
Es importante destacar cuatro tipos de smells de código en particular que aparecen en
Clean Code, porque representan síntomas de otros problemas que habitualmente pueden solu-
cionarse con refactorizaciones simples. Estos cuatro se identifican por el acrónimo inglés
SOFA, que especifica que un método bien escrito debe:
• realizar una única tarea (only One thing), para que las pruebas se puedan concentrar en
esa única tarea;
• tener pocos argumentos (Few arguments), de manera que se puedan probar todas las
posibles combinaciones de valores para los argumentos;
Figura 9.12. Cuatro tipos smells de código con nombres curiosos de la lista de 22 de Fowler, acompañados de las
refactorizaciones (de las que veremos algunas en la próxima sección) que podrían ponerles remedio si se aplican. Refiérase
al libro de Fowler para las refactorizaciones mencionadas en la tabla pero no explicadas en este libro.
Figura 9.13. Algunas pautas para nombres de variables basados en lenguaje simple, extraído de Green and Ledgard 2011.
Dado que el espacio en disco es casi gratis y los editores modernos poseen la característica de autocompletado que evita
tener que reescribir el nombre completo, sus colegas le agradecerán que escriba @es_ganador_oscar en vez de GanOs.
http://pastebin.com/ybbRJHG0
1 time_setter . rb -- 5 warnings :
2 TimeSetter # self . convert calls ( y + 1) twice ( Duplication )
3 TimeSetter # self . convert has approx 6 statements ( LongMethod )
4 TimeSetter # self . convert has the parameter name 'd ' ( U n c o m m u n i c a t i v e N a m e )
5 TimeSetter # self . convert has the variable name 'd ' ( U n c o m m u n i c a t i v e N a m e )
6 TimeSetter # self . convert has the variable name 'y ' ( U n c o m m u n i c a t i v e N a m e )
No es DRY (línea 2). Ciertamente es sólo un pequeño duplicado, pero como cualquier
smell, merece la pena cuestionarse por qué el código se implementó así.
Nombres poco descriptivos (líneas 4–6). La variable y parece ser un entero (líneas 6,
7, 10, 14) y está relacionada con otra variable d —¿Ésta qué puede ser?— En este sentido,
¿qué hace la clase TimeSetter para establecer la fecha, y qué se convierte a qué en convert?
Hace cuatro décadas, la memoria era un recurso escaso y por ello los nombres de las variables
se acortaban para tener más espacio para el código. Hoy no hay excusa para tener nombres
de variables así; la figura 9.13 proporciona varias sugerencias.
Demasiado largo (línea 3). Más líneas de código por método significa más lugares para
tener fallos escondidos, más caminos por probar, y más objetos simulados —mocks— a usar
en las pruebas. Sin embargo, una longitud excesiva es realmente un síntoma que aparece
debido a otros problemas más específicos —en este caso, el no conseguir aferrarse a un
344 CAPÍTULO 9. MANTENIMIENTO DE CÓDIGO HEREDADO
http://pastebin.com/xP9B1iEy
1 start with Year = 1980
2 while ( days remaining > 365)
3 if Year is a leap year
4 then if possible , peel off 366 days and advance Year by 1
5 else
6 peel off 365 days and advance Year by 1
7 return Year
Figura 9.14. El cálculo del año actual dado el número de días desde el inicio del año de comienzo (1980) se ve mucho más
claro cuando está escrito en pseudocódigo. Vea que rápidamente se entiende lo que hace el método, incluso aunque cada paso
deba dividirse en más detalle cuando se implemente en código. Refactorizaremos el código Ruby para plasmar lo claro y
conciso que es este pseudocódigo.
único nivel de Abstracción—. Como muestra la figura 9.14, el método convert en realidad
La antigua sabiduría
consiste en un número pequeño de pasos de alto nivel, cada uno de los cuales se puede
sobre que la longitud de un
método no debe exceder el dividir en subpasos. Pero en el código no hay modo de detallar dónde se encontrarían las
alto de una pantalla fronteras entre pasos y subpasos, lo que hace que el método sea complicado de entender. De
completa estaba basada en hecho, la sentencia condicional anidada de las líneas 6 a 8 dificulta al programador “pasear”
terminales de texto con 24
líneas de 80 caracteres
mentalmente por el código, y complica sobremanera las pruebas al tener que seleccionar
cada una. Un monitor conjuntos de casos de prueba que ejerciten cada uno de los distintos caminos.
moderno de 22 pulgadas Como resultado de todas estas deficiencias, probablemente sea una labor ardua el
muestra 10 veces más averiguar qué hace este método (un método relativamente simple, por otro lado). (Podría
líneas, así que directrices
como SOFA son más fiables
culpar de ello a la falta de comentarios en el código, pero una vez corregidos los smells de
hoy en día. este código, apenas habrá necesidad de ellos). Normalmente, el lector avispado verá las cons-
tantes 1980, 365 y 366 y deducirá que el método está relacionado con los años bisiestos y
que 1980 es un año especial. De hecho, convert calcula el año actual dado 1980 como año
de inicio y el número de días transcurridos desde el 1 de enero de dicho año, tal y como
muestra la figura 9.14, utilizando simple pseudocódigo. En la sección 9.5, se transformará
el código Ruby en algo tan transparente como lo es su pseudocódigo a través de la refacto-
rización del mismo —aplicando transformaciones que mejoren su estructura sin cambiar su
comportamiento—.
Resumen
• Las métricas software proporcionan una medida cuantitativa de la calidad del
código. Aunque existen opiniones variadas acerca de qué métricas son las más útiles
y cuáles deben ser sus valores “normales” (especialmente en lenguajes de tipado
dinámico como Ruby), métricas como la complejidad ciclomática y la métrica ABC
pueden utilizarse para guiarle hacia código que requiere atención en particular, al
igual que una cobertura C0 pobre identifica código poco probado.
• Los smells de código proporcionan descripciones cualitativas pero muy específicas
de problemas que hacen que el código sea difícil de leer. Dependiendo del catálogo
que se use, hay identificadas más de 60 deficiencias específicas.
• El acrónimo SOFA expone cuatro propiedades deseables de un método: debe ser
corto (Short), realizar una única tarea (do One thing), tener pocos argumentos (Few
arguments) y mantener un único nivel de abstracción (Abstraction).
9.6. REFACTORIZACIÓN A NIVEL DE MÉTODO 345
Autoevaluación 9.5.2. ¿Qué pauta de SOFA —ser corto, realizar una única tarea, tener
pocos argumentos, atenerse a un único nivel de abstracción— piensa que es la más impor-
tante desde el punto de vista de pruebas unitarias?
⇧ Menos argumentos implica menos formas de que los caminos del método puedan depen-
der de los argumentos, lo que hace las pruebas más manejables. Ciertamente, los métodos
cortos son más fáciles de probar, pero esta propiedad normalmente es una consecuencia del
cumplimiento de las otras tres.
Figura 9.15. Cuatro ejemplos de refactorizaciones, con el capítulo entre paréntesis en el que aparece cada una de ellas en el
libro de Fowler. Cada refactorización tiene un nombre, una problemática que resuelve y un resumen de la(s)
transformación(es) que solucionan el problema. El libro de Fowler incluye también los pasos mecánicos de cada
refactorización, tal y como muestra la figura 9.16.
Al principio la refactorización puede parecer algo abrumador; sin conocer qué tipos de
refactorización existen, es difícil decidir cómo mejorar un trozo de código. Hasta que no se
tenga cierta experiencia mejorando partes de código, puede hacerse complicado comprender
las explicaciones de dichas refactorizaciones o las motivaciones de por qué usarlas. No se
desanime por este problema similar al del huevo y la gallina; al igual que ocurre con TDD y
BDD, lo que parece sobrecogedor en un principio puede volverse familiar rápidamente.
Para comenzar, la figura 9.15 muestra cuatro refactorizaciones de Fowler que aplicaremos
a nuestro código. En su libro, cada refactorización está acompañada de un ejemplo y una
lista exhaustiva de pasos mecánicos para llevar a cabo el proceso, en algunos casos refirién-
dose a otras refactorizaciones que pueden ser necesarias para realizar ésta. Por ejemplo, la
figura 9.16 muestra los primeros pasos para aplicar la refactorización de extracción de método
(Extract Method). Teniendo estos ejemplos en mente, se puede refactorizar la figura 9.6.
El smell más obvio del código de la figura 9.6 es ser un método largo, pero es sólo un
síntoma genérico al que contribuyen varios problemas específicos. La puntuación alta según
la métrica ABC (23) del método convert sugiere un sitio inicial donde fijar la atención: la
condición del if de las líneas 6 y 7 es díficil de comprender, y se trata de una sentencia
condicional anidada a otra anterior. Tal y como sugiere la figura 9.15, una expresión condi-
cional complicada de leer puede mejorarse aplicando la refactorización de descomposición
de condicional (Decompose Conditional), que de hecho se basa en la extracción de método.
Tal y como muestra la figura 9.17, movemos una parte del código a un nuevo método con un
nombre descriptivo. Observe que además de aumentar la legibilidad de la sentencia condi-
cional, la definición separada del cálculo de año bisiesto leap_year? hace que dicho cálculo
se pueda probar por separado, y proporciona un seam en la línea 6 donde podemos simular
dicho método para simplificar las pruebas de convert, de forma análoga al ejemplo de la
explicación al término de la sección 8.6. En general, cuando un método mezcla código que
dice qué hacer con código que dice cómo hacerlo, debería ser una alerta para comprobar si se
necesita utilizar la extracción de método para mantener un nivel de Abstracción consistente.
Además, la sentencia condicional está anidada en dos niveles, lo que la hace difícil de
entender y aumenta la puntuación de convert según la métrica ABC. La refactorización de
descomposición de condicional rompe también esta condición compleja sustituyendo cada
parte del mismo con un método. Obsérvese, sin embargo, que ambas partes del condicional
9.6. REFACTORIZACIÓN A NIVEL DE MÉTODO 347
1. Cree un método nuevo, y póngale un nombre basándose en el propósito del método (nómbrelo según lo que
hace, y no según cómo lo hace). Si el código que se desea extrar es muy simple, como un mensaje aislado
o una llamada a una función, se debe realizar la extracción si el nombre del nuevo método describe mejor la
intención del código. Si no es capaz de ponerle un nombre más elocuente, no extraiga el código.
2. Copie el código extraído desde el método origen al método destino.
3. Busque referencias en el código extraído a cualquier variable que sea local en el ámbito del método origen:
variables locales y parámetros del método.
4. Compruebe si hay alguna(s) variable(s) temporal(es) que se utilice(n) únicamente en el código extraído. En
caso afirmativo, declárela(s) en el método destino como variable(s) temporal(es).
5. Compruebe si el código extraído modifica alguna de estas variables de ámbito local. Si se modifica alguna
variable, analice si puede tratar el código extraído como una consulta y asignar el resultado a la variable
en cuestión. Si esto resulta tedioso o hay más de una variable modificada, entonces no se puede extraer el
método tal y como está. Puede que necesite dividir variable temporal (Split Temporary Variable) y volver
a intentarlo. Puede eliminar variables temporales con sustituir temporal por consulta (Replace Temp with
Query) (vea la discusión en los ejemplos).
6. Pase como parámetros del método destino las variables locales usadas (lectura) en el método extraído.
7. . . .
Figura 9.16. Los pasos detallados de Fowler para la refactorización de extracción de método. En su libro, cada proceso de
refactorización se describe como una transformación paso a paso del código que puede hacer referencia a otras
refactorizaciones.
http://pastebin.com/N90nw3bu
1 # NOTE : line 7 fixes bug in original version
2 class TimeSetter
3 def self . convert ( d )
4 y = 1980
5 while ( d > 365) do
6 if leap_year ?( y )
7 if ( d >= 366)
8 d -= 366
9 y += 1
10 end
11 else
12 d -= 365
13 y += 1
14 end
15 end
16 return y
17 end
18 private
19 def self . leap_year ?( year )
20 year % 400 == 0 ||
21 ( year % 4 == 0 && year % 100 != 0)
22 end
23 end
Figura 9.17. Al aplicar la extracción de método a las líneas 3 y 4 de la figura 9.6, el propósito de la sentencia condicional
queda claro de inmediato (línea 6) al sustituir la condición por un método con nombre descriptivo (líneas 19 a 22), que
declaramos como private para mantener encapsulados los detalles de implementación de la clase. En aras de alcanzar aún
una mayor transparencia, podríamos aplicar nuevamente la extracción de método a leap_year? para extraer métodos
como every_400_years? y every_4_years_except_centuries?. Nota: La línea 7 evidencia el arreglo del fallo descrito
en el screencast 9.3.1.
348 CAPÍTULO 9. MANTENIMIENTO DE CÓDIGO HEREDADO
http://pastebin.com/gdT1DzjG
1 # NOTE : line 7 fixes bug in original version
2 class TimeSetter
3 ORIGIN_YEAR = 1980
4 def self . c a l c u l a t e _ c u r r e n t _ y e a r ( d a y s _ s i n c e _ o r i g i n )
5 @@year = ORIGIN_YEAR
6 @@days_remaining = days_since_origin
7 while ( @ @ d a y s _ r e m a i n i n g > 365) do
8 if leap_year ?
9 peel_off_leap_year !
10 else
11 peel_off_regular_year !
12 end
13 end
14 return @@year
15 end
16 private
17 def self . p e e l _ o f f _ l e a p _ y e a r !
18 if ( @ @ d a y s _ r e m a i n i n g >= 366)
19 @ @ d a y s _ r e m a i n i n g -= 366 ; @@year += 1
20 end
21 end
22 def self . p e e l _ o f f _ r e g u l a r _ y e a r !
23 @ @ d a y s _ r e m a i n i n g -= 365 ; @@year += 1
24 end
25 def self . leap_year ?
26 @@year % 400 == 0 ||
27 ( @@year % 4 == 0 && @@year % 100 != 0)
28 end
29 end
Figura 9.18. Reemplazando cada parte de la sentencia condicional por un método extraído, se descompone la sentencia de
la línea 7. Puede verse que a pesar de que el número total de líneas de código ha aumentado, el método convert es ahora
más corto (Shorter), y sus pasos se corresponden ahora fielmente con el pseudocódigo de la figura 9.14, aferrándose a un
único nivel de Abstracción a la vez que delega los detalles a los métodos helper extraídos.
http://pastebin.com/V9pfQtkg
1 # An example call would now be :
2 # year = TimeSetter . new (367) . c a l c u l a t e _ c u r r e n t _ y e a r
3 # rather than :
4 # year = TimeSetter . c a l c u l a t e _ c u r r e n t _ y e a r (367)
5 class TimeSetter
6 ORIGIN_YEAR = 1980
7 def initialize ( d a y s _ s i n c e _ o r i g i n )
8 @year = ORIGIN_YEAR
9 @ da ys _ re ma i ni n g = d a y s _ s i n c e _ o r i g i n
10 end
11 def c a l c u l a t e _ c u r r e n t _ y e a r
12 while ( @ da ys _ re m ai ni n g > 365) do
13 if leap_year ?
14 peel_off_leap_year !
15 else
16 peel_off_regular_year !
17 end
18 end
19 return @year
20 end
21 private
22 def p e e l _ o f f _ l e a p _ y e a r !
23 if ( @d a ys _ re ma i ni ng >= 366)
24 @ da ys _ re m ai ni n g -= 366 ; @year += 1
25 end
26 end
27 def p e e l _ o f f _ r e g u l a r _ y e a r !
28 @ da ys _ re ma i ni n g -= 365 ; @year += 1
29 end
30 def leap_year ?
31 @year % 400 == 0 ||
32 ( @year % 4 == 0 && @year % 100 != 0)
33 end
34 end
Figura 9.19. Si tomamos en cuenta las recomendaciones de Fowler para refactorizar, el código es mucho más claro ya que
ahora se utilizan variables de instancia en vez de variables de clase para registrar efectos colaterales, pero también cambia la
forma en la que se invoca a calculate_current_year, porque ahora se ha convertido en un método de instancia. Esto podría
romper el código y las pruebas existentes, por lo que podría aplazarse hasta el final dentro del proceso de refactorización.
ter.convert. Pero al aplicar sustituir método por objeto, la interfaz para llamar a con-
vert cambia, de forma que provoca que las pruebas fallen. Si estuviéramos trabajando
con código heredado de verdad, deberíamos localizar todas las ocurrencias de llamadas a
convert, cambiarlas para utilizar la nueva interfaz de llamada y cambiar consecuentemente
cualquier prueba que fallase. En un proyecto real, queremos evitar cambios que rompan in-
necesariamente las interfaces, por lo que se debería considerar cuidadosamente si lo ganado
en legibilidad al aplicar esta refactorización compensa el riesgo de introducir este cambio en
la interfaz.
Resumen de refactorización:
mantiene todas las modificaciones de cualquier objeto, como detallamos en las secciones 10.4
y 10.5.
Los párrafos anteriores deberían resultar familiares, ya que estamos describiendo el de-
sarrollo ágil; de hecho, los tres puntos anteriores pertenecen al Manifiesto Ágil (ver la sec-
ción 1.3). Esto es, el mantenimiento es, en esencia, un proceso ágil. Las peticiones de cambio
se parecen a las historias de usuario; la clasificación de estas peticiones es similar a la asig-
nación de puntos y el uso de Pivotal Tracker para decidir cómo priorizar las historias; y las
nuevas versiones del producto software serían como las iteraciones ágiles del prototipo. El
mantenimiento en las metodologías clásicas sigue incluso la misma estrategia de dividir una
petición de cambio extensa en peticiones más pequeñas para facilitar su evaluación e imple-
mentación, de la misma forma que se hace con las historias de usuario puntuadas con más de
ocho puntos (ver la sección 7.2). Por tanto, si es el mismo equipo quien desarrolla y mantiene
el software, nada cambia con el ciclo de vida ágil al liberar la primera versión del software.
A pesar de que existe un artículo que detalla cómo se mantuvo con éxito mediante pro-
cesos ágiles un software que fue desarrollado utilizando metodologías clásicas (Poole and
Huisman 2001), lo común es que las compañías que usan metodologías clásicas para el de-
sarrollo las usen también para el soporte. Como hemos visto en anteriores capítulos, este
proceso requiere de un jefe de proyecto cualificado que se encargue de la estimación de
costes, desarrolle la planificación, reduzca los riesgos inherentes al proyecto y diseñe un plan
meticuloso para todas las piezas del proyecto. Este plan se refleja en varios documentos, que
vimos en las figuras 7.13 y 8.22 y que veremos en el siguiente capítulo en las figuras 10.9,
10.10 y 10.11. Por tanto, el impacto de un cambio en los procesos clásicos no contempla sólo
el cambio en código, sino también los cambios en la documentación y el plan de pruebas.
Dado que hay más objetos, supone un mayor esfuerzo sincronizarlos todos para mantener la
consistencia cuando se realiza un cambio.
Un comité de control de cambios examina todas las peticiones importantes para decidir
si la siguiente versión del sistema debe incluir estos cambios. Este grupo necesita tener es-
timaciones del coste de cada cambio para decidir si aprobar o no la petición. El jefe de
mantenimiento debe estimar el esfuerzo y el tiempo para implementar cada cambio, como el
jefe de proyecto hizo inicialmente para el proyecto (ver la sección 7.10). El comité también
debe consultar con el equipo de calidad el coste de las pruebas, incluyendo la ejecución de
todas las pruebas de regresión y el desarrollo de nuevos casos de pruebas (si son necesa-
rios) para cada cambio. El grupo que documenta también estimará el coste de modificar la
documentación. Finalmente, el grupo de soporte al cliente comprueba si existe una solución
alternativa para decidir si el cambio es urgente o no. Además del coste, el comité considerará
el valor añadido que aporta el cambio al tomar una decisión.
El lector no se verá sorprendido al conocer que el IEEE ofrece estándares para ayudar a
realizar el seguimiento de las tareas a realizar según los procesos clásicos.
Idealmente, todos los cambios se pueden planificar para mantener sincronizados el
código, documentos y planes de pruebas con el lanzamiento de la nueva versión. Lamentable-
mente, algunos cambios son tan urgentes que cualquier otra cosa se aparca para intentar que
el cliente disponga de la nueva versión cuanto antes. Por ejemplo:
• El software deja de funcionar.
Figura 9.20. Relaciones entre las tareas relativas al mantenimiento en las metodologías clásicas frente a las ágiles.
Tabla de contenidos
1. Introducción
2. Referencias
3. Definiciones
4. Introducción al mantenimiento de software
4.1 Organización
4.2 Prioridades de planificación
4.3 Resumen de recursos
4.4 Responsabilidades
4.5 Herramientas, técnicas y métodos
5. Proceso de mantenimiento software
5.1 Identificación, clasificación y priorización del problema
5.2 Análisis
5.3 Diseño
5.4 Implementación
5.5 Pruebas del sistema
5.6 Pruebas de aceptación
5.7 Entrega
6. Requisitos de comunicación del mantenimiento de software
7. Requisitos administrativos del mantenimiento de software
7.1 Resolución de anomalías y comunicación
7.2 Política de incumplimiento
7.3 Procedimientos de control
7.4 Estándares, prácticas y convenios
7.5 Auditoría del rendimiento
7.6 Plan de control de calidad
8. Requisitos de documentación del mantenimiento de software
Figura 9.21. Esquema del plan de mantenimiento, tomado del estándar para el mantenimiento de sistemas e ingeniería del
software del IEEE 1219-1998.
354 CAPÍTULO 9. MANTENIMIENTO DE CÓDIGO HEREDADO
• Nuevas versiones del sistema operativo o de librerías que provocan cambios en el soft-
ware para que éste siga funcionando.
• Un competidor lanza al mercado un producto o una funcionalidad que en caso de no in-
corporarla en el software podría afectar gravemente la relación comercial con el cliente.
• Se aprueban nuevas leyes que afectan al software.
Backfilling es el término
utilizado por los ingenieros A pesar de que se asume que el equipo actualizará la documentación y planes de prueba
de soporte para describir el tan pronto como la emergencia haya sido resuelta, en la práctica las urgencias pueden ser
proceso de resincronización
después de una tan frecuentes que el equipo de soporte no pueda tener todo sincronizado. La acumulación
emergencia. de estas actualizaciones pendientes es lo que se conoce como deuda técnica. Este tipo
de procrastinación puede derivar en una dificultad creciente para mantener el código, lo que
conlleva una mayor necesidad de refactorizarlo a medida que la “viscosidad” del código hace
más y más difícil el añadir funcionalidades de forma limpia. Mientras que la refactorización
es una parte natural de la metodología ágil, es mucho menos común que el comité de con-
trol de cambios apruebe modificaciones que requieran refactorizaciones, debido a que estos
cambios son mucho más caros. Es decir —tal y como intenta expresar el término— si no
se reintegra la deuda técnica, ésta crece: ¡cuánto “menos elegante” se vuelve el código, más
propenso a errores y más tiempo será necesario para refactorizar!
Aparte de realizar la estimación de costes para cada cambio potencial en el software para
el comité de control de cambios, cabe preguntarse también por el coste anual del manteni-
miento de un proyecto. El jefe de mantenimiento puede basar esta estimación en métricas
software, del mismo modo que un jefe de proyecto puede usar métricas para estimar el coste
de desarrollo de un proyecto (ver la sección 7.10). Las métricas son diferentes en la fase de
mantenimiento, porque están midiendo el proceso de mantenimiento. El tiempo medio para
analizar o implementar una petición de cambio y un incremento en el número de peticiones
de cambio realizadas y/o aprobadas son ejemplos de métricas que pueden indicar la dificultad
creciente del mantenimiento.
En algún punto en el ciclo de vida de un producto software surge la pregunta sobre si ha
llegado el momento de sustituirlo. Una alternativa relacionada con la refactorización es la lla-
mada reingeniería. Al igual que con la refactorización, la idea es mantener la funcionalidad
intacta a la vez que el código se hace más fácil de mantener. Por ejemplo:
Resumen: La idea de esta sección es que se pueda ver la metodología ágil como un
proceso de mantenimiento, donde el cambio es la norma, se está en contacto continuo
con el cliente y las nuevas iteraciones del producto se ponen a disposición de los usuarios
periódicamente como nuevas versiones. De ahí que las pruebas de regresión y la refacto-
rización sean lo normal dentro del proceso ágil como lo son en la fase de mantenimiento
de las metodologías clásicas. En los ciclos de vida clásicos:
• Los clientes y otros colaboradores remiten peticiones de cambio que son priori-
zadas por un comité de control de cambios basándose en el beneficio de ese cambio
y las estimaciones de costes realizadas por el jefe de mantenimiento, el equipo de
documentación y el de aseguramiento de la calidad.
• Las pruebas de regresión desempeñan un papel importante para asegurar que las
nuevas funcionalidades no interfieren con las antiguas.
• La refactorización juega también un papel crucial, en parte debido a que es menos
común refactorizar en la fase de desarrollo de los ciclos clásicos que en los desarro-
llos ágiles.
• Una alternativa a tener que empezar de nuevo cuando el código se vuelve cada vez
más inmanejable es la reingeniería del mismo para rebajar los costes, al tener un
sistema que es más fácilmente mantenible.
Un argumento a favor del desarrollo ágil es el siguiente: si dos tercios del coste de un pro-
ducto recaen en la fase de mantenimiento, ¿por qué no usar el mismo proceso de desarrollo
del software (que es compatible con el proceso de mantenimiento) para todo el ciclo de
vida?
Autoevaluación 9.7.1. Verdadero o falso: el coste del mantenimiento suele ser mayor que el
coste de desarrollo.
⇧ Verdadero.
Autoevaluación 9.7.2. Verdadero o falso: refactorización y reingeniería son sinónimos.
⇧ Falso. A pesar de ser términos relacionados entre sí, la reingeniería suele depender de
herramientas automáticas y sobreviene a medida que el software envejece y el mantenimiento
se vuelve más difícil, mientras que refactorizar es un proceso continuo de mejora del código
que se lleva a cabo tanto en la fase de desarrollo como en la de mantenimiento.
Autoevaluación 9.7.3. Establezca la correspondencia entre los términos de
la metodología clásica (izquierda) y los de la metodología ágil (derecha):
356 CAPÍTULO 9. MANTENIMIENTO DE CÓDIGO HEREDADO
STOP
Falacia. Es más rápido comenzar desde cero que parchear el diseño actual.
Dejando de lado las consideraciones prácticas de que la dirección muy probablemente y
sabiamente le prohíba hacerlo de todas formas, existen muchas razones para demostrar que
esta creencia es casi siempre errónea. En primer lugar, si no se ha tenido tiempo para com-
prender un sistema, no se está en posición de estimar cómo de complicado será el rediseño
del mismo, y es probable que se subestime considerablemente el esfuerzo necesario, dado
el incurable optimismo de los desarrolladores. En segundo lugar, a pesar de lo desagradable
que pueda resultar, el sistema actual funciona; uno de los principios fundamentales de rea-
lizar iteraciones cortas en la metodología ágil es “tener siempre código que funciona”, y al
comenzar desde cero se está desechando todo eso. Tercero, si se usan métodos ágiles para
el rediseño, se tendrán que desarrollar historias de usuario y escenarios para guiar el trabajo,
lo que implica que se deberán priorizar unos sobre otros y escribir un número adecuado de
ellos para asegurar que se ha cubierto al menos la funcionalidad del sistema actual. Probable-
mente resulte más rápido usar las técnicas presentadas en este capítulo para crear escenarios
9.9. OBSERVACIONES FINALES: REFACTORIZACIÓN CONTINUA 357
para aquellas partes del sistema que se mejorarán y que el código parta desde aquí, en vez de
reescribir el código completamente.
¿Significa esto que nunca se debe hacer borrón y cuenta nueva? No. Como señala Rob
Mee de Pivotal Labs, llegará un momento en el que el código base actual será una pobre
imagen de la intención del diseño original que se transformará en un pasivo, y comenzar de
nuevo podría ser la mejor acción a tomar. (¡A veces esto sucede por no haber refactorizado en
el momento adecuado!) Pero en la mayoría de casos excepto para los sistemas más triviales,
esta opción debería ser considerada como la “opción radical” cuando el resto de alternativas
se han considerado minuciosamente y se ha determinado que conforman peores opciones
para satisfacer las necesidades del cliente.
Categoría Refactorizaciones
Métodos de Extraer método Sustituir temporal por método Introducir variable explicativa
composición Sustituir método por objeto Temporal inline Dividir variable temporal
Eliminar asignaciones de Sustituir algoritmo
parámetro
Organización Atributo auto-encapsulado Sustituir valor por objeto Cambiar valor por referencia
de datos Sustituir array/hash por objeto Sustituir número mágico por constante simbólica
Simplificación Descomponer condicional Consolidar condicional Introducir aserción
de senten- Sustituir condicional por polimor- Sustituir atributo de tipo por Sustituir condicional anidado por
cias condi- fismo polimorfismo sentencias aisladas
cionales
Consolidar fragmentos condi- Eliminar flag de control Sustituir por objeto nulo
cionales duplicados
Simplificación Renombrar método Añadir parámetro Separar consulta y modificación
de llamadas Sustituir parámetro por métodos Preservar objeto completo Sustituir código de error por ex-
explícitos cepción
Figura 9.22. Otras refactorizaciones de Fowler, las presentadas en este capítulo en cursiva.
Figura 9.23. Otros smells de código de Fowler y Martin, los presentados en este capítulo en cursiva.
ingeniería del software, se han elaborado muchas otras métricas para calibrar la calidad del
código, y se han realizado muchos estudios analíticos y empíricos sobre los costes y los be-
neficios del mantenimiento de software. Robert Glass (Glass 2002) ha publicado una breve
colección de Hechos y falacias de la ingeniería del software, asentados en la experiencia y
por bibliografía académica, centrándose en particular en los costes y beneficios (percibidos
frente a reales) de las actividades de mantenimiento.
M. Feathers. Working Effectively with Legacy Code. Prentice Hall, 2004. ISBN
9780131177055.
J. Fields, S. Harvie, M. Fowler, and K. Beck. Refactoring: Ruby Edition. Addison-Wesley
Professional, 2009. ISBN 0321603508.
M. Fowler, K. Beck, J. Brant, W. Opdyke, and D. Roberts. Refactoring: Improving the
Design of Existing Code. Addison-Wesley Professional, 1999. ISBN 0201485672.
R. L. Glass. Facts and Fallacies of Software Engineering. Addison-Wesley Professional,
2002. ISBN 0321117425.
R. Green and H. Ledgard. Coding guidelines: Finding the art in the science. Communica-
tions of the ACM, 54(12):57–63, Dec 2011.
360 NOTAS
Notas
1 http://www.akit.org/2011/06/clipper-could-be-overcharging-you-for.html
2 http://campfirenow.com
3 http://basecamphq.com
4 http://en.wikibooks.org/wiki/Ruby_Programming/RubyDoc
5 http://api.rubyonrails.org
6 http://paulschreiber.com/blog/2010/06/15/rake-task-extracting-database-contents/
7 http://c2.com/doc/oopsla89/paper.html
8 https://vimeo.com/24668095
9 http://codeclimate.org
• Obtener las fechas de las actividades de un curso (clases magristrales, clases en gru-
pos reducidos, etc., cada uno con su día y hora)
• Matricular a un estudiante en un curso
Ejercicio 9.2. Partiendo de su diseño para el ejercicio 9.1, estime el impacto de una petición
de cambio que contemplara la impartición de varias clases de un mismo curso en el mismo
semestre de forma simultánea. El cambio podría afectar al esquema, interacción entre clases,
cómo se manejan las búsquedas, etc.
Ejercicio 9.3. Elija un servicio web externo ya existente que posea una interfaz sencilla, y
utilice Cucumber para crear varias pruebas de caracterización a nivel de integración. Puede
usar la gema mechanize para hacer que Cucumber se ejecute contra un sitio remoto.
Ejercicio 9.4. Identifique un sistema software heredado en funcionamiento para su estudio.
Como sugerencia, puede usar la lista de proyectos Rails de código abierto en Open Source
Rails1 , o puede seleccionar uno o dos proyectos creados por estudiantes que han usado este
libro: ResearchMatch2 , que ayuda a asociar estudiantes con oportunidades de investigación
en sus universidades, y VisitDay3 , que facilita la organización de reuniones entre estudiantes
y miembros de la facultad.
Elija uno de estos proyectos, clone o haga un fork del repositorio, y consiga ejecutar
la aplicación en un entorno de desarrollo. Probablemente esto supondrá crear una base
de datos de desarrollo, ajustar la configuracion de config/development.rb y crear el
esquema de la base de datos con db/schema.rb.
Ejercicio 9.5. Continuando con el ejercicio 9.4, trate de tener los planes de pruebas eje-
cutándose en desarrollo. Una vez que las pruebas funcionen, utilice SimpleCov para evaluar
la cobertura de pruebas. Pista: como se describió en el capítulo 8, se puede añadir Simple-
Cov en el archivo de configuración de RSpec spec/spec_helper.rb.
Ejercicio 9.6. Partiendo del ejercicio 9.4, obtenga varias métricas sobre la calidad del
código y smells de código. Puede utilizar las herramientas descritas en este capítulo como
reek y metric_fu, o puede usar CodeClimate4 , que ofrece revisión de código como un
servicio web.
Ejercicio 9.7. Continuando con el ejercicio 9.4, seleccione un subsistema (por ejemplo, el
modelo, vista y controlador asociado con un tipo de recurso) de la aplicación y lleve a cabo
una revisión del diseño. Identifique una debilidad en el diseño actual, y elimínela refactori-
zando. Asegúrese de que dispone de la suficiente cobertura de pruebas para garantizar que
la refactorización no cambia ninguna funcionalidad existente.
Ejercicio 9.8. Partiendo del ejercicio 9.4, realice un análisis exhaustivo y revisión de código
de un fichero fuente no trivial. Identifique uno o varios smells de código en el archivo y
elimínelos mediante refactorización. Asegúrese de que dispone de la suficiente cobertura de
pruebas para garantizar que la refactorización no cambia ninguna funcionalidad existente.
Gestión de proyectos: Scrum,
10 programación en pareja y sistemas
de control de versiones
Fred Brooks, Jr. No hay ganadores en un equipo perdedor y no hay perdedores en un equipo ganador.
(1931–) es el autor de un
Fred Brooks, citando al entrenador de baloncesto de Carolina del Norte, Dean Smith,
libro clásico de ingeniería
software, The Mythical 1990
Man-Month, basado en sus
años liderando el desarrollo
del sistema operativo IBM 10.1 Se necesita un equipo: dos pizzas y Scrum . . . . . . . . . . . . . . . 364
OS/360 después de dirigir el
10.2 Programación en pareja . . . . . . . . . . . . . . . . . . . . . . . . . 365
proyecto System/360 y con
dependencia directa del 10.3 ¿Diseño ágil y revisiones de código? . . . . . . . . . . . . . . . . . . . 368
presidente de IBM T.J. 10.4 Control de versiones en el equipo: conflictos merge . . . . . . . . . . 368
Watson Jr. La familia de
ordenadores System/360 10.5 Uso efectivo de las ramas (branch) . . . . . . . . . . . . . . . . . . . 372
fue la primera con una 10.6 Comunicar y solucionar errores: las cinco R . . . . . . . . . . . . . . 376
arquitectura basada en un 10.7 La perspectiva clásica . . . . . . . . . . . . . . . . . . . . . . . . . . 378
juego de instrucciones
compatible para toda la 10.8 Falacias y errores comunes . . . . . . . . . . . . . . . . . . . . . . . 386
familia de productos, así que 10.9 Equipos, colaboración y control de versiones . . . . . . . . . . . . . . 387
muchos han argumentado
10.10Para saber más . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 388
que es el primer sistema al
que se puede aplicar en 10.11Ejercicios propuestos . . . . . . . . . . . . . . . . . . . . . . . . . . . 390
sentido estricto el término
“arquitectura de
ordenadores”. Brooks fue
galardonado en 1999 con el
premio Turing por sus
emblemáticas
contribuciones a la
arquitectura de
ordenadores, sistemas
operativos e ingeniería
software.
363
Conceptos
Los principales conceptos de este capítulo son el tamaño y organización del equipo y la
gestión de la configuración para controlar los productos desarrollados por un equipo.
La versión de estos conceptos en el ciclo de vida ágil es:
En el ciclo de vida clásico, se familiarizará con los mismos conceptos desde una
perspectiva diferente:
• Aunque los tamaños de los grupos son similares al ciclo de vida ágil, pueden
crearse equipos grandes combinando grupos en una jerarquía bajo el jefe de
proyecto, teniendo cada equipo su propio líder.
• Las inspecciones permiten a personas ajenas al proyecto dar realimentación
sobre el diseño actual y planes futuros y comprueban si se siguen las buenas
prácticas.
• La gestión de la configuración incluye el control de versiones de componentes
software durante el desarrollo, desarrollo del sistema a partir de dichos
componentes, gestión de entregas para lanzamientos de nuevas versiones de
productos y gestión del cambio durante el mantenimiento de un producto entre-
gado.
3. Pánico
4. Búsqueda de culpables
5. Castigo de inocentes
Como hemos repetido muchas veces en este libro, el ciclo de vida ágil se estructura en
iteraciones de una o dos semanas que comienzan y terminan con un prototipo operativo.
En cada iteración se implementan algunas historias de usuario, que sirven como pruebas de
aceptación o integración. Tras cada iteración, las partes interesadas examinan el producto,
para comprobar si es lo que todo el mundo esperaba, y priorizan las historias de usuario
pendientes.
Los días de los héroes programadores han quedado en el pasado. Aunque hubo un tiempo
en que un individuo brillante podía crear software revolucionario, el listón de exigencia de
funcionalidad y calidad conlleva que el desarrollo software ahora sea fundamentalmente un
deporte de equipo.
Por consiguiente, el primer paso en un proyecto de desarrollo software es formar y orga-
Jeff Bezos, CEO de
Amazon titulado en
nizar el equipo. Respecto al tamaño, los “equipos de dos pizzas” —grupo que puede alimen-
informática, acuñó la “regla tarse con dos pizzas en una reunión— son típicos en el desarrollo de proyectos SaaS. Nuestro
de las dos pizzas” para diálogo con ingenieros software senior sugiere que el tamaño típico del equipo varía según la
dimensionar los equipos. empresa, pero entre cuatro y nueve personas es un rango que incluye los más habituales.
Si bien hay muchas formas de organizar un desarrollo software de “dos pizzas”, una
alternativa popular en la actualidad es Scrum (Schwaber and Beedle 2001). Su nombre se
inspira en sus reuniones cortas y frecuentes —15 minutos cada día, en el mismo sitio y a la
misma hora—, donde cada miembro del equipo responde tres preguntas:
En Rugby, se forma
un scrum (melé) 1. ¿Qué ha hecho desde ayer?
después de cada infracción
leve. El juego se detiene 2. ¿Qué va a hacer hoy?
para congregar a los
jugadores en una “reunión” 3. ¿Ha tenido algún problema que le haya impedido alcanzar su objetivo?
rápida para reiniciar el
juego. La ventaja de estos scrums diarios es que, al entender qué está haciendo cada uno de sus
miembros, el equipo puede identificar tareas que ayudarían a otros a avanzar más rápido.
Combinada con el modelo ágil de iteraciones semanales o quincenales para recopilar rea-
limentación de todas las partes interesadas, la organización Scrum aumenta las probabilidades
de progresar rápidamente hacia lo que quiere el cliente. En vez de utilizar el término iteración
de la metodología ágil, Scrum emplea el término sprint.
En Scrum hay tres roles principales:
10.2. PROGRAMACIÓN EN PAREJA 365
Resumen: SaaS forma un buen equipo con la regla de las dos pizzas y Scrum, un equipo
pequeño autoorganizado que se reúne diariamente. Dos componentes del equipo asumen
los roles de ScrumMaster, que elimina obstáculos y mantiene al equipo centrado, y Pro-
duct Owner, que representa al cliente. Puede ser útil seguir estrategias estructuradas de
resolución de conflictos cuando se presenten.
Figura 10.1. Sarah Mei y JR Boyens en Pivotal Labs programando en pareja. Sarah conduce y JR revisa. Aunque aparecen
dos teclados, sólo está codificando Sarah; el ordenador frente a JR está para documentación e información relevante como
Pivotal Tracker, como puede verse en la foto de la derecha. Todos los puestos de parejas (visibles al fondo) son idénticos y no
disponen de e-mail ni otro software instalado; puede leerse el correo en otros ordenadores aparte de los puestos de parejas.
Foto de Tonia Fox, cortesía de Pivotal Labs.
Dilbert sobre pair Normalmente, una pareja alternará los papeles de conductor y observador. La figura 10.1
programming La tira muestra fotos de ingenieros de Pivotal Labs —autores de Pivotal Tracker— que pasan la
cómica de Dilbert trata con mayor parte de la jornada haciendo programación en pareja.
humor la programación en
pareja en estas dos12
La programación en pareja es cooperativa y debería implicar mucha comunicación. Fo-
historietas13 . caliza el esfuerzo en la tarea actual, y tener dos personas trabajando en equipo mejora las
probabilidades de seguir buenas prácticas de desarrollo. Si un compañero está callado o
leyendo el correo, entonces no es programación en pareja, sino dos personas sentadas una
junto a la otra.
Como efecto colateral, la programación en pareja facilita la transmisión de conocimiento
entre la pareja, incluyendo expresiones, trucos de herramientas, procesos de la empresa, de-
seos del cliente, etc. Por tanto, para ampliar la base de conocimiento, algunos equipos in-
tercambian los compañeros por tarea, de modo que eventualmente todos trabajen juntos. Por
10.2. PROGRAMACIÓN EN PAREJA 367
Para que un equipo colabore en un código común con muchas revisiones y componentes
interdependientes, necesita herramientas para ayudar a gestionar el trabajo.
Mantener buenas prácticas de control de versiones es aún más crítico para un equipo que
Esta sección y la siguiente para los individuos. ¿Cómo se gestiona el repositorio? ¿Qué ocurre si un miembro del equipo
asumen que está usted accidentalmente hace cambios conflictivos en un conjunto de ficheros? ¿Qué líneas de un
familiarizado con las fichero dado han cambiado, cuándo y quién hizo el cambio? ¿Cómo trabaja un desarrollador
prácticas básicas de control en una nueva funcionalidad sin introducir problemas en un código ya estable? Cuando es
de versiones con Git. La
sección A.6 resume los un equipo el que desarrolla el software en vez de un individuo, puede utilizarse control de
fundamentos y la sección versiones para abordar estas cuestiones, mediante las funcionalidades fusionar (merge) y
10.10 recomienda recursos ramificar (branch). Ambas acciones suponen combinar cambios realizados por muchos
para profundizar.
desarrolladores en un único código base, tarea que a veces requiere intervención manual para
resolución de conflictos.
Los equipos reducidos que colaboran en el desarrollo de un conjunto de funcionalidades
común normalmente usan un modelo de repositorio compartido (shared-repository) para ges-
tionar el repo: se designa como autoritativa una copia particular del repo (el origen –origin–)
y todos los desarrolladores suben sus cambios (push) al origen y periódicamente bajan (pull)
de ahí los cambios de los demás. Como todo el mundo sabe, Git no se preocupa de cuál
es la copia autoritativa —cualquier desarrollador puede subir (push) y bajar (pull) cambios
de la copia de cualquier otro desarrollador si la configuración de permisos del repositorio lo
10.4. CONTROL DE VERSIONES EN EL EQUIPO: CONFLICTOS MERGE 369
http://pastebin.com/ZNhAt2RR
1 Roses are red ,
2 Violets are blue .
3 <<< <<<< HEAD : poem . txt
4 I love GitHub ,
5 =======
6 ProjectLocker rocks ,
7 >>> >>>> 77976 d a 3 5 a 1 1 d b 4 5 8 0 b 8 0 a e 2 7 e 8 d 6 5 c a f 5 2 0 8 0 8 6 : poem . txt
8 and so do you .
Figura 10.2. Cuando Bob intenta fusionar (merge) los cambios de Amy, Git inserta marcadores de conflicto en poema.txt
para señalar un conflicto de fusión (merge conflict). La línea 3 marca el inicio de la region conflictiva con <<<; todo lo que
hay hasta === (line 5) muestra el contenido del fichero en HEAD (el último commit en el repositorio local de Bob) y todo lo Muchos VCS
siguiente hasta el marcador de fin de conflicto >>>(línea 7) muestra los cambios de Amy (el fichero tal como aparece en el anteriores como
commit conflictivo de Amy, cuyo identificador commit-ID aparece en la línea 7). Las líneas 1, 2 y 8 o bien no estaban
afectadas por el conflicto o bien Git pudo mezclarlas automáticamente. Subversion soportaban sólo
el modelo de repositorio
compartido, y el “único
repositorio verdadero” a
permite—, pero con equipos pequeños es conveniente (y lo más convencional) que el repo- menudo se denominaba
sitorio origen se aloje en la nube, por ejemplo en GitHub o ProjectLocker. Cada miembro master, término con un
del equipo crea una copia local (clone) del repositorio en su máquina de desarrollo, trabaja, significado bastante
confirma (commit) y sube (push) los cambios al origen. diferente en Git.
Ya sabemos que git push y git pull pueden utilizarse para tener una copia de
respaldo del repositorio en la nube, pero estas operaciones adquieren importantes conno-
taciones adicionales en el contexto de un equipo: si Amy hace commit de sus cambios en
su repositorio, dichos cambios no son visibles para su compañero Bob hasta que ella hace
push y Bob pull. Esto plantea la posibilidad de un escenario de conflicto al fusionar (merge
conflict):
1. Amy y Bob tienen cada uno una copia actualizada del repositorio origen.
2. Amy realiza y hace commit de una serie de cambios en el fichero A.
3. Amy realiza y hace commit de otra serie de cambios en el fichero B.
4. Amy sube (push) sus cambios al origen.
5. Bob realiza y hace commit de sus propios cambios en el fichero A, pero no toca el
fichero B.
6. Bob intenta subir (push) sus commits, pero no puede porque ha habido otros en el
repositorio origen desde que Bob se lo bajó (pull) por última vez. Bob debe actualizar
su copia del repositorio respecto al origen antes de que pueda subir sus cambios.
Fíjese que la existencia de commits adicionales no implica necesariamente que haya con-
git pull en realidad
flicto —en concreto, a Bob no le afectan los commits de Amy en el fichero B (paso 3)—. En combina los comandos git
nuestro ejemplo, sin embargo, los pasos 2 y 5 sí suponen cambios conflictivos en el mismo fetch, que copia los
fichero. Cuando Bob ejecuta git pull, Git intentará fusionar (merge) las modificaciones commits nuevos desde el
origen, y git merge, que
de Bob y Amy en el fichero A. Si Amy y Bob hubieran editado partes distintas del fichero A, intenta reconciliarlos con el
Git incorporaría automáticamente ambos juegos de cambios en el fichero A y haría commit repositorio local.
del fichero mezclado en el repositorio de Bob. Sin embargo, si Amy y Bob editan partes
del fichero A que se superponen, como en la figura 10.2, Git concluirá que no puede crear
automáticamente de forma segura una versión del fichero que refleje ambos conjuntos de
modificaciones, y dejará en el repositorio de Bob una versión sin comitar del fichero con
conflictos, que Bob tendrá que editar y hacer commit manualmente.
370 CAPÍTULO 10. GESTIÓN DE PROYECTOS: SCRUM, PAREJAS Y VCS
Figura 10.3. Cuando un merge sale mal, estos comandos pueden ayudarle a recuperarse deshaciendo toda o parte de la
mezcla.
git blame [fichero] Anota cada línea de un fichero para mostrar quién fue el último en modificarla y
cuándo.
git diff [fichero] Muestra las diferencias entre la versión de trabajo actual de fichero y la última
versión confirmada (commit).
git diff rama [fichero] Muestra las diferencias entre la versión actual de fichero y como aparece en el
commit más reciente de la rama indicada (ver sección 10.5).
git log [ref ..ref] [ficheros] Muestra las entradas de log que afectan a todos los ficheros entre los dos commits
especificados por las referencias ref (que deben separarse exactamente con dos
puntos), o si se omite, la historia completa del log que afecta a dichos ficheros.
git log --since="fecha" ficheros Muestra las entradas de log que afectan a todos los ficheros desde la fecha dada
(ejemplos: "25-Dec-2011", "2 weeks ago").
Figura 10.4. Comandos para facilitar el seguimiento de quién cambió qué fichero y cuándo. Muchos comandos aceptan la
opción --oneline para generar una salida más compacta. Si se omite el argumento opcional [fichero], por defecto se
aplican a “todos los ficheros registrados en el seguimiento”. Fíjese que todos estos comandos tienen muchas más opciones,
que puede consultar con el comando git help.
Figura 10.5. Formas prácticas de referirse a determinados commits en los comandos Git, en vez de usar el identificador
completo de 40 dígitos o un prefijo único del mismo. git rev-parse expr resuelve cualquiera de las expresiones anteriores
para generar un commit-ID completo.
10.4. CONTROL DE VERSIONES EN EL EQUIPO: CONFLICTOS MERGE 371
En cualquier caso, una vez Bob haga commit del fichero A, puede subir (push) sus cam-
bios, después de lo cual el repositorio origen reflejará correctamente los cambios de ambos,
Amy y Bob. La próxima vez que Amy haga pull, recibirá la mezcla (merge) de sus cambios
y los de Bob.
Funcionalidades avanzadas
Este ejemplo muestra una regla práctica importante: haga siempre commit antes que de Git como rebase y
merge (y, en consecuencia, antes que pull, que ejecuta implícitamente un merge). Git avisa commit squash permiten
si se intenta hacer merge o pull con ficheros de los que no se ha hecho commit e incluso fusionar muchos commits
proporciona mecanismos de recuperación si se decide seguir adelante de todos modos (ver pequeños en unos pocos
más grandes para mantener
figura 10.3); pero su vida será más fácil si confirma cambios pronto y a menudo, facilitando limpio el historial de
deshacer o recuperarse de los errores. cambios visible
Puesto que trabajar en equipo significa que diversos desarrolladores modifican los con- públicamente.
tenidos de los ficheros, la figura 10.4 proporciona un listado de comandos Git útiles para
ayudar a llevar un seguimiento de quién hizo qué y cuándo. La figura 10.5 muestra algu-
nas alternativas prácticas de notación para los engorrosos identificadores de 40 dígitos de los
commits de Git
Resumen de gestión de fusiones (merge) en equipos pequeños:
Autoevaluación 10.4.1. Verdadero o falso: si intenta hacer git push y falla con un men-
saje como “Non-fast-forward (error): failed to push some refs,” significa que algún fichero
contiene algún conflicto entre la versión de su repositorio local y del repositorio origen.
⇧ Falso. Sólo significa que en su copia local del repositorio faltan algunos commits que exis-
372 CAPÍTULO 10. GESTIÓN DE PROYECTOS: SCRUM, PAREJAS Y VCS
y
Am
a B master c d
D obee (b)
Time
Figura 10.6. Cada círculo representa un commit. Amy, Bob y Dee empiezan cada uno una nueva rama, basadas en el mismo
commit (a) para trabajar en distintas funcionalidades de RottenPotatoes. Después de varios commits, Bob decide que su
funcionalidad no va a resultar, así que elimina su rama (b); mientras, Amy completa sus pruebas y su código y fusiona la
rama de su funcionalidad de vuelta con la rama principal (master), creando el commit de la fusión (merge-commit) (c).
Finalmente, Dee completa su funcionalidad, pero puesto que la rama principal (master) ha cambiado debido al merge-commit
de Amy (c), Dee tiene que resolver manualmente los conflictos para terminar de refusionar su rama (merge-commit) (d).
ten en la copia origen, y hasta que fusione dichos commits pendientes no podrá subir (push)
sus propios commits. Fusionar estos commits pendientes puede provocar un conflicto, pero
con frecuencia no es así.
a c d master
dep
b e v1_3
loy
Time
Figura 10.7. (a) Se crea una nueva rama de versión para “congelar” la versión 1.3 de RottenPotatoes. Se detecta un error en
la versión y se corrige en la rama de la versión (b); vuelve a desplegarse la aplicación desde la rama de la versión. Los
commits que contienen el parche se fusionan con la rama principal (master) (c), pero el código de la rama principal ha
evolucionado lo suficiente respecto al código de la versión como para hacer necesario realizar ajustes manuales en el parche
del error. Mientras, el equipo de desarrollo que trabaja en la rama principal detecta un fallo de seguridad crítico y lo
soluciona con un commit. El commit específico que soluciona el problema de seguridad puede mezclarse (merge) en la rama
de la versión (e) mediante git cherry-pick, puesto que no queremos aplicar ningún otro cambio de la rama principal en
la rama de la versión, excepto éste. En Flickr actualmente los
desarrolladores usan16 un
repositorio sin ninguna rama
principal y, posteriormente, se decide eliminarla (quizás porque no aporta al cliente el valor de funcionalidad —¡los
añadido esperado), a veces pueden deshacerse los commits específicos relacionados con la cambios se confirman
directamente en la rama
fusión (merge) de la rama de la funcionalidad, siempre que no haya habido muchos cambios principal!—.
en la rama principal basados en la nueva funcionalidad.
La figura 10.7 muestra cómo se usan las ramas de versión (release branches) para arre-
glar problemas detectados en una determinada versión. Se utilizan ampliamente para entregar
productos no SaaS, como bibliotecas o gemas, cuyas versiones se lanzan con suficiente dis-
tancia entre ellas como para que la rama principal diverja sustancialmente de la última rama
de versión. Por ejemplo, el kernel Linux, con los desarrolladores haciendo check in de miles
de líneas de código al día, emplea este tipo de ramas para designar versiones de lanzamiento
del kernel estables y de largo recorrido. Las ramas de versión reciben a menudo múltiples GitFlow17 , una estrategia
de gestión de ramas que
merges de la rama de desarrollo o principal, y viceversa. Las ramas de versión son menos captura muchas buenas
comunes en SaaS por la tendencia a la integración/despliegue continuos (sección 1.8): si prácticas, puede ser útil en
despliega varias veces por semana, no dará tiempo a que la versión instalada pierda la sin- proyectos grandes con
cronización con la rama principal, así que igual podría desplegar directamente desde dicha ramas de largo recorrido.
rama principal. Trataremos con más detalle el despliegue continuo en el capítulo 12.
La figura 10.8 muestra algunos comandos Git para manipulación de ramas. Los reposi-
torios Git recién creados arrancan con una única rama denominada master. En cualquier
momento, la rama actual (current branch) es cualquiera en la que usted esté trabajando
en su copia del repositorio. Puesto que, en general, cada copia del repositorio contiene to-
das las ramas, puede cambiar de rama rápidamente en el mismo repositorio (pero vea en el
screencast 10.5.1 una advertencia importante respecto a hacer esto).
Cuando existen múltiples ramas, ¿cómo puede especificarse a cuál deberían subirse o
bajarse los cambios? Como se muestra en la figura 10.8, los comandos git push y git
pull que hemos utilizado hasta ahora son en realidad casos especiales abreviados —estos
comandos manejan ramas también—.
374 CAPÍTULO 10. GESTIÓN DE PROYECTOS: SCRUM, PAREJAS Y VCS
• git branch
*. Si utiliza usted sh o una shell derivada de bash en un sistema tipo Unix, poniendo este
código18 en el fichero ~/.profile hará que el prompt del intérprete de comandos muestre la
rama Git actual cuando esté en un directorio de un repositorio Git.
http://pastebin.com/KP0suYyx
1 export PS1 = " [ � git branch --no - color 2 >/ dev / null | \
2 sed -e '/^[^*]/ d ' -e 's /* \(.*\) /\1/ ' �]% "
Figura 10.8. Comandos Git habituales para manejar ramas (branch) y fusiones (merge). La gestión de ramas implica
fusiones; la figura 10.3 explica cómo deshacer fusiones que han salido mal.
10.5. USO EFECTIVO DE LAS RAMAS ( BRANCH) 375
• Las ramas permiten introducir variantes en el código base. Por ejemplo, las ramas
de funcionalidad dan soporte al desarrollo de nuevas funcionalidades sin desestabi-
lizar el código ya operativo, y las ramas de versiones permiten solucionar errores en
versiones anteriores cuyo código ha divergido de la línea principal de desarrollo.
• Fusionar (merge) cambios de una rama en otra (por ejemplo, de una rama de funcio-
nalidad en la rama master) puede provocar conflictos en ciertos ficheros. Por tanto,
siempre debe hacerse commit antes de merge y antes de cambiar a otra rama.
• En la metodología ágil + SaaS, las ramas de funcionalidad suelen ser de corta du-
ración y las ramas de versión son poco habituales.
Autoevaluación 10.5.1. Describa un escenario en el cual las fusiones (merge) podrían pro-
ducirse en ambos sentidos —cambios en una rama de funcionalidad fusionados en la princi-
pal, y cambios en la rama principal fusionados en una rama de funcionalidad— (en Git, esto
se denomina crisscross merge –fusión cruzada–)).
⇧ Diana inicia una nueva rama para trabajar en una funcionalidad. Antes de completarla, se
arregla un error importante y se hace merge de la solución en la rama principal. Puesto que
el error está en una parte del código que interactúa con la funcionalidad de Diana, ésta hace
merge de la solución desde la rama master a su rama de funcionalidad. Por último, cuando
completa su desarrollo, su rama de funcionalidad se fusiona de nuevo en la principal.
376 CAPÍTULO 10. GESTIÓN DE PROYECTOS: SCRUM, PAREJAS Y VCS
1. Reportar el error.
2. Reproducir el problema o, si no, Reclasificarlo como “no es un error” o “no se solu-
cionará”.
3. Crear una prueba de Regresión que demuestre el error.
4. Reparar el error.
5. Liberar una nueva entrega (Release) con el código reparado.
Cualquiera puede encontrar y reportar un error en el código Saas, tanto en el lado cliente
como en el lado servidor. Un miembro del equipo de desarrollo o de QA debe entonces
reproducir el error, documentando el entorno y pasos necesarios para desencadenarlo. Este
proceso puede resultar en la reclasificación del error como “no es un error” por diversas
razones:
• No es un error sino una petición de mejora o de cambio de un comportamiento que
funciona de acuerdo al diseño.
• El error se encuentra en una parte del código que se está desinstalando o, en general,
ha dejado de mantenerse.
• El error se produce sólo en un entorno de usuario no soportado por el sistema, como un
navegador muy antiguo que no cumple los requisitos mínimos de esta aplicación SaaS.
• El error ya está solucionado en la última versión (poco habitual en SaaS, puesto que
los usuarios siempre usan la última versión).
Una vez se confirma que se trata de un error auténtico y reproducible, se introduce en un
sistema de gestión de errores. Existen multitud de estos sistemas, pero hay una herramienta
que usted ya conoce y que puede satisfacer las necesidades de muchos equipos pequeños y
medianos: Pivotal Tracker permite marcar una historia como un error en vez de como carac-
terística, lo que asigna cero puntos a la historia pero en cambio permite seguir su evaluación
hasta que se complete, como cualquier historia de usuario. Una ventaja de esta herramienta
Los errores de
“gravedad de nivel 1”
es que gestiona el ciclo de vida del error por usted, de modo que los procesos disponibles para
en Amazon.com exigen que entregar historias de usuario pueden adaptarse fácilmente a la solución de errores. Por ejem-
los ingenieros responsables plo, solucionar el error debe ser prioritario respecto a otras tareas; en un proceso en cascada,
inicien una conferencia en esto puede significar priorizar respecto a otros errores en la fase de mantenimiento, pero en
un plazo de 15 minutos
desde que se detectan
un proceso ágil normalmente significa priorizar respecto a desarrollar nuevas funcionalidades
—¡un requisito de respuesta a partir de las historias de usuario. Con Tracker, el responsable de producto puede dar más o
más estricto que para los menos prioridad a la historia de error respecto a otras historias dependiendo de la gravedad
médicos de guardia!— del error y su impacto en el cliente. Por ejemplo, deberá asignarse una prioridad muy alta a
(Bodík et al. 2006)
los errores que pueden provocar pérdidas de datos en producción.
10.6. COMUNICAR Y SOLUCIONAR ERRORES: LAS CINCO R 377
El siguiente paso es reparar, lo que siempre comienza creando primero la prueba au-
tomática más simple posible que falle por el error, y modificando el código para que pase
la(s) prueba(s). Esto debería sonarle familiar a estas alturas, como especialista en TDD, pero
aplica también a entornos no-TDD: no puede cerrarse ningún error sin una prueba. Depen-
diendo del error, requerirá pruebas unitarias, funcionales, de integración o una combinación
de varias. Lo más simple posible implica que la prueba dependa de las mínimas precondi-
ciones posibles, circunscribiendo estrictamente el error. Por ejemplo, simplificar una prueba
unitaria RSpec puede consistir en minimizar la inicialización previa a la prueba en sí o en el
bloque before, y simplificar un escenario Cucumber puede implicar minimizar el número de
pasos Given o Background. Estos tests suelen añadirse a la batería de pruebas de regresión
para que el error
no pase desapercibido si vuelve a aparecer. Solucionar un error complejo puede requerir
múltiples commits; una política común en los proyectos BDD+TDD es no fusionar en la
rama principal de desarrollo commits con tests pendientes hasta que pasan todas las pruebas
(verde).
Muchos sistemas de seguimiento de errores pueden incluir en los informes de error refe-
rencias cruzadas a los identificadores de commit que corresponden a los parches y pruebas de
regresión. Por ejemplo, los puntos de enganche (hooks) de servicios19 de GitHub permiten
anotar un commit con el identificador de la correspondiente historia de error o de funciona-
lidad en Tracker. Cuando se sube el cambio a GitHub, la historia se marca automáticamente
como entregada (delivered). Dependiendo del protocolo del equipo y del sistema de gestión
de errores utilizado, el error puede “cerrarse” inmediatamente anotando qué versión con-
tendrá el arreglo, o una vez lanzada la versión.
Como veremos en el capítulo 12, la mayoría de equipos ágiles liberan versiones con
frecuencia, acortando el ciclo de vida de los errores.
• Los errores que en realidad son peticiones de mejoras o que aparecen sólo en ver-
siones obsoletas del código o entornos sin soporte pueden reclasificarse para indicar
que no van a corregirse.
Autoevaluación 10.6.1. ¿Por qué cree que las historias de “corrección de errores” valen
cero puntos en Tracker, aun siguiendo el mismo ciclo de vida que las historias de usuario
normales?
⇧ La velocidad del equipo se inflaría artificialmente corrigiendo errores, puesto que primero
conseguirían puntos por implementar la funcionalidad y después más puntos por conseguir
que funcione realmente.
Autoevaluación 10.6.2. Verdadero o falso: un error que se desencadena por la interac-
ción con otro servicio (por ejemplo, autenticación vía Twitter) no puede capturarse en una
378 CAPÍTULO 10. GESTIÓN DE PROYECTOS: SCRUM, PAREJAS Y VCS
prueba de regresión porque las condiciones necesarias requieren que controlemos el com-
portamiento de Twitter.
⇧ Falso: casi siempre pueden utilizarse mocks y stubs a nivel de integración, por ejemplo,
usando la gema FakeWeb20 o las técnicas descritas en la sección 8.6, para emular las condi-
ciones externas necesarias para reproducir el error en una prueba automática.
Autoevaluación 10.6.3. Verdadero o falso: un error en el que el navegador muestra el di-
seño o el contenido de forma incorrecta debido a problemas JavaScript puede reproducirse
manualmente por un ser humano, pero no puede capturarse en una prueba de regresión
automática.
⇧ Falso: pueden emplearse herramientas como Jasmine y Webdriver (sección 6.7) para desa-
rrollar dichas pruebas.
Repasamos las cuatro tareas básicas de los jefes de proyecto que aumentan sus posibilidades
de éxito:
1. Tamaño del equipo, roles, espacio y comunicación
2. Gestión de personas y de conflictos
10.7. LA PERSPECTIVA CLÁSICA 379
Figura 10.9. Formato de un plan de gestión de proyecto, del estándar IEEE 16326-2009 ISO/IEC/IEEE Systems and Software
Engineering–Life Cycle Processes–Project Management.
3. Inspecciones y métricas
4. Gestión de la configuración
1. Tamaño del equipo, roles, espacio y comunicación. Los procesos clásicos pueden
escalar a tamaños mayores, con líderes de grupo que rinden cuentas ante el jefe de proyecto.
Sin embargo, cada subgrupo mantiene típicamente el tamaño de las dos pizzas que vimos en
la sección 10.1. Suele recomendarse un tamaño de entre tres y siete personas (Braude and
Berstein 2011) hasta no más de diez (Sommerville 2010). Fred Brooks explicaba la razón en
el capítulo 7: si añade más gente al equipo, incrementa el número de personas que pueden
trabajar en paralelo, pero también aumenta la cantidad de tiempo que cada uno debe dedicar
a comunicarse. Estos tamaños de equipo son razonables teniendo en cuenta el porcentaje de
tiempo dedicado a la comunicación.
Una vez sabemos el tamaño del equipo, en los procesos clásicos pueden asignarse a los
miembros de un subgrupo distintos roles que se espera que lideren. Por ejemplo (Pressman
2010):
Uno de los principales factores de éxito era el hecho de que los miembros del equipo
estaban a mano, listos para tener una reunión espontánea, asesorar sobre un problema,
enseñar/aprender algo nuevo, etc. Sabemos por trabajos anteriores que los beneficios de
estar a mano caen de forma significativa cuando las personas están, primero, fuera de la
vista y, aún más drásticamente, cuando se encuentran a más de 30 metros de distancia.
Allen and Henn 2006
Aunque el equipo confíe en el correo electrónico y los mensajes de texto para comuni-
carse, y comparta información en wikis y similares, normalmente también hay una reunión
semanal para ayudar a coordinar el proyecto. Recuerde que el objetivo es minimizar el tiempo
Las samosas son un dedicado innecesariamente a la comunicación, de modo que es importante que las reuniones
popular aperitivo relleno frito sean efectivas. A continuación tiene nuestro resumen de consejos sobre cómo mantener reu-
de la India. niones eficientes, extraídos de las muchas guías existentes en la web. Utilizamos el acrónimo
SAMOSAS como recurso mnemotécnico; ¡seguramente llevar un plato de este aperitivo ayu-
dará a que la reunión sea eficiente!
• Ser puntuales: empezar y terminar (Start/stop) la reunión puntualmente.
• Agenda: definida con antelación; si no hay agenda, se cancela la reunión.
• Minutas de la reunión: deben registrarse de modo que todos puedan recordar los resul-
tados después; el primer punto de la agenda es designar al encargado de tomar notas.
• Orden: hablar por turnos, uno (One) cada vez; no interrumpir a otro mientras habla.
• Enviar (Send) los materiales por adelantado, ya que la gente lee mucho más rápido que
los ponentes hablan.
• Acciones: definir las acciones a realizar al término de la reunión, de modo que todos
sepan qué deberían hacer como resultado de la misma.
• Siguiente reunión: fijar la fecha y hora para la próxima reunión.
2. Gestionar personas y conflictos. Se han escrito miles de libros sobre cómo gestionar
personas, pero los dos más útiles que hemos encontrado son The One Minute Manager y
How to Win Friends and Influence People ( Blanchard and Johnson 1982; Carnegie 1998).
Lo que nos gusta del primero es que ofrece consejos cortos y rápidos. Sea claro acerca de los
objetivos de lo que quiere que se haga y cómo de bien debería hacerse, pero deje al equipo
resolver cómo hacerlo para fomentar la creatividad. En las reuniones para revisar el progreso
10.7. LA PERSPECTIVA CLÁSICA 381
individual, empiece con los puntos positivos para fomentar la confianza y déles tiempo para
aprender las tareas en curso. Simultáneamente, usted tiene que ser sincero con ellos acerca de
lo que no va bien y qué tienen que hacer para resolverlo. Del segundo, nos gusta que ayuda
a enseñar el arte de la persuasión, para conseguir que los demás hagan lo que usted cree que
debería hacerse sin ordenárselo. Estas habilidades ayudan también a convencer a gente a la
que usted no puede dar órdenes: sus clientes y su equipo de dirección.
Ambos libros sirven de ayuda para resolver conflictos en el equipo. Los conflictos no
son necesariamente malos, en el sentido de que puede ser mejor tener el conflicto que dejar
que el proyecto se estrelle y explote. Intel Corporation denomina esta actitud confrontación
constructiva. Si usted está firmemente convencido de que alguien está haciendo una pro-
puesta técnicamente incorrecta, está obligado a tocar el tema, incluso informar a sus jefes. La
cultura Intel es hablar alto incluso si no está de acuerdo con la gente de más alto rango de la
sala.
Si el conflicto persiste, dado que los procesos clásicos cuentan con un jefe de proyecto,
dicha persona puede tomar la decisión definitiva. Una de las razones por las que los EE.UU.
consiguieron llegar a la luna en los años 60 es que uno de los directores de la NASA, Wernher
von Braun, tenía un don para resolver rápidamente los conflictos sobre decisiones reñidas. A
su juicio, seleccionar una alternativa de forma arbitraria pero rápidamente era con frecuencia
mejor, puesto que la elección era aproximadamente 50-50, de modo que el proyecto podía
seguir adelante en vez de tomarse el tiempo para recopilar cuidadosamente todas las eviden-
cias para ver qué decisión era ligeramente mejor.
Sin embargo, una vez tomada una decisión, el equipo tiene que apoyarla y seguir adelante.
El lema de Intel para esta resolución es discrepe y comprométase (en inglés, disagree and
commit): “No estoy de acuerdo, pero voy a ayudar incluso aunque no esté de acuerdo”.
3. Inspecciones y métricas. Inspecciones como las revisiones de diseño y revisiones
de código permiten disponer de realimentación sobre el sistema incluso antes de que fun-
cione todo. La idea es que una vez se tiene un plan de diseño e implementación inicial, se
está listo para recabar realimentación de desarrolladores fuera del equipo. Las revisiones de
diseño y de código siguen el ciclo de vida en cascada en el sentido de que se completa cada
fase secuencialmente antes de pasar a la siguiente o, al menos, para las fases de una única
iteración en el desarrollo en espiral o RUP.
Una revisión de diseño es una reunión en la que los autores de un programa presentan su
diseño con el objetivo de mejorar la calidad del software, beneficiándose de la experiencia
de los asistentes a la reunión. La revisión de código se celebra una vez se ha implementado
el diseño. Esta realimentación orientada a los compañeros también contribuye al intercam-
bio de conocimiento en la organización y ofrece coaching que puede ayudar en las carreras
profesionales de quienes presentan.
Shalloway sugiere que las revisiones formales de diseño y código con frecuencia llegan
demasiado tarde en el proceso para tener un gran impacto en el resultado (Shalloway 2002).
Recomienda, en cambio, celebrar reuniones más reducidas, que él llama “revisiones de en-
foque”. La idea es tener unos pocos desarrolladores senior ayudando al equipo a idear una
aproximación para resolver el problema. El grupo intercambia ideas sobre distintos enfoques
para ayudar a encontrar uno adecuado.
Si usted planea realizar una revisión de diseño formal, Shalloway sugiere que primero
celebre una “revisión de mini-diseño”, una vez se haya decidido el enfoque y el diseño se
aproxime a su conclusión. Involucra a las mismas personas que antes, pero el propósito es
preparar la revisión formal.
382 CAPÍTULO 10. GESTIÓN DE PROYECTOS: SCRUM, PAREJAS Y VCS
La revisión formal en sí debería empezar con una descripción de alto nivel de lo que
los clientes quieren. A continuación, se presenta la arquitectura del software, mostrando
las API de los componentes. Será importante destacar los patrones de diseño utilizados en
los distintos niveles de abstracción (ver capítulo 11). Se espera que explique el porqué de las
decisiones tomadas y si consideró alternativas plausibles. Dependiendo del tiempo disponible
y los intereses de los asistentes, la fase final consistiría en revisar el código de los métodos
implementados. En todas estas fases, usted puede obtener más valor de la revisión si tiene
una lista concreta de preguntas o issues de los cuales le gustaría oír hablar.
Una ventaja de las revisiones de código es que animan a la gente de fuera del equipo a
mirar los comentarios además del código. Puesto que no tenemos una herramienta que pueda
reforzar la recomendación del capítulo 9 de asegurarse de que los comentarios aumentan el
nivel de abstracción, el único mecanismo de refuerzo es la revisión del código.
Además de revisar el código y los comentarios, en los procesos clásicos las inspecciones
dan realimentación sobre todas las partes del proyecto: plan de proyecto, planificación, re-
quisitos, plan de pruebas, etc. Esta realimentación ayuda con la verificación y validación
del proyecto completo, para confirmar que va por el buen camino. Incluso hay un estándar
del IEEE para documentar los planes de verificación y validación del proyecto (figura 10.10)
Al igual que los modelos algorítmicos de estimación de costes (ver sección 7.10), algunos
investigadores han abogado por la posibilidad de reemplazar las inspecciones o revisiones
con métricas de software para evaluar la calidad y el progreso de los proyectos. La idea es
recopilar métricas de muchos proyectos de la organización a lo largo del tiempo, establecer
una referencia para futuros proyectos, y así evaluar cómo evoluciona un proyecto comparado
con la referencia. Esta cita captura el argumento en favor de las métricas:
Sin métricas es difícil saber cómo se está llevando a cabo un proyecto y el nivel de calidad
del software.
Braude and Berstein 2011
Figura 10.10. Esquema de un plan de verificación y validación de software y de sistema basado en el estándar IEEE
1012-2012.
384 CAPÍTULO 10. GESTIÓN DE PROYECTOS: SCRUM, PAREJAS Y VCS
Sin embargo, aún estamos muy lejos de esa situación ideal y no hay señales de que la
evaluación automática de la calidad vaya a convertirse en una realidad en un futuro
próximo.
Sommerville 2010
• Aunque hay que resolverlos, los conflictos pueden ayudar a encontrar la mejor op-
ción para el proyecto.
• Las inspecciones como las revisiones de diseño y las revisiones de código permiten
que personas ajenas al equipo proporcionen realimentación sobre el diseño y planes
futuros, con lo cual el equipo se beneficia de la experiencia de otros. También son
una buena forma de comprobar si se siguen las buenas prácticas y si los planes y
documentos son sensatos.
• La gestión de la configuración es una categoría amplia que incluye la gestión del
cambio durante el mantenimiento del producto, control de versiones de compo-
nentes software, construcción del sistema para producir un programa coherente y
que funcione a partir de dichos componentes, y gestión de entregas para hacer llegar
el producto a los clientes.
Autoevaluación 10.7.1. Compare el tamaño de los equipos en los procesos clásicos frente a
10.7. LA PERSPECTIVA CLÁSICA 385
Índice
1. Visión general
1.1 Alcance
1.2 Propósito
2. Definiciones, acrónimos y abreviaturas
2.1 Definiciones
2.2 Acrónimos y abreviaturas
3. Adaptación
4. Audiencia
5. El proceso de gestión de la configuración (CM)
6. Planificación CM: proceso de bajo nivel
6.1 Propósito
6.2 Actividades y tareas
7. Gestión CM: proceso de bajo nivel
7.1 Propósito
7.2 Actividades y tareas
8. Identificación de la configuración: proceso de bajo nivel
8.1 Propósito
8.2 Actividades y tareas
9. Control de cambios de configuración: proceso de bajo nivel
9.1 Propósito
9.2 Actividades y tareas
10. Estado de la configuración: proceso de bajo nivel
10.1 Propósito
10.2 Actividades y tareas
11. Auditoría de configuración CM: proceso de bajo nivel
11.1 Propósito
11.2 Actividades y tareas
12. Control de interfaz: proceso de bajo nivel
12.1 Propósito
12.2 Actividades y tareas
13. Control de elemento de configuración del proveedor: proceso de bajo nivel
13.1 Propósito
13.2 Actividades y tareas
14. Gestión de entregas: proceso de bajo nivel
14.1 Propósito
14.2 Actividades y tareas
Figura 10.11. Índice para el IEEE 828-2012 Standard for Configuration Management in Systems and Software Engineering.
386 CAPÍTULO 10. GESTIÓN DE PROYECTOS: SCRUM, PAREJAS Y VCS
los ágiles.
⇧ Los procesos clásicos pueden formar jerarquías de subgrupos para crear un proyecto mucho
más grande, pero cada subgrupo tiene básicamente el mismo tamaño que un “equipo de dos
pizzas” en los procesos ágiles.
Autoevaluación 10.7.2. Verdadero o falso: las revisiones de diseño son reuniones destinadas
a mejorar la calidad del producto software mediante los conocimientos y experiencia de
los asistentes, pero también resultan en un intercambio de información técnica y pueden
ser altamente educativas para los miembros con menos experiencia de la organización, ya
presenten o simplemente asistan.
⇧ Verdadero.
No es conveniente dejar que su copia del repositorio diverja demasiado del origen, o
las fusiones (sección 10.5) resultarán laboriosas. Debería actualizar siempre su copia (git
pull) antes de empezar a trabajar, y publicar sus cambios (git push) tan pronto como sus
cambios locales sean suficientemente estables como para compartirlos. Si trabaja en una
rama de funcionalidad de largo recorrido que corre el riesgo de perder la sincronización con
la principal, consulte la documentación de git rebase para “resincronizar” periódicamente
su rama sin fusionarla con la principal hasta que esté lista.
STOP
Falacia. Es correcto hacer cambios simples en la rama principal.
Los programadores somos optimistas. Cuando vamos a modificar nuestro código, siem-
pre pensamos que será un cambio de una línea. Luego se convierte en un cambio de cinco
líneas; entonces nos damos cuenta de que el cambio afecta a otro fichero, que también hay
que modificar; y entonces resulta que necesitamos añadir o modificar las pruebas que se basa-
ban en el código antiguo; y así sucesivamente. Por esta razón, siempre debe crear una rama de
funcionalidad cuando empiece una nueva tarea. Las ramificaciones son casi instantáneas con
Git y, si el cambio finalmente resulta ser pequeño, siempre puede eliminar la rama después
de fusionarla para que no embarulle el espacio de nombres de las ramas.
Falacia. Como cada subequipo trabaja en su propia rama, no necesitan co-
STOP
municarse regularmente o hacer merge con frecuencia.
Las ramas son una forma excelente de que distintos miembros del equipo trabajen en
diversas funcionalidades simultáneamente. Pero si no hacen merge con frecuencia y no está
claro quién está trabajando en qué, corren el riesgo de aumentar la probabilidad de tener
conflictos de fusión y perder trabajo accidentalmente cuando un desarrollador “resuelve” un
conflicto merge borrando los cambios de otro desarrollador.
distribuido. Subversion, creado en 2001, ofrecía un soporte mucho mejor para ramifica-
ciones, permitiendo a los desarrolladores trabajar de forma independiente en diferentes ver-
siones de un proyecto; pero aún asumía que todos los desarrolladores que trabajaban en un
árbol de código determinado subieran sus cambios a una única copia “maestra” (“master”)
del repositorio. Git completó la descentralización permitiendo que cualquier copia del re-
positorio suba o baje cambios de cualquier otra copia, posibilitando un “equipo de equipos”
completamente descentralizado, y facilitando ramificaciones y fusiones mucho más rápidas y
fáciles que sus predecesores. Hoy en día, colaborar de forma distribuida es la norma: en vez
de un equipo grande distribuido, fork & pull permite que un gran número de equipos ágiles
de dos pizzas progresen independientemente, y el uso de Git como soporte se ha convertido
en ubicuo. Este nuevo tamaño de equipo de dos pizzas facilita la organización respecto a los
equipos de programación gigantes que pueden darse en la metodología de desarrollo clásica.
Pese a estas mejoras, los proyectos software todavía son famosos por sus retrasos y sobre-
costes. Las técnicas de este capítulo pueden ayudar al equipo ágil a evitar esos contratiempos.
Hacer un chequeo cada iteración con todos los interesados orienta al equipo para destinar sus
recursos de forma más efectiva y aumenta las probabilidades de conseguir un resultado que
satisfaga al cliente dentro del plazo y presupuesto. La organización de equipo de Scrum
encaja bien con el ciclo de vida ágil y los desafíos inherentes al desarrollo SaaS. El uso dis-
ciplinado de control de versiones permite a los desarrolladores avanzar en muchos frentes
simultáneamente sin interferir mutuamente en el trabajo de los demás, y también permite una
gestión disciplinada y sistemática del ciclo de vida de los errores.
Los procesos clásicos confían en el jefe de proyecto para estimar tiempo y coste, evaluar
riesgos y ejecutar el proyecto de modo que se entregue el producto a tiempo y dentro del
presupuesto con la funcionalidad requerida. Este enfoque más autocrático contrasta con la
aproximación igualitaria de Scrum, donde los roles de ScrumMaster y Product Owner rotan
entre los miembros del equipo ágil. En los ciclos de vida clásicos, todo está documentado,
incluyendo el plan de gestión del proyecto, el plan de gestión de la configuración y el plan de
cómo verificar y validar que el proyecto sigue los planes. Las inspecciones de desarrolladores
ajenos al equipo proporcionan realimentación sobre los planes y el código, y ayudan a evaluar
el progreso del proyecto.
En cualquier ciclo de vida, una vez se completa el proyecto es importante tomarse tiempo
para meditar sobre lo que ha aprendido antes de lanzarse de cabeza al siguiente. Reflexionar
sobre qué fue bien, qué no, y qué se haría de diferente manera. Cometer un error no es ningún
delito, siempre y cuando se aprenda de ello; el problema es repetir el mismo error una y otra
vez.
• Muchos proyectos de tamaño medio que no usan Pivotal Tracker, o cuyas necesidades
de gestión de errores van más allá de lo que Tracker proporciona, cuentan con la funcio-
REFERENCIAS 389
ACM IEEE-Computer Society Joint Task Force. Computer science curricula 2013, Ironman
Draft (version 1.0). Technical report, February 2013. URL http://ai.stanford.edu/
users/sahami/CS2013/.
T. J. Allen and G. Henn. The Organization and Architecture of Innovation: Managing the
Flow of Technology. Butterworth-heinemann, 2006.
A. Begel and N. Nagappan. Pair programming: What’s in it for me? In Proceedings of
the Second ACM-IEEE international symposium on Empirical software engineering and
measurement, pages 120–128, Kaiserslautern, Germany, October 2008.
K. H. Blanchard and S. Johnson. The One Minute Manager. William Morrow, Cambridge,
MA, 1982.
P. Bodík, A. Fox, M. I. Jordan, D. Patterson, A. Banerjee, R. Jagannathan, T. Su, S. Teng-
inakai, B. Turner, and J. Ingalls. Advanced tools for operators at Amazon.com. In First
Workshop on Hot Topics in Autonomic Computing (HotAC’06), Dublin, Ireland, June 2006.
E. Braude and M. Berstein. Software Engineering:Modern Approaches, Second Edition.
John Wiley and Sons, 2011. ISBN 9780471692089.
D. Carnegie. How to Win Friends and Influence People. Pocket, 1998.
A. Cockburn and L. Williams. The costs and benefits of pair programming. Extreme Pro-
gramming Examined, pages 223–248, 2001.
J. Hannay, T. Dyba, E. Arisholm, and D. Sjoberg. The effectiveness of pair programming:
A meta-analysis. Information and Software Technology, 51(7):1110–1122, July 2009.
L. Hoffmann. Q&a: Big challenge. Communications of the ACM (CACM), 56(9):112–ff,
Sept. 2013.
D. Holland. Red Zone Management. WinHope Press, 2004. ISBN 0967140188.
J. Loeliger. Version Control with Git: Powerful Tools and Techniques for Collaborative
Software Development. O’Reilly Media, 2009. ISBN 0596520123.
R. Pressman. Software Engineering: A Practitioner’s Approach, Seventh Edition. McGraw
Hill, 2010. ISBN 0073375977.
K. Schwaber and M. Beedle. Agile Software Development with Scrum (Series in Agile
Software Development). Prentice Hall, 2001. ISBN 0130676349.
A. Shalloway. Agile Design and Code Reviews. 2002. URL http://www.
netobjectives.com/download/designreviews.pdf.
390 NOTAS
Notas
1 http://www.opensourcerails.com/
2 http://github.com/ucberkeley/researchmatch
3 http://github.com/vinsonchuong/meetinglibs
4 http://codeclimate.com
5 https://github.com/bbatsov/rails-style-guide
6 http://google-styleguide.googlecode.com/svn/trunk/pyguide.html
7 http://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml
8 http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml
9 https://github.com/bbatsov/rails-style-guide
10 http://google-styleguide.googlecode.com/svn/trunk/pyguide.html
11 http://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml
12 http://dilbert.com/strips/comic/2003-01-09/
13 http://dilbert.com/strips/comic/2003-01-11/
14 http://help.github.com/fork-a-repo/
15 http://help.github.com/send-pull-requests/
16 http://code.flickr.com/blog/2009/12/02/flipping-out/
17 http://nvie.com/posts/a-successful-git-branching-model/
18 http://pastebin.com/8JkAQDx0
19 http://github.com/
20 http://fakeweb.rubyforge.org
21 http://progit.org/2011/07/11/reset.html
22 http://book.git-scm.com
23 http://git-scm.com/book/es/v1
24 http://mozilla.org
Ejercicio 10.3. (Debate) Piense sobre el último proyecto en el que trabajó en equipo. ¿Cuán-
tas de las ideas y prácticas comentadas en este capítulo emplearon usted y su grupo? De las
utilizadas, ¿cuáles encontró más útiles? ¿Qué métodos, de los no utilizados, cree usted que
habrían sido más provechosos?
Ejercicio 10.4. (Debate) Uno de los proyectos propuestos en el capítulo 1 era hacer un lis-
tado de las 10 aplicaciones más importantes. Dado dicho listado, ¿cuáles se desarrollarían
y mantendrían mejor con un equipo de tamaño dos pizzas organizado según Scrum? ¿Cuáles
no? Enumere sus razones para cada elección.
10.11. EJERCICIOS PROPUESTOS 391
Ejercicio 10.5. (Debate) ¿Por qué cree usted que Pivotal agregó Epics a Tracker? ¿Qué
problema resolvían con esta nueva funcionalidad?
Ejercicio 10.6. (Debate) Localice a un colega programador cercano y pruebe la progra-
mación en pareja por unos días. Varios de los proyectos propuestos en los primeros capítulos
son buenos candidatos para programar en pareja. ¿Cómo de difícil fue encontrar un puesto
donde pudieran sentarse uno al lado del otro? ¿Ha encontrado que les fuerza a ambos a
concentrarse más para crear código de más calidad, apartando parcialmente las distrac-
ciones, o parece menos productivo puesto que, en esencia, sólo una persona está haciendo el
trabajo?
Ejercicio 10.7. Con un compañero de programación, invente un sitio web o aplicación senci-
llos que podrían construir. Idealmente, ¡no debería ser más complicado que RottenPotatoes!
Desarrolle y complete el proyecto empleando los métodos y herramientas descritos en este
capítulo –programación en pareja, velocidad, control de versiones–. ¿Qué herramientas o
métodos han resultado más útiles?
Ejercicio 10.8. (Debate) La próxima vez que asista a una reunión, haga un recuento de
cuántas recomendaciones SAMOSA se están violando. Si son varias, proponga como experi-
mento probar a seguir SAMOSA. ¿Qué observó sobre las diferencias entre las dos reuniones?
¿Ayudó o perjudicó SAMOSA?
Ejercicio 10.9. Emprenda, como parte de una actividad en grupo, una inspección de un
segmento de código de tamaño medio. Nota: el icono al margen identifica proyectos del
estándar de Ingeniería del Software ACM/IEEE 2013 (ACM IEEE-Computer Society Joint
Task Force 2013).
Ejercicio 10.10. (Debate) Subversion (svn) era un popular sistema de control de versiones
desarrollado por CollabNet cinco años antes de que Linus Torvalds creara git. ¿Qué proble-
mas de svn intentaba resolver Torvalds con git? ¿En qué medida tuvo éxito? ¿Cuáles cree
usted que son los pros y los contras de svn respecto a git?
Ejercicio 10.11. Explique la diferencia entre gestión de configuración de software centrali-
zada y distribuida.
Ejercicio 10.15. Identifique y justifique los roles necesarios en un equipo de desarrollo soft-
ware para un proceso clásico.
Ejercicio 10.16. Enumere las fuentes, riesgos y potenciales beneficios de los conflictos de
equipo.
Ejercicio 10.17. Aplique una estrategia de resolución de conflictos en una reunión de equipo.
392 NOTAS
Ejercicio 10.18. Siguiendo un ciclo de vida clásico, prepare un plan de proyecto para un
proyecto software que incluya estimaciones de tamaño y esfuerzo, una planificación, asig-
nación de recursos, control de configuración, gestión del cambio e identificación y gestión
de riesgos del proyecto.
William Kahan (1933–) Las cosas son verdaderamente fáciles cuando puedes pensar correctamente acerca de lo
recibió el premio Turing en que pasa alrededor sin tener muchos pensamientos confusos o superfluos que te estorban.
1989 por sus contribuciones Piensa en la máxima de Einstein, “Las cosas deberían simplificarse todo lo que sea
clave al análisis numérico. posible, pero no hacerse más simples”.
Kahan se dedicó a “hacer el
mundo seguro para los ”A Conversation with William Kahan,” Dr. Dobbs’ Journal, 1997
cálculos numéricos”.
Conceptos
El principal concepto de este capítulo es que los patrones de diseño pueden mejorar la
calidad de las clases. Un patrón de diseño condensa soluciones validadas a problemas,
separando las cosas que cambian de aquellas que no lo hacen.
El acrónimo SOLID identifica cinco principios del diseño orientado a objetos que des-
criben un buen diseño de interacciones entre clases. Un antipatrón de diseño identifica
un diseño de clases pobre, en el que se pueden observar problemas o smells de diseño.
De este modo, utilizar los smells de diseño para detectar vulneraciones de los principios
SOLID para el buen diseño de clases es análogo a usar smells de código para detectar
cuándo un código no cumple alguno de los principios SOFA para el buen diseño de
un método (ver la sección 9.7).
Las cinco letras del acrónimo SOLID responden a:
1. Principio de Única Responsabilidad (Single Responsability Principle): una
clase debe tener una y sólo una responsabilidad; es decir, una única razón para
existir. La métrica de falta de cohesión entre métodos es sinónimo del
antipatrón de clases demasiado extensas.
2. Principio de Abierto/Cerrado (Open/Closed Principle): una clase debe estar
abierta para su extensión, pero cerrada para su modificación. El smell de diseño
Sentencia Case sugiere que no se cumple este principio.
3. Principio de Sustitución de Liskov (Liskov Substitution Principle): un método
diseñado para trabajar con un objeto de tipo T debe poder trabajar también con
un objeto de cualquier subtipo de T. Es decir, todos los subtipos de T deben preservar
el “contrato” de T. El smell de diseño conocido como Legado Rechazado suele
indicar que el código no cumple esta directriz.
4. Principio de Inyección de Dependencias (Dependency Injection Principle):
si dos clases dependen entre sí y sus implementaciones pueden cambiar, es mejor
que dependan de una interfaz abstracta separada que se “inyecta” entre ambas.
5. Principio de Demeter (Demeter Principle): un método puede invocar a otros
métodos de su clase y de las clases de sus variables de instancia; el resto es tabú.
Un smell de diseño asociado a este principio es el de Intimidad Inapropiada.
En la metodología ágil, la refactorización es el vehículo para mejorar el diseño de
clases y métodos; en algunos casos, refactorizar le permitirá aplicar un patrón de diseño
apropiado. Por el contrario, para los ciclos de vida clásicos:
• La fase inicial de diseño facilita la selección de una buena arquitectura inicial del
software y de buenos diseños de clases.
• La especificación se divide en problemas y después en subproblemas, y los de-
sarrolladores tratan de usar patrones para solucionarlos.
• Como el diseño precede al código, las revisiones del diseño pueden aportar
realimentación temprana.
• Que el diseño tenga que cambiar una vez se ha comenzado la codificación puede
representar un problema.
396 CAPÍTULO 11. PATRONES DE DISEÑO PARA CLASES SAAS
Legacy (Ch. 9)
Figura 11.1. El ciclo de desarrollo de software ágil y sus relaciones con los capítulos de este libro. Este capítulo detalla los
patrones de diseño, que influye sobre BDD y TDD para nuevas aplicaciones y para mejorar código heredado.
Capítulo 9 Capítulo 11
Los smells de código alertan sobre problemas en méto- Los smells de diseño alertan sobre problemas en las
dos de una clase relaciones entre clases
Variedad de catálogos de smells de código y refactori- Variedad de catálogos de smells de diseño y patrones;
zaciones; usamos la taxonomía de Fowler usamos las versiones específicas de Ruby de los pa-
trones de diseño de la Gang of Four (GoF)
Métrica ABC y de complejidad ciclomática comple- Métricas LCOM (falta de cohesión entre métodos)
mentan a los smells de código con las alertas cuantita- complementan a los smells de diseño con las alertas
tivas cuantitativas
Refactorización por extracción de métodos y desplaza- Refactorización por extracción de clases y desplaza-
miento de código dentro de una clase mientos de código entre clases
Directrices SOFA para métodos adecuados (Cortos, Directrices SOLID para arquitectura de clases ade-
Realizar una Tarea, Pocos Argumentos, Nivel de Abs- cuada (Única Responsabilidad, Abierto/Cerrado,
tracción Único) Sustitución de Liskov, Inyección de Dependencias,
Demeter)
Algunos smells de código no aplican en Ruby Algunos smells de diseño no aplican en Ruby o SaaS
Figura 11.2. Paralelismos entre los síntomas de alerta y remedios presentados para las clases y métodos individuales en el
capítulo 9 y aquellos explicados para las relaciones entre clases en este capítulo. Por las razones explicadas en el texto,
aunque la mayoría de los libros usan la letra I del acrónimo SOLID para Segregación de la Interfaz (un smell que no
aparece en Ruby) y la D para Inyección de Dependencias, aquí usaremos I para Inyección de Dependencias y D para el
Principio de Demeter, que sucede frecuentemente en Ruby.
moviendo código entre clases— para eliminar los problemas. En algunos casos, podemos
refactorizar para hacer que el código siga un patrón de diseño existente y probado. En otros
casos, la refactorización no resulta necesariamente en grandes cambios estructurales de la
arquitectura de clases.
Al igual que ocurre con la refactorización a nivel de método, la aplicación de patrones de
diseño se aprende mejor con la práctica, aparte de que el número de patrones de diseño exis-
Debido a que los patrones
tentes excede lo que se puede cubrir en un capítulo de un libro. De hecho, hay libros enteros de diseño GoF se
dedicados exclusivamente a patrones de diseño, incluyendo el imprescindible Design Pat- desarrollaron en el contexto
terns: Elements of Reusable Object-Oriented Software (Gamma et al. 1994), cuyos autores de los lenguajes de
son conocidos como “Gang of Four” o GoF (“la banda de los cuatro”), y su clasificación de programación de tipado
estático, algunos problemas
patrones como los “patrones de diseño GoF ”. Los 23 patrones de diseño GoF se dividen que solucionan no se dan
en patrones de creación, estructurales y de comportamiento, como muestra la figura 11.3. Al en Ruby. Por ejemplo,
igual que sucede con el original de Fowler con la refactorización, el libro de patrones de dise- patrones que eliminan los
ño de GoF ha dado lugar a multitud de libros con ejemplos de patrones de diseño adaptados cambios en las firmas o
signaturas de los tipos (que
a lenguajes de programación específicos, incluyendo Ruby (Olsen 2007). harían necesaria una
Los cuatro autores GoF citan dos principios generales del buen diseño orientado a objetos recompilación del código)
que subyacen en la mayoría de patrones: prácticamente no se usan
en Ruby, que no se compila
• Preferencia por la composición (o agregación) y la delegación frente a la herencia. y no usa tipos para hacer
cumplir las declaraciones.
• Programar con una interfaz, y no con una implementación.
El significado de estas dos sentencias se irá comprendiendo a medida que se estudien algunos
patrones específicos.
En un mundo ideal, todos los programadores utilizarían con exquisitez los patrones de
diseño, refactorizarían continuamente su código tal y como sugiere el capítulo 9, y todo el
código sería elegante. No es necesario aclarar que no siempre es el caso. Un antipatrón es una
parte del código que parece querer estar estructurada según un patrón de diseño conocido pero
398 CAPÍTULO 11. PATRONES DE DISEÑO PARA CLASES SAAS
Patrones de creación
Abstract Factory, Factory Method (Factoría Abstracta, Método Factoría): Proporciona una interfaz para crear
familias de objetos relacionados o dependientes sin especificar su clase concreta.
Singleton (Instancia Única): Asegura que sólo existe una instancia de una determinada clase, y proporciona un
punto global de acceso a la misma.
Prototype (Prototipo): Especifica el tipo de objetos a crear usando una instancia prototipo, y crea los nuevos
objetos copiando éste. Como se comenta en el capítulo 6, la herencia basada en prototipos es parte del lenguaje
JavaScript.
Builder (Constructor): Separa la creación de un objeto complejo de su representación permitiendo crear varias
representaciones en el mismo proceso.
Patrones estructurales
Adapter, Proxy, Facade, Bridge (Adaptador, Proxy, Fachada, Puente): Convierte la interfaz de programación
de una clase en otra (en ocasiones más sencilla) que espera el cliente, o desacopla una interfaz abstracta de su
implementación, para inyección de dependencias o por rendimiento.
Decorator: Añade responsabilidades adicionales a un objeto dinámicamente, manteniendo la misma interfaz.
Ayuda a “preferir la composición o delegación sobre la herencia”.
Composite (Compuesto): Proporciona operaciones que funcionan tanto en un objeto individual como en una
colección de ese tipo de objetos.
Flyweight (Peso Ligero): Comparte información para soportar eficientemente un gran número de objetos simi-
lares.
Patrones de comportamiento
Template Method, Strategy (Método Plantilla, Estrategia): Encapsulan múltiples y variadas estrategias para la
misma tarea.
Observer (Observador): Una o más entidades requieren ser notificadas cuando sucede un evento en un objeto.
Iterator, Visitor (Iterador, Visitante): Separa el recorrido de una estructura de datos de las operaciones realiza-
das sobre cada elemento de esa estructura.
Null Object (Objeto nulo): (No aparece en el catálogo GoF) Proporciona un objeto con comportamientos neu-
trales que puede ser invocado de forma segura, para suplir los condicionales introducidos para evitar llamadas
a métodos.
State (Estado): Encapsula un objeto cuyos comportamientos (métodos) cambian dependiendo del estado in-
terno del objeto.
Chain of Responsibility (Cadena de responsabilidad): Evita acoplar al emisor de una petición con su receptor
teniendo más de un objeto para que pueda atender la petición, pasando ésta por la cadena hasta que un objeto
la gestiona.
Mediator (Mediador): Define un objeto que encapsula la interacción de un conjunto de objetos sin que estos
objetos tengan que referirse a los otros de forma explícita, permitiendo el desacoplamiento.
Interpreter (Intérprete): Define la representación de un lenguaje y proporciona un intérprete que ejecuta la
representación.
Command (Comando): Encapsula una operación en un objeto, permitiendo parametrizar varios clientes con
peticiones distintas, encolar o registrar peticiones, y soportar operaciones que se pueden deshacer.
Figura 11.3. Los 23 patrones de diseño GoF divididos en tres categorías. En cursiva el subconjunto de patrones que irán
apareciendo a lo largo del capítulo al ir ilustrando y solucionando vulneraciones de las pautas SOLID, y agrupados en una
única entidad aquellos patrones que están íntimamente relacionados entre sí (como sucede con Abstract Factory y Factory
Method). Siempre que se presente un patrón de diseño, se explicará su propósito, se añadirá una representación UML (ver
la siguiente sección) de la arquitectura de clases antes y después de la refactorización según dicho patrón, y cuando sea
posible, se acompañará de un ejemplo de uso del patrón “en la vida real” en Rails o en una gema Ruby.
11.1. PATRONES, ANTIPATRONES Y ARQUITECTURA DE CLASES SOLID 399
Figura 11.4. Las directrices de diseño SOLID y algunos smells que pueden sugerir vulneraciones de una o más de las
mismas. En contraste con el uso estándar de SOLID, usamos la I para Inyección de Dependencias y la D para la Ley de
Demeter, aunque la mayoría de la literatura usa la I como Segregación de Interfaz (que no aplica en Ruby) y D para
Inyección de Dependencias.
no lo está —a menudo debido a que el código (elegante) original ha ido evolucionando para
satisfacer los requisitos sin irse refactorizando durante el proceso—. Los smells de diseño, al
igual que sucede con los smells de código que se vieron en el capítulo 9, son signos que alertan
de que el código podría estar dirigiéndose hacia un antipatrón. A diferencia de los smells de
código, que normalmente aplican a los métodos dentro de una misma clase, los smells de
diseño se asocian a las relaciones entre clases y a cómo se reparten las responsabilidades
entre ellas. Por tanto, mientras que refactorizar un método implica mover código de un lado
a otro en la misma clase, refactorizar un diseño implica mover código entre clases, creando
nuevas clases o módulos (quizá extrayendo la parte común desde las clases existentes), o
quitar aquellas clases que no aportan nada.
Como ocurría con el acrónimo SOFA (capítulo 9), el nemotécnico SOLID (atribuido a “Tío Bob” Martin,
Robert C. Martin) representa un conjunto de cinco principios que debe respetar el código. ingeniero de software y
Como en el capítulo 9, los smells de diseño y las métricas cuantitativas pueden avisarnos consultor1 americano desde
1970, es el fundador de la
cuando se esté en peligro de quebrantar una o varias directrices SOLID; la solución normal- metodología ágil/XP y unos
mente pasa por refactorizar y así eliminar el problema adaptando el código con uno o más de los miembros destacados
patrones de diseño. del movimiento Software
La figura 11.4 muestra los nemónicos SOLID y lo que dicen acerca de la buena com- Craftsmanship, que
alienta a los programadores
posición de clases. En la discusión sobre los patrones de diseño seleccionados, veremos a verse a sí mismos como
vulneraciones de cada una de estas directrices, y mostraremos cómo la refactorización del profesionales creativos que
mal código (en algunos casos, con el propósito de aplicar un patrón de diseño) puede solu- aprenden una profesión
cionar el problema. En general, las pautas SOLID aspiran a una arquitectura de clases que disciplinada partiendo como
aprendices.
400 CAPÍTULO 11. PATRONES DE DISEÑO PARA CLASES SAAS
Al igual que sucedía con la refactorización y el código heredado, tratar de localizar smells
de diseño y eliminarlos mediante refactorización usando patrones de diseño racionalmente es
una habilidad que se aprende con la práctica. Por tanto, en vez de mostrar una lista sin más de
smells de diseño, refactorizaciones y patrones de diseño, nos centraremos en los principios
SOLID y daremos algunos ejemplos representativos del proceso genérico de detección de
smells de diseño y evaluación de alternativas para solucionarlos. A medida que usted se
enfrente a ellos en sus aplicaciones, la lista de recursos recomendados en la sección 11.11 se
convertirá en un aliado inestimable.
• El buen código debe permitir acomodar los cambios evolutivos de forma elegante.
Los patrones de diseño conforman soluciones validadas a problemas comunes que
frustran este objetivo. Funcionan proporcionando una forma limpia de separar las
cosas que pueden cambiar o evolucionar de aquellas que quedarán inmutables y una
forma limpia de introducir esos cambios.
• Como ocurre con los métodos, los smells de diseño y las métricas pueden servir
como alertas tempranas de un antipatrón —una parte de código que estaría mejor
estructurado si siguiera un patrón de diseño—.
11.2. LO JUSTO SOBRE UML 401
Autoevaluación 11.1.1. Verdadero o falso: una medida de la calidad de un software es el Grady Booch (1955–),
grado de utilización de patrones de diseño. reconocido
internacionalmente por su
⇧ Falso. Aunque los patrones de diseño proporcionan soluciones validadas a algunos pro- trabajo en el campo de la
blemas comunes, el código que no presenta dichos problemas puede no necesitar patrones, ingeniería del software y
pero esto no lo convierte en código pobre. Los autores GoF alertaban explícitamente contra entornos colaborativos de
la medición de la calidad del código en términos de uso de los patrones de diseño. desarrollo, desarrolló UML
con Ivar Jacobson y James
Rumbaugh.
11.2 Lo justo sobre UML
El Lenguaje Unificado de Modelado (UML, por sus siglas en inglés, Unified Modeling
Language) no es un lenguaje basado en texto sino un conjunto de técnicas de notación grá-
fica para “especificar, visualizar, modificar, construir y documentar los objetos de un sistema
de software orientado a objetos en desarrollo5 .” UML evolucionó desde 1995 hasta nuestros
días unificando varios estándares anteriores de lenguajes de modelado y tipos de diagramas,
enumerados en la figura 11.5.
Aunque este libro se centra en un tipo de modelado (ágil) más ligero —de hecho, el mo-
delado basado en UML es criticado por ser “excesivo” y muy pesado— algunos tipos de dia-
gramas UML son ampliamente utilizados incluso en modelado ágil. La figura 11.6 muestra
un diagrama de clases UML, que refleja cada clase existente en la aplicación, los métodos
y las variables (de clase y de instancia) más importantes y las relaciones con otras clases,
como asociaciones de tipo “tiene-muchos” o “pertenece-a”. Cada extremo de las líneas que
conectan dos clases con asociación entre sí va acompañada del número mínimo y máximo
de instancias que pueden participar en cada “lado” de dicha asociación, lo que se conoce
como multiplicidad de la asociación. Se utiliza el símbolo * para “ilimitado”. Por ejemplo,
una multiplicidad 1..* significa “uno o más”, 0..* representa “cero o más”, y 1 quiere decir
“exactamente uno”. UML distingue entre dos tipos de asociaciones de “posesión” (tiene-uno
o tiene-muchos). En una agregación, los objetos poseídos sobreviven a la destrucción del
objeto poseedor. Por ejemplo, Curso tiene varios estudiantes es una agregación ya que afor-
tunadamente, ¡los estudiantes no se destruyen cuando el curso finaliza! En una composición,
normalmente los objetos poseídos son destruidos cuando el poseedor es destruido a su vez.
Por ejemplo, Película tiene varias críticas es una composición ya que al borrar la película,
todas sus críticas deberían ser borradas con ella.
Los diagramas de clases son muy populares incluso entre los ingenieros de software que
no usan el resto de diagramas UML. Con esta introducción a UML, podemos utilizar los
diagramas de clases para ilustrar el “antes y después” de la arquitectura de clases al mejorar
el código apoyándonos en las directrices SOLID y los patrones de diseño.
402 CAPÍTULO 11. PATRONES DE DISEÑO PARA CLASES SAAS
Diagramas de estructura
Clases Describe la estructura de un sistema mostrando las clases del sistema, sus atributos y las rela-
ciones entre clases.
Componentes Describe cómo se divide el sistema software en componentes y muestra las dependencias entre
dichos componentes.
Estructura compuesta Describe la estructura interna de una clase y las asociaciones que esta estructura hace posibles.
Despliegue Describe el hardware utilizado en la instalación del sistema y los entornos de ejecución y arte-
factos desplegados en el hardware.
Objetos Muestra una vista completa o parcial de la estructura del modelo de un sistema en un momento
determinado.
Paquetes Describe cómo se divide un sistema en agrupaciones lógicas y muestra las dependencias exis-
tentes entre dichas agrupaciones.
Perfiles Describe los “estereotipos” de objetos reutilizables y específicos del dominio a partir de los
cuales se pueden derivar tipos de objetos específicos que se usan en una aplicación determinada.
Diagramas de interacción
Comunicación Muestra las interacciones entre objetos o partes en términos de secuencias de mensajes. Repre-
sentan una combinación de información extraída de los diagramas de clases, secuencia y casos
de uso que describen la estructura estática y el comportamiento dinámico de un sistema.
Resumen de interacción Proporciona una visión general en la que los nodos representan diagramas de comunicación.
Secuencia Muestra cómo los objetos se comunican unos con otros en términos de secuencias de mensajes.
Además plasma el tiempo de vida de los objetos en relación a estos mensajes.
Tiempo Un tipo de diagrama específico del diagrama de interacción que se enfoca en las restricciones
de tiempo.
Diagramas de comportamiento
Actividad Describe paso a paso los flujos lógicos y operacionales de los componentes de un sistema. Un
diagrama de actividad muestra todos los flujos de control.
Estado Describe los estados y las transiciones entre los mismos en un sistema.
Casos de uso Describe la funcionalidad de un sistema en términos de actores, sus objetivos representados
como casos de uso y cualquier dependencia entre dichos casos de uso.
Figura 11.5. Los catorce tipos de diagramas que define UML 2.2 para la descripción de un sistema software. Estas
descripciones se basan en el resumen de UML de la Wikipedia, que muestra además un ejemplo de cada tipo de
diagrama. Los diagramas de casos de uso son parecidos a las historias de usuario de la metodología ágil, pero adolecen del
nivel de detalle que posibilitan herramientas como Cucumber para acortar la distancia existente entre historias de usuario y
pruebas de integración/aceptación.
1 0..*
AccountCode 1 Item Vouchertype Showdate
0..* price name date_and_time
sold_on season start_sales
Customer transfer_to_customer merge_with end_sales
1 0..* refund expunge! house_capacity
name
email 0..* 1 revenue_per_seat
subscriber? Voucher 1 1..*
0..* 1
merge_with Donation reserved? Show
expunge! fund_code changeable? name
reserve! list_starting
redeemable_for_show? revenue_per_seat
Figura 11.6. Este diagrama de clases muestra un subconjunto de las clases de la aplicación de venta de entradas de teatro
coherente con las figuras 9.4 y 9.5. Cada caja representa una clase con sus métodos y atributos más importantes
(responsabilidades). La herencia se representa a través de una flecha. Las clases con asociaciones entre sí se conectan
mediante líneas cuyos extremos llevan asociados su multiplicidad y opcionalmente un diamante —hueco para agregaciones,
relleno para composiciones y no presente en el resto de casos—.
11.3. PRINCIPIO DE ÚNICA RESPONSABILIDAD 403
Autoevaluación 11.2.1. Sea un diagrama de clases UML que describe la relación “Univer-
sidad tiene varios departamentos”. ¿Qué multiplicidades pueden permitirse en cada extremo
de la asociación?
⇧ El extremo de la universidad tiene multiplicidad 1, ya que un departamento debe pertenecer
a una y sólo una universidad. El extremo del departamento tiene multiplicidad 1..* ya que
uno o más departamentos pueden pertenecer a una universidad.
Figura 11.7. La puntuación “recomendada” para la métrica de falta de cohesión entre métodos (LCOM) depende en gran
medida de qué variante de LCOM se está utilizando. La tabla muestra dos de las variantes más ampliamente empleadas.
http://pastebin.com/hi5175Wr
1 class Moviegoer
2 attr_accessor : name , : street , : phone_number , : zipcode
3 validates : phone_number , # ...
4 validates : zipcode , # ...
5 def f o r m a t _ p h o n e _ n u m b e r ; ... ; end
6 def verify _zipco de ; ... ; end
7 def format _addre ss ( street , phone_number , zipcode ) # data clump
8 # do formatting , calling f o r m a t _ p h o n e _ n u m b e r and veri fy_zip code
9 end
10 end
11 # After applying Extract Class :
12 class Moviegoer
13 attr_accessor : name
14 has_one : address
15 end
16 class Address
17 belongs_to : moviegoer
18 attr_accessor : phone_number , : zipcode
19 validates : phone_number , # ...
20 validates : zipcode , # ...
21 def format _addre ss ; ... ; end # no arguments - operates on ' self '
22 private # no need to expose these now :
23 def f o r m a t _ p h o n e _ n u m b e r ; ... ; end
24 def verify _zipco de ; ... ; end
25 end
Figura 11.8. Para llevar a cabo la extracción de clase, se identifican el grupo de métodos que comparten una
responsabilidad distinta de la del resto de la clase, se mueven dichos métodos a una nueva clase, se transforman los datos
sobre los que actúan estos métodos (los “viajeros en compañía”) en variables de instancia de la clase, y se pasa una instancia
de la clase en vez de los valores individuales.
Moviegoer
Address
name
phone_number
phone_number Moviegoer 1 1
zipcode
zipcode name
check_zipcode
check_zipcode
format_phone_number
format_phone_number
Figura 11.9. Diagramas de clase UML antes (izq.) y después (dcha.) de extraer la clase Address desde Moviegoer.
http://pastebin.com/xxHCeLzV
1 class Report
2 def output
3 formatter =
4 case @format
5 when : html
6 HtmlFormatter . new ( self )
7 when : pdf
8 PdfFormatter . new ( self )
9 # ... etc
10 end
11 end
12 end
Figura 11.10. La clase Report depende de su clase base Formatter, que tiene subclases HtmlFormatter y PdfFormatter.
Debido a la selección explícita del formato del informe, al añadir un nuevo tipo de informe es necesario modificar
Report#output, y probablemente obliga a cambiar otros métodos de Report que tengan una lógica similar —es la llamada
cirugía de escopeta—.
http://pastebin.com/HZsLHVcc
1 class Report
2 def output
3 f or ma t te r_ c la s s =
4 begin
5 @format . to_s . classify . constantize
6 rescue NameError
7 # ... handle ' invalid formatter type '
8 end
9 formatter = f or m at te r _c l as s . send (: new , self )
10 # etc
11 end
12 end
Figura 11.11. La metaprogramación de Ruby y el tipado dinámico (duck typing) posibilitan una implementación elegante
del patrón Abstract Factory. Rails proporciona classify para convertir snake_case a UpperCamelCase. constantize es una
sintaxis simplificada disponible en Rails que invoca al método Ruby de introspección Object#const_get sobre el receptor.
Se atiende también el caso de valor no válido en la clase Formatter, que el código anterior no atendía.
PdfFormatter HtmlFormatter
PdfFormatter HtmlFormatter output() output()
header() header() header() header()
body() body() body() cssStyles()
footer() footer() pdfTrailer() body()
Figura 11.12. En el patrón Template Method (izq.), los puntos de extensión vienen representados por header, body y
footer, ya que el método Report#output invoca a @formatter.header, @formatter.body, etc., cada uno de los cuales
delega su tarea en un colega especializado de la subclase apropiada. (En gris claro los métodos que simplemente delegan su
función en una subclase). En el patrón Strategy (dcha.), el punto de extensión es el propio método output, que delega la
tarea completa en una subclase. La delegación es muy común en la composición, motivo por el cual mucha gente se refiere a
ésta como patrón de diseño de delegación.
408 CAPÍTULO 11. PATRONES DE DISEÑO PARA CLASES SAAS
Formatter Formatter
output() output()
PdfWithPassword-
RegularPdfFormatter
Formatter PdfWithWatermark- PdfWithPassword-
output()
output() Formatter Formatter
PdfWithPasswordAnd- PdfWithWatermark- @base @base
WatermarkFormatter Formatter output() output()
output() output() add_watermark() protect_with_password()
Figura 11.13. (Izq.) La multiplicación de subclases como resultado de intentar solucionar el problema de Formatter
utilizando herencia demuestra por qué el diseño de las clases debe “preferir la composición sobre la herencia”. (Dcha.) Una
solución más elegante que utiliza el patrón de diseño Decorator.
(en los lenguajes de tipado estático es necesario crear un método factoría para cada subclase
y hacer que todos implementen una interfaz común —de aquí viene el nombre del patrón—).
Otro enfoque diferente es aprovechar los patrones Strategy (Estrategia) o Template
Method (Método Plantilla). Ambos soportan el caso en el que hay una estrategia genérica
para realizar una tarea pero diferentes variantes posibles. La diferencia existente entre am-
bos es el nivel al cual se captura la parte común. Con Template Method, aunque la imple-
mentación de cada paso puede variar, el conjunto de pasos es el mismo para todas las varian-
tes; es por ello que normalmente se implementa mediante herencia. Con el patrón Strategy,
la tarea global es la misma, pero el conjunto de pasos puede ser distinto en cada variante; por
lo que se suele implementar utilizando composición. La figura 11.12 muestra cómo se puede
aplicar cada patrón al formateador de informes. Si cualquier tipo de Formatter sigue la
misma secuencia de pasos de alto nivel —por ejemplo, generar la cabecera, generar el cuerpo
del informe y después generar el pie de página— se puede usar el patrón Template Method.
En caso contrario, si los pasos son diferentes, tiene más sentido emplear el patrón Strategy.
Un ejemplo de la vida real del patrón Strategy es OmniAuth (ver la sección 5.2): muchas
aplicaciones necesitan autenticación por terceros y los pasos son distintos dependiendo del
proveedor de autenticación, pero la API para todos ellos es la misma. De hecho, OmniAuth
incluso se refiere a sus plug-ins como “estrategias”.
Una forma diferente de vulneración del principio OCP surge cuando se desea añadir
nuevos comportamientos a una clase existente y se descubre que no se puede hacer sin modi-
ficarla. Por ejemplo, los ficheros PDF se pueden generar con o sin protección con contraseña
y con o sin marca al agua “Borrador” de fondo. Ambas características representan un com-
portamiento adicional a la funcionalidad que ya realiza PdfFormatter. Si usted tiene amplia
experiencia en programación orientada a objetos, su primera idea podría ser resolver el pro-
blema utilizando herencia, como muestra el diagrama UML de la figura 11.13 (izq.), pero
hay cuatro permutaciones de características por lo que acabaría con cuatro subclases con du-
plicados entre ellos —lo que no respeta la filosofía DRY—. Afortunadamente, el patrón
Decorator (Decorador ) puede ser de ayuda: se “decora” o “envuelve” una clase o método
envolviéndolo en una versión mejorada que presenta la misma API, pudiéndose crear múlti-
Los “decoradores”8 ples decoradores según se requieran. La figura 11.14 muestra el código correspondiente al
de Python,
desafortunadamente, no diseño (más elegante) basado en el patrón Decorator del formateador de PDF mostrado en la
guardan ninguna relación figura 11.13 (dcha.).
con el patrón de diseño En el mundo real, el módulo ActiveSupport de Rails proporciona un medio para aplicar
Decorator. el patrón Decorator a nivel de método a través de alias_method_chain, que es realmente
11.4. PRINCIPIO DE ABIERTO/CERRADO 409
http://pastebin.com/u8aYdwEL
1 class PdfFormatter
2 def initialize ; ... ; end
3 def output ; ... ; end
4 end
5 class P d f W i t h P a s s w o r d F o r m a t t e r < PdfFormatter
6 def initialize ( base ) ; @base = base ; end
7 def p r o t e c t _ w i t h _ p a s s w o r d ( or ig i na l_ o ut p ut ) ; ... ; end
8 def output ; p r o t e c t _ w i t h _ p a s s w o r d @base . output ; end
9 end
10 class P d f W i t h W a t e r m a r k F o r m a t t e r < PdfFormatter
11 def initialize ( base ) ; @base = base ; end
12 def add_watermark ( or ig i na l_ o ut p ut ) ; ... ; end
13 def output ; add_watermark @base . output ; end
14 end
15 end
16 # If we just want a plain PDF
17 formatter = PdfFormatter . new
18 # If we want a " draft " watermark
19 formatter = P d f W i t h W a t e r m a r k F o r m a t t e r . new ( PdfFormatter . new )
20 # Both password protection and watermark
21 formatter = P d f W i t h W a t e r m a r k F o r m a t t e r . new (
22 P d f W i t h P a s s w o r d F o r m a t t e r . new ( PdfFormatter . new ) )
Figura 11.14. Para aplicar el patrón Decorator a una clase, se “envuelve” creando una subclase (para cumplir con el
Principio de Sustitución de Liskov, que veremos en la sección 11.5). La subclase delega en la clase o método originales para
la funcionalidad que no sufre cambios, e implementa los métodos añadidos que extienden características. Podemos entonces
“ir aumentando” de forma sencilla hasta conseguir la versión de PdfFormatter que necesitemos “apilando” decoradores.
http://pastebin.com/rdyrjyAN
1 # reopen Mailer class and decorate its send_email method .
2 class Mailer
3 a l i a s _ m e t h o d _ c h a i n : send_email , : cc
4 def s e n d _ e m a i l _ w i t h _ c c ( recipient , body ) # this is our new method
5 s e n d _ e m a i l _ w i t h o u t _ c c ( recipient , body ) # will call original method
6 copy_sender ( body )
7 end
8 end
9 # now we have two methods :
10 send_email (...) # calls s e n d _ e m a i l _ w i t h _ c c
11 s e n d _ e m a i l _ w i t h _ c c (...) # same thing
12 s e n d _ e m a i l _ w i t h o u t _ c c (...) # call ( renamed ) original method
Figura 11.15. Para aplicar el patrón Decorator al método ya existente Mailer#send_email, reabrimos la clase y
utilizamos alias_method_chain para ello. Sin cambiar ninguna clase que invoque a send_email, todas las llamadas usan
ahora la versión “decorada” que envía un correo electrónico con copia al remitente.
410 CAPÍTULO 11. PATRONES DE DISEÑO PARA CLASES SAAS
útil en combinación con las clases abiertas de Ruby, tal y como muestra la figura 11.15.
Un ejemplo más interesante del patrón Decorator en la vida real es el servidor de aplica-
ciones Rack que hemos venido usando desde el capítulo 2. El corazón de Rack es un módulo
“middleware” que acepta peticiones HTTP y devuelve un array de tres elementos: el código
HTTP de respuesta, las cabeceras HTTP y el cuerpo de la respuesta. Una aplicación basada
en Rack define una “pila” de componentes middleware por el que pasan todas las peticiones:
para añadir un comportamiento a una petición HTTP (por ejemplo, para interceptar ciertas
peticiones como hace OmniAuth para iniciar un proceso de autenticación), se “decora” el
comportamiento básico de la petición HTTP. Existen decoradores adicionales que propor-
cionan soporte para SSL (Secure Sockets Layer), que permiten medir el rendimiento de la
aplicación y realizar algunos tipos de cacheo HTTP.
• Para conseguir que una clase esté abierta para su extensión pero cerrada para su
modificación, se necesitan mecanismos que habiliten ciertos puntos de extensión
en los sitios donde pensamos que se puede necesitar una extensión de funcionali-
dad en el futuro. El smell de diseño Sentencia Case es un síntoma de una posible
vulneración del principio OCP.
http://pastebin.com/hr0DqtWt
1 class Rectangle
2 attr_accessor : width , : height , : t o p_ le f t_ c or ne r
3 def new ( width , height , top_left ) ... ; end
4 def area ... ; end
5 def perimeter ... ; end
6 end
7 # A square is just a special case of rectangle ... right ?
8 class Square < Rectangle
9 # ooops ... a square has to have width and height equal
10 attr_reader : width , : height , : side
11 def width =( w ) ; @width = @height = w ; end
12 def height =( w ) ; @width = @height = w ; end
13 def side =( w ) ; @width = @height = w ; end
14 end
15 # But is a Square really a kind of Rectangle ?
16 class Rectangle
17 def m a k e _ t w i c e _ a s _ w i d e _ a s _ h i g h ( dim )
18 self . width = 2* dim
19 self . height = dim # doesn 't work !
20 end
21 end
Figura 11.16. Desde el punto de vista del comportamiento, los rectángulos tienen posibilidades que no pueden ofrecer los
cuadrados —por ejemplo, la capacidad de establecer las longitudes de sus lados independientemente, como en
Rectangle#make_twice_as_wide_as_high—.
http://pastebin.com/1hJ4bYsM
1 # LSP - compliant solution : replace inheritance with delegation
2 # Ruby 's duck typing still lets you use a square in most places where
3 # rectangle would be used - but no longer a subclass in LSP sense .
4 class Square
5 attr_accessor : rect
6 def initialize ( side , top_left )
7 @rect = Rectangle . new ( side , side , top_left )
8 end
9 def area ; rect . area ; end
10 def perimeter ; rect . perimeter ; end
11 # A more concise way to delegate , if using ActiveSupport ( see text ) :
12 # delegate : area , : perimeter , : to = > : rectangle
13 def side =( s ) ; rect . width = rect . height = s ; end
14 end
Figura 11.17. Al igual que ocurría con varias vulneraciones de OCP, el problema proviene de un uso inadecuado de la
herencia. Como muestra la figura 11.17, tener preferencia por la composición y la delegación frente a la herencia soluciona
el problema. La línea 12 muestra una sintaxis concreta para la delegación disponible para las aplicaciones que usen
ActiveSupport (y todas las aplicaciones Rails lo hacen); una funcionalidad semejante para aplicaciones Ruby pero no Rails
la proporciona el módulo Forwardable en la librería estándar de Ruby.
Figura 11.18. Izquierda: diagrama de clases UML que representa el código original, el cual vulnera el principio LSP en la
figura 11.16, ya que sobrescribe destructivamente Rectangle#make_twice_as_wide_as_high. Derecha: diagrama de
clases para el código refactorizado y que cumple con el principio LSP de la figura 11.17.
11.6. PRINCIPIO DE INYECCIÓN DE DEPENDENCIAS 413
de Ruby, este uso de la composición y la delegación aún nos permite pasar una instancia
de Square en la mayoría de sitios donde se esperaría un Rectangle, aunque no sea ya una
subclase de aquél; un lenguaje de tipado estático nos obligaría a crear explícitamente una
interfaz que declarara las operaciones comunes a Square y Rectangle.
http://pastebin.com/ZdhcYb7w
1 class EmailList
2 attr_reader : mailer
3 delegate : send_email , : to = > : mailer
4 def initialize
5 @mailer = MailerMonkey . new
6 end
7 end
8 # in R ottenP otatoe s E m a i l L i s t C o n t r o l l e r :
9 def a d v e r t i s e _ d i s c o u n t _ f o r _ m o v i e
10 moviegoers = Moviegoer . interested_in params [: movie_id ]
11 EmailList . new . send_email_to moviegoers
12 end
Suponga que esta funcionalidad tiene tanto éxito que usted decide extender el mecanismo
de forma que los usuarios que están en la red social Amiko puedan también activar el reenvío
automático de estos correos electrónicos a sus amigos en la red social, mediante la nueva
gema Amiko que envuelve la API REST de Amiko para listas de amigos, publicación en los
muros, mensajería, etc. Sin embargo, existen dos problemas.
El primero es que EmailList#initialize depende de código de MailerMonkey, pero
ahora en ocasiones se necesita usar Amiko en su lugar. Este cambio en tiempo de ejecución
es el problema que soluciona la inyección de dependencias —ya que no se conoce hasta
tiempo de ejecución qué tipo de mailer se necesitará, modificamos EmailList#initialize
para poder “inyectar” el valor adecuado en tiempo de ejecución—:
http://pastebin.com/8PHBpm5k
1 class EmailList
2 attr_reader : mailer
3 delegate : send_email , : to = > : mailer
4 def initialize ( mailer_type )
5 @mailer = mailer_type . new
6 end
7 end
8 # in R ottenP otatoe s E m a i l L i s t C o n t r o l l e r :
9 def a d v e r t i s e _ d i s c o u n t _ f o r _ m o v i e
10 moviegoers = Moviegoer . interested_in params [: movie_id ]
11 mailer = if Config . has_amiko ? then Amiko else MailerMonkey end
12 EmailList . new ( mailer ) . send_email_to moviegoers
ActiveRecord ha sido 13 end
criticado por configurar la
base de datos al inicio Se puede pensar en el principio DIP como la inyección de una costura o seam entre ambas
desde database.yml en clases y, de hecho, en los lenguajes de tipado estático, DIP ayuda facilitando las pruebas. Esta
vez de utilizar DIP. ventaja es menos aparente en Ruby, ya que como hemos visto se pueden crear seams en casi
Presuntamente, los
diseñadores creyeron que la
cualquier contexto en tiempo de ejecución utilizando mocking o simulando (stubbing) junto
base de datos no cambiaría con las características de lenguaje dinámico que posee Ruby.
mientras la aplicación El segundo problema es que Amiko expone una interfaz diferente y más compleja
estuviera en ejecución. que el método sencillo send_email que proporciona MailerMonkey (en el que delega
Aunque los seams creados
por el principio DIP ayudan
EmailList#send_email en la línea 3), y nuestro método controlador sigue invocando a
también con la simulación send_email en el objeto mailer. El patrón Adapter (Adaptador ) puede ser de ayuda
(mocking, stubbing), el aquí: está diseñado para convertir la API existente en otra que sea compatible con el cliente
capítulo 8 muestra cómo las que la utiliza. En este caso, se puede definir una nueva clase AmikoAdapter que transforma
clases abiertas de Ruby y
sus características de la API más compleja de Amiko en otra más simple que se ajusta a lo esperado por nuestro
metaprogramación permiten controlador, proporcionando el mismo método send_email que ya proporciona Mailer-
insertar seams para Monkey:
pruebas en cualquier lugar
que sea necesario.
11.6. PRINCIPIO DE INYECCIÓN DE DEPENDENCIAS 415
http://pastebin.com/js6C67mJ
1 class Config
2 def self . email_enabled ? ; ... ; end
3 def self . emailer ; if has_amiko ? then Amiko else MailerMonkey end ; end
4 end
5 def a d v e r t i s e _ d i s c o u n t _ f o r _ m o v i e
6 if Config . email_enabled ?
7 moviegoers = Moviegoer . interested_in ( params [: movie_id ])
8 EmailList . new ( Config . emailer ) . send_email_to ( moviegoers )
9 end
10 end
http://pastebin.com/avRQAgZc
1 class Config
2 def self . emailer
3 if em ail_di sabled ? then NullMailer else
4 if has_amiko ? then AmikoAdapter else MailerMonkey end
5 end
6 end
7 end
8 class NullMailer
9 def initialize ; end
10 def send_email ; true ; end
11 end
12 def a d v e r t i s e _ d i s c o u n t _ f o r _ m o v i e
13 moviegoers = Moviegoer . interested_in ( params [: movie_id ])
14 EmailList . new ( Config . emailer ) . send_email_to ( moviegoers )
15 end
16 end
Figura 11.19. Arriba: una forma ingenua de deshabilitar un comportamiento determinado es a través de condicionales que
lo excluyan cada vez que ocurre. Abajo: el patrón Null Object (Objeto Nulo) elimina la necesidad de condicionales
proporcionando métodos "ficticios", seguros de invocar pero que no realizan ninguna acción.
http://pastebin.com/Eimsw8ZF
1 class AmikoAdapter
2 def initialize ; @amiko = Amiko . new (...) ; end
3 def send_email
4 @amiko . authenticate (...)
5 @amiko . send_message (...)
6 end
7 end
8 # Change the controller method to use the adapter :
9 def a d v e r t i s e _ d i s c o u n t _ f o r _ m o v i e
10 moviegoers = Moviegoer . interested_in params [: movie_id ]
11 mailer = if Config . has_amiko ? then AmikoAdapter else MailerMonkey end
12 EmailList . new ( mailer ) . send_email_to moviegoers
13 end
Cuando el patrón Adapter no sólo convierte la API sino que también la simplifica —
por ejemplo, la gema Amiko expone muchas otras funciones de Amiko no relacionadas con
el correo electrónico, pero AmikoAdapter únicamente “adapta” la parte de la API que es
específica del correo electrónico— es lo que en ocasiones se conoce como el patrón Facade
(Fachada).
Por último, incluso en los casos en los que la estrategia de correo electrónico a utilizar ya
es conocida cuando la aplicación comienza a ejecutarse, ¿qué ocurre si se quiere deshabilitar
completamente el envío de correos electrónicos cada cierto tiempo? La figura 11.19 (arriba)
muestra un enfoque algo ingenuo: se mueve la lógica para determinar qué emailer utilizar en
una nueva clase Config, pero además hay que añadir un condicional para “sacar” la lógica de
envío de emails en el método controlador si se ha desactivado el envío de correos electróni-
cos. Pero en el caso de que haya más sitios en la aplicación donde se haga una comprobación
416 CAPÍTULO 11. PATRONES DE DISEÑO PARA CLASES SAAS
EmailList EmailList
GenericMailer
@mailer @mailer
send_email()
EmailList send_email() send_email()
@mailer
send_email() AmikoAdapter MailerMonkey AmikoAdapter MailerMonkey
@mailer send_email() @mailer send_email()
MailerMonkey send_email() send_email()
send_email() NullMailer
Amiko send_email() Amiko NullMailer
authenticate() authenticate() send_email()
send_message() send_message()
Figura 11.20. Izq.: sin inyección de dependencias, EmailList depende directamente de MailerMonkey. Centro: con
inyección de dependencias, se puede establecer @mailer para usar en tiempo de ejecución cualquier objeto de la clase
MailerMonkey, NullMailer (que implementa el patrón Null Object para deshabilitar el envío de emails), o AmikoAdapter
(que implementa el patrón Adapter/Facade sobre Amiko), donde todas estas clases exponen la misma API. Dcha.: en
lenguajes de tipado estático, la clase abstracta GenericMailer formaliza de facto que los tres mailers poseen unas API
compatibles, pero en Ruby esta superclase a menudo se omite si consta únicamente de métodos abstractos (como es el caso),
ya que los métodos y clases abstractos no son parte del lenguaje.
similar, se tendrá que replicar allí la misma condición para “sacar” esa característica (cirugía
de escopeta o shotgun surgery). Una alternativa mejor que esa es usar el patrón Null Object
(Objeto Nulo), en el que se crea un objeto “ficticio” que contiene los mismos comportamien-
tos que un objeto real pero no se realiza ninguna acción al invocarlos. La figura 11.19 (abajo)
aplica este patrón al ejemplo, evitando la proliferación de condicionales a lo largo del código.
La figura 11.20 muestra el diagrama de clases UML que corresponde a varias de las
versiones generadas durante el presente ejemplo del principio DIP.
Un patrón interesante y relacionado con Adapter y Facade es el patrón Proxy , en el
que un objeto “reemplaza” a otro que posee la misma API. El cliente interactúa con el proxy
en vez de con el objeto original; el proxy puede reenviar algunas peticiones directamente al
objeto original (es decir, delegarlas en él) pero puede realizar otras acciones en respuesta a
peticiones diferentes, quizás por razones de rendimiento o eficiencia.
Un ejemplo clásico de este patrón son las asociaciones de ActiveRecord (ver la sec-
ción 5.3). Recuerde que bajo la relación Película tiene varias críticas, podemos escribir
[email protected]. ¿Qué tipo de objeto es r? Aunque hayamos visto que podemos tratar
r como una colección enumerable, en realidad es un objeto proxy que responde a todos los
métodos de la colección (length, <<, etc.), pero sin realizar consultas a la base de datos ex-
cepto en aquellas ocasiones en las que realmente lo necesita. Otro ejemplo de uso del patrón
Proxy puede ser para enviar correo electrónico mientras se está desconectado de Internet. Si
a través del método send_email se accede al servicio de email real en Internet, un objeto
proxy podría proporcionar un método send_email que simplemente almacenara el correo
electrónico en el disco local hasta la próxima vez que la máquina se conecte a Internet. Este
proxy protegería al cliente (IU de envío de correo) de tener que cambiar su comportamiento
cuando el usuario no dispone de conexión.
11.7. PRINCIPIO DE DEMETER 417
Autoevaluación 11.6.1. ¿Por qué el uso correcto de DIP tiene un impacto mayor en los
lenguajes de tipado estático?
⇧ En estos lenguajes, no se puede crear una costura o seam en tiempo de ejecución para so-
brescribir un comportamiento “cableado”, tal y como se puede hacer en lenguajes de tipado
dinámico como Ruby, por lo que el seam debe proporcionarse por adelantado inyectando una
dependencia.
http://pastebin.com/iaNeSeCJ
1 # This example is adapted from Dan Manges 's blog , dcmanges . com
2 class Wallet ; attr_accessor : credit _balan ce ; end
3 class Moviegoer
4 attr_accessor : wallet
5 def initialize
6 # ... setup wallet attribute with correct credit balance
7 end
8 end
9 class MovieTheater
10 def collect_money ( moviegoer , amount )
11 # VIOLATION OF DEMETER ( see text )
12 if moviegoer . wallet . credit _balan ce < amount
13 raise I n s u f f i c i e n t F u n d s E r r o r
14 else
15 moviegoer . wallet . credi t_bala nce -= due_amount
16 @ c o l l e c t e d _ a m o u n t += due_amount
17 end
18 end
19 end
20 # Imagine testing the above code :
21 describe MovieTheater do
22 describe " collecting money " do
23 it " should raise error if moviegoer can 't pay " do
24 # " Mock trainwreck " is a warning of a Demeter violation
25 wallet = mock ( ' wallet ' , : cr edit_b alance = > 5.00)
26 moviegoer = mock ( ' moviegoer ' , : wallet = > wallet )
27 lambda { @theater . collect_money ( moviegoer , 10.00) }.
28 should raise_error (...)
29 end
30 end
31 end
Figura 11.21. La línea 12 muestra una vulneración del Principio de Demeter: aunque es razonable para una sala de cine
MovieTheater saber acerca de un cliente Moviegoer, no lo es tanto tener conocimiento sobre la implementación de la clase
cartera Wallet (a través del su atributo wallet manipula el valor del saldo de la cartera credit_balance). Además, se está
atendiendo el problema de “no hay suficiente dinero” en MovieTheater, aunque lógicamente parece una tarea que
pertenece a Wallet.
http://pastebin.com/QtxWkUy6
1 # Better : delegate cr edit_b alance so MovieTheater only accesses Moviegoer
2 class Moviegoer
3 def credit _balan ce
4 self . wallet . cre dit_b alance # delegation
5 end
6 end
7 class MovieTheater
8 def collect_money ( moviegoer , amount )
9 if moviegoer . credi t_bala nce >= amount
10 moviegoer . cre dit_ba lance -= due_amount
11 @ c o l l e c t e d _ a m o u n t += due_amount
12 else
13 raise I n s u f f i c i e n t F u n d s E r r o r
14 end
15 end
16 end
http://pastebin.com/rgB4LnMk
1 class Wallet
2 attr_reader : cre dit_ba lance # no longer attr_accessor !
3 def withdraw ( amount )
4 raise I n s u f f i c i e n t F u n d s E r r o r if amount > @ cr ed i t_ ba l an c e
5 @ cr e di t_ b al an c e -= amount
6 amount
7 end
8 end
9 class Moviegoer
10 # behavior delegation
11 def pay ( amount )
12 wallet . withdraw ( amount )
13 end
14 end
15 class MovieTheater
16 def collect_money ( moviegoer , amount )
17 @ c o l l e c t e d _ a m o u n t += moviegoer . pay ( amount )
18 end
19 end
Figura 11.22. (Arriba) Si Moviegoer delega credit_balance en su cartera wallet, entonces la clase MovieTheater no
tiene que conocer ya nada sobre la implementación de wallet. Sin embargo, aún puede resultar inapropiado que el
comportamiento asociado a una transacción o pago (restar el total del pago del saldo) quede expuesto a MovieTheater
cuando en realidad debería ser responsabilidad únicamente de Moviegoer o Wallet. (Abajo) Delegando el comportamiento
del pago completo, en vez de sólo los atributos a través de los que se acomete, soluciona el problema y permite así que el
código cumpla el Principio de Demeter.
puede observarse en las líneas 25 a 27 de la figura 11.21: para poder probar código que vul-
nera el Principio de Demeter, nos vemos abocados a establecer una “cadena” de mocks para
poder invocar el método que queremos probar.
Una vez más, la delegación viene al rescate. Delegando en el atributo credit_balance de
Wallet, como se muestra en la figura 11.22 (arriba), ya se consigue una gran mejora de forma
sencilla. Aunque la forma óptima de delegación es la mostrada en la figura 11.22 (abajo),
donde el comportamiento de un pago (como, por ejemplo, cuándo lanzar un error para pagos
fallidos), está completamente encapsulado dentro de Wallet.
Tanto el smell de Intimidad Inapropiada como las vulneraciones del Principio de Deme-
ter pueden aparecer en cualquier situación en la que usted tenga la sensación de “traspasar”
una interfaz para acometer cierta tarea, de manera que se expone a depender de los detalles
de la implementación de una clase que no debería ser de su incumbencia. Existen tres pa-
trones de diseño que abordan escenarios comunes que de lo contrario podrían derivar hacia
vulneraciones del Principio de Demeter.
420 CAPÍTULO 11. PATRONES DE DISEÑO PARA CLASES SAAS
http://pastebin.com/zznALkdt
1 class EmailList
2 observe Review
3 def after_create ( review )
4 moviegoers = review . moviegoers # from has_many : through , remember ?
5 self . email ( moviegoers , " A new review for #{ review . movie } is up . " )
6 end
7 observe Moviegoer
8 def after_create ( moviegoer )
9 self . email ([ moviegoer ] , " Welcome , #{ moviegoer . name }! " )
10 end
11 def self . email ; ... ; end
12 end
Figura 11.23. Un subsistema de lista de correo electrónico observa otros modelos de manera que puede generar un correo
electrónico en respuesta a ciertos eventos. El patrón Observer es una solución perfecta ya que recoge toda la problemática
sobre cuándo enviar los correos en un único lugar.
Autoevaluación 11.7.2. A pesar de que “la delegación es el principal mecanismo” para re-
solver violaciones del Principio de Demeter, ¿por qué razón debería preocuparse si encuen-
tra muchas delegaciones de la clase A en la clase B simplemente para resolver vulneraciones
del Principio de Demeter presentes en la clase C?
⇧ Debería preguntarse si no debería existir una relación directa entre la clase C y la clase B,
o si la clase A adolece de Envidia de Características respecto de la clase B, lo que le estaría
indicando que la división de responsabilidades existente entre A y B necesita ser rediseñada.
ellos, el equipo analiza uno o varios patrones de arquitectura que puedan solucionar el proble-
ma. El equipo entonces desciende al siguiente nivel de subproblemas, y de nuevo investiga
patrones de diseño que puedan solucionarlos. La filosofía es aprender de la experiencia de
otros, capturada en forma de patrones, de manera que se evite repetir los errores de sus pre-
decesores. Otro modo de obtener realimentación de ingenieros más cualificados o con más
experiencia es mantener reuniones de revisión del diseño (ver sección 10.7). Tenga en
cuenta que en las metodologías clásicas las revisiones de diseño pueden llevarse a cabo antes
de que se haya escrito ninguna línea de código.
Por lo tanto, en comparación con las metodologías ágiles, los ciclos clásicos dedican un
esfuerzo considerablemente mayor en empezar con un buen diseño. Como señala Martin
Fowler en su artículo Is Design Dead?11 , una crítica recurrente del ciclo ágil es que fomenta
que los desarrolladores se lancen y comiencen a programar sin ningún diseño, y se apoya
demasiado en la refactorización para solucionar las cosas más tarde. Como dicen en ocasiones
los críticos, se puede construir una caseta para el perro apilando materiales y planificando
sobre la marcha, pero no se puede construir un rascacielos de la misma manera.
Los seguidores de las metodologías ágiles argumentan que los métodos clásicos son igual-
mente malos: al no permitir comenzar el desarrollo hasta que se finaliza el diseño, es imposi-
ble estar seguro de que el diseño se podrá implementar o de que capture las necesidades de
los clientes. Esta crítica se sustenta en los casos en los que los arquitectos/diseñadores no
son los mismos que escribirán el código o no están al día de las herramientas y prácticas de
codificación actuales. Como consecuencia de todo esto, los partidarios de las metodologías
ágiles dicen que en el momento en el que se empieza a desarrollar el código, hay que cambiar
el diseño de todos modos.
Ambas posturas tienen algo de razón, aunque la crítica puede ser redactada con un matiz
algo diferente: “¿hasta dónde tiene sentido diseñar inicialmente?” Por ejemplo, los desarro-
lladores ágiles tienen en mente el almacenamiento de datos como parte de sus aplicaciones
SaaS, incluso aunque las primeras pruebas BDD y TDD que escriban no toquen la base de
datos. Un ejemplo más sutil es el escalado horizontal. Como ya aludimos en el capítulo 2, y
analizaremos más en detalle en el capítulo 12, los diseñadores de aplicaciones SaaS exitosas
deben pensar en la escalabilidad horizontal desde el inicio del desarrollo. Aun cuando pasarán
meses antes de que la escalabilidad tenga importancia, las decisiones iniciales de diseño
del proyecto pueden lastrar la escalabilidad, y podría ser complicado cambiar esto sin una
refactorización costosa.
El artículo de Fowler describe una norma empírica que representa una posible solución al
dilema. Si anteriormente en un proyecto se impuso una restricción de diseño o un elemento
concreto, es razonable plantearlo también en un nuevo proyecto que sea similar, ya que su
experiencia previa le guiará con mucha probabilidad a decisiones de diseño razonables esta
vez.
Resumen: Los procesos clásicos contemplan una fase de diseño explícita que se adapta
de forma natural al uso de patrones de diseño en el proceso de desarrollo del software. Una
posible desventaja es la incertidumbre asociada a la arquitectura inicial y los patrones de
diseño, por si tienen que cambiar en el futuro a medida que se va escribiendo el código y el
sistema evoluciona. Por el contrario, los procesos ágiles se basan en la refactorización para
incorporar patrones de diseño según evoluciona el código, aunque los desarrolladores con
más experiencia pueden pensar en arquitecturas software y patrones de diseño que creen
que necesitarán, basándose en proyectos previos similares.
424 CAPÍTULO 11. PATRONES DE DISEÑO PARA CLASES SAAS
STOP
Falacia. Los principios SOLID no son necesarios con los lenguajes dinámicos.
Como hemos visto en este capítulo, algunos de los problemas que resuelven los principios
SOLID no aparecen en los lenguajes de tipado dinámico como Ruby. Sin embargo, las direc-
trices SOLID siguen representando un buen diseño; simplemente, en los lenguajes estáticos
es mucho más visible el coste inicial de ignorarlas. En los lenguajes dinámicos, aunque existe
la facilidad de usar características dinámicas que harán su código más elegante y respetuoso
con el principio DRY sin los artificios extras que necesitan algunas de las pautas SOLID,
llevan asociado el riesgo de una mayor facilidad para sucumbir a la pereza y terminar con
antipatrones de código.
Figura 11.24. Algunos smells son relativamente sencillos de corregir con una pequeña modificación. Tomados del libro de
Fowler Refactoring, Ruby Edition (Fields et al. 2009).
complicó la vida al tratar de diferenciar los patrones de diseño de los entornos en un intento de
dejar claro qué son los patrones —más abstractos, con enfoque más restringido y no dirigido
a un dominio de problema particular— los entornos modernos representan un buen camino
para los desarrolladores menos experimentados para comenzar con los patrones de diseño.
Al examinar los patrones en un entorno en el que son instanciados como código, usted puede
obtener experiencia para crear su propio código basado en patrones de diseño.
“los que evitan cambios”, etc., un resumen de su artículo de revista en 200614 sobre esta
temática.
ACM IEEE-Computer Society Joint Task Force. Computer science curricula 2013, Ironman
Draft (version 1.0). Technical report, February 2013. URL http://ai.stanford.edu/
users/sahami/CS2013/.
Notas
1 http://butunclebob.com
2 http://martinfowler.com/eaaCatalog
3 http://www.cs.uiuc.edu/homes/snir/PPP/
4 http://ui-patterns.com
5 http://foldoc.org/index.cgi?query=UML&action=Search
6 http://cruise.site.uottawa.ca/umple/
7 http://try.umple.org
8 http://en.wikipedia.org/wiki/Python_syntax_and_semantics#Decorators
9 http://nokogiri.org
10 http://www.ruby-doc.org/stdlib-1.9.3/libdoc/observer/rdoc/Observable.html
11 http://www.martinfowler.com/articles/designDead.html
12 http://martinfowler.com/refactoring/catalog
13 http://www.soberit.hut.fi/mmantyla/BadCodeSmellsTaxonomy.htm
14 http://www.soberit.hut.fi/~mmantyla/ESE_2006.pdf
428 NOTAS
• Determinar cómo modelar los recursos y relaciones entre ellos (asociaciones) para
soportar la nueva funcionalidad
• Determinar cómo encapsular la interacción con la pasarela de pago, aunque no sepa
aún qué servicio se utilizará
Para los siguientes ejercicios, necesitará tener un sistema software heredado en fun-
cionamiento para su estudio. Como sugerencia, puede usar la lista de proyectos Rails de
código abierto en Open Source Rails1 , o puede seleccionar uno o dos proyectos creados por
estudiantes que han usado este libro: ResearchMatch2 , que ayuda a asociar estudiantes con
oportunidades de investigación en sus universidades, y VisitDay3 , que facilita la organización
de reuniones entre estudiantes y miembros de la facultad.
Ejercicio 11.2. Describa uno o más patrones de diseño que podrían ser aplicados al diseño
del sistema. Nota: El icono del margen identifica proyectos del éstandar de Ingeniería del
Software ACM/IEEE 2013 (ACM IEEE-Computer Society Joint Task Force 2013).
Ejercicio 11.3. Dado un sistema simple que responde a una historia de usuario concreta,
analice y elija un paradigma de diseño adecuado.
Ejercicio 11.4. Aplique ejemplos sencillos de patrones en el diseño del software.
Ejercicio 11.5. Analice y elija una arquitectura software apropiada que se ajuste a una his-
toria de usuario concreta de este sistema. ¿La implementación en el sistema de esa historia
de usuario refleja su idea de arquitectura?
Ejercicio 11.6. Analice el diseño software desde la perspectiva de un atributo de calidad
significativo como la facilidad de mantenimiento o la falta de viscosidad.
11.12. EJERCICIOS PROPUESTOS 429
Requisitos no funcionales y SaaS:
12 rendimiento, lanzamientos,
fiabilidad y seguridad
Barbara Liskov Nunca se necesita el rendimiento óptimo, lo que se necesita es un rendimiento lo suficien-
(1939–), una de las temente bueno . . . Los desarrolladores están demasiado obsesionados con el rendimiento.
primeras mujeres en
doctorarse en ciencias de la Barbara Liskov, 2011
computación (1968) en
EEUU, recibió el premio
Turing en 2008 por sus 12.1 Del desarrollo al despliegue . . . . . . . . . . . . . . . . . . . . . . . 432
aportaciones clave en el
12.2 Cuantificando la responsividad . . . . . . . . . . . . . . . . . . . . . 435
diseño de lenguajes de
programación. Entre sus 12.3 Integración continua y despliegue continuo . . . . . . . . . . . . . . . 437
invenciones destacan los 12.4 Lanzamientos y activadores de funcionalidad . . . . . . . . . . . . . 439
tipos de datos abstractos y
los iteradores, ambos 12.5 Cuantificando la disponibilidad . . . . . . . . . . . . . . . . . . . . . 443
conceptos fundamentales 12.6 Monitorización y localización de cuellos de botella . . . . . . . . . . . 445
en Ruby. 12.7 Mejorar el renderizado y el rendimiento con cachés . . . . . . . . . . 447
12.8 Evitar consultas abusivas a base de datos . . . . . . . . . . . . . . . . 452
12.9 Proteger los datos de los usuarios en su aplicación . . . . . . . . . . . 455
12.10La perspectiva clásica . . . . . . . . . . . . . . . . . . . . . . . . . . 461
12.11Falacias y errores comunes . . . . . . . . . . . . . . . . . . . . . . . 463
12.12Rendimiento, fiabilidad, seguridad y abstracciones con grietas . . . . 466
12.13Para saber más . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 467
12.14Ejercicios propuestos . . . . . . . . . . . . . . . . . . . . . . . . . . . 471
431
Conceptos
El concepto principal de este capítulo es cómo evitar los siguientes dolores de cabeza
una vez desplegada la aplicación: caídas (crashes), falta de respuesta si experimenta un
aumento de popularidad o poner en peligro los datos de sus usuarios. Estas característi-
cas no funcionales pueden ser más importantes incluso que los requisitos funcionales ya
que estos problemas pueden espantar a sus usuarios.
En el ciclo de vida ágil:
• En una aplicación SaaS, el difícil reto del rendimiento viene encabezado por la
latencia, que puede mejorarse en algunos casos mediante el sobredimensiona-
miento. La métrica Apdex representa un estándar para medir si una aplicación
cumple su objetivo de nivel de servicio (Service Level Objective, SLO).
• Mantener la aplicación ejecutándose en una plataforma como servicio (Platform
as a Service, PaaS) incrementa las posibilidades de cumplir el SLO. Estas platafor-
mas gestionan la mayoría de tareas de administración y escalado por usted.
• La base de datos del backend es normalmente la razón por la que una aplicación
se ve abocada a abandonar la solución PaaS, pero se puede mantener la base de
datos durante más tiempo utilizando cachés, creando índices y evitando consultas
a la base de datos que no sean necesarias y requieran muchos recursos.
• Los lanzamientos o releases son un reto mayor en SaaS debido a que nor-
malmente se necesita desplegar las nuevas versiones sin detener previamente las
antiguas. Los activadores de funcionalidad (feature flags) facilitan el despliegue
rápido de nuevas funcionalidades y su desactivación igualmente rápida en caso
de necesidad.
• La seguridad puede reforzarse si se sigue el principio de mínimo privilegio y las
opciones seguras por defecto, que limitan el acceso a los objetos según las
necesidades, y el principio de aceptación psicológica, que establece que
la interfaz de usuario no debe ser más compleja con características de protección
que sin ellas.
• La programación defensiva anticipa defectos antes de que éstos aparezcan y
puede ayudar a desarrollar sistemas más fiables y seguros.
En las metodologías clásicas:
• El rendimiento es simplemente un posible requisito no funcional.
• Las entregas son menos frecuentes y representan eventos de mayor importancia
que en la metodología ágil.
• El tiempo medio entre fallos (Mean Time To Failure, MTTF) es una medida
integral, que incluye errores debidos al hardware, software y los operadores.
Reducir el tiempo medio de reparación (Mean Time To Repair, MTTR) puede ser
tan efectivo como intentar incrementar el MTTF, y es más fácil de medir que éste.
• La seguridad puede reforzarse haciendo que el sistema sea robusto frente a defectos
del software que puedan dejarlo abierto a ataques, como desbordamientos de
buffer , desbordamientos aritméticos y condiciones de carrera.
432CAPÍTULO 12. RENDIMIENTO, LANZAMIENTOS, FIABILIDAD Y SEGURIDAD
Legacy (Ch. 9)
Figura 12.1. El ciclo de vida ágil del software y sus relaciones con los capítulos de este libro. Este capítulo versa sobre el
despliegue de la aplicación en la nube de forma que el cliente pueda evaluar la iteración actual del ciclo de vida ágil.
éstos se solían albergar normalmente en máquinas compartidas por los proveedores de ser-
vicios de Internet (“managed-hosting ISP”) , en máquinas virtuales que se ejecutaban en
hardware compartido (servidores privados virtuales o VPS) o en una o más máquinas dedi-
cadas, localizadas físicamente en el centro de datos del ISP (“hosting service”). Hoy en día, el
escalado horizontal que hace posible la computación en la nube (sección 2.4) ha dado lugar a
compañías como Heroku que proporcionan una plataforma como servicio (Platform as a
Service, PaaS): una pila de software verificada y preparada para que usted despliegue su apli-
cación, sin que tenga que preocuparse de gran parte de la responsabilidad de administración
y gestión del escalado, lo que hace el despliegue mucho más amigable para el desarrollador.
Los proveedores de PaaS pueden disponer de sus propios centro de datos o, cada vez de
forma más habitual, basarse en proveedores de infraestructura como servicio (Infrastructure
as a Service, IaaS) de más bajo nivel, como la nube pública de Amazon, tal y como hace
Heroku. Otras PaaS que están emergiendo son CloudFoundry, una capa de software PaaS
que puede desplegarse tanto en los servidores existentes de una compañía como en una nube
pública; y Microsoft Azure, un conjunto de servicios gestionados basados en Windows Server
y que se ejecutan en la nube de Microsoft.
Para las aplicaciones SaaS en fase inicial y para muchas otras más maduras, PaaS es
la forma preferida para el despliegue: administradores SaaS profesionales gestionan las inci-
dencias de escalado y la optimización del rendimiento por usted; estos administradores tienen
más experiencia en sistemas que la mayoría de los desarrolladores. Por supuesto, cuando un
sitio se convierte en algo suficientemente extenso o popular, sus necesidades técnicas pueden
llegar a superar lo que PaaS puede ofrecer, o puede resultar interesante traer las operacio-
nes de administración “a casa” por motivos económicos, lo que como veremos representa
un compromiso importante. Es por esto que un objetivo de este capítulo es ayudarle con su
aplicación para que pueda permanecer en el amigable nivel que ofrece PaaS mientras esto sea
posible. De hecho, si su aplicación está disponible únicamente de puertas adentro, de manera
que la cantidad de usuarios máxima está limitada y se ejecuta en un entorno más protegido
y menos hostil que aquellas aplicaciones que se ejecutan de cara al público, podría tener la
gran fortuna de permanecer en este nivel indefinidamente.
Como veremos, una clave para gestionar el crecimiento de su aplicación es controlar las
peticiones que se realizan a la base de datos, que es más difícil de escalar horizontalmente.
Una idea reveladora de este capítulo es que los problemas de rendimiento y seguridad que
usted se encontrará son los mismos tanto para las aplicaciones SaaS pequeñas como para las
más extensas, pero las soluciones son diferentes porque los proveedores de PaaS pueden ser
de gran ayuda para resolver algunos de estos problemas, evitándole el trabajo que supone una
solución personalizada.
A pesar del título de este capítulo, los términos rendimiento y seguridad a menudo se
sobreutilizan y definen de manera poco precisa. Presentamos una lista más enfocada a los
criterios claves de funcionamiento que abordaremos.
La responsividad es el retardo percibido entre una acción llevada a cabo por un usuario
(como hacer clic sobre un enlace) y la percepción de una respuesta, como nuevo contenido
que aparece en la página. Técnicamente, la responsividad se compone de dos partes: la laten-
cia, que es el retardo inicial hasta que se empieza a recibir el nuevo contenido, y el through-
put, que representa el tiempo que transcurre hasta que se entrega todo el contenido. Hace
tan poco tiempo como mediados de los años 90, muchos usuarios domésticos se conectaban
a Internet a través de módems telefónicos que tardaban 100 ms (milisegundos) en entregar
el primer paquete de información, y podían mantener como máximo velocidades de 56 Kbps
(56 ⇥ 103 bits por segundo) mientras transferían el resto, por lo que una página web o una
imagen de 50 KBytes o 400 KBits podían tardar más de ocho segundos en entregarse. Sin
embargo, los usuarios domésticos de hoy en día cada vez utilizan más las conexiones de
banda ancha cuyos throughputs oscilan entre 1 y 20Mbps, por lo que la responsividad de una
página está condicionada por la latencia en mucha mayor medida que por el throughput.
Como la responsividad tiene un efecto muy marcado sobre el comportamiento del usuario,
los operadores SaaS vigilan cuidadosamente la responsividad de sus sitios. Por supuesto, en
la práctica no todas las interacciones de los usuarios con el sitio toman la misma cantidad de
tiempo, por lo que la evaluación del rendimiento requiere una caracterización apropiada de la
distribución de los tiempos de respuesta. Considere un sitio en el que 8 de cada 10 peticiones
se completan en 100 ms, 1 de cada 10 se completa en 250 ms y la restante de cada 10 necesita
850 ms. Si el umbral de satisfacción del usuario, T , para este sitio es de 200 ms, es cierto
que el tiempo de respuesta medio de (8(100) + 1(250) + 1(850))/10 = 190 ms se encuentra
por debajo del umbral de satisfacción. Pero por otro lado, el 20% de las peticiones (y por
tanto, hasta el 20% de los usuarios) están recibiendo un servicio no satisfactorio. Para medir
la latencia de forma que sea imposible ignorar la mala experiencia incluso de un número
pequeño de usuarios, se utilizan dos definiciones:
• Un objetivo de nivel de servicio (SLO), que habitualmente toma la forma de una
sentencia cuantitativa sobre los cuantiles de la distribución de latencias en una ven- SLA frente a SLO: Un
tana de tiempo de duración determinada. Por ejemplo, “el 95% de las peticiones en acuerdo de nivel de
cualquier ventana de 5 minutos deben tener una latencia menor de 100 ms”. En tér- servicio (Service Level
Agreement, SLA) es un
minos estadísticos, el percentil 95 de la distribución de latencias no debe exceder los contrato entre un proveedor
100 ms. de servicios y sus clientes
que estipula un pago al
• La puntuación Apdex (índice de rendimiento de la aplicación) es un estándar abierto4 cliente si no se cumple el
que calcula un SLO simplificado como un número entre 0 y 1 (ambos incluidos), que SLO.
representa la fracción de usuarios satisfechos. Dado un umbral de latencia de satisfac-
ción de usuario, T , elegido por el operador de la aplicación, una petición es satisfac-
toria si se completa en el tiempo T , tolerable si lleva más de T pero menos de 4T , e
insatisfactoria en el resto de casos. La puntuación Apdex se calcula como (Satisfac-
toria +0.5(Tolerable)) / (Número de muestras). En el ejemplo de arriba, la puntuación
Apdex sería de (8 + 0.5(1))/10 = 0.85.
436CAPÍTULO 12. RENDIMIENTO, LANZAMIENTOS, FIABILIDAD Y SEGURIDAD
Por supuesto, el tiempo total de respuesta percibido por los usuarios incluye muchos
factores que están más allá del control de su aplicación SaaS. Incluye la consulta DNS, el
tiempo para configurar la conexión TCP y enviar la petición HTTP al servidor, y la laten-
cia introducida por Internet para recibir una respuesta que contenga suficiente contenido de
manera que el navegador pueda empezar a mostrar algo en pantalla (el llamado “tiempo de
Google cree5 que este presentación”, una expresión que pronto parecerá tan pintoresca como “en el sentido con-
hecho les pone bajo mayor
presión para ser trario de las agujas del reloj”). Especialmente cuando se utilizan PaaS de calidad, los desa-
responsivos, de forma que rrolladores y operadores SaaS disponen del mayor control sobre los caminos del código de
obtener una respuesta de sus aplicaciones: enrutado y envío, acciones de controlador, métodos del modelo y acceso
cualquier servicio de Google a base de datos. Nos centraremos por tanto en medir y mejorar la responsividad en estos
no sea más lento que lo que
se tarda en contactar con componentes.
dicho servicio. Para sitios pequeños, una manera perfectamente razonable de mitigar la latencia es so-
bredimensionar (proporcionar un exceso de recursos respecto al estado estable) en uno o
varios niveles, como sugiere la sección 2.4 para las capas de presentación y de lógica de ne-
gocio. Hace unos años, sobredimensionar significaba comprar hardware adicional que podía
quedarse ocioso, pero la computación en la nube de pago por servicio permite “alquilar” más
servidores a cambio de céntimos por hora sólo cuando se necesitan. De hecho, compañías
como RightScale6 ofrecen precisamente este servicio sobre la plataforma EC2 de Amazon.
Como veremos más adelante, una idea clave que nos ayuda es que los problemas que nos
hacen salir del nivel amigable de PaaS son los mismos que entorpecerán la escalabilidad de
nuestra solución posterior a PaaS, por lo que comprender qué tipo de problemas existen y
saber cómo resolverlos le servirá de ayuda en cualquiera de las situaciones.
¿Cuáles son los umbrales para la satisfacción de usuario o para la responsividad? Un
estudio clásico de 1968 que proviene de la literatura HCI (Human-Computer Interaction,
interacción persona-ordenador) (Miller 1968) encontró tres umbrales interesantes: si un sis-
tema responde a una acción de un usuario en 100 ms, se percibe como instantáneo; dentro
del primer segundo, el usuario aún percibirá una relación de causa-efecto entre su acción y la
respuesta, aunque percibirá el sistema como lento; y después de 8 segundos, la atención del
usuario se aleja de la tarea mientras espera una respuesta. Sorprendentemente, más de treinta
años después, un estudio especializado del año 2000 (Bhatti et al. 2000) y otro de la firma
independiente Zona Research en 2001 reafirmaron la “regla de los ocho segundos”. Aunque
muchos creen que un Internet más rápido y ordenadores más veloces han aumentado las ex-
pectativas de los usuarios, la regla de los ocho segundos sigue siendo una directriz general.
New Relic, cuyo servicio de monitorización introduciremos más tarde, informó en marzo de
2012 que el tiempo de carga medio para todas las páginas que monitorizan a lo largo del
mundo es de 5,3 segundos y la puntuación Apdex media es 0,86.
12.3. INTEGRACIÓN CONTINUA Y DESPLIEGUE CONTINUO 437
Resumen
• La responsividad mide cómo de rápida se percibe una aplicación por parte de sus
usuarios. Con la alta velocidad actual de las conexiones a Internet y los ordenadores
más veloces, la responsividad depende casi exclusivamente de la latencia. Los ob-
jetivos de nivel de servicio (SLO) cuantifican las metas de responsividad con frases
como “el 99% de las peticiones en cualquier ventana de 5 minutos deben tener una
latencia menor de 100 ms”.
• La puntuación Apdex es una medida sencilla de los SLO. Comprendida entre 0,0
y 1,0, un sitio obtiene “toda la puntuación” para aquellas peticiones que completa
en un umbral de latencia T específico del sitio, “la mitad de la puntuación” para
las peticiones completadas dentro de 4T de tiempo, y ninguna puntuación para las
peticiones que tardan más tiempo.
• Los problemas que amenazan la disponibilidad y la responsividad son los mismos se
use PaaS o no, pero merece la pena intentar permanecer en el nivel de uso de PaaS
porque proporciona mecanismos para ayudar a mitigar estos problemas.
ejemplo, una funcionalidad solicitada por un cliente puede requerir implementar múltiples
cambios, cada uno de los cuales puede desplegarse por separado, aunque la funcionalidad
en conjunto permanece “oculta” en la interfaz de usuario hasta que se completen todos los
cambios. “Activar” la funcionalidad podría ser un hito interesante de la entrega. Por este
motivo, muchos flujos de trabajo de integración continua asignan etiquetas diferentes y a
menudo caprichosas a lanzamientos específicos (como “Bamboo” y “Cedar” en el caso de las
pilas de software de Heroku), pero utilizan el identificador del commit en Git para identificar
despliegues que no incluyen ningún cambio visible de cara al cliente.
Explicación. Preproducción
Muchas organizaciones mantienen un entorno adicional además de los de desarrollo y pro-
ducción, llamado entorno de preproducción (staging). Normalmente, el entorno de prepro-
ducción es idéntico al de producción, excepto que es más pequeño en escala, utiliza una base
de datos separada con datos de prueba (posiblemente extraídos a partir de datos de clientes
reales) y está cerrado a los usuarios externos. La razón fundamental para este entorno es que
las pruebas de estrés y de integración sobre una versión en preproducción es la experiencia
más cercana posible al entorno de producción. Otro uso consiste en probar migraciones sobre
una base de datos que se asemeja completamente a la base de datos de producción, antes de
desplegar dichas migraciones en el entorno de producción. Rails y sus herramientas soportan
la definición de un entorno de preproducción definiendo un entorno adicional staging: en
config/environments/staging.rb y config/database.yml.
http://pastebin.com/T32gfwVL
1 class C h a n g e N a m e T o F i r s t A n d L a s t < ActiveRecord :: Migration
2 def up
3 add_column ' moviegoers ' , ' first_name ' , : string
4 add_column ' moviegoers ' , ' last_name ' , : string
5 Moviegoer . all . each do | m |
6 m . u p d a t e _ a t t r i b u t e s (: first = > $1 , : last = > $2 ) if
7 m . name =~ /^(.*) \ s +(.*) $ /
8 end
9 remove_column ' moviegoers ' , ' name '
10 end
11 end
Figura 12.2. Una migración que cambia el esquema y modifica los datos para que se ajusten al cambio. En la sección 12.4
explicamos por qué no hay un método para deshacer la migración (down-migration) (use Pastebin para copiar y pegar este
código).
http://pastebin.com/NsarhWSE
1 class SplitName1 < ActiveRecord :: Migration
2 def up
3 add_column ' moviegoers ' , ' first_name ' , : string
4 add_column ' moviegoers ' , ' last_name ' , : string
5 add_column ' moviegoers ' , ' migrated ' , : boolean
6 add_index ' moviegoers ' , ' migrated '
7 end
8 end
Figura 12.3. Una migración parcial que únicamente añade columnas pero no cambia ni borra ninguna. La sección 12.8
explica por qué el índice (línea 6) es una buena idea.
funciona con el esquema antiguo, o viceversa. Para concretarlo con un ejemplo, suponga
que RottenPotatoes actualmente tiene una tabla moviegoers con una columna name, pero
queremos cambiar el esquema para tener columnas separadas first_name y last_name en
su lugar. Si cambiamos el esquema antes de cambiar el código, la aplicación dejará de fun-
cionar porque los métodos que esperan encontrar la columna name fallarán. Si modificamos
el código antes de cambiar el esquema, entonces la aplicación dejará de funcionar porque los
nuevos métodos buscarán las columnas first_name y last_name, que aún no existen.
Podemos intentar solventar este problema desplegando el código y la migración de mane-
ra atómica: detener el servicio, aplicar la migración de la figura 12.2 para realizar el cambio
de esquema y copiar los datos en la nueva columna, y reiniciar el servicio. Esta estrategia es
la solución más simple, pero puede causar una indisponibilidad inaceptable: una migración
compleja en una base de datos de cientos de miles de filas puede llevar decenas de minutos o
incluso horas para ejecutarse.
La segunda opción es dividir el cambio entre múltiples despliegues utilizando un ac-
tivador de funcionalidad —una variable de configuración cuyo valor se puede modificar
mientras la aplicación se está ejecutando para controlar qué caminos del código de la apli-
cación se ejecutan—. Nótese que cada paso mostrado abajo es no-destructivo: tal y como
hicimos con la refactorización en el capítulo 9, si algo va mal en un paso determinado, la
aplicación todavía se queda funcionando en un estado intermedio.
1. Cree una migración que realice sólo aquellos cambios en el esquema que añaden tablas
o columnas nuevas, incluyendo una columna que indica si el registro actual ha sido
migrado al nuevo esquema o no, como aparece en la figura 12.3.
2. Genere la versión n + 1 de la aplicación en la que cada camino del código que se vea
12.4. LANZAMIENTOS Y ACTIVADORES DE FUNCIONALIDAD 441
http://pastebin.com/5B8KcNze
1 class Moviegoer < ActiveRecord :: Base
2 # here 's version n +1 , using Setler gem for feature flag :
3 scope : old_schema , where : migrated = > false
4 scope : new_schema , where : migrated = > true
5 def self . f i n d _ m a t c h i n g _ n a m e s ( string )
6 if Featureflags . n ew _n a me _s c he ma
7 Moviegoer . new_schema . where ( ' last_name LIKE : s OR first_name LIKE : s ' ,
8 : s = > " %#{ string }% " ) +
9 Moviegoer . old_schema . where ( ' name like ? ' , " %#{ string }% " )
10 else # use only old schema
11 Moviegoer . where ( ' name like ? ' , " %#{ string }% " )
12 end
13 end
14 # automatically update records to new schema when they are saved
15 before_save : update_schema , : unless = > lambda { | m | m . migrated ? }
16 def update_schema
17 if name =~ /^(.*) \ s +(.*) $ /
18 self . first_name = $1
19 self . last_name = $2
20 end
21 self . migrated = true
22 end
23 end
Figura 12.4. Método del modelo que encuentra espectadores (moviegoers) buscando coincidencias en una cadena de
caracteres con sus nombres y apellidos, envuelto con un activador de funcionalidad (feature flag). Las líneas 15 a 22 instalan
una callback before_save (antes de guardar) que actualiza de manera automática un registro al nuevo esquema cuando se
salva el modelo, por lo que el uso normal de la aplicación provocará que los registros se vayan migrando poco a poco.
afectado por la modificación del esquema se divida en dos caminos, de forma que se
ejecute uno u otro según el valor de un activador de funcionalidad. Para este paso es
crítico que se ejecute el código correcto independientemente del valor del activador de
funcionalidad en cualquier momento, de manera que dicho valor se pueda cambiar sin
tener que parar y reiniciar la aplicación; normalmente esto se consigue almacenando el
activador de funcionalidad en una tabla especial de la base de datos.
3. Despliegue la versión n + 1, lo que puede requerir subir el código a varios servidores,
un proceso que puede tomar varios minutos.
4. Una vez finalice el despliegue (todos los servidores se hayan actualizado a la versión
n + 1 del código), establezca el valor del activador de funcionalidad a verdadero mien-
tras la aplicación está ejecutándose. En el ejemplo de la figura 12.4, cada registro se
migrará al esquema nuevo la próxima vez que se modifique por cualquier razón. Si se
desea acelerar el proceso, podría además ejecutar una tarea de poca carga en segundo
plano que de forma oportuna vaya migrando unos cuantos registros cada vez para mini-
mizar la carga adicional en la aplicación, o que migre muchos registros a la vez durante
las horas en las que la aplicación se encuentra con carga mínima, si es posible. Si algo
falla en este paso, apague el activador de funcionalidad; el código revertirá al com-
portamiento de la versión n, ya que el esquema nuevo es un superconjunto apropiado
del esquema antiguo y la callback before_save no es destructiva (es decir, actualiza
correctamente el nombre del usuario tanto con el esquema viejo como con el nuevo).
5. Si todo va bien, una vez estén migrados todos los registros, despliegue la versión n + 2
del código, en el que el activador de funcionalidad se elimina y se deja únicamente el
camino del código asociado al nuevo esquema.
442CAPÍTULO 12. RENDIMIENTO, LANZAMIENTOS, FIABILIDAD Y SEGURIDAD
6. Finalmente, aplique una nueva migración que elimine la columna antigua name y la
temporal migrated (y, por tanto, el índice de esa columna).
¿Y qué ocurre con un cambio de esquema que suponga modificar el nombre de una
columna o su formato en vez de añadir o quitar columnas? La estrategia es la misma: añadir
una columna nueva, eliminar la antigua y, si es necesario, renombrar la columna nueva uti-
lizando activadores de funcionalidad durante la transición de forma que cada versión del
código desplegada funcione con ambas versiones del esquema.
Cuando introdujimos las migraciones en el capítulo 4, destacamos que una migración
podía incluir los métodos up y down, aunque el ejemplo de la figura 12.3 no tiene método
down. ¿No deberíamos incluir uno para el caso en que la actualización salga mal? Sor-
prendentemente, no. Las migraciones hacia atrás son de utilidad durante el desarrollo, pero
arriesgadas en producción. Debido a que se usan muy raramente, normalmente no se prueban
de forma concienzuda, y en medio del pánico que produce descubrir que algo no ha salido
bien, es difícil confiar en que la migración hacia atrás funcionará de verdad sin causar incluso
más daños.
Incluso aunque esté seguro de que la migración hacia atrás funciona correctamente, otros
desarrolladores pueden haber subido migraciones irreversibles después de la que usted trata
de migrar hacia atrás. Y en algún punto, usted mismo necesitará crear una migración irre-
versible, y necesitará una forma de recuperarse de los problemas cuando la aplique. Los
activadores de funcionalidad pueden serle de ayuda: si algo va mal, cambie de nuevo el valor
del activador de funcionalidad para que el código vuelva a su antiguo comportamiento, y
después tómese su tiempo para depurar el problema.
Resumen
• Para llevar a cabo una actualización compleja que realice cambios sobre el código
y el esquema, utilice un activador de funcionalidad (feature flag) cuyo valor pueda
modificarse mientras la aplicación se está ejecutando. Comience con una migración
y modificaciones en el código que incluyan la versión vieja y la nueva, y cuando esté
ejecutándose esta versión intermedia, cambie el valor del activador de funcionalidad
para habilitar los caminos nuevos en el código que utilicen el nuevo esquema.
• Una vez se hayan migrado progresivamente todos los datos como resultado de cam-
biar el valor del activador de funcionalidad, puede desplegar una nueva migración y
cambios del código que eliminen los caminos viejos y el esquema antiguo. Por otro
lado, si algo va mal durante el lanzamiento, puede cambiar el activador de funcio-
nalidad a su valor antiguo para continuar usando el esquema y el código anteriores
hasta que haya determinado qué ocurrió.
• Las aplicaciones desplegadas siempre avanzan hacia delante: si algo va mal, arré-
glelo en una nueva migración hacia delante que deshaga el daño en vez de intentar
aplicar una migración hacia atrás que no ha sido probada en producción y que puede
empeorar las cosas si se aplica.
12.5. CUANTIFICANDO LA DISPONIBILIDAD 443
• Comprobar los valores de entrada. Una causa común de problemas se origina cuando
el usuario introduce valores que el desarrollador no espera. Comprobar que la entrada
está en un rango razonable en caso de valores individuales, que no es demasiado grande
para el caso de series de datos y que la colección de datos de entrada son consistentes
lógicamente, puede reducir las oportunidades de caídas.
• Comprobar los tipos de los datos de entrada. Otro error que pueden cometer los usua-
rios es introducir un dato de tipo no esperado como respuesta a una consulta. Asegu-
rarse de que el usuario introduce datos de tipos válidos incrementa las probabilidades
de éxito de la aplicación.
Otro reto para la disponibilidad son los bugs que causan caídas pero sólo se manifiestan
después de mucho tiempo de ejecución o bajo condiciones de mucha carga. Un ejemplo
clásico es una fuga (leak) de recursos: un proceso que lleva ejecutándose durante mucho
tiempo se queda eventualmente sin algún recurso, como puede ser la memoria, debido a que
no puede reciclar el 100% del recurso no utilizado por un fallo de la aplicación o por el
diseño inherente de un lenguaje o un entorno. El rejuvenecimiento del software es una
estrategia arraigada para paliar las fugas de recursos: el servidor web Apache ejecuta un
número idéntico de subprocesos para ejecución de tareas (workers), y cuando uno de estos
procesos se hace lo suficientemente viejo, ese worker deja de aceptar peticiones y muere,
para ser reemplazado por un nuevo subproceso. Como sólo se “rejuvenece” un worker a la
vez (1/n de la capacidad total), este proceso se conoce a veces como reinicio balanceado,
y la mayoría de las plataformas PaaS emplean alguna variante del mismo. Otro ejemplo
es quedarse sin espacio de almacenamiento de sesiones para el caso en que las sesiones se
almacenen en una tabla de la base de datos, razón por la que el comportamiento por defecto
de Rails es serializar cada objeto de sesión de usuario en una cookie que se guarda en el
navegador del usuario, aunque esto limita el tamaño máximo de los objetos de sesión de
usuario a 4KiB.
12.6. MONITORIZACIÓN Y LOCALIZACIÓN DE CUELLOS DE BOTELLA 445
Resumen
• La disponibilidad mide el porcentaje de tiempo sobre una ventana de tiempo de-
terminada durante el que su aplicación está respondiendo correctamente a las peti-
ciones de los usuarios. La disponibilidad se mide habitualmente en “nueves” con el
estándar de oro del 99,999% (“cinco nueves”, correspondientes a cinco minutos de
parada al año) establecido por el servicio telefónico de EEUU y que raramente es
alcanzado por las aplicaciones SaaS.
• La programación defensiva mejora la disponibilidad al incluir código que ges-
tiona fallos potenciales antes de que se conozcan.
• El rejuvenecimiento del software mejora la disponibilidad reiniciando miem-
bros de un conjunto de procesos idénticos con una planificación balanceada para
neutralizar las fugas de recursos.
Autoevaluación 12.5.1. Para que una aplicación SaaS escale a un gran número de usuarios,
debe mantener su ____ y la ____ según aumenta el número de usuarios, sin que se incremente
el ____.
⇧ Disponibilidad; responsividad; coste por usuario.
Figura 12.5. Los diferentes tipos de monitorización y ejemplos de herramientas que los soportan para las aplicaciones SaaS
de Rails. Todas ellas excepto la última fila (monitorización del estado a nivel de proceso) son aplicaciones SaaS y ofrecen una
capa de servicio gratuito que proporciona monitorización básica.
consultas a base de datos, etc. Dado que esta información se envía a la aplicación SaaS de
New Relic donde usted puede verla y analizarla, esta arquitectura se denomina en ocasiones
monitorización remota de rendimiento (Remote Performance Monitoring, RPM). La funcio-
nalidad gratuita de New Relic se encuentra disponible como un add-on de Heroku o como
una gema independiente que puede desplegar en su propio entorno de producción no PaaS.
La monitorización interna puede realizarse también durante el desarrollo, lo que a menudo
se denomina análisis de rendimiento de software o profiling . New Relic y otras solu-
ciones de monitorización pueden instalarse en modo desarrollo también. ¿Cuánto profiling
se debe hacer? Si ha seguido unas buenas prácticas al escribir y probar su aplicación, puede
resultar más productivo simplemente desplegar y ver el comportamiento de la aplicación bajo
condiciones de carga, especialmente dadas las diferencias inevitables entre los entornos de
desarrollo y producción, como la falta de actividad de usuarios reales y el uso de una base
de datos orientada al desarrollo como SQLite3 en vez de una base de datos de producción
altamente optimizada, como PostgreSQL. Después de todo, con el desarrollo ágil, es fácil
desplegar parches progresivos para implementar una caché básica (sección 12.7) y solucionar
usos abusivos de la base de datos (sección 12.8).
Un segundo tipo de monitorización es la monitorización externa (a veces llamada ex-
ploración o monitorización activa), en la que un sitio independiente hace peticiones a su
aplicación para comprobar la disponibilidad y el tiempo de respuesta. ¿Por qué habría de
necesitar monitorización externa, dada la detallada información disponible mediante la mo-
nitorización interna que dispone de acceso a la aplicación? La monitorización interna puede
no ser capaz de revelar que su aplicación se ejecuta con lentitud o está completamente caída,
especialmente si el problema es debido a factores ajenos al código de la aplicación —por
ejemplo, problemas de rendimiento en la capa de presentación o en otras partes de la pila
de software, más allá de los límites de la aplicación—. La monitorización externa, al igual
que una prueba de integración, es una prueba real de principio a fin de un conjunto limitado
de los caminos de código de la aplicación tal y como lo ven los usuarios reales “desde el
exterior”. La figura 12.5 distingue los diferentes tipos de monitorización y nombra algunas
herramientas que ayudan en cada caso, muchas de ellas disponibles como aplicaciones SaaS.
Una vez que una herramienta de monitorización ha identificado las peticiones más lentas
o más costosas, las pruebas de estrés o pruebas de longevidad en un servidor de pre-
12.7. MEJORAR EL RENDERIZADO Y EL RENDIMIENTO CON CACHÉS 447
• Al igual que sucede con las pruebas, no existen un único tipo de monitorización que
le pueda alertar de todos los problemas: use una combinación de monitorización
interna y externa (de principio a fin).
La idea que subyace tras una caché es sencilla: la información que no ha cambiado desde
la última vez que fue solicitada puede ser simplemente regurgitada en vez de recalculada.
En SaaS, la caché puede ayudar en dos tipos de cálculos. En primer lugar, si la información
de la base de datos necesaria para completar una acción no ha cambiado, podemos evitar
por completo realizar consultas a la base de datos. En segundo lugar, si la información
subyacente a una vista particular o a un fragmento de una vista no ha cambiado, podemos
448CAPÍTULO 12. RENDIMIENTO, LANZAMIENTOS, FIABILIDAD Y SEGURIDAD
Web Model
Browser Controller Data-
.html server base
View
Figura 12.6. El objetivo de tener varios niveles de cachés es satisfacer cada petición HTTP desde el punto más cercano al
usuario. (a) Un navegador web que ha visitado previamente una página puede reutilizar la copia de su caché local después
de verificar con el servidor que su copia no ha cambiado. (b) Si no, el servidor web puede servirla desde su caché de páginas,
evitando completamente el uso de Rails. (c) Si (b) no es posible y la página se generó mediante una acción protegida por un
filtro previo (before-filter), Rails puede ser capaz de servirla desde la caché de acciones sin consultar la base de datos ni
renderizar ninguna plantilla. (d) Si no, alguno de los fragmentos que contienen las plantillas de las vistas podría estar en la
caché de fragmentos. (e) Como último recurso, la caché de consultas de la base de datos proporciona el resultado de
consultas recientes cuyos resultados no han cambiado, como en Movie.all.
La figura 12.6 muestra cómo se puede utilizar una caché en cada capa de una arquitectura
SaaS de 3 capas y qué entidades Rails se cachean en cada nivel. Lo más sencillo es almace-
nar en caché la página HTML completa resultante de renderizar una acción del controlador
en particular. Por ejemplo, la acción MoviesController#show y su correspondiente vista
dependen sólo de los atributos de la película que se está mostrando (la variable @movie
en el método controlador y la plantilla Haml). La figura 12.7 muestra cómo almacenar en
caché la página HTML completa para una película, de forma que peticiones posteriores a
esta página no accedan a la base de datos ni ejecuten de nuevo el renderizado Haml, como en
la figura 12.6(b).
Por supuesto, esto no es adecuado para acciones de controlador protegidas por filtros
previos (before-filters), como en el caso de páginas que solicitan que el usuario se haya
autenticado y por tanto requieren ejecutar el filtro del controlador. En estos casos, cam-
biar caches_page por caches_action seguirá ejecutando cualquier filtro pero permitirá a
Rails proporcionar una página de la caché sin consultar la base de datos para renderizar de
nuevo las vistas, como en la figura 12.6(c). La figura 12.9 muestra los beneficios de usar
12.7. MEJORAR EL RENDERIZADO Y EL RENDIMIENTO CON CACHÉS 449
http://pastebin.com/7PycU0MK
1 class M o v i e s C o n t r o l l e r < A p p l i c a t i o n C o n t r o l l e r
2 caches_page : show
3 cache_sweeper : movie_sweeper
4 def show
5 @movie = Movie . find ( params [: id ])
6 end
7 end
http://pastebin.com/UzJHx6As
1 class MovieSweeper < A c t i o n C o n t r o l l e r :: Caching :: Sweeper
2 observe Movie
3 # if a movie is created or deleted , movie list becomes invalid
4 # and rendered partials become invalid
5 def after_save ( movie ) ; invalidate ; end
6 def after_destroy ( movie ) ; invalidate ; end
7 private
8 def invalidate
9 expire_action : action = > [ ' index ' , ' show ']
10 e xp ir e _f ra g me n t ' movie '
11 end
12 end
Figura 12.7. (Arriba) La línea 2 especifica que Rails debe almacenar en caché el resultado de la acción show. La caché de
acciones está implementada como un filtro previo que comprueba si se debe usar la versión en la caché y un filtro que se
ejecuta antes y después (around-filter) y captura y almacena en caché la salida renderizada, lo que supone un ejemplo del
patrón de diseño Decorator (sección 11.4). (Abajo) Este “barrendero”, referenciado en la línea 3 del controlador, utiliza el
patrón de diseño Observer (sección 11.7) para añadir puntos de interrupción en el ciclo de vida de ActiveRecord (sección 5.1)
para hacer expirar cualquier objeto que pueda haber quedado obsoleto como resultado de actualizar una película concreta.
cachés de página y acciones en este ejemplo sencillo. Observe que en la caché de páginas de
Rails, el nombre del objeto en la caché ignora los parámetros embebidos en los URI como
/movies?ratings=PG+G, por lo que los parámetros que afecten a la apariencia de la página
deben ser parte de la ruta REST, como en /movies/ratings/PG+G.
Un caso intermedio viene dado por la caché de acciones donde el contenido princi-
pal de la página no cambia, pero sí la disposición. Por ejemplo, app/views/layouts/
application.html.haml podría incluir un mensaje como “Bienvenida, Alice” con el nom-
bre del usuario autenticado. Para permitir que la caché de acciones funcione correctamente
en este caso, pasar :layout=>false a caches_action provocará que se renderice de nuevo
completamente todo el layout excepto la acción (parte del contenido de la página) que se
aprovecha de la caché de acciones. Tenga en mente que como no se ejecutará el controlador
de la acción, cualquier contenido dinámico que aparezca en el layout debe ser configurado en
un filtro previo.
La caché de páginas no es útil para las páginas cuyos contenidos cambian dinámicamente.
Por ejemplo, la página con la lista de películas (acción MoviesController#index) cambia
cuando se añaden nuevas películas o cuando el usuario filtra la lista según la clasificación
MPAA. Pero aún nos podemos beneficiar del uso de cachés viendo que la página índice
consiste en una colección de filas de una tabla, cada una de las cuales depende únicamente
de los atributos de una película concreta, como muestra la figura 5.2 de la sección 5.1. La
figura 12.8 muestra cómo, al añadir una única línea a la vista parcial de la figura 5.2, se
almacena en caché el fragmento HTML renderizado que corresponde a cada película.
Rails proporciona un atajo muy práctico que consiste en que si el argumento pasado
a cache es un objeto ActiveRecord cuya tabla incluye una columna updated_at o
updated_on, la caché expirará automáticamente un fragmento si su fila de la tabla se ha
actualizado desde que el fragmento fue almacenado en la caché la primera vez. Sin embargo,
450CAPÍTULO 12. RENDIMIENTO, LANZAMIENTOS, FIABILIDAD Y SEGURIDAD
http://pastebin.com/XxPdsdQf
1 -# A single row of the All Movies table
2 - cache ( movie ) do
3 % tr
4 % td = movie . title
5 % td = movie . rating
6 % td = movie . release_date
7 % td = link_to " More about #{ movie . title } " , movie_path ( movie )
Figura 12.8. Comparado con la figura 5.2 de la sección 5.1, sólo hemos añadido 2 líneas. Rails generará un nombre para el
fragmento almacenado en la caché, basándose en el nombre del recurso pluralizado y la clave primaria, por ejemplo
movies/23.
por claridad, la línea 10 del “barrendero” de la figura 12.7 muestra cómo expirar explícita-
mente un fragmento cuyo nombre coincida con el argumento de cache cuando quiera que el
objeto movie subyacente se guarde o se borre.
A diferencia de la caché de acciones, que evita completamente ejecutar la acción del
controlador, la comprobación de la caché de fragmentos sucede después de que la acción de
controlador se haya ejecutado. Partiendo de este hecho, se estará preguntando cómo puede la
caché de fragmentos ayudar a reducir la carga de la base de datos. Por ejemplo, suponga que
añadimos una vista parcial a la página con la lista de películas para mostrar el @top_5 de
las películas basándonos en la media de las puntuaciones de las críticas, y que añadimos una
línea a la acción index del controlador para establecer la variable:
http://pastebin.com/3Ba360Vt
1 -# a cacheable partial for top movies
2 - cache ( ' top_ movieg oers ') do
3 % ul # topmovies
4 - @top_5 . each do | movie |
5 % li = moviegoer . name
http://pastebin.com/x88niV53
1 class M o v i e g o e r s C o n t r o l l e r < A p p l i c a t i o n C o n t r o l l e r
2 def index
3 @movies = Movie . all
4 @top_5 = Movie . joins (: reviews ) . group ( ' movie_id ') .
5 order ( " AVG ( potatoes ) DESC " ) . limit (5)
6 end
7 end
La caché de acciones es ahora de menor utilidad, ya que la vista index puede cambiar
cuando se añade una nueva película o cuando se escribe una crítica (lo que puede hacer
cambiar cuáles son las 5 películas mejor puntuadas). Si la acción del controlador se ejecuta
antes de que se compruebe la caché de fragmentos, ¿no estamos eliminando el beneficio de
la caché, ya que invocar a @top_5 en las líneas 4 y 5 del controlador provoca una consulta
a la base de datos?
Sorprendentemente, no. De hecho, las líneas 4 y 5 no provocan ninguna consulta: ¡cons-
truyen un objeto que puede realizar la consulta si se le pregunta por el resultado! Esto se de-
nomina evaluación perezosa, una técnica muy poderosa de los lenguajes de programación
que proviene de la programación funcional subyacente del cálculo lambda. El subsistema
Rails ActiveRelation (ARel), utilizado por ActiveRecord, usa la evaluación perezosa. La con-
sulta real a la base de datos no tiene lugar hasta que se invoca each en la línea 5 de la plantilla
Haml, porque ese es el primer momento en el que se consulta el objeto ActiveRelation para
devolver un resultado. Pero como esa línea está dentro del bloque cache que comienza en la
línea 2, si el objeto se encuentra en la caché de fragmentos, no se ejecutará la línea de código
12.7. MEJORAR EL RENDERIZADO Y EL RENDIMIENTO CON CACHÉS 451
Sin caché Caché de Aceleración vs. Caché de Aceleración vs. Aceleración vs.
acciones sin caché páginas sin caché caché de acciones
449 ms 57 ms 8x 21ms 21x 3x
Figura 12.9. Para una base de datos PostgreSQL compartida en Heroku, que contiene 1000 películas y alrededor de 100
críticas por película, la tabla muestra el tiempo en milisegundos para obtener la lista de las 100 primeras críticas ordenadas
por fecha de creación, con y sin caché de páginas y de acciones. Los números se obtienen de los ficheros de registro visibles
con heroku logs.
Las versiones más antiguas
de Rails no disponían de la
y por tanto no se realizará la consulta a la base de datos. Por supuesto, usted aún debe incluir evaluación perezosa de
consultas, por lo que las
la lógica necesaria en el barrendero para expirar el fragmento con las 5 mejores películas acciones de controlador
cuando se añade una nueva crítica. tenían que comprobar
En resumen, ambas cachés (páginas y fragmentos) recompensan nuestra habilidad de explícitamente la caché de
separar las cosas que cambian (unidades que no se pueden almacenar en caché) de aquellas fragmentos para evitar
consultas innecesarias —no
que permanecen igual (unidades que se pueden almacenar en caché). Para las cachés de muy acorde con la filosofía
páginas o acciones, divida las acciones de controlador protegidas por filtros previos en una DRY—.
acción “no protegida” que pueda usar la caché de páginas y una acción filtrada que pueda
usar la caché de acciones (en un caso extremo, puede contratar una red de entrega de
contenidos (Content Distribution Network, CDN) como Amazon CloudFront para replicar
la página en cientos de servidores alrededor del mundo). Para la caché de fragmentos, utilice
vistas parciales para aislar cada entidad que no se pueda almacenar en caché, como una
instancia de modelo, en su propia vista parcial que pueda ser almacenada en la caché de
fragmentos.
Resumen
Para maximizar los beneficios de las cachés, separe las unidades que se pueden almacenar
en caché de las que no: las acciones de controlador pueden dividirse en versiones que
pueden y no pueden almacenarse en caché dependiendo de si se debe ejecutar un filtro
previo, y las vistas parciales pueden usarse para dividir las vistas en fragmentos que se
puedan guardar en la caché.
acción.
1. El problema de las n+1 consultas aparece cuando al recorrer una asociación se ejecutan
más consultas de las necesarias.
2. El problema del recorrido de tabla (table scan) sucede cuando las tablas no poseen los
índices apropiados para acelerar ciertas consultas.
http://pastebin.com/kN8Fdz2H
1 # assumes class Moviegoer with has_many : movies , : through = > : reviews
2
3 # in controller method :
4 @fans = Moviegoer . where ( " zip = ? " , code ) # table scan if no index !
5
6 # in view :
7 - @fans . each do | fan |
8 - fan . movies . each do | movie |
9 // BAD : each time thru this loop causes a new database query !
10 % p = movie . title
11
12 # better : eager loading of the association in controller .
13 # Rails automatically traverses the through - association between
14 # Moviegoers and Movies through Reviews
15 @fans = Moviegoer . where ( " zip = ? " , code ) . includes (: movies )
16 # GOOD : preloading movies reviewed by fans avoids N queries in view .
17
18 # BAD : preload association but don 't use it in view :
19 - @fans . each do | fan |
20 % p = @fan . name
21 // BAD : we never used the : movies that were preloaded !
Figura 12.10. La consulta de la acción del controlador (línea 4) accede una vez a la base de datos para obtener las filas de
@fans, pero cada iteración en el bucle de las líneas 8 a 10 provoca otro nuevo acceso a la base de datos, resultando en n + 1
accesos para un fan que haya realizado críticas de n películas. Por el contrario, en la línea 15 se realiza una única consulta
de carga temprana que también obtiene todas las películas, lo que es casi tan rápido como la línea 4 ya que la mayoría del
coste de consultas pequeñas se produce en el acceso a la base de datos.
http://pastebin.com/zrGFXsbt
1 class A d d E m a i l I n d e x T o M o v i e g o e r s < ActiveRecord :: Migration
2 def up
3 add_index ' moviegoers ' , ' email ' , : unique = > true
4 # : unique is optional - see text for important warning !
5 add_index ' moviegoers ' , ' zip '
6 end
7 end
Figura 12.11. Al añadir un índice en una columna, se aceleran las consultas que coinciden con dicha columna. Un índice es
incluso más rápido si especifica :unique, equivalente a hacer la promesa de que no existirán dos filas con el mismo valor del
atributo indexado; para evitar errores en caso de existir valores duplicados, utilice esto junto con una validación de valor
único, como se describe en la sección 5.1.
454CAPÍTULO 12. RENDIMIENTO, LANZAMIENTOS, FIABILIDAD Y SEGURIDAD
Figura 12.12. Esta tabla presenta los beneficios y penalizaciones de los índices para una base de datos PostgreSQL
compartida en Heroku que contiene unas 1.000 películas, 1.000 espectadores y de 2.000 a 200.000 críticas. La primera parte
compara el tiempo, en segundos, empleado en leer 100 críticas sin índices frente al uso de índices en las claves foráneas
movie_id y moviegoer_id en la tabla reviews. La segunda parte compara el tiempo ocupado en crear 1.000 críticas en
ausencia y en presencia de índices sobre cualquier posible par de columnas de reviews, mostrando que incluso en este caso
compulsivo, la penalización por el uso de índices es suave.
valores de varias columnas. Además de los atributos nombrados explícitamente en las consul-
tas where, normalmente las claves foráneas (el sujeto de la asociación) deben ser indexadas
también. Por ejemplo, en el ejemplo de la figura 12.10, el campo moviegoer_id en la tabla
reviews podría necesitar un índice para acelerar las consultas relativas a fan.movies.
Por supuesto, los índices no son gratuitos: cada índice ocupa un espacio que es propor-
cional al número de filas de la tabla, y dado que cada índice de la tabla debe actualizarse
cada vez que se añaden o modifican filas, las actualizaciones de tablas con muchos índices
pueden verse afectadas. Sin embargo, debido al comportamiento típico de las aplicaciones
SaaS, donde la mayor parte de las operaciones son lecturas, y a que las consultas son rela-
tivamente sencillas comparadas con otros sistemas que se apoyan en bases de datos, como
el procesamiento de transacciones en línea (Online Transaction Processing, OLTP), es muy
probable que su aplicación tenga otros cuellos de botella antes de que los índices comien-
cen a limitar su rendimiento. La figura 12.12 muestra un ejemplo de la dramática mejora
conseguida gracias al uso de los índices.
• Los recorridos completos de tabla en las consultas se pueden evitar utilizando los
índices de la base de datos de forma sensata, pero cada índice ocupa espacio y
degrada el rendimiento de las actualizaciones. Un buen punto de partida es crear
índices para todas las columnas que representan claves foráneas y para todas las
columnas referenciadas en las claúsulas where de consultas frecuentes.
12.9. PROTEGER LOS DATOS DE LOS USUARIOS EN SU APLICACIÓN 455
12.9 Seguridad: proteger los datos de los usuarios en su aplicación Ronald Rivest
(1947–), Adi
Mi respuesta fue “Enhorabuena, Ron, eso debería funcionar”. Shamir(1952–) y
Leonard Adleman
Len Adleman, en respuesta a la propuesta de cifrado de Ron Rivest, 1977 (1945–) recibieron el
Premio Turing en 2002 por
Como la seguridad tiene su propio campo en la informática, no hay escasez de material dar utilidad práctica a la
para consultar o temas que estudiar. Quizás como resultado de esto, los expertos en seguridad criptografía de clave pública.
En el algoritmo RSA
han reducido sus recomendaciones a principios que puedan seguir los desarrolladores. Aquí
(llamado así por los
presentamos tres: apellidos de sus autores),
las propiedades de
• El principio de mínimo privilegio enuncia que a un usuario o a un componente seguridad de un par de
software no se le deben dar más privilegios —es decir, mayor acceso a información o claves está determinado por
la dificultad de factorizar
recursos— de los que son necesarios para llevar a cabo la tarea que se le ha asignado. números enteros muy
Esto es análogo al principio de “necesidad de saber” para la información clasificada. grandes y realizar la
Un ejemplo de este principio en el mundo Rails lo encontramos en los procesos Unix exponenciación modular,
que corresponden a su aplicación Rails, la base de datos y el servidor web (capa de esto es, determinar m tal
que C = mE mod N .
presentación), que deben ejecutarse con mínimos privilegios y en un entorno en el que
no puedan ni siquiera crear nuevos archivos en el sistema de ficheros. Los mejores
proveedores de PaaS, incluyendo Heroku, ofrecen un entorno de despliegue configu-
rado según este principio.
• El principio de opciones seguras por defecto dice que, excepto si a un usuario o
componente software se le da acceso explícito a un objeto, dicho acceso le debe
ser denegado. Es decir, por defecto se debe negar el acceso. El uso adecuado de
attr_accessible, tal y como se describe en la sección 5.2, sigue este principio.
• El principio de aceptación psicológica expone que los mecanismos de protección no
deberían hacer que la aplicación sea más difícil de utilizar que si no hubiera tal protec-
ción. Es decir, la interfaz de usuario debe ser fácil de usar para que los mecanismos de
seguridad se sigan de forma rutinaria.
456CAPÍTULO 12. RENDIMIENTO, LANZAMIENTOS, FIABILIDAD Y SEGURIDAD
rottenpotatoes.com, que sólo lo puede descifrar usando KP. Este secreto compartido es
usado entonces para cifrar el tráfico HTTP utilizando criptografía de clave secreta (o
criptografía simétrica) durante el tiempo de duración de la sesión. En este punto, cualquier
contenido enviado por HTTPS es razonablemente seguro y está a salvo de fisgones, y el
navegador de Alice cree que el servidor con el que está hablando es el servidor original de
RottenPotatoes, ya que sólo un servidor que esté en posesión de KP podría haber completado
el paso del intercambio de claves.
Es importante reconocer que este es el límite de lo que puede hacer SSL. En particular,
el servidor no sabe nada sobre la identidad de Alice, y no se puede garantizar nada sobre los
datos de Alice excepto su privacidad durante la comunicación con RottenPotatoes.
Falsificación de peticiones en sitios cruzados. Un ataque CSRF (a veces pronunciado
“sea-surf”) implica engañar al navegador del usuario para que visite un sitio web diferente
de aquel al que pertenece la cookie válida del usuario, y realizar una acción ilícita en ese
sitio web como ese usuario. Por ejemplo, suponga que Alice se ha autenticado recientemente
en su cuenta de MyBank.com, por lo que su navegador posee ahora una cookie válida para
MyBank.com que demuestra que ella se ha autenticado en el sistema. Ahora Alice visita
un foro donde el malicioso Mallory ha compartido un mensaje con la siguiente “imagen”
embebida:
http://pastebin.com/rtzYtTmj
1 <p > Here 's a risque picture of me :
2 < img src =" http :// mybank . com / transfer / mallory /5000" >
3 </p >
Cuando Alice ve el mensaje, o si recibe un correo electrónico con este enlace embebido
en él, su navegador intentará traer la imagen desde su URI REST, que transfiere 5.000$ en
la cuenta de Mallory. Alice verá un icono de “imagen rota” sin darse cuenta del daño. Los
ataques CSRF se combinan a menudo con secuencias de comandos en sitios cruzados (ver el
siguiente punto) para realizar ataques más sofisticados.
Existen dos medios para frustrar estos ataques. El primero es asegurar que las acciones
REST realizadas con el método HTTP GET no tengan efectos colaterales. Una acción como
una retirada de fondos o la finalización de una compra debe manejarse mediante un POST.
Esto dificulta que el atacante pueda entregar la “carga útil” utilizando etiquetas embebidas
de un recurso estático como IMG, que los navegadores siempre gestionan usando GET. El
segundo medio es insertar una cadena de caracteres generada aleatoriamente basada en la
sesión actual en cada vista y acordar incluir su valor como un campo oculto de formulario
en cada formulario. Este string será distinto para Alice que para Bob, ya que sus sesiones
son diferentes. Cuando se envía un formulario sin la cadena de caracteres aleatoria correcta,
el envío se rechaza. Rails automatiza esta defensa: todo lo que necesita hacer es renderizar
csrf_meta_tags en cada vista y añadir protect_from_forgery a cada controlador que
pudiera gestionar un envío de formulario. De hecho, cuando usted utiliza rails new
para generar una nueva aplicación, estas defensas se incluyen en app/views/layouts/
application.html.haml y app/controllers/application_controller.rb respec-
tivamente.
Inyección SQL y secuencias de comandos en sitios cruzados. Ambos ataques se
aprovechan de las aplicaciones SaaS que manejan de manera no segura el contenido pro-
porcionado por el atacante. En la inyección SQL, Mallory introduce datos en el formulario
de forma que espera que se inserten directamente en una consulta SQL que ejecuta la apli-
cación. La figura 12.14 muestra un ejemplo y cómo defenderse de este ataque —utilizando
procedimientos almacenados—. En el caso de las secuencias de comandos en sitios
458CAPÍTULO 12. RENDIMIENTO, LANZAMIENTOS, FIABILIDAD Y SEGURIDAD
http://pastebin.com/h1spRdpd
1 class M o v i e s C o n t r o l l e r
2 def search
3 movies = Movie . where ( " name = '#{ params [: title ]} ' " ) # UNSAFE !
4 # movies = Movie . where (" name = ?" , params [: title ]) # safe
5 end
6 end
Figura 12.13. Código vulnerable a un ataque de inyección SQL. Quitando el comentario de la línea 4 y borrando la línea 3
se frustraría el ataque al utilizar un procedimiento almacenado, que permite a ActiveRecord “inmunizar” entradas
maliciosas antes de insertarlas en la consulta.
Figura 12.14. Si Mallory introduce el texto de la segunda fila de la tabla como el nombre de la película, la línea 3 de la
figura 12.13 se convierte en una sentencia SQL peligrosa que borra la tabla completa (el -- final, el carácter SQL para
comentarios, evita la ejecución de cualquier código SQL que pudiera venir detrás de DROP TABLE). La inyección SQL a
menudo tenía éxito contra entornos antiguos como PHP, en el que las consultas se construían a mano por los programadores.
http://pastebin.com/rxwYGwB6
1 <h2 > <%= movie . title % > </ h2 >
2 <p > Released on <%= movie . release_date % >. Rated <%= movie . rating % >. </ p >
http://pastebin.com/ytYnC2h6
1 <h2 > < script > alert ( " Danger ! " ) ; </ script > </ h2 >
2 <p > Released on 1992 -11 -25 00:00:00 UTC . Rated G . </p >
http://pastebin.com/QN5KcdTy
1 <h2 >& lt ; script & gt ; alert ( " Danger ! " ) ;& lt ;/ script & gt ; </ h2 >
2 <p > Released on 1992 -11 -25 00:00:00 UTC . Rated G . </p >
Figura 12.15. Arriba: un fragmento de la plantilla de una vista utilizando el renderizador eRB, embebido en Rails, en vez de
Haml. Medio: Mallory introduce una película nueva cuyo “título” es la cadena de caracteres
<script>alert("Danger!");</script>. Cuando se renderiza la acción Show para esta película, el “título” se inserta
directamente en la vista HTML, provocando que se ejecute el código JavaScript cuando el navegador de Alice muestra la
página. Abajo: la defensa es “inmunizar” cualquier entrada que vaya a ser insertada en HTML. Afortunadamente, el
operador = de Haml hace esto automáticamente, de forma que el código queda inutilizado cuando se escapan
adecuadamente los paréntesis angulares (“<”,”>”) para HTML.
Figura 12.16. Algunos ataques comunes contra las aplicaciones SaaS y los mecanismos de Rails para defenderse de ellos.
460CAPÍTULO 12. RENDIMIENTO, LANZAMIENTOS, FIABILIDAD Y SEGURIDAD
No almacene contraseñas en texto plano; guárdelas cifradas o, mejor aún, confíe en otra
entidad para la autenticación, como se describe en la sección 5.2, para evitar incidentes30
embarazosos31 de32 robo33 de contraseñas34 . No piense siquiera en almacenar números de
tarjetas de crédito, ni aunque estén cifrados. La asociación de la industria de tarjetas de pago
impone una responsabilidad de auditoría que cuesta decenas de miles de dólares al año a
cualquier sitio web que haga eso (para prevenir el fraude35 con las tarjetas36 de crédito37 ),
y la responsabilidad es ligeramente menos severa si su código no manipula ningún número
de tarjeta de crédito, incluso si no lo almacena. En su lugar, descargue esta responsabilidad
en sitios web como PayPal38 o Stripe39 , que están especializados en cumplir estas fuertes
responsabilidades.
• Además de desplegar defensas a nivel de aplicación, los datos de los clientes par-
ticularmente sensibles deben ser almacenados de forma cifrada o no guardarse en
absoluto, subcontratando su manejo a servicios especializados.
Autoevaluación 12.9.1. Verdadero o falso: si un sitio web tiene un certificado SSL válido,
los ataques de falsificación de peticiones en sitios cruzados (CSRF) y de inyección SQL son
más complicados de realizar contra ese sitio web.
⇧ Falso. La seguridad del canal HTTP es irrelevante para ambos ataques. El ataque CSRF
se basa únicamente en un sitio web que acepta de forma errónea una petición que tiene una
cookie válida pero generada en algún otro sitio. La inyección SQL se basa sólo en que el
código del servidor SaaS inserta de manera insegura las cadenas de caracteres introducidas
por el usuario en una consulta SQL.
Autoevaluación 12.9.2. ¿Por qué los ataques de CSRF no se pueden frustrar comprobando
la cabecera Referer: de la petición HTTP?
⇧ La cabecera se puede falsificar de forma trivial.
12.10. LA PERSPECTIVA CLÁSICA 461
Así, las organizaciones suelen guardar todo lo que pueden de los proyectos para aprender
qué pueden hacer para mejorar sus procesos. Por ejemplo, el estándar ISO 9001 se otorga
a las compañías que tienen sus procesos en vigor, un método para ver si se está siguiendo
el proceso y si guardan los resultados de cada proyecto para hacer mejoras en el proceso.
Sorprendentemente, la aprobación del estándar no tiene en cuenta la calidad del código re-
sultante, sino únicamente el proceso de desarrollo.
Finalmente, como ocurre con el rendimiento, la fiabilidad también se puede medir. Pode-
mos mejorar la disponibilidad aumentando el tiempo entre fallos (MTTF) o reiniciando la
aplicación más rápidamente —tiempo medio de reparación (MTTR) —, como muestra
la siguiente ecuación:
MTTR
no_disponibilidad ⇡ (12.1)
MTTF
Aunque es difícil medir las mejoras en el MTTF, ya que puede pasar mucho tiempo entre
fallos, sí podemos medir fácilmente el MTTR. Simplemente provocando una caída en una
máquina y viendo cuánto tiempo tarda en reiniciarse la aplicación. Y lo que se puede medir,
se puede mejorar. Por eso, puede ser mucho más barato intentar mejorar el MTTR que el
MTTF por el hecho de que es más fácil medir los progresos. Sin embargo, no son mutuamente
excluyentes, por lo que los desarrolladores pueden intentar aumentar la fiabilidad siguiendo
ambos caminos.
Seguridad. Aunque la fiabilidad puede depender de la probabilidad para calcular la
disponibilidad —es muy poco probable que varios discos fallen simultáneamente si el sis-
tema de almacenamiento está diseñado sin dependencias ocultas—, este no es el caso de
la seguridad. Aquí hay adversarios humanos que están explorando los límites de su diseño
en busca de vulnerabilidades para aprovecharse de ellas y entrar en su sistema. La base de
datos de vulnerabilidades y riesgos comunes41 enumera ataques comunes para ayudar a los
desarrolladores a entender la dificultad de los retos de la seguridad.
Afortunadamente, además de hacer su sistema más robusto frente a fallos, la progra-
mación defensiva puede ayudar también a hacer su sistema más seguro. Por ejemplo, en
un ataque por desbordamiento de buffer , el adversario envía demasiados datos a un
buffer para sobreescribir la memoria adyacente con su propio código que viaja dentro de
los datos. Comprobar las entradas para asegurar que el usuario no está enviando demasia-
dos datos puede ayudar a prevenir dichos ataques. De forma similar, la base de un ataque
por desbordamiento aritmético sería proporcionar un número inesperadamente grande
de forma que al sumarlo a otro número parezca más pequeño, dada la naturaleza envolvente
de los desbordamientos en la aritmética de 32 bits. Comprobar los valores de entrada, así
como capturar excepciones, pueden ser mecanismos para prevenir este ataque. Como los or-
denadores actuales normalmente tienen múltiples procesadores (“multicore”), un ataque cada
vez más común es un ataque de condición de carrera donde el programa tiene un com-
portamiento no determinístico dependiendo de la entrada. Estos defectos de programación
concurrente son mucho más complicados de detectar y corregir.
Las pruebas constituyen un gran reto para la seguridad, pero una estrategia es utilizar un
Tiger team o equipo de expertos como adversarios que realizan pruebas de vulnerabili-
dad . El equipo informa después a los desarrolladores de las vulnerabilidades encontradas.
12.11. FALACIAS Y ERRORES COMUNES 463
Falacia. El esfuerzo adicional que supone probar condiciones muy poco pro-
bables con pruebas de integración continua aporta menos valor que los pro-
STOP
blemas que ocasiona.
A ritmos de 1 millón de peticiones al día, un evento “poco probable” de uno entre un
millón puede, estadísticamente, suceder todos los días. Un millón de peticiones al día fue el
volumen de Slashdot en 2010. A 8 mil millones (8 ⇥ 109 ) de peticiones diarias, que fue el
volumen de Facebook42 en 2010 , se pueden esperar 8.000 eventos de “uno entre un millón”
al día. Esta es la razón por la que la revisión de código en compañías como Google a menudo
se centra en los casos límite: a escala amplia, los eventos astronómicamente improbables
ocurren durante todo el tiempo (Brewer 2012). La resistencia extra que proporciona el código
de gestión de errores puede ayudarle a dormir mejor por las noches.
Falacia. La aplicación se encuentra aún en desarrollo, así que podemos igno-
STOP
rar el rendimiento.
464CAPÍTULO 12. RENDIMIENTO, LANZAMIENTOS, FIABILIDAD Y SEGURIDAD
Figura 12.17. Los efectos medidos de latencias añadidas en la interacción de los usuarios con varias e importantes
aplicaciones SaaS, a partir de la presentación “Design Fast Websites” del ingeniero de rendimiento de Yahoo, Nicole
Sullivan45 y de una presentación conjunta en la conferencia Velocity 200946 por Jake Brutlag, de Google, y Eric Schurman,
de Amazon.
Es cierto que Knuth dijo que la optimización prematura es la raíz de todos los males
“. . . cerca del 97% del tiempo”. Pero la cita continúa: “Aunque no deberíamos desaprovechar
nuestras oportunidades en ese 3% crítico”. Ignorar ciegamente defectos de diseño como la
falta de índices o consultas innecesarias que se repitan una y otra vez, es tan malo como
centrarse de forma miope en el rendimiento en etapas tempranas. Evite errores atroces con
el rendimiento y será capaz de conducir la situación por un camino satisfactorio entre ambos
extremos.
Error. Creer que no hay que preocuparse por el rendimiento porque las apli-
caciones de 3 capas que utilizan computación en la nube escalarán “mágica-
! mente”.
Esto no es realmente una falacia, porque si está utilizando una PaaS de calidad, la senten-
cia anterior encierra algo de verdad hasta cierto punto. Sin embargo, si su aplicación crece
más allá de lo que la PaaS puede ofrecerle, los problemas fundamentales de escalabilidad
y balanceo de carga pasarán a su tejado. En otras palabras, PaaS no le permite ahorrarse
tener que comprender y evitar dichos problemas, sino que temporalmente se ahorra tener que
ofrecer sus propias soluciones para esos problemas. Cuando empiece a configurar su propio
sistema desde cero, tardará poco en apreciar el valor que aporta PaaS.
Falacia. Los ciclos de procesador son gratuitos porque los ordenadores se
STOP
han vuelto muy rápidos y baratos.
En el capítulo 1 discutíamos sobre intercambiar la potencia extra de computación que
tenemos hoy en día por herramientas y lenguajes más productivos. Sin embargo, es fácil
llevar este argumento demasiado lejos. En 2008, el ingeniero de rendimiento Nicole Sullivan
informó sobre los experimentos llevados a cabo por varios operadores importantes de SaaS
que mostraban cómo las latencias adicionales afectaban a sus sitios web. La figura 12.17
muestra claramente que cuando el tiempo extra que emplea el procesador se convierte en
latencia extra (y, por tanto, en una reducción de la responsividad) para el usuario final, los
ciclos de procesador dejan de ser gratuitos.
http://pastebin.com/tsvAfTzE
1 require ' timeout '
2 # call external service , but abort if no answer in 3 seconds :
3 Timeout :: timeout (3.0) do
4 begin
5 # potentially slow operation here
6 rescue Timeout :: Error
7 # what to do if timeout occurs
8 end
9 end
Figura 12.18. Utilizar timeouts en las llamadas a un servicio externo protege su aplicación ante la posibilidad de volverse
lenta si el servicio externo es lento.
paciones sobre la seguridad y el rendimiento a veces requieren que usted como desarrollador
tenga algo de conocimiento sobre cómo funcionan estas abstracciones. Por ejemplo, el pro-
blema de las n + 1 consultas no es obvio al ver código Rails, ni lo es tampoco la solución
de proporcionar pistas como :include para las consultas sobre asociaciones, ni el uso de
attr_accessible o attr_protected para proteger los atributos sensibles de ser asignados
en masa por un usuario malicioso.
En el capítulo 4 hacíamos hincapié en la importancia de mantener sus entornos de desa-
rrollo y producción lo más similares posibles. Esto sigue siendo un buen consejo, aunque
obviamente si su entorno de producción involucra varios servidores y una base de datos in-
mensa, puede ser inviable replicarlo en su entorno de desarrollo. De hecho, en este libro
comenzamos desarrollando nuestras aplicaciones con la base de datos SQLite3 pero desple-
gando en Heroku con PostgreSQL. Dadas estas diferencias, ¿seguirán aplicando en produc-
ción las mejoras de rendimiento realizadas durante el desarrollo (reducción del número de
consultas, añadido de índices, añadido de cachés)? Totalmente. Heroku y otros sitios web
PaaS hacen un fantástico trabajo afinando el rendimiento base de sus bases de datos y su pila
de software, pero no hay ajustes que puedan compensar estrategias ineficientes de consultas
como el problema de las n + 1 consultas o no desplegar cachés para disminuir la carga de la
base de datos.
• Secuencias de clics: ¿cuáles son las secuencias de páginas más populares que visitan
sus usuarios?
• Tiempos de estancia: ¿cuánto tiempo permanece el usuario típico en una página dada?
• Abandono: si su sitio web contiene un flujo que posee un final bien definido, como
la realización de una venta, ¿qué porcentaje de usuarios “abandona” el flujo en vez de
completarlo y hasta dónde llegan?
Google Analytics proporciona una solución gratuita y básica de análisis como servicio: usted
incrusta una pequeña pieza de JavaScript en todas las páginas de su sitio web (por ejemplo,
incrustándolo en una plantilla de diseño por defecto) que envía información a Google Ana-
lytics cada vez que se carga una de sus páginas. Para ayudarle a utilizar esta información,
el sitio PageSpeed50 de Google enlaza a una colección asombrosa y exhaustiva de artículos
que tratan sobre todos los diferentes caminos que puede tomar para acelerar sus aplicaciones
SaaS, incluyendo muchas optimizaciones para reducir el tamaño global de sus páginas y
mejorar la velocidad a la que los servidores web pueden mostrarlas. El blog de RailsLab51
mantenido por New Relic también reúne buenas prácticas y técnicas para ajustar aplicaciones
Rails, incluyendo screencasts sobre optimización en Rails y cómo usar New Relic en modo
desarrollo para evaluar52 . Tenga en cuenta, sin embargo, que algunos de los ejemplos con-
cretos, especialmente los que tratan sobre cachés, ya no son válidos debido a los cambios
introducidos entre las versiones 2 y 3 de Rails.
Comprender qué sucede durante el despliegue y el funcionamiento (especialmente en
despliegues automáticos) es un requisito previo para depurar problemas de rendimiento más
complejos. La inmensa mayoría de aplicaciones SaaS de hoy en día, incluidas aquellas alo-
jadas en servidores Windows, se ejecutan en un entorno basado en el modelo Unix origi-
nal de procesos y de entrada/salida, por lo que el conocimiento de este entorno es crucial
para depurar cualquier problema no trivial de rendimiento. The Unix Programming Environ-
ment(Kernighan and Pike 1984), del que es coautor uno de los creadores de Unix, ofrece un
recorrido intenso y de aprendizaje mediante la práctica (¡en C!) de la arquitectura y filosofía
que hay detrás de Unix.
El sharding y la replicación son técnicas muy potentes para escalar una base de datos
que requieren un alto grado de reflexión sobre el diseño de antemano. Aunque existen gemas
Rails para ayudar con ambas, normalmente estas técnicas requieren modificaciones de con-
figuración a nivel de base de datos, algo no soportado por muchos de los proveedores de PaaS.
El sharding y la replicación se han vuelto especialmente importantes con el surgimiento de
base de datos “NoSQL”, que juegan con la expresividad y la independencia de los formatos
de datos en SQL para mejorar la escalabilidad. The NoSQL Ecosystem, un capítulo en el que
ha contribuido Adam Marcus en The Architecture of Open Source Applications Marcus 2012,
dispone de un buen enfoque de estos temas.
La seguridad es un concepto extremadamente amplio; nuestro objetivo ha sido ayudarle a
evitar errores básicos mediante el uso de mecanismos ya existentes para frustrar los ataques
más comunes que existen contra su aplicación y los datos de sus usuarios. Por supuesto,
un atacante que no pueda comprometer la información interna de su aplicación, aún podría
causar daño atacando la infraestructura en la que se apoya la misma. Los ataques de dene-
gación de servicio (DDoS) inundan un sitio web con tanto tráfico que éste no responde
a sus usuarios reales. Un cliente malicioso puede dejar su servidor de aplicaciones o su
servidor web “contra las cuerdas” consumiendo las respuestas del mismo de forma patológi-
camente lenta, a no ser que su servidor web (capa de presentación) disponga de timeouts
REFERENCIAS 469
internos. Los ataques de DNS spoofing intentan llevarle a un sitio web impostor propor-
cionando una dirección IP incorrecta cuando el navegador busca el nombre del servidor, y
suele combinarse con un ataque de “hombre en medio” o man in the middle que falsifica
el certificado que atestigua la identidad del servidor. El sitio web impostor tiene la misma
apariencia y comportamiento que el sitio web real pero recolecta información sensible de los
usuarios (en septiembre de 2011, varios hackers suplantaron los sitios web de la CIA, MI6 y
Mossad53 comprometiendo a DigiNotar , la compañía que firmó los certificados originales de
estos sitios web, lo que condujo a DigiNotar a la bancarrota). Incluso software maduro como
el intérprete de comandos seguro ssh y Apache son vulnerables: la base de datos nacional
de vulnerabilidades54 americana enumera 10 nuevos bugs relacionados con la seguridad en
Apache sólo entre marzo y mayo de 2012, dos de los cuales son de severidad alta, lo que
significa que podrían permitir a un atacante tomar el control completo de su servidor. No
obstante, pese a vulnerabilidades ocasionales55 , los sitios web PaaS suelen contar con ad-
ministradores de sistemas con gran experiencia profesional y que están al día de los últimos
mecanismos para evitar dichas vulnerabilidades, lo que hace de ellos la mejor primera línea
de defensa de sus aplicaciones SaaS. La guía básica de seguridad en Rails56 en el sitio web
de Ruby on Rails repasa muchas características de Rails orientadas a neutralizar los ataques
más comunes contra las aplicaciones SaaS, y este artículo de CodeClimate57 (una compañía
que proporciona métricas de código como servicio) enumera una serie de errores importantes
relacionados con la seguridad que se cometen en las aplicaciones Rails.
Finalmente, en algún momento ocurrirá lo impensable: su sistema en producción entrará
en un estado en el que algunos o todos sus usuarios dejarán de recibir el servicio. Ya sea
porque la aplicación haya dejado de funcionar o esté “colgada” (no es capaz de progresar),
desde un punto de vista de negocio ambas condiciones parecen iguales, porque la aplicación
no está generando ingresos. En este escenario, la prioridad absoluta es restaurar el servicio,
lo que puede requerir reiniciar servidores o realizar otras acciones que destruyan el estado
“postmortem” que le gustaría analizar para determinar qué causó el problema en primer lugar.
Un registro extenso de datos puede ayudar, dado que los logs proporcionan un historial casi
permanente que puede examinar con detenimiento después de haber restituido el servicio.
En The Evolution of Useful Things (Petroski 1994), el ingeniero Henry Petroski propone
cambiar el lema “la forma sigue a la función” (originalmente del mundo de la arquitectura)
por “la forma sigue al fallo” después de demostrar que el diseño de muchos productos exi-
tosos estaba influenciado principalmente por errores en diseños anteriores que condujeron a
diseños revisados. Para un ejemplo de buen diseño, lea la entrada en el blog técnico58 de
Netflix sobre cómo su diseño sobrevivió a la caída de Amazon Web Services en 2011, que
paralizó muchos otros sitios web que se basaban en AWS.
ACM IEEE-Computer Society Joint Task Force. Computer science curricula 2013, Ironman
Draft (version 1.0). Technical report, February 2013. URL http://ai.stanford.edu/
users/sahami/CS2013/.
L. Barroso and J. Dean. The tail at scale: Tolerating variability in large-scale online services.
Communications of the ACM, 2012.
N. Bhatti, A. Bouch, and A. Kuchinsky. Integrating user-perceived quality into web server
design. In 9th International World Wide Web Conference (WWW–9), pages 1–16, 2000.
E. Brewer. Personal communication, May 2012.
470 NOTAS
S. Hansma. Go fast and don’t break things: Ensuring quality in the cloud. In Workshop on
High Performance Transaction Systems (HPTS 2011), Asilomar, CA, Oct 2011. Summa-
rized in Conference Reports column of USENIX ;login 37(1), February 2012.
Notas
1 http://www.opensourcerails.com/
2 http://github.com/ucberkeley/researchmatch
3 http://github.com/vinsonchuong/meetinglibs
4 http://apdex.org
5 http://code.google.com/speed
6 http://rightscale.com
7 http://github.com/capistrano
8 http://travis-ci.org
9 http://saucelabs.com
10 https://github.com/jamesgolick/rollout
11 http://www.google.com/apps/intl/en/business/details.html
12 http://newrelic.com
13 http://pingdom.com
14 http://sitescope.com
15 http://analytics.google.com
16 http://newrelic.com
17 http://scoutapp.com
18 http://exceptional.io
19 http://airbrake.io
20 http://godrb.com
21 http://www.hpl.hp.com/research/linux/httperf
22 http://pivotaltracker.com
23 https://github.com/flyerhzm/bullet
24 http://weblog.rubyonrails.org/2011/12/6/what-s-new-in-edge-rails-explain
25 http://github.com/nesquena/query_reviewer
26 http://apidock.com/rails/ActionController/ForceSSL/ClassMethods/force_ssl
27 http://eval.symantec.com/mktginfo/enterprise/white_papers/b-whitepaper_exec_
summary_internet_security_threat_report_xiii_04-2008.en-us.pdf
28 https://devcenter.heroku.com/articles/progstr-filer
12.14. EJERCICIOS PROPUESTOS 471
29 https://devcenter.heroku.com/articles/background-jobs-queueing
30 http://www.huffingtonpost.co.uk/2012/06/08/lastfm-hit-by-password-leak_n_1580012.
html?ref=uk
31 http://www.huffingtonpost.com/2012/06/07/eharmony-passwords-leaked-linkedin_n_
1577175.html
32 http://www.zdnet.com/blog/btl/26000-email-addresses-and-passwords-leaked-check-
this-list-to-see-if-youre-included/50424
33 http://hothardware.com/News/55000-Twitter-Accounts-Hacked-You-Should-Probably-
Change-Your-Password/
34 http://www.neowin.net/news/main/09/10/05/thousands-of-hotmail-passwords-leaked-
online
35 http://redtape.msnbc.msn.com/_news/2012/03/30/10940640-global-payments-under-15-
million-account-numbers-hacked?lite
36 http://www.msnbc.msn.com/id/17853440/#.T9JsqxztEmY
37 http://www.businessweek.com/technology/content/jul2009/tc2009076_891369.htm
38 http://paypal.com
39 http://stripe.com
40 http://nebraska.edu/security
41 http://cvedetails.com/
42 http://royal.pingdom.com/2010/01/05/facebook-twitter-myspace-page-views
43 http://www.slideshare.net/stubbornella/designing-fast-websites-presentation
44 http://velocityconf.com/velocity2009/public/schedule/detail/8523
45 http://www.slideshare.net/stubbornella/designing-fast-websites-presentation
46 http://velocityconf.com/velocity2009/public/schedule/detail/8523
47 http://guides.rubyonrails.org/caching_with_rails.html
48 http://perspectives.mvdirona.com
49 http://perspectives.mvdirona.com/2009/10/31/TheCostOfLatency.aspx
50 https://developers.google.com/speed/pagespeed
51 http://railslab.newrelic.com
52 http://newrelic.com/demos/developer-mode.html
53 http://catless.ncl.ac.uk/Risks/26.56.html#subj6
54 http://nvd.nist.gov
55 http://daverecycles.com/post/2858880862/heroku-hacked-dissecting-herokus-critical-
security
56 http://guides.rubyonrails.org/security.html
57 http://blog.codeclimate.com/blog/2013/03/27/rails-insecure-defaults
58 http://techblog.netflix.com/2011/04
“dyno” o una unidad de paralelización de tareas, de modo que las peticiones se procesan de
forma secuencial).
Ejercicio 12.3. Use monitorización externa para analizar un diseño software desde el punto
de vista de un atributo de calidad significativo, como funcionalidad, rendimiento o disponi-
bilidad. Nota: el icono en el margen identifica proyectos del estándar ACM/IEEE 2013 de
Ingeniería de Software (ACM IEEE-Computer Society Joint Task Force 2013).
Ejercicio 12.4. Continuando el ejercicio anterior, añada una caché de acciones para la vista
index de modo que, si no se especifican opciones de ordenación o filtrado, la acción index
simplemente devuelva todas las películas. Compare la latencia y rendimiento con y sin caché
de acciones. Resuma los resultados de los tres ejercicios en una tabla.
Lanzamientos y activadores de funcionalidad:
Ejercicio 12.5. Investigue el coste de disponibilidad de realizar una actualización y mi-
gración “atómicas” del esquema como se describe en la sección 12.4. Para ello, repita la
siguiente secuencia de pasos para N = 210 , 212 , 214 :
1. Inicialice la base de datos de preproducción con N películas generadas aleatoria-
mente con clasificaciones aleatorias.
2. Realice una migración en la base de datos de preproducción que convierta la columna
rating en la tabla movies de string a entero (1=G, 2=PG, etc.).
3. Observe el tiempo reportado por rake db:migrate.
Suponga que su objetivo de tiempo de funcionamiento era 99.9% sobre cualquier ventana de
30 días. Cuantifique el efecto que tiene sobre la disponibilidad hacer la migración comentada
previamente sin parar el servicio.
Ejercicio 12.6. Resuma el proceso de pruebas de regresión y su papel en la gestión de lan-
zamientos.
12.14. EJERCICIOS PROPUESTOS 473
Fiabilidad:
Ejercicio 12.7. Enumere estrategias de minimización de fallos que pueden aplicarse en cada
etapa del ciclo de vida clásico.
Ejercicio 12.8. En la sección 5.2 integramos autenticación de terceros en RottenPotatoes
añadiendo el nombre de un proveedor de autenticación y el UID de un proveedor específico
al modelo Moviegoer.
Ahora nos gustaría ir más allá y permitir al mismo espectador identificarse y entrar en
la misma cuenta con uno cualquiera de los proveedores de autenticación. Es decir, si Alice
tiene cuentas tanto en Twitter como en Facebook, debería poder entrar en la misma cuenta
de RottenPotatoes con cualquiera de dichos identificadores.
1. Describa los cambios en los modelos y tablas existentes necesarios para soportar este
esquema.
2. Describa una secuencia de despliegues y migraciones que hagan uso de activadores
de funcionalidad para implementar el nuevo esquema sin parar la aplicación.
Seguridad:
Ejercicio 12.9. La columna ThreatLevel de Wired Magazine de julio de 20121 informó de
que 453.000 contraseñas de usuarios de Yahoo! Voice habían sido robadas por hackers. Los
hackers aclararon, en una nota publicada en línea, que las contraseñas estaban almacenadas
sin cifrar en los servidores de Yahoo y que usaron un ataque de inyección SQL para hacerse
con ellas. Debatir.
Ejercicio 12.10. Describa las prácticas de codificación segura y codificación defensiva en
general.
Para el resto de ejercicios, necesitará identificar un sistema de software heredado en fun-
cionamiento que pondrá bajo examen. Como sugerencias, podría usar el listado de proyectos
Rails de código abierto en Open Source Rails2 o seleccionar uno o dos proyectos creados
por estudiantes que han utilizado este libro: ResearchMatch3 , que ayuda a poner en contacto
a alumnos con oportunidades de investigación en su universidad, y VisitDay4 , que ayuda a
organizar reuniones entre alumnos y profesores.
Ejercicio 12.11. Desde la perspectiva de usar un sistema de gestión de bases de datos rela-
cionales, describa prácticas de codificación segura y codificación defensiva. ¿Sigue estas
prácticas el sistema que está analizando?
Ejercicio 12.12. Desde la perspectiva del desarrollo de una aplicación SaaS con un en-
torno como Rails, describa algunas prácticas concretas de codificación defensiva. Teniendo
en cuenta las guías de seguridad de Rails y blogs como la Guía de seguridad básica de
Rails5 , este artículo de CodeClimate6 y usando Google para buscar incidentes de seguridad
recientes en aplicaciones SaaS Rails, ¿qué problemas de seguridad son consecuencia de no
seguir estas prácticas?
Ejercicio 12.13. Reescriba un programa sencillo para eliminar vulnerabilidades comunes,
como desbordamientos de buffer, desbordamientos aritméticos y condiciones de carrera.
Ejercicio 12.14. Enuncie y aplique los principios de mínimo privilegio y opciones seguras
por defecto. ¿Cómo se aplican en nuestra aplicación RottenPotatoes?
13 Epílogo
SAAS ON
CLOUD
COMPUTING
ENGINEERING
SAAS
HIGHLY
PRODUCTIVE AGILE
FRAMEWORKS DEVELOPMENT
& TOOLS
Framework and
tools remove obstacles
to practicing Agile development
Figura 13.1. El Triángulo Virtuoso de Desarrollo SaaS lo forman las tres joyas de la corona de la ingeniería software: (1)
SaaS en la nube, (2) entorno y herramientas altamente productivos y (3) desarrollo ágil.
476 CAPÍTULO 13. EPÍLOGO
Figura 13.2. Términos introducidos en los tres primeros capítulos del libro.
13.3. MIRANDO HACIA ADELANTE 477
más fácil seguir el ciclo de vida ágil. La figura 13.3 muestra nuestra iteración ágil, repetida
a menudo, pero esta vez decorada con las herramientas y servicios que usamos en el libro.
Estas 14 herramientas y servicios dan soporte a ambos, el ciclo de vida ágil y el desarrollo de
aplicaciones SaaS. De forma similar, la figura 13.4 resume la relación entre las fases de los
ciclos de vida clásicos y sus equivalentes ágiles, mostrando cómo las técnicas descritas en
profundidad en este libro desempeñan papeles similares a las correspondientes en modelos
de proceso software previos.
Rails es muy potente pero ha evolucionado mucho desde la versión 1.0, que se extrajo
originalmente de una aplicación específica. De hecho, la propia Web ha evolucionado de
detalles concretos a patrones de arquitectura más genéricos:
• de documentos estáticos en 1990 a contenido dinámico hacia 1995;
• de URI opacos a principios de la década de 1990 a REST a principios de los 2000.
• de trucos para simular una sesión (URI complejos, campos ocultos, etc.) a principios
de los 90 a cookies y sesiones reales para mediados de los 90; y
• de instalar y administrar sus propios servidores ad hoc en 1990 a despliegues en
plataformas en la nube en los 2000.
Los lenguajes de programación Java y Ruby ofrecen otra demostración de que las buenas
ideas pueden tener una rápida acogida si son progresivas, pero que las grandes ideas más
radicales requieren tiempo para ser aceptadas.
Java y Ruby tienen la misma edad, ambos aparecieron en 1995. En pocos años, Java se
convirtió en uno de los lenguajes de programación más populares, mientras que Ruby vio
restringido su interés a los “literatos” de los lenguajes de programación. Ruby alcanzó la
popularidad una década más tarde, con el lanzamiento de Rails. Ruby y Rails demostraron
que las grandes ideas en lenguajes de programación realmente pueden mejorar la productivi-
dad mediante reutilización intensiva de software. Comparando Java y su entorno con Ruby y
Rails, (Stella et al. 2008) y (Ji and Sedano 2011) encontraron reducciones en factores de 3 a
5 del número de líneas de código, que es un indicador de productividad.
Legacy (Ch. 9)
RSpec SimpleCov
Autotest
Measure Velocity (Ch. 7)
Git GitHub
Pivotal Tracker ProjectLocker
Figura 13.3. Una iteración del ciclo de vida software ágil y su relación con los capítulos del libro, con las herramientas (en
letras rojas/negritas) y servicios (en letras azules/cursivas) de soporte identificados con cada etapa.
Figura 13.4. Aunque las metodologías ágiles no son adecuadas para todos los proyectos, el ciclo de vida ágil abarca las
mismas etapas de proceso que modelos tradicionales como cascada y espiral; pero reduce cada etapa a una única iteración,
de modo que puedan repetirse con frecuencia, refinando constantemente una versión funcional del producto.
13.3. MIRANDO HACIA ADELANTE 479
forman una cadena de causa-efecto, que expande hasta que identifica el conjunto mínimo de
cambios en las variables de entrada que provocan la aparición del error. Aunque requiere
muchas ejecuciones del programa, este análisis se realiza a toda velocidad y sin intervención
del programador, de modo que ahorra tiempo de desarrollo.
La síntesis de programas puede estar lista para un salto cualitativo. El estado del arte
actual es que, dados fragmentos incompletos de programas, las herramientas de síntesis de
programas pueden, a menudo, completar el código que falta. Uno de las aplicaciones más
interesantes de esta tecnología se encuentra en Microsoft Office Excel 2013, la función Re-
lleno Rápido, que hace programación basada en ejemplos (Gulwani et al. 2012). Usted le da
ejemplos de lo que quiere hacer a las filas o columnas y Excel intentará repetir y generalizar
sus acciones. Además, usted puede corregir las tentativas de Excel para dirigirlo a lo que
usted desea (Gantenbein 2012).
La brecha entre desarrollo clásico y ágil puede ahondarse con los avances para hacer más
prácticos los métodos formales. El tamaño de los programas susceptibles de ser verificados
formalmente crece con el tiempo, con mejoras en las herramientas, ordenadores más rápidos
y mejor comprensión de cómo escribir especificaciones formales. Si el esfuerzo de preparar
cuidadosamente las especificaciones antes de codificar pudiera recompensarse con no nece-
sitar pruebas y aun así tener programas verificados rigurosamente, entonces la balanza se
decantaría en función de los cambios. Para que funcionen los métodos formales, claramente
el cambio tiene que ser algo excepcional. Cuando los cambios se producen de forma común
y corriente, las metodologías ágiles son la respuesta, puesto que el cambio es la esencia de la
filosofía ágil.
Si bien hoy en día las metodologías ágiles funcionan mejor que otras metodologías soft-
ware para algunos tipos de aplicaciones, probablemente no son la respuesta definitiva. Si
una nueva metodología pudiera simplificar la inclusión de una buena arquitectura software
y buenos patrones de diseño, manteniendo la facilidad del cambio de la metodología ágil,
podría hacerse más popular. Históricamente, surge una nueva metodología cada una o dos
décadas, de modo que puede que pronto sea el momento para una nueva.
Este mismo libro se ha desarrollado en los albores del movimiento MOOC (Massive
Open Online Course, curso en línea masivo abierto), que es otra tendencia que pronos-
ticamos que será más significativa en los próximos años. Como muchos otros avances en
este mundo moderno, no tendríamos cursos MOOC sin SaaS y computación en la nube. Los
elementos que lo posibilitaron fueron:
• Distribución escalable de vídeo mediante servicios como YouTube.
• Sistemas sofisticados de corrección automática en la nube que evalúan las entregas de
los alumnos inmediatamente y, aun así, pueden escalar a decenas de miles de estudian-
tes.
• Foros de debate como solución escalable para hacer preguntas y obtener respuestas
tanto de otros alumnos como de la plantilla docente.
Estos componentes se combinan para formar un maravilloso vehículo de bajo coste para
alumnos de todo el mundo. Por ejemplo, seguramente mejorará la formación continua de
profesionales en un campo de rápida evolución como el nuestro, permitirá a los estudiantes
preuniversitarios más aventajados ir más allá de lo que sus escuelas e institutos pueden en-
señar, y permitirá acceder a una buena educación a estudiantes aplicados de todo el mundo
que no tienen acceso a las grandes universidades.
480 NOTAS
Notas
1 http://www.wired.com/threatlevel/2012/07/yahoo-breach
2 http://www.opensourcerails.com/
NOTAS 481
3 http://github.com/ucberkeley/researchmatch
4 http://github.com/vinsonchuong/meetinglibs
5 http://guides.rubyonrails.org/security.html
6 http://blog.codeclimate.com/blog/2013/03/27/rails-insecure-defaults
7 http://en.wikipedia.org/wiki/Ariane_5_Flight_501
A Uso de la biblioteca de recursos
Frances Allen (1932–) Todas las cosas que hago son de una pieza. Estoy explorando los bordes, encontrando
recibió el Premio Turing en formas nuevas de hacer las cosas. Me mantiene muy, muy ocupada.
2006 por sus contribuciones
pioneras a la teoría y Fran Allen, en su placa del Premio como Miembro del Museo Histórico de Ordenadores,
práctica de técnicas de 2000
optimización de
compiladores que sentaron
las bases de los A.1 Guía general: leer, preguntar, buscar, postear . . . . . . . . . . . . . 484
compiladores optimizados
modernos y la paralelización A.2 Visión general de la biblioteca de recursos . . . . . . . . . . . . . . . 484
automática. A.3 Uso de la VM de la biblioteca de recursos . . . . . . . . . . . . . . . 485
A.4 Trabajando con código: editores y técnicas de supervivencia en Unix 486
A.5 Introducción al intérprete de comandos seguro (ssh) . . . . . . . . . . 487
A.6 Introducción a Git para el control de versiones . . . . . . . . . . . . . 489
A.7 Introducción a GitHub . . . . . . . . . . . . . . . . . . . . . . . . . . 491
A.8 Despliegue en la nube usando Heroku . . . . . . . . . . . . . . . . . 492
A.9 Lista de verificación: creación de una nueva aplicación Rails . . . . . 497
A.10 Falacias y errores comunes . . . . . . . . . . . . . . . . . . . . . . . 499
A.11 Para saber más . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 501
483
Conceptos
Este libro no sólo trata de crear SaaS: también depende en gran medida de SaaS, IaaS
(Infrastructure as a Service, infraestructura como servicio) y PaaS (Platform as a
Service, plataforma como servicio), todos ellos modelos de computación en la nube.
Este apéndice describe las tecnologías en la nube que no sólo simplifican su vida como
estudiante, sino que también son partes esenciales del ecosistema que va a usar cuando
despliegue aplicaciones SaaS reales.
En el momento de escribir este libro, todos estos servicios en la nube ofrecen una
capa de uso a coste cero que es suficiente para realizar el trabajo que se propone a lo
largo del libro.
• GitHub2 es un sitio SaaS que le permite tener una copia de respaldo de sus
proyectos con control de versiones, además de colaborar en ellos junto con otros
desarrolladores.
• Heroku3 es un proveedor de una plataforma como servicio donde puede desple-
gar sus aplicaciones Rails.
484 APÉNDICE A. USO DE LA BIBLIOTECA DE RECURSOS
Figura A.1. Su clave pública se copia en los servicios a los que quiere acceder, mientras que su clave privada se guarda sólo
en el(los) equipo(s) desde el(los) que accede. Las claves pública y privada de cada pareja aparecen del mismo color, con un
candado indicando la clave pública. (a) Cuando ejecuta la VM de la biblioteca de recursos en VirtualBox, tiene que copiar la
clave privada que creó en su equipo a la VM de VirtualBox. (b) Ejecutar la VM de la biblioteca de recursos en EC2 de
Amazon24 también requiere que se conecte desde su equipo a la instancia de EC2 a través de ssh. Una manera de hacer esto
en la configuración de la instancia EC2 es que Amazon le genere una nueva pareja de claves con este fin, mostrado en gris.
(c) Otra opción para hacerlo en EC2 es subir la pareja de claves que ya tenga25 al AWS, así usa la misma clave para acceder
tanto a la instancia AWS como al resto de servicios. En este escenario, las dos claves, la pública y la privada, terminan
subidas a la VM.
al que aún tenga acceso —aunque algunos servicios ni siquiera le permiten ver las claves
públicas que haya subido, para mayor seguridad—. Así que trate sus parejas de claves como
un pasaporte: personal, de valor y de larga duración.
De este modo, un paso clave al preparar su entorno de trabajo es el de generar una pareja
de claves (si aún no tiene una), y asegurarse de que la clave privada está en todos los equipos
que use para el desarrollo, y que ha añadido la clave pública a todos los servicios que soportan
acceso seguro y práctico a través de ssh.
Para este libro, y como muestra la figura A.1, esto significa que la clave privada se queda
tanto en su equipo como en la VM, tanto si la ha desplegado localmente usando VirtualBox,
como si la ha desplegado usando la EC2 de Amazon.
ssh viene del mundo Unix, así que se encontrará con un entorno de línea de comandos
al modo Unix. Tanto Mac OS X (a través de la aplicación llamada Terminal) como Linux (a
través de xterm) tienen este entorno, pero Windows no. Así que recomendamos el uso de
Git para Windows26 , gratuito, que no sólo ofrece un servicio de asistencia de calidad para
el sistema de control de versiones de Git en Windows (que veremos más adelante), sino que
también ofrece una versión de bash para Windows, el intérprete de comandos de Unix que
ofrece un entorno como el del propio Unix, soportando ssh y otros comandos muy utilizados.
El resto de esta sección, y los tutoriales en línea y recursos a los que se refiere, asumen que
los usuarios de Windows han instalado esta herramienta.
Hay gente que usa Si todavía no dispone de su pareja de claves en su equipo, le recomendamos las excelentes
diferentes claves ssh para
servicios distintos, evitando
instrucciones de GitHub27 para generar una nueva pareja, donde se incluyen instrucciones
así jugárselo todo a una para Mac OS, Linux y Windows (habiendo instalado Git para Windows), y que añada la
carta si alguna clave privada clave pública a su cuenta de GitHub (cubriremos los conceptos básicos de GitHub en la
se ve comprometida. sección A.7).
A.6. INTRODUCCIÓN A GIT PARA EL CONTROL DE VERSIONES 489
una instantánea de los ficheros seguidos que se almacenará permanentemente junto con
los comentarios. A esta instantánea se le asigna un ID de confirmación (commit ID),
un número de 40 dígitos hexadecimales que, sorprendentemente, es único en el universo
(no sólo dentro de este repo Git, sino único para todos los repos); un ejemplo podría ser
El algoritmo SHA-1
se usa para calcular el 1623f899bda026eb9273cd93c359326b47201f62. Este ID de confirmación es la forma
resultado de 40 dígitos de la canónica para referirse al estado del proyecto en ese instante temporal, pero, como veremos
función hash de un sentido, más tarde, Git ofrece otras maneras para referirse a una confirmación (commit) además de
y representa el árbol ese ID engorroso. Una manera muy común es especificando un prefijo en la confirmación
completo del proyecto en en
ese instante temporal. que sea único dentro del repo, como 1623f8 para el ejemplo de arriba.
Para especificar que Git use el editor vim para realizar los cambios, usted tendría
que escribir git config --global core.editor 'vim'. No importa en qué di-
rectorio se encuentre cuando realice esta operación, porque --global especifica que
Al contrario que Mac OS X, esa opción se aplica a todas sus operaciones en Git y para todos los repositorios (la
el intérprete de comandos
de Windows (command
mayoría de variables de configuración de Git también se pueden establecer por cada
prompt) difiere de las repositorio). Otros valores útiles para esta configuración en particular son 'mate -w'
convenciones de Unix, por para el editor TextMate de MacOS, 'edit -w' para TextWrangler en MacOS, y el incó-
lo que muchas herramientas modo "'C:/Program Files/Notepad++/notepad++.exe' -multiInst -notabbar
Unix no funcionan bien. Le
recomendamos que trabaje
-nosession -noPlugin" para Windows. En todos los casos, las comillas son necesarias
usando la VM basada en para evitar que los espacios dividan el nombre del editor en múltiples argumentos del
Linux en vez de en comando.
Windows. Screencast A.6.1. Flujo básico de trabajo en Git para un solo desarrollador.
http://vimeo.com/34754947
En este flujo simple de trabajo, git init se usa para empezar a seguir un proyecto con Git,
git add y git commit se usan para añadir y confirmar dos ficheros. Después se modifica
un fichero y, cuando git status muestra que un fichero seguido tiene algún cambio, git
diff se usa para visualizar los cambios que serán confirmados. Finalmente, git commit
se usa otra vez para confirmar los cambios nuevos, y git diff se usa para mostrar las
diferencias entre las dos versiones confirmadas de uno de los ficheros, mostrando que git
diff puede comparar tanto dos confirmaciones de un fichero como el estado actual de un
fichero con el estado anterior de alguna confirmación previa.
Es importante recordar que mientras git commit registra permanentemente una instan-
tánea del estado del repo actual, que puede ser reconstruido en algún momento del futuro,
no crea una copia de respaldo del repo en ningún otro lugar, ni hace que sus cambios sean
accesibles al resto de desarrolladores del proyecto. La siguiente sección describe cómo usar
un servicio de hospedaje para Git basado en la nube con estos propósitos.
A.7. INTRODUCCIÓN A GITHUB 491
2. Para crear un repo en GitHub, que será una copia remota de su repo del proyecto
real, rellene y envíe el formulario Nuevo Repositorio (New Repository)30 y fíjese en el
nombre que le da al repo. Una buena elección es darle el mismo nombre que el del
directorio raíz de su proyecto, como por ejemplo myrottenpotatoes.
3. De vuelta a su equipo de trabajo, en una ventana de intérprete de comandos teclee cd
para dirigirse al directorio raíz de su proyecto (donde previamente tecleó git init) y
492 APÉNDICE A. USO DE LA BIBLIOTECA DE RECURSOS
El primer comando le dice a Git que está añadiendo una nueva copia remota de su repo
que estará localizada en GitHub, y que el nombre corto origin será usado a partir de ahora
para referirse a esa copia remota (este nombre se usa como convención entre los usuarios de
Git por las razones que se explican en el capítulo 10). El segundo comando le dice a Git que
suba o empuje (push) a la copia remota origin cualquier cambio de su repo local que no
esté todavía allí.
Estos pasos de gestión de claves y de configuración de cuenta sólo se tienen que hacer
una vez. El proceso de creación de un nuevo repositorio usando git remote para añadirlo
se debe hacer por cada nuevo proyecto. Cada vez que usted usa git push en un repositorio
en concreto, está propagando todos los cambios al repo desde su última subida al remoto, lo
que produce el gran efecto secundario de mantener una copia actualizada de su proyecto.
La figura A.2 resume los comandos fundamentales que se han introducido en este capí-
tulo, y que le deberían resultar suficientes como punto de partida como desarrollador indivi-
dual. Cuando trabaje en equipo, necesitará usar las características y comandos adicionales de
Git introducidos en el capítulo 10.
Los conceptos del capítulo 4 Las nuevas tecnologías de computación en la nube como Heroku hacen más fácil que nunca
son fundamentales para el despliegue de aplicaciones SaaS. Cree una cuenta gratuita en Heroku32 si no lo ha hecho
este tema, así que lea ese todavía; la cuenta gratuita ofrece suficiente funcionalidad para realizar los proyectos de este
capítulo primero si todavía
no lo ha hecho.
libro. Heroku soporta aplicaciones en muchos lenguajes y entornos de trabajo (frameworks).
Para desplegar aplicaciones en Rails, Heroku ofrece una gema llamada heroku, que ya viene
preinstalada en la VM de la biblioteca de recursos. Una vez que haya creado una cuenta
Heroku, instale Heroku Toolbelt33 , una colección de herramientas de línea de comandos que
simplifica el acceso a Heroku.
Primero necesita añadir su clave pública ssh en Heroku para permitir realizar despliegues
allí. Las instrucciones de Heroku34 explican cómo hacer esto una vez que haya instalado
Toolbelt. Sólo necesita realizar este proceso una sola vez.
A.8. DESPLIEGUE EN LA NUBE USANDO HEROKU 493
Figura A.2. Comandos comunes de Git. Algunos de estos comandos pueden parecer hechizos arbitrarios porque son casos
muy específicos de comandos mucho más generales y potentes, y algunos irán cobrando más sentido a medida que aprenda
más características de Git.
494 APÉNDICE A. USO DE LA BIBLIOTECA DE RECURSOS
Básicamente, Heroku se comporta como un repo remoto de Git (sección A.7) que sólo
conoce una única rama llamada master, y realizar un push de los cambios a dicho repo
Las ramas de Git se
discuten en el capítulo 10. remoto tiene el efecto secundario de desplegar su aplicación. Cuando usted realiza un push
de los cambios, Heroku detecta qué entorno de trabajo usa su aplicación, para determinar
cómo desplegarla. Para las aplicaciones que usan Rails, Heroku ejecuta bundle para instalar
las gemas de su aplicación, compila los recursos estáticos (assets, descritos a continuación)
y arranca la aplicación.
El capítulo 4 describe los tres entornos (de desarrollo, de producción y de pruebas) que
define Rails; cuando despliega en Heroku o en cualquier otra plataforma, su aplicación de-
splegada se ejecutará en el entorno de producción. Hay dos cambios que debe realizar para
armonizar algunas diferencias importantes entre su entorno de desarrollo y el entorno de pro-
ducción de Heroku.
Primero, Heroku necesita algunas gemas adicionales para soportar estas diferencias.
Heroku requiere configuraciones específicas para el entorno de producción de su aplicación,
que están recogidas en una gema llamada rails_12factor35 . Además, Heroku usa el gestor
de base de datos PostgreSQL, en vez de SQLite. El siguiente fragmento de código muestra
cómo cambiar el fichero Gemfile de su aplicación para reflejar estas dos diferencias. Ten-
drá que realizar este paso por cada nueva aplicación que cree y que vaya a ser desplegada
en Heroku. Como siempre, no olvide ejecutar bundle después de cambiar su fichero Gem-
file, y no olvide confirmar (commit) y subir (push) los cambios tanto de Gemfile como de
Gemfile.lock.
http://pastebin.com/bfjxEq5r
1 # making your Gemfile safe for Heroku
2 ruby ' 1.9.3 ' # just in case - tell Heroku which Ruby version we need
3 group : development , : test do
4 # make sure sqlite3 gem ONLY occurs inside development & test groups
5 gem ' sqlite3 ' # use SQLite only in development and testing
6 end
7 group : production do
8 # make sure the following gems are in your production group :
9 gem ' pg ' # use PostgreSQL in production ( Heroku )
10 gem ' rai ls_12f actor ' # Heroku - specific production settings
11 end
Segundo, después de instalar cualquier otra gema, el siguiente paso de Heroku en cada
despliegue es manejar los recursos estáticos (assets) de su aplicación, como los ficheros CSS
(sección 2.3) y los ficheros JavaScript (capítulo 6). Desde la versión 3.1 de Rails, éste so-
porta el lenguaje Sass de hojas de estilo en cascada (Sass Cascade Stylesheet Lan-
guage, SCSS) de alto nivel para crear hojas de estilo CSS y el lenguaje CoffeeScript
para generar JavaScript siguiendo la directriz DRY. Como los navegadores consumen CSS
y JavaScript pero no SCSS o CoffeeScript, existe una secuencia de pasos denominados en
conjunto como tubería de estáticos36 (asset pipeline) que realizan las siguientes tareas de
generación de código:
1. Todos los ficheros CoffeeScript que hubiera en app/assets, si hay alguno, son con-
vertidos a JavaScript.
2. Todos los ficheros JavaScript son concatenados en otro fichero JavaScript más grande
que se minimiza para que ocupe menos espacio, para lo cual elimina espacios en
blanco, comentarios y quizá cambie el nombre de variables por otros más cortos. El
fichero JavaScript resultante se coloca en el directorio public/assets.
3. Todos los ficheros SCSS en app/assets, si hay alguno, se traducen a CSS.
A.8. DESPLIEGUE EN LA NUBE USANDO HEROKU 495
4. Todos los ficheros CSS se concatenan en un fichero CSS más grande que se minimiza
y se coloca en public/assets.
5. Rails adapta el nombre de cada uno de estos ficheros grandes para incluir una “huella
digital” que identifique inequívocamente el contenido del fichero, permitiendo que los
activos estáticos se puedan almacenar en la caché tanto de servidores como de nave-
gadores (sección 12.7), siempre y cuando el contenido del fichero no varíe, algo que
en el entorno de producción sólo ocurre cuando se despliega una nueva versión de la
aplicación.
Por tanto, el segundo cambio que tiene que realizar en su aplicación es especificar con
cual de las tres formas va gestionar Heroku la tubería de activos. La primera forma es pre-
compilando los recursos estáticos ejecutando localmente en su equipo la tubería de recursos
(assets pipeline), y realizando después el seguimiento de versiones de los archivos CSS y
JavaScript generados en Git. La segunda forma es hacer que Heroku prepare y compile los
activos estáticos en tiempo de ejecución la primera vez que se solicite cada tipo de recurso.
Este método puede causar un comportamiento impredecible cuando se usa, y ni nosotros
ni Heroku lo recomendamos. La tercera forma, que es la que recomendamos, es dejar que
Heroku compile los ficheros estáticos una sola vez en el momento de despliegue. Este método
es el que sigue la filosofía DRY más fielmente: como usted sólo almacena los ficheros origi-
nales (JavaScript y/o CoffeeScript, CSS y/o SCSS) bajo el control de versiones, sólo hay un
lugar donde se puede modificar la información de los activos estáticos. También simplifica la
configuración si está usando Jasmine para probar su código JavaScript o CoffeeScript.
Para hacer que Heroku precompile sus activos estáticos en el momento de despliegue,
añada la siguiente línea en config/environments/production.rb:
http://pastebin.com/7PDq3tid
1 # in config / environments / application . rb :
2 config . assets . i n i t i a l i z e _ o n _ p r e c o m p i l e = false
Esta línea evita que Heroku intente inicializar el entorno Rails antes de precompilar sus re-
cursos: en Heroku, algunas variables de entorno en las que se apoya Rails no se inicializan
hasta más tarde, y su ausencia podría causar un error durante el despliegue. Este artículo37
contiene algunos consejos para resolver problemas relacionados con la tubería de recursos
(asset pipeline) en el momento de despliegue, incluyendo cómo compilar la tubería en local
para aislar problemas. Cuidado: si compila la tubería de recursos de forma local, se creará el
fichero public/assets/manifest.yml; asegúrese de que no realiza el seguimiento de este
fichero con Git, porque su presencia avisará a Heroku de que está precompilando sus propios
activos ¡y que no quiere que Heroku lo haga por usted!
Una vez que haya realizado estos cambios, que se hacen una única vez, en su aplicación
(y tras haber confirmado y subido los resultados), el despliegue de cada nueva versión de la
aplicación sigue una receta simple, comenzando en el directorio raíz de su aplicación:
496 APÉNDICE A. USO DE LA BIBLIOTECA DE RECURSOS
Figura A.3. Cómo conseguir la funcionalidad de algunos de los comandos en el modo de desarrollo para la versión
desplegada de su aplicación.
La figura A.3 resume cómo algunos de los comandos más útiles que ha estado usando en
el modo de desarrollo se pueden aplicar a la aplicación desplegada en Heroku.
A.9. LISTA DE VERIFICACIÓN: CREACIÓN DE UNA NUEVA APLICACIÓN RAILS497
6. Ejecute git init para configurar su directorio raíz como repo GitHub (§A.6, screen-
cast A.6.1).
1. Cree un repo GitHub a través de la interfaz web de GitHub, realice la primera confir-
mación (commit) y suba (push) el repo de su nueva aplicación (§A.7).
2. Apunte CodeClimate hacia el repo GitHub de su aplicación (§9.5).
1. En su fichero Gemfile, añada soporte para Cucumber (§7.6), RSpec (§8.2), depuración
interactiva (§4.1), SimpleCov (§8.7), Autotest (§8.2), FactoryGirl (§8.5), Jasmine si
tiene pensado usar JavaScript (§6.7) y Metric-Fu para mantener un seguimiento de las
métricas de su código:
http://pastebin.com/y4MaVP72
1 # debugger is useful in development mode too
2 group : development , : test do
3 gem ' debugger '
4 gem ' jasmine - rails ' # if you plan to use JavaScript / CoffeeScript
5 end
6 # setup Cucumber , RSpec , autotest support
7 group : test do
8 gem ' rspec - rails ' , ' 2.14 '
9 gem ' simplecov ' , : require = > false
10 gem ' cucumber - rails ' , : require = > false
11 gem ' cucumber - rails - training - wheels ' # basic imperative step defs
12 gem ' d a t a b a s e _ c l e a n e r ' # required by Cucumber
13 gem ' autotest - rails '
14 gem ' f a c t o r y _ g i r l _ r a i l s ' # if using FactoryGirl
15 gem ' metric_fu ' # collect code metrics
16 end
(ver la sección 6.7 para conocer más gemas que soportan datos fixture y métodos stub
de AJAX en sus ficheros JavaScript de prueba).
2. Ejecute bundle, ya que ha cambiado su fichero Gemfile. Confirme los cambios de
Gemfile y Gemfile.lock.
A.10. FALACIAS Y ERRORES COMUNES 499
3. Si todo va bien, cree los subdirectorios y ficheros que usan RSpec, Cucumber y Jas-
mine, y si está usándolos, ejecute los pasos fundamentales de Cucumber:
http://pastebin.com/BvJvHezi
1 rails generate rspec : install
2 rails generate cucumber : install
3 rails generate c u c u m b e r _ r a i l s _ t r a i n i n g _ w h e e l s : install
4 rails generate jasmine_rails : install
4. Si está usando SimpleCov, algo que le recomendamos, coloque las siguientes líneas al
principio de spec/spec_helper.rb para activarlo:
http://pastebin.com/G5BV1efA
1 # at TOP of spec / spec_helper . rb :
2 require ' simplecov '
3 SimpleCov . start
5. Si está usando FactoryGirl para gestionar factorías (factories) (§8.5), añada este
código:
http://pastebin.com/VDnhECsQ
1 # For RSpec , create this file as spec / support / factory_girl . rb
2 RSpec . configure do | config |
3 config . include FactoryGirl :: Syntax :: Methods
4 end
http://pastebin.com/Wx7veG8E
1 # For Cucumber , add at the end of features / support / env . rb :
2 World ( FactoryGirl :: Syntax :: Methods )
6. Ejecute git add y luego realice un commit de cualquier fichero creado o modificado
en estos pasos.
7. Asegúrese de que el despliegue en Heroku todavía funciona: git push heroku
master
Ahora ya está preparado para crear y aplicar la primera migración (§4.2), de-
spués redespliegue a Heroku y aplique la migración en producción (heroku run rake
db:migrate).
Añada otras gemas útiles:
Algunas de las gemas que recomendamos son:
• railroady dibuja diagramas de las relaciones entre sus clases, como relaciones has-
many, belongs-to, etc. (§5.3).
• omniauth añade autenticación portable de terceros (§5.2).
• devise añade autoregistro en las páginas, y funciona opcionalmente con omniauth.
Git hace que las operaciones commit sean rápidas y fáciles de realizar, así que debería
hacerlas frecuentemente para que, si alguna confirmación introduce algún problema, no tenga
que deshacer todos los cambios. Por ejemplo, si modificó dos ficheros que se ocupaban del
tema A y otros tres ficheros trabajaban en el tema B, haga dos confirmaciones por separado
por si se da el caso de que uno de los dos conjuntos de cambios se tenga que deshacer más
tarde. De hecho, los usuarios avanzados de Git usan git add para seleccionar un subcon-
junto de los ficheros modificados a incluir en la confirmación: añada los ficheros específicos
que quiera, y omita la opción -a en git commit.
un fichero, ¡debería!
Notas
1 http://virtualbox.org
2 http://github.com
3 http://heroku.com
4 http://stackoverflow.com
5 http://stackoverflow.com
6 http://github.com
7 http://heroku.com
8 http://pivotaltracker.com
9 http://saasbook.info
10 http://pastebin.com/u/saasbook
11 http://vimeo.com/saasbook
12 http://virtualbox.org
13 http://www.saasbook.info/bookware-vm-instructions
14 http://github.com/saasbook/courseware
15 http://catb.org/~esr/writings/homesteading/cathedral-bazaar/
16 http://aptana.com/
17 http://code.tutsplus.com/articles/25-vim-tutorials-screencasts-and-resources--net-
14631
18 http://www.gnu.org/software/emacs/tour/
19 http://www.saasbook.info/bookware-vm-instructions
20 http://www.barebones.com/products/textwrangler/
21 http://notepad-plus-plus.org/
22 http://www.saasbook.info/bookware-vm-instructions/ec2
23 http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-key-pairs.html#how-to-
generate-your-own-key-and-import-it-to-aws
24 http://www.saasbook.info/bookware-vm-instructions/ec2
25 http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-key-pairs.html#how-to-
generate-your-own-key-and-import-it-to-aws
26 http://msysgit.github.io/
27 https://help.github.com/articles/generating-ssh-keys
28 http://www.saasbook.info/bookware-vm-instructions
29 http://github.com/edu
30 https://github.com/repositories/new
31 https://help.github.com/articles/which-remote-url-should-i-use
32 http://heroku.com
33 https://toolbelt.heroku.com/
34 https://devcenter.heroku.com/articles/keys
35 https://github.com/heroku/rails_12factor
36 http://guides.rubyonrails.org/asset_pipeline.html
37 https://devcenter.heroku.com/articles/rails-asset-pipeline
38 https://devcenter.heroku.com/articles/git#deploying-code
39 http://devcenter.heroku.com/articles/taps
40 http://devcenter.heroku.com/articles/rails3
41 http://book.git-scm.com/4_ignoring_files.html
42 http://book.git-scm.com/
Índice
fase de mantenimiento, 351 CLU (lenguaje), fundamentos de Combinar cambios, ver Commit
gestión de proyectos, 378, 388 yield, 101, 101 squashing
historias de usuario, 265 Clusters, 23 Comentarios
lenguajes de especificación for- CMM (Capability Maturity Model), malos, ejemplo, 339
males, 267 11 nivel de abstracción, 339
métricas software, 341 Cobertura C0, 337 resumen, 339
objetivos del ciclo de vida, 6 Cobertura de camino (C2), 310 Comité de control de cambios, peti-
patrones de diseño, 422 Cobertura de condición/decisión ciones de mantenimiento, 352
primera versión, 7 modificada, ver MCDC (Modified Commit, ver Confirmar cambios
pros y contras de BDD, 276 Condition/Decision Coverage) Commit ID, ver ID de confirmación
pruebas, visión general, 314 Cobertura de entrada/salida (S1), Commit squashing, Git, 371
puntos de función, 270 310 Comodines
requisitos no funcionales, 461 Cobertura de llamada (S1), 310 expresiones regulares, 80
RUP, 8, 10 Cobertura de método (S0), 310 rutas, 116
sitio web ACA, 6 Cobertura de pruebas Comparación, operadores
tareas principales, 266 falacias del no bug|hyperpage, 318 JavaScript, 228
visión general, 6, 270 pasar antes de la entrega, 318 Compatibilidad de versiones, inte-
visión general de las pruebas, 315 Cobertura de rama (C1), 310 gración continua, 438
Cifrado, protección de datos, 456 Cobertura de sentencia (C0) Compiladores
Cinco nueves, 23 comprobación con SimpleCov, automatización, 30
Cirugía de escopeta, 406 311 JavaScript, 230
Claridad mediante concisión, mejora definición, 310 Premio Turing, 182, 482
de la productividad, 28 ejemplo, 337
Complejidad ciclomática
Clase Cobertura definición-uso (DU), 313
definición, 341
definición en Ruby, 88 Cobertura del flujo de control, 314
ejemplo, 341
métodos privados, 424 Cobertura, conceptos, 307, 310
invención, 341
objetos Ruby, 88 COCOMO (Constructive Cost
Complejidad, SOLID, 400
Clase, variables Model), 269
Comportamiento, diagramas UML,
Ruby, 90 Codd, Edgar F. “Ted”, 65
401
Clase-Responsabilidad- Code-to-test ratio, ver Ratio
Composición
Colaboración, ver CRC (Class- código/pruebas
CodeClimate duck typing, 411
Responsibility-Collaborator)
lista de verificación de una apli- preferencia sobre herencia, 408,
Clases, diagrama UML, 401, 403,
cación Rails nueva, 498 410, 411
406, 411
métricas múltiples, 342 UML, 403
Class (clase), objetos Ruby, 84
Código de vida corta, 25 Comprobaciones previas al vuelo,
Clausuras
Código elegante activadores de funcionalidad, 443
bloques en Ruby, 95
costes de mantenimiento, 341 CompuServe, 49
Clave de desarrollador, API REST,
285 definición, 25 Computación como servicio, 24
Clave pública, servicios de Código heredado Computación en la nube, 22
hospedaje para Git, 491 características, 328 definición, 24
Claves foráneas, 165 definición, 25 despliegue en Heroku, 492
consultas abusivas a base de datos, Microsoft Zune, 337 PaaS, 433
454 CoffeeScript, 192, 230, 230, 494 rendimiento y escalabilidad, 464
ejemplo, 166 críticas de diseño, 230 Comunicación
click, autenticación mediante ejemplo, 230 cookies, 52
evento, 200 vulneración del principio de mín- HTTP y los URI, 50
Cliente universal, navegador web, 49 ima sorpresa, 230 petición HTTP, 52
Cliente, colaboración con, manteni- Cohesión pull del cliente vs. push del servi-
miento, 351 definición, 404 dor, 53
Cliente, JavaScript del lado ejemplo, 404 requisitos SaaS, 22
DRY, 185 refactorización, 349 Concepción, fase de RUP, 9
Cliente-servidor, arquitectura, 48 Colaboración a distancia, Git, 371 Concisión
patrón de diseño, 49 Colecciones mejora de la productividad, 29
perspectiva de alto nivel, 48 Enumerable, 99 reflexión y metaprogramación,
Clientes de producción, 48 has_many, 167 126
Clone, control de versiones, 368 objeto proxy, 124 Condición de carrera, ataque, 462
CloudFoundry, 433 operadores, 96 Condición de coincidencia (Match-
CloudFront, 451 reutilización, 29 condition), TMDb, 294
506 ÍNDICE
Conductor, programación en pareja, Copiloto, programación en pareja, lista de verificación de una apli-
366 366 cación Rails nueva, 498
Confirmar cambios Cortafuegos palabras clave, 253
confirmaciones grandes, 500 acceso a GitHub, 492 pros y contras de BDD, 275, 276
confusión con la subida (push), Cortafuegos, falacia sobre la prueba de errores, 376
500 plataforma segura, 466 pruebas de caracterización, 338
ejemplo de rama, 372 Coste del diseño de software, errores pruebas de integración, 287
fundamentos Git, 489, 491 comunes, 36 relaciones entre herramientas para
Conflictos de fusión, control de ver- Costuras, 290 pruebas, 275
siones, 368 CouchDB, DataMapper, 167 uso, 256
Confrontación constructiva, gestión CRC (Class-Responsibility- Cuellos de botella, localización, 445
de personas, 381 Collaborator), tarjetas, 334, 336 Curso en línea masivo y abierto,
Consejo, AOP, 158 Crear copia local, ver Clone ver MOOC (Massive Open Online
Consistencia de datos, escalabilidad create Course)
de RDBMS, 73 asociaciones, 172 CVS (Concurrent Versions System),
Construcción, fase de RUP, 9 ejemplo de aplicación Rails, 121, 387
Constructores, JavaScript, 195 137
Consultas, composición con ámbitos Dahl, Ole-Johan, 150
envío de formularios, 134
reutilizables, 175 Dapper, 467
sustitución por, 153
context, 304 DataMapper
Criptografía de clave pública comparación con ActiveRecord,
Control de código fuente, 489 concepto básico, 456
Control de flujo, grafo, 341, 341 167
explicación sobre redes, 51 Google AppEngine, 169
Control de versiones Premio Turing, 455 debugger, 114
conflictos de fusión (merge), 369 Criptografía de clave secreta, 457 debugger (sentencia), ejemplo de
fundamentos Git, 489 Crisscross merge, Git, 375 aplicación Rails, 133
historia, 387 Cross-site scripting Declarativos, escenarios
Controlador de página, ver Page pruebas fuzz, 314 pruebas, 262
Controller CRUD Decorator (patrón)
Controlador frontal, ver Front Con- asociaciones, 173 caché, 449
troller
definición, 66 función, 408
Controlador Index, Template View,
editar/actualizar y destruir, 140 Deflating, ver Serialización, 192
71
modelos, 120 Degradación elegante, JavaScript,
Controlador, programación en
Rails, fundamentos, 114 185
pareja, 366
rutas, 69 Delegación (patrón)
Controladores
vistas, 124 duck typing, 411
acción REST index, 125 ejemplo, 408
csrf_meta_tags, 457
acción show, 128 preferencia sobre herencia, 411
desarrollo, 291 CSS (Cascading Style Sheets)
AJAX, 209 Demeter, proyecto, 417
ejemplo de aplicación Rails, 124 Demostración automática de teore-
ejemplo de asignación en masa, construcciones, 56
construcciones Haml, 71 mas, 315
136 Denegación de servicio, ataque, 458,
ejemplo TMDb, 295 editor para, 138
HTML dinámico, 200 468
extensos, 143 Denegación de servicio, ataque dis-
función en MVC, 288 introducción, 56
tribuido, 468
interacciones de validación, 155 mensaje flash, 139
Departamento de Defensa de los Es-
métodos privados, cuestiones de renderizado del contenido, 56
tados Unidos, ver DOD (Depart-
seguridad, 458 visión general, 54
ment Of Defense)
MVC, 62 CSS Zen Garden, 55 Dependencias
rutas, 116 Cuadrado entorno de desarrollo vs. produc-
terminación, 291 duck typing, 411 ción, 500
visión general, 66 LSP, 411 sustitución por seams, 345
Convenciones de código, 365 Cucumber Depuración
Conversión de tipos, llamadas a automatización, 30 convencional, comparación con
métodos, 86 automatización de pruebas, 274 TDD, 320
convert (método) comparación de escenarios, 263 depuración delta, 477
ejemplo, 343 diagramas UML, 401 ejemplo de aplicación Rails, 131
pruebas de caracterización, 337 ecosistema BDD, 264 JavaScript, 188
refactorización, 346, 349 ejemplo, 253, 256, 261 lista de verificación de una apli-
Cookies, HTTP, 53 ejemplo de TMDb, 259 cación Rails nueva, 498
ÍNDICE 507
Depuración delta, 477 DNS spoofing, 469 ejemplo de aplicación Rails, 140,
Depurador, Ruby, 137 Doble, 290 140
Desarrollo guiado por compor- Documentación EDSAC, Premio Turing, 2
tamiento, ver BDD (Behavior- código, 329 Efecto 2000, 286
Driven Development) embebida, 331 Eficiencia por ordenador, 73
Desarrollo orientado a pruebas, ver DOD (Department Of Defense), Eich, Brendan, 184
TDD (Test-Driven Development) adquisición de software, 16 Ejemplos
Desbordamiento aritmético, ataque, DOM (Document Object Model) ejemplo de código, 294
462 ejemplo jQuery, 201 RSpec, 289
Desbordamiento de buffer, ataque, HTML dinámico, 200 terminación, 291
462 JavaScript, 196 TMDb, controlador, 295
Descomposición de condicional jQuery, 222 Elaboración, fase de RUP, 9
aplicación, 349 manejadores de eventos en Emacs
ejemplo, 348 JavaScript, 199 trabajando con código, 486
refactorización, 346 manipulación con jQuery, 197, Embebida (documentación), RDoc,
describe 199 331
Jasmine, 214 Rotten Potatoes, 196 Emparejamiento promiscuo, 366
TMDb, 295, 296, 302 uso del término, 196 Encapsulación, ssh, 487
Deserialización, 65 down (método), ejemplo de apli- Encapsulamiento, Ruby Java, 91
Deserializar, 221 cación Rails, 118 Encapsular campo, refactorización,
Desmantelamiento, RSpec, 292 Driver, programación en pareja, 366 350
Despliegue DRY (Don’t Repeat Yourself) Entidad
activadores de funcionalidad, 439 características de metaprogra- autenticación, 159
acumulación de basura, 465 mación en Ruby, 80 Entidades arquetípicas, 456
continuo, 437 cosas similares, 140 Entorno y herramientas altamente
Heroku, 492 despliegue en la nube con Heroku, productivos, triángulo virtuoso del
visión general, 432 495 desarrollo SaaS, 474
Despliegue continuo, 437 ejemplo de aplicación Rails, 117 Entorno, conceptos
destroy (método), ejemplo de apli- ejemplo del controlador TMDb, fundamentos Rails
cación Rails, 122, 140 295 bases de datos y migraciones,
Destructivos, uso de métodos, 97 ejemplo TMDb, 261 117
Deuda técnica, 354 eventos personalizados en controladores y vistas, 124
Devolución de llamada, ver Call- JavaScript, 205 depuración, 131
back, función factoría, 300 editar/actualizar y destruir, 140
DigiNotar, 469 JavaScript, 186 envío de formularios, 134
Dijkstra, 179 metaprogramación, 92 modelos, 119
Dijkstra, Edsger W., 179, 315 MVC, 152 redirección y flash, 136
DIP (Dependency Injection Princi- reutilización, 30 visión general, 112
ple), 413 search_tmdb, 296 fundamentos Ruby
almacenamiento de objetos en DSL (Domain Specific Language) bloques, 95
caché, 451 diferencia con lenguaje de do- clases, métodos, herencia, 88
ejemplo, 416 minio, 263 expresiones idiomáticas, 104
impacto en OCP, 417 RSpec, 287 iteradores con yield, 101
Directorios, estructura, ejemplo de Duck typing, ver Tipado dinámico metaprogramación, 92
aplicación Rails, 112 Duración limitada, historias de mix-ins y tipado dinámico, 99
Disciplinados, procesos, 8 usuario SMART, 245 objetos, 84
Diseño asistido por ordenador, ver Dynabook, 474 operación como método, 86
CAD (Computer Aided Design) visión general, 79
Diseño, revisiones each (método) JavaScript
ciclos de vida clásicos, 422 clase Array, 99 AJAX, 206
Disponibilidad definición, 95 DOM y jQuery, 196
cuantificación, 443 definición de iterador, 100 eventos y funciones callback,
definición, 433 tipado dinámico, 96 199
requisitos SaaS, 23 eBay funciones y constructores, 195
Dispositivo de interfaz de red, 51 ciclo de vida ágil, 21 pruebas, 212
DNS (Domain Name System) ECMAScript, 184 SPA y API JSON, 221
correspondencia de nombres de edit visión general, 185, 229
equipo, 51 vista parcial, 152 Rails avanzado
zona raíz, 51 Editar asociaciones through, 169
508 ÍNDICE
Indicadores clave de desempeño, ver Intimidad Inapropiada, vulnera- DOM y jQuery, 196
KPI (Key Performance Indicators) ciones de Demeter, 418 ECMAScript, 184
Índice de rendimiento de la apli- Intrusivo, código JavaScript no, 185 ejemplo de aplicación Rails, 127
cación (Apdex), 435 Inyección SQL errores del código de producción,
Índice, fundamentos Git, 491 aplicaciones nicho, 466 227
Indisciplinado, ciclo de vida ágil, 12 IP, dirección eventos y funciones callback, 199
Industria de tarjetas de pago, 460 explicación sobre redes, 52 fallos silenciosos, 226
Ingeniería, disciplinas de, 9 red TCP/IP, 51 fichero minimizado, 494
Ingenieros software de manteni- IPv6, explicación sobre redes, 51 funciones y constructores, 195
miento, 351 ISO 9001, estándar, 462 helpers para vistas Rails, 205
Inicialización, 122 ISP (Interface Segregation Princi- herencia de prototipos, 195
clases, 88 ple), 405 HTML dinámico, 200
initialize ISP (Internet Service Provider), 433 lista de verificación de una apli-
patrones de factoría, 425 ISP, despliegue, 433 cación Rails nueva, 498
Inmovilidad, SOLID, 400 Iteración del ciclo de vida ágil, 477 mejora del sitio web, 225
Inmunización, Haml, 126 Iterador orígenes de Scheme, 184
Inspecciones, ciclos de vida clási- sintaxis, 421 paralelismo de tareas, 211
cos, 381 Iteradores política del mismo origen, 225
Instancia, variables, 90 bloques Ruby, 95 problemas con, 228
Instrumentación, depuración, 133 hechos con yield, 101 pruebas, 212
Integración ascendente, 315 Principio de Demeter, 420 sincronización con la aplicación,
Integración continua (CI), pruebas 226
Jacobson, Ivar, 401
condiciones poco probables, 463 uso de la palabra reservada this,
Jasmine
despliegue, 437 227
configuración, 212
lenguajes compilados, 438 visión general, 184, 229
creación de directorio, 212
Integración descendente, 314 vista parcial, 152
espías, 214
Integración, pruebas JavaScript API, ver JSAPI
funcionalidades de uso común,
comparación con pruebas uni- (JavaScript API)186
214
tarias, 307 JavaScript del lado cliente
lista de verificación de una apli-
comparación de métodos de definición, 184
cación Rails nueva, 498
prueba, 311 JavaScript y XML asíncronos, ver
pruebas de AJAX, 212, 214
Cucumber, 253 AJAX (Asynchronous JavaScript
Jasmine-jQuery
errores, 376 And XML)
fixtures, 222
pruebas de caracterización, 337 JavaScript, ámbito de función, 228
funcionalidades de uso común,
requisitos explícitos frente a im- 214 JavaScript, intérprete, 230
plícitos, 262 Jasmine-Rails, 212 Jefe de mantenimiento, 351
RSpec vs. Cucumber, 287 Java Join, claves foráneas, 166
software, 27 excepciones, 306 Joy, Bill, 486
tipos, 314 integración continua, 438 jQuery
Integridad de los datos, 434 relación con JavaScript, 187 Adapter, 186
Interacción, diagramas UML, 401 Java, Ruby DOM, manipulación, 197
Interfaz de programación de aplica- clases, 91 ejemplo, 209
ciones, ver API (Application Pro- codificando en Ruby, 103 invocación, 203
gramming Interface) conversión de tipos, 86 Jasmine, 212
Interfaz de usuario (UI), UI poco de- Enumerable, 99 JavaScript para programadores
tallada, 248 errores de sintaxis, 132 Ruby, 188
Internet Explorer 5 metaprogramación, 92 manipulación del DOM, 199
JavaScript, 186 objetos, 84 minificar, 192
XmlHttpRequest, 206 Ruby, visión general, 79 visión general, 196
Interpolación, Template View, 71, traducción de características, 91 jQuery Mobile, 229
71 yield, 101 JSAPI (JavaScript API), 186, 196,
Intérprete de comandos, 487 JavaScript 218
sustituto de rsh, 487 AJAX, 206 JSLint, 226
Intérprete de comandos remoto, ver API de jQuery, 200 JSON (JavaScript Object Notation),
rsh (Remote SHell) código no intrusivo, 188 184, 221
Intérprete de comandos seguro, ver Capybara, 255 datos estructurados, 192
ssh (Secure SHell) cuestiones de seguridad de XSS, definición, 188
visión general, 487 458 ejemplo de respuesta, 222
Intérpretes, automatización, 30 despliegue, 438 fixtures Jasmine-jQuery, 222
512 ÍNDICE
simular con stubs, 224 Lenguaje unificado de modelado, herramientas Unix, 490
JSON.org, 188 ver UML (Unified Modeling Lan- ssh, 488
guage) VirtualBox, 485
Lenguajes compilados, integración Maestro-esclavo, arquitectura de tres
Kahan, William, 394
continua, 438 capas, 59
Kahn, Bob, 51 Lenguajes de especificación for- MailerMonkey, 414, 416
Kanban, 16 males, documentación de requisi- Makefiles, automatización, 30
Kay, Alan, 474 tos, 267 Maliciosos, comprobación de en-
Kernel, Linux, 485 Lenguajes de programación tradas de usuarios, 153
Knuth, Donald, 282 mejora de la productividad, 28 Malware, despliegue, 438
KPI (Key Performance Indicators), Premio Turing, 110, 182, 238, 282, Manejador de eventos, JavaScript,
445 315 199
premio Turing, 430 Manifiesto Ágil
lambda refactorización, 350 estimación de costes, 252
capturar excepciones en RSpec, reglas de estilo, 365 inspiración, 266
304 seams, 294 modelo de desarrollo, 13
LAMP, pila, 59 Lenguajes de programación de alto visión general, 11
Lampson, Butler, 326 nivel, mejora de la claridad, 29 Mantenibilidad, categorías, 328
Latencia Lenguajes de scripting, mejora de la Mantenimiento adaptativo, 329
añadida, efectos, 464 productividad, 29 Mantenimiento correctivo, 328
monitorización, 467 Ley de Demeter, 417 Mantenimiento perfectivo, 328
Ley de Moore, mejora de la produc- Mantenimiento preventivo, 329
responsividad, 435
tividad, 28 Mantenimiento, fase de
sobredimensionamiento, 443
Ley del Cuidado de Salud a Bajo ciclos de vida clásicos, 351
LCOM (Lack of Cohesion of Meth-
Precio, ver ACA (Affordable Care
ods), 404, 404 código heredado, 328
Act)
Leer, Preguntar, Buscar y Postear, comentarios, 339
Ligero, ciclo de vida ágil, 12
ver RASP (Read, Ask, Search, Post) exploración del código heredado,
Líneas de código (LOC)
Lenguaje de consultas estructurado, 331
estimación de costes, 269
ver SQL (Structured Query Lan- IEEE 1219-1998, 352
exploración del código heredado,
guage) métricas, smells de código, SOFA,
333
Lenguaje de dominio, Cucumber, 340
longitud de método, 343
263 pruebas de caracterización, 336
link_to
Lenguaje de marcado, 54 refactorización, 345
helpers para vistas Rails,
Lenguaje de marcado de hipertexto, Mantenimiento, peticiones, 351
JavaScript, 205
ver HTML (HyperText Markup Rails, ejemplo de aplicación, 126 Máquina virtual, ver VM (Virtual
Language) link_to, ejemplo de aplicación Rails, Machine)
Lenguaje de marcado de hipertexto 128 Marshalling, ver Serialización, 192
extendido, ver XHTML (eXtended Linux Martin, Robert C., 399
HTML) kernel, 485 max (método), Enumerable, 99
Lenguaje de marcado extensible ssh, 488 McCabe, Sr. Frank, 341
(XML), ver XML (eXtensible VirtualBox, 485 McCarthy, John, 22
Markup Language) Liskov, Barbara, 101, 411, 430 MCDC (Modified Condition/Deci-
Lenguaje de marcado generalizado Lisp sion Coverage), 310
estándar, ver SGML (Standard Gen- creador, 22 Mecanización de los contratos de
eralized Markup Language) tipado dinámico, 99 servicios de la administración, ver
Lenguaje dinámico, características LiveScript, 184, 228 MOCAS (Mechanization of Con-
SOLID, 424 Llaves, modo poético, 87 tract Administration Services)
specs legibles, 300 Lo-Fi, interfaz de usuario Medible, historias de usuario
Lenguaje embebido específico del bocetos sin storyboards, 274 SMART, 246
dominio, RSpec, 287 bocetos y storyboards, 257 Merge
Lenguaje específico del dominio, ver ejemplo de TMDb, 257 control de versiones, 368
DSL (Domain Specific Language) Localizador de recurso uniforme, gestión de ramas, 373
Lenguaje externo específico del do- ver URL ramas, errores comunes, 386
minio, 287 logger.debug, 133 Merge (conflictos), control de ver-
Lenguaje interno específico del do- LSP (Liskov Substitution Principle) siones, 368
minio, RSpec, 287 diagrama de clases UML, 411 Merge-commit, 372
Lenguaje SaaS de hojas de estilo en resumen, 411 Metaclase, objetos Ruby, 84
cascada, ver SCSS (Sass Cascade Metaprogramación
Stylesheet Language) Mac OS X ámbitos, 176
ÍNDICE 513
ciclos de vida clásicos, 265 ejemplo de commit, 372 resistir a hacer mejoras, 356
historias de usuario, 243 fusionar o cambiar, 386 smells de código, SOFA, 342
pros y contras de BDD, 276 Git, 373 Reflexión
Puntos de cambio, código heredado, uso efectivo, 372 concisión, 126
331, 334 Ranuras, JavaScript del lado cliente, mejora de la productividad, 29
Puntos de corte, AOP, 158 188 objetos Ruby, 85
Puntos de función, 270 RASP (Read, Ask, Search, Post), Regla de las dos pizzas, 363
Push, ver Subir cambios 484 Reglas de estilo, lenguajes de pro-
Push del servidor, comparación con depuración, 131 gramación, 365
pull del cliente, 53 Ratio código/pruebas Regresión, pruebas
definición, 307 mantenimiento, 351
QA (Quality Assurance) requisitos mínimos, 318 Reingeniería, 354
errores, 376 RCS (Revision Control System), Reinicio balanceado, 444
pruebas, visión general, 26 387 Release branch, ver Rama de versión
visión general, 286 RDBMS (Relational Database Man- Relevante, historias de usuario
quirksmode.org, 186 agement System) SMART, 246
Active Record, patrón de arquitec- Remoto, servicios de hospedaje para
Rack, servidor de aplicaciones tura , 65 Git, 491
Decorator, 408 diseño, 65 Renderizado, caché, 447
fundamentos Rails, 58, 112 ejemplo de aplicación Rails, 117 Rendimiento
integración continua, 438 escalabilidad, 73 base de datos, caché, 447
Rails, conceptos tabla de ejemplo, 65 ciclos de vida clásicos, 461
asociaciones through, 169 RDoc, 331 escalabilidad de computación en la
asociaciones y claves foráneas, Rebase nube, 464
165
definición, 375 falacias sobre ordenadores rápi-
bases de datos y migraciones, 117
Git, 371 dos, 464
consultas con ámbitos reutiliz-
Recargando, ejemplo de aplicación optimización con medidas, 464
ables, 175
Rails, 115 servidores SOA, 465
controladores y vistas, 124
Receptor visión general, 466
defensas de seguridad, 458
métodos para colecciones, 97 Rendimimento
depuración, 131
objetos Ruby, 84 aplicaciones en desarrollo, 463
editar/actualizar y destruir, 140
Recorrido de tabla, bases de datos, Reorganizar, ver Rebase
envío de formularios, 134
452 Repetición, SOLID, 400
lista de verificación para nueva
Rectángulo Replicación, escalabilidad de la base
aplicación, 497
modelos, 119 ejemplo, 411 de datos, 468
MVC, DRY, 152 LSP, 411 Repositorio compartido, control de
redirección y flash, 136 Recurso versiones, 368
rutas REST para asociaciones, 172 aplicación web como, 67 Repositorios
SSO y autenticación a través de URI, 52 añadir ficheros, 500
terceros, 159 Recursos para la VM control de versiones, 368
visión general, 112 VirtualBox, 485 fundamentos Git, 489
rails_12factor, 494 Red de distribución de contenidos, Repositorios públicos, colaboración
RailsCasts, 265 ver CDN (Content Distribution Net- a distancia, 371
raise params.inspect, depuración, work) Repositorios, colaboración a distan-
133 Redes cia, 371
rake cucumber, 260 explicación, 51 require ’debugger’, 133
rake jasmine, 212 trabajos iniciales, 51 require, confusión con include, 104
Rama actual, 373 Redirección Requisitos explícitos
Rama de funcionalidad, 372 ejemplo de aplicación Rails, 136 pruebas, 262
Rama de versión Refactorización TMDb, 303
definición, 372 a nivel de método, 345 Requisitos funcionales, obtención,
ejemplo, 372 continua, 357 267
Rama inicial, exploración del código definición, 329, 345 Requisitos implícitos, 262, 302
heredado, 332 deuda técnica, 354 descubrimiento y prueba, 303
Ramas ejemplo de código limpio, 349 expresión como spec, 303
cambios en master, 387 ejemplos, 346, 359 TMDb, 303
comandos Git, 373 elección del lenguaje, 350 Requisitos no funcionales
control de versiones, 368 extracción de clase, 406 caché, 447
de largo recorrido, 375 puntos de cambio, 330 ciclos de vida clásicos, 461
ÍNDICE 517
ejemplo para añadir película, 260 Transform View, alternativas a Tem- AJAX XHR, 207, 209
expectativas, mocks, stubs, config- plate View, 72 before-filters, 173
uración, reseteo, 294 Transición, fase de RUP, 9 comparación con URL, 52
FIRST, TDD, RSpec, 286 Traza, RASP, 131 CSRF, 457
fixtures y factorías, 298 Tres capas, arquitectura definición, 52
requisitos implícitos, 302 visión general, 58, 59 ejemplo de aplicación Rails, 128,
simular Internet con stubs, 307 Triángulo virtuoso del desarrollo 140
TDD, ciclo, 290 SaaS, 37, 474 fundamentos Rails, 112, 114
Then trigger, eventos personalizados en hash params, 116
palabra clave de Cucumber, 253 JavaScript, 205 httperf, 447
Then, palabra clave de Cucumber, Tubería de activos, 192 métodos helper para rutas, 127
253 Túnel, ssh, 487 petición HTTP, 67
TurboTax Online, 21, 474 petición HTTP, 52
Therac-25, 11
Twitter post-receive, GitHub, 438
this, palabra reservada
autenticación, 159, 162 rutas, 67
JavaScript, 201
Big Brother Bird, 467 rutas anidadas, 173
uso incorrecto, 227
OAuth, 159 URI base, 51
Thompson, Ken, 46, 487 OmniAuth, 163 URI completo, 52
Through, asociaciones, 169, 169 programación en pareja, 367 URI parcial, 52
Throughput
URL (Uniform Resource Locator)
responsividad, 435 UML (Unified Modeling Language) AJAX XHR, 207
Tiempo medio de reparación, ver ActiveRecord vs. DataMapper, comparación con URI, 52
MTTR (Mean Time To Repair) 169 Jasmine, 218
Tiempo medio entre fallos, ver asociaciones, 165
MTTF (Mean Time To Failure decisiones de uso, 403
Time (clase) diagrama de casos de uso, 243 Validación
aritméticas, operaciones, 92 diagrama de clases, 401, 406, 411 asociaciones, 171
ejemplo de aplicación Rails, 121 exceso de confianza en, 424 BDD, 241
operaciones aritméticas, 92, 94 exploración del código heredado, calidad del software, 26
333, 333 ciclos de vida clásicos, 382
tipado dinámico, 100
resumen, 401 IEEE 1012-2012, 382
timeout, función manejadora, 224
UMPLE, 403 interacciones de controlador/vista,
Timeouts, protección del
Unión natural, claves foráneas, 166 155
rendimiento, 465
Unitarias, pruebas predefinida, ActiveModel, 153
TimeSetter, 338, 348
comparación con pruebas de inte- sustitución de acciones, 153
Tipado dinámico
gración, 307 Validación, pruebas
clase Time, 100
comparación de métodos de Cucumber, 253
composición/delegación, 411 requisitos explícitos frente a im-
Lisp, 99 prueba, 311
Cucumber, 255 plícitos, 262
objetos Ruby, 84 software, 27
errores, 376
patrón Abstract Factory, 406 var, JavaScript, 228
pruebas de caracterización, 337
reutilización, 30 Variable, nombrado
software, 27
sintaxis del patrón de diseño, 421 pautas, 343
Unix
visión general, 99 creadores, 46 problemas, 104
TLS (Transport Layer Security), 456 habilidades para el curso, 486 Variables de clase
TMDb, ver The Open Movie herramientas en Mac OS X/Win- cohesión, 404
Database (TMDb) dows, 490 Variables de entorno, despliegue en
Token de acceso, autenticación, 160 origen de Linux, 485 la nube con Heroku, 495
Tom Knight and the Lisp Machine, representación del tiempo, 92 Variables de instancia
131 update cohesión, 404
Top-down, integración, ver Inte- ejemplo de aplicación Rails, 140, VBScript, 458
gración descendente 140 VCR
Torvalds, Linus, 486, 489 eventos personalizados en gema, 305
Tracked files, ver Ficheros bajo JavaScript, 205 VCS (Version Control System)
seguimiento sustitución por, 153 automatización, 30
Transacción de base de datos, spec update_attributes fundamentos Git, 489
run in, 299 , validación, 153 primeros VCS, 368
Transferencia de estado representa- ejemplo de aplicación Rails, 121 Velocidad
cional, ver REST (Representational validación, 153 automatización, 30
State Transfer) URI (Uniform Resource Identifier) ciclo de vida ágil, 13
ÍNDICE 521