La empresa DataCorp Inc. había conseguido lo que representaba uno de sus contratos más importantes. El sistema de ventas para la importante compañía regional SalesTales había sido firmado y había que entregarlo en poco menos de cuatro meses. El equipo de ingenieros a cargo del desarrollo, había establecido que se usaría Ruby on Rails como tecnología que atendería a los requerimientos necesarios de forma ágil y eficiente. Cinco meses después, luego de la entrega del producto, saltaron todas las alarmas en la compañía: lo que parecía un contrato importante se convirtió en una pesadilla importante…
Los problemas se presentaron cuando por algún error que el equipo desconocía – y no había probado – dos usuarios con el mismo correo pudieron estar registrados en la base de datos, violando el principio de unicidad que estos casos requieren. Al parecer según pudieron rastrear, cuando un cliente nuevo intentó registrarse, luego de no obtener respuesta instantánea, realizó varios clics ocasionando que el formulario fuese enviado dos veces, y así: ejecutar dos registros con la misma identidad. Las pérdidas se calcularon en aproximadamente 300.000 euros. Y a pesar de que el problema se logro corregir a tiempo, SalesTale perdió una venta importante, y peor aún: un cliente importante. DataCorp tuvo que asumir las pérdidas en conjunto, y lógicamente corregir los fallos y garantizar que no volviesen a ocurrir. Pero de todo fallo, se aprende.
Los fallos
Durante el proceso de desarrollo se cometieron diversos fallos: falta de diseño eficaz de pruebas que comprobasen elementos críticos para el desarrollo de negocio, puesta en producción discontinúa y apurada sin pruebas preliminares por parte del equipo de desarrollo en conjunto con el equipo de negocios del cliente, y falta de documentación acerca del framework que estaban utilizando. En este artículo nos centraremos en la última de las fallas identificadas. El principal problema se presentó al definir un modelo de ActiveRecord de Ruby on Rails de la siguiente forma:
class User < ActiveRecord::Base . . . validates :email, presence: true, format: { with: VALID_EMAIL_REGEX }, uniqueness: { case_sensitive: false } end
Específicamente en la validación:
uniqueness: { case_sensitive: false }
La documentación de Rails es muy clara acerca de este apartado de unicidad[1]:
Este asistente confirma que el valor del atributo es único justo antes de que el objeto se guarde. No crea una restricción de unicidad en la base de datos, por lo que puede ocurrir que dos conexiones de bases de datos diferentes crean dos registros con el mismo valor para una columna que va a ser única. Para evitar esto, es necesario crear un índice único en la base de datos.
Rails suele hacer cosas «mágicas», por lo que no hubiese sido irrazonable suponer que esa validación sería suficiente para garantizar la unicidad de los datos. Lamentablemente el equipo de desarrollo se confió y no leyó la documentación.
Las soluciones
La solución, tras leer la documentación consiste en añadir un índice a la base de datos con el atributo que queremos hacer único. Por lo que simplemente bastaría con:
class AddIndexToUsersEmail < ActiveRecord::Migration def change add_index :users, :email, unique: true end end
Pero tras este importante fallo, es importante además aprender de los errores de fondo y no solo de forma que participaron en el contexto del inconveniente. En ese sentido, una buena prueba antes de diseñar la funcionalidad – por ejemplo si aplicamos TDD – hubiese dado cuenta del problema a muy temprana etapa sin ocasionar costos considerables:
describe "when email address is already taken" do before do user_with_same_email = @user.dup user_with_same_email.save end it { should_not be_valid } end
Estas técnicas en conjunto con puestas en producción tempranas si es posible y el proyecto lo amerita – es esencial aplicar metodologías ágiles no solo de fotografía sino también de radiografía – pueden reducir significativamente los errores que luego como en esta historia pueden resultar costosos.
Aproximaciones finales
¿Son los frameworks un posible peligro potencial? , depende, pese a que generalizar es equivocarse siempre, existen casos como el presentado anteriormente donde los inconvenientes sobrepasan a los beneficios. Esto no debe interpretarse como un error en los marcos de trabajo ni se debe radicalizar en favor de su desuso; todo lo contrario: los marcos de trabajo existen para facilitarnos la vida y para automatizar tareas, que de otra forma habría que repetir siempre y generaría mayores costes de desarrollo en los proyectos. Pero este conjunto de beneficios vienen con una responsabilidad implícita: debemos asumir el compromiso de ser responsable por cada pieza de código en un desarrollo, sean nuestros o de terceros. Por ello es importante documentarnos acerca de lo que usamos y como impacta en nuestro sistema,y de acompañar el uso de frameworks con técnicas de test y metodologías de trabajo adecuadas. No se debe olvidar que la finalidad es maximizar el valor entregado con el menor coste posible. Pero este menor coste posible no solo debe ser durante el desarrollo, debe ser a largo plazo: durante la vida útil de un software que no se desgasta, se deteriora.
Referencias
[1] http://edgeguides.rubyonrails.org/active_record_validations.html
Idea tomada para construir el caso de : railstutorials.org