Los bugs también enamoran
Cómo un bug en las rutas de nuestra aplicación se convirtió en una feature que los usuarios adoptaron sin saberlo.
Siempre viví en casas viejas y hay algo a lo que le tengo cierto apego culposo: las cosas que dejaron de funcionar como debían y pasaron a funcionar con algún truquito, un parche que funciona como un código secreto entre los que conocemos el artefacto. Puertas y ventanas que a la vista parecen trabadas, pero hay que hacer cierto movimiento y la magia ocurre; cadenas de inodoros que si el botón no se aprieta de cierta manera quedan perdiendo agua; o llaves que solo una persona puede hacer girar. En nuestras aplicaciones sucede lo mismo, pero no es tan placentero. La gente comienza a utilizar algo que funciona, pero tiene un error en la implementación. Entonces cambiarlo se puede volver difícil y hasta peligroso, porque podemos romper algo que muchos usuarios ya están utilizando.
Recientemente, tuvimos un problema así en una de las aplicaciones en las que estábamos trabajando. Los usuarios podían gestionar distintos tipos de documentos y carpetas, donde podían anidar más carpetas y documentos dentro de ellas. Sin embargo, teníamos un error en cómo estábamos gestionando las rutas de los recursos:
GET /documents/:key/editapuntaba a la edición del documentoDELETE /documents/:keyapuntaba a la eliminación del documentoPOST /documents/:keyapuntaba a la creación de un nuevo documento
Pero:
GET /documents/:key, en lugar de ir al documento con el ID:key, buscaba la carpeta con el ID:key
Esto significa que, por algún motivo, habíamos dejado de tener las rutas RESTful adecuadas. La solución era bastante sencilla: simplemente modificamos la forma en la que se puede acceder a una carpeta:
GET /documents/browse/:dirpath
Con este cambio, ya podíamos disponer de las rutas RESTful adecuadas:
GET /documents/:keyapuntaba a la obtención del documento con el ID:key
Hicimos los tests correspondientes (todo funcionaba perfectamente), hicimos el deploy a staging e hicimos QA, y finalmente hicimos el deploy a producción.
Pero como ya les advertí: la gente se enamora de nuestras features, pero también de nuestros bugs y de nuestra deuda técnica. Y los reclamos no tardaron en llegar: mucha gente tenía links a documentos y carpetas con la vieja estructura de rutas, y esos enlaces estaban en documentos importantes como protocolos de operación y procedimientos. Agregando /browse para las carpetas, todos los enlaces de carpetas estaban rotos, ya que ahora trataba de encontrar un documento con el ID :key, pero ese :key era en realidad el nombre de una carpeta.
Para solucionar esto, tuvimos que buscar un cambio que asegurara la compatibilidad con los enlaces antiguos. Una posible solución sería agregar una redirección temporal para los enlaces antiguos que apuntan a carpetas:
GET /documents/:key→/documents/browse/:key
Sin embargo, esto no es tan sencillo y no se ve tan bien en el código, porque puede pasar que el usuario sí esté queriendo acceder a un documento. Entonces podíamos tratar de buscar un documento con ese ID y, si no lo encontramos, redirigir al usuario a la carpeta correspondiente:
GET /documents/:key→/documents/browse/:keysi no se encuentra un documento con ese ID
Esto funciona y es una solución aceptable, aunque requiere dejar algunos comentarios en el código explicando por qué existe esa redirección.
def show
if @document = Document.find_by(id: params[:key])
serve @document
else
redirect_to "/documents/browse/#{params[:key]}"
end
end
Sin embargo, nosotros optamos por otra solución. Como nuestros links antiguos (ahora rotos) apuntaban a carpetas, decidimos que lo mejor era dejar esa ruta como estaba y, en su lugar, acceder al documento con una ruta especial:
GET /documents/view/:key→ Ver documento con ID:keyGET /documents/:key→ Ver carpeta con ID:key
Esto nos permitió mantener las responsabilidades separadas y evitar confusiones en el código. Sin embargo, perdimos la posibilidad de tener un recurso con las rutas RESTful:
resources :documents, except: %i[show index]
get "documents/view/:id" => "documents#show", as: :document
No es una solución tan fea ni un problema tan grave. Y por suerte pudimos resolverlo rápidamente. Pero nos llevamos un aprendizaje importante: cambiar una implementación actual que ya está funcionando y siendo utilizada por los usuarios puede tener consecuencias inesperadas, incluso si no hay ningún error en el código. Sea un bug o deuda técnica, los usuarios ya lo han adoptado sin darse cuenta de que había algo mal, y nosotros tenemos que priorizar siempre su experiencia antes que cualquier otra consideración técnica.