Auch Bugs können geliebt werden

Von
/13.01.26
Bugs can be loved too
Wenn aus Fehlern geliebte Gewohnheiten werden.
Ich habe schon mein ganzes Leben in alten Häusern gewohnt. Dabei habe ich eine fast schon etwas schräge Vorliebe für Dinge entwickelt, die nicht mehr ganz so funktionieren, wie sie eigentlich sollten. Ich meine diese kleinen Macken, für die es einen ganz speziellen Handgriff braucht – eine Art Geheimcode unter denjenigen, die das Objekt kennen. Da gibt es Türen oder Fenster, die auf den ersten Blick klemmen, die man aber nur in einem bestimmten Winkel anheben muss, damit sie aufgleiten. Oder Toilettenspülungen, die ewig weiterlaufen, ausser man drückt den Knopf mit einem ganz gezielten Klick. Sogar Schlüssel, die im Schloss nur von einer einzigen Person gedreht werden können.
In unseren Applikationen passiert oft genau dasselbe, auch wenn es dort deutlich weniger charmant ist. Nutzer gewöhnen sich an Funktionen, die zwar laufen, aber technisch gesehen fehlerhaft implementiert sind. Solche Dinge später zu korrigieren, ist schwierig und oft sogar gefährlich: Wir riskieren dabei, Workflows zu zerstören, auf die sich viele Leute im Alltag verlassen.

Das Problem mit den Routen

Kürzlich hatten wir in einem Projekt genau so einen Fall. In der Applikation können Nutzer verschiedene Dokumente und Ordner verwalten und diese beliebig ineinander verschachteln. Bei der Umsetzung der Resource-Routes hat sich jedoch ein Fehler eingeschlichen:
  • GET /documents/:key/edit führte zur Bearbeitung eines Dokuments.
  • DELETE /documents/:key löschte ein Dokument.
  • POST /documents/:key erstellte ein neues Dokument.
Der Haken an der Sache:
  • GET /documents/:key lieferte – entgegen dem Standard – nicht das Dokument mit der ID :key aus, sondern öffnete den Ordner mit dieser ID :key
Uns war klar: Wir hatten unsere korrekten RESTful-Routes verloren. Die Lösung schien simpel und logisch: Wir passten den Zugriff auf die Ordner an und führten eine neue Struktur ein:
  • GET /documents/browse/:dirpath
Damit war der Weg frei für saubere Standards:
  • GET /documents/:key zeigte nun wie vorgesehen das Dokument mit der ID :key
Wir schrieben die entsprechenden Tests (alles lief perfekt), pushten den Code ins Staging, machten das QA und rollten das Ganze schliesslich auf die Produktion aus.
Aber wie ich schon sagte: Menschen gewöhnen sich nicht nur an die Features, sondern auch an unsere Bugs und Altlasten. Es dauerte nicht lange, bis die ersten Reklamationen eintrudelten. Viele User hatten sich Links zu Dokumenten und Ordnern in der alten Struktur gespeichert – und diese Links standen in wichtigen Betriebsprotokollen und Verfahrensanweisungen. Durch die neue /browse-Struktur für Ordner funktionierten all diese Links nicht mehr. Das System versuchte nun, ein Dokument mit der ID :key zu finden, obwohl der Key in Wahrheit ein Ordnername war.
Wir mussten also eine Lösung finden, die die alten Links weiterhin unterstützt. Eine Idee war ein temporärer Redirect:
  • GET /documents/:key → /documents/browse/:key
Das ist im Code aber nicht besonders elegant, da man nie genau weiss, ob der User nicht doch gerade versucht, ein echtes Dokument aufzurufen. Wir hätten prüfen können, ob ein Dokument mit dieser ID existiert, und nur im Fehlerfall auf den Ordner umleiten:
  def show
  if @document = Document.find_by(id: params[:key])
    serve @document
  else
    # Redirect the user to the corresponding folder if no document with that ID is found
    # This is necessary because old links may point to folders instead of documents
    redirect_to "/documents/browse/#{params[:key]}"
  end
end
Das würde zwar funktionieren, braucht aber Kommentare im Code, um dieses seltsame Verhalten zu rechtfertigen. Am Ende haben wir uns für einen anderen Weg entschieden. Da die alten (nun kaputten) Links ohnehin auf Ordner verweisen, liessen wir diese Route einfach so, wie sie war. Dokumente sprechen wir stattdessen nun über eine dedizierte Route an:
  • GET /documents/view/:key → Dokumente anzeigen mit ID :key
  • GET /documents/:key → Ordner anzeigen mit ID :key
So konnten wir die Verantwortlichkeiten im Code sauber trennen, auch wenn wir dafür die "reine Lehre" der RESTful-Routes opfern mussten.
Es ist vielleicht keine technisch perfekte Lösung, aber sie hat das Problem schnell und unkompliziert behoben. Was wir daraus gelernt haben? Eine bestehende Implementierung zu ändern, die bereits aktiv genutzt wird, kann fatale Folgen haben – selbst wenn der ursprüngliche Code fehlerhaft war. Ob Bug oder Technical Debt: Wenn die Nutzer einen Workflow adoptiert haben, steht deren Erfahrung für uns immer über technischer Perfektion.