Von der Idee zum Code: Der erste Schritt in der Softwareauslieferung

Jede Funktion beginnt auf die gleiche Weise: Jemand hat eine Idee, das Team stimmt zu, dass es sich lohnt, sie umzusetzen, und ein Entwickler öffnet seinen Editor, um Code zu schreiben. An diesem Punkt existiert der Code nur auf dem Laptop einer Person. Er läuft in einer Umgebung, die eine Person vollständig kontrolliert.

Der Entwickler tippt, testet, optimiert und beobachtet die Ausgabe auf dem eigenen Bildschirm. Alles funktioniert. Der Button erscheint, wo er sein soll. Die Daten werden korrekt geladen. Die Funktion tut, was sie tun soll.

Das fühlt sich nach Fortschritt an. Und das ist es auch. Aber der Code, der auf einem Laptop läuft, ist noch nicht bereit für die Auslieferung.

Die Falle der lokalen Umgebung

Wenn Code nur auf dem Rechner eines Entwicklers lebt, läuft er in einer Blase. Die installierten Bibliotheken, die Betriebssystemversion, die Datenbankkonfiguration, sogar die Portnummern – all das ist spezifisch für diesen einen Laptop. Ein anderer Entwickler im selben Team könnte Python 3.11 haben, während du 3.10 verwendest. Er könnte PostgreSQL 15 nutzen, während du 14 verwendest. Die Node-Version könnte abweichen. Die Dateipfade sind mit Sicherheit unterschiedlich.

Diese Unterschiede sind während der lokalen Entwicklung unsichtbar. Der Code funktioniert auf deinem Rechner, also nimmst du an, dass er überall funktioniert. Aber sobald jemand anderes versucht, ihn auszuführen, tauchen Probleme auf. Eine Bibliothek wurde nicht installiert. Eine Konfiguration verweist auf einen lokalen Pfad, der auf einem anderen Rechner nicht existiert. Ein Versionskonflikt bei einer Abhängigkeit führt zu einem kryptischen Fehler.

Das ist das erste echte Problem in der Softwareauslieferung: Code, der auf einem Laptop einwandfrei läuft, kann überall sonst scheitern. Und je weiter sich der Code vom Entwicklerrechner entfernt, desto schwieriger wird es, die Ursache zu diagnostizieren.

Code schreiben, der reist

Um Code zu schreiben, der über einen einzelnen Laptop hinausgehen kann, müssen Entwickler mit Disziplin arbeiten. Nicht wegen Regeln oder Prozessen, sondern weil Code, der auf einem Rechner bleibt, für alle anderen nutzlos ist.

Die erste Disziplin ist das Abhängigkeitsmanagement. Jede Bibliothek, jedes Framework und jedes Tool, das der Code benötigt, muss explizit festgehalten werden. Nicht manuell installiert und vergessen. Nicht als vorhanden vorausgesetzt. Festgehalten in einer Konfigurationsdatei, die ein anderer Entwickler oder ein Server lesen und automatisch installieren kann. Wenn der Code eine bestimmte Version einer Bibliothek benötigt, muss diese Version fixiert werden. Wenn ein Systemtool benötigt wird, sollte dieses Tool dokumentiert sein.

Ein Node.js-Projekt deklariert seine Abhängigkeiten beispielsweise in einer package.json-Datei wie folgt:

{
  "name": "my-app",
  "version": "1.0.0",
  "dependencies": {
    "express": "4.18.2",
    "pg": "8.11.3",
    "lodash": "4.17.21"
  },
  "devDependencies": {
    "jest": "29.7.0",
    "eslint": "8.56.0"
  }
}

Die zweite Disziplin ist die Trennung der Konfiguration. Datenbankadressen, API-Schlüssel, Dateipfade und umgebungsspezifische Einstellungen sollten niemals fest in den Quelldateien codiert sein. Sie gehören in Umgebungsvariablen oder Konfigurationsdateien, die zur Laufzeit geladen werden. Auf diese Weise kann derselbe Code ohne Änderungen auf dem Entwicklerlaptop, einem Testserver und in der Produktion laufen. Nur die Konfiguration ändert sich.

Die dritte Disziplin ist das Führen eines Änderungsverlaufs. Jedes Mal, wenn ein Entwickler eine logische Arbeitseinheit abschließt, speichert er diesen Zustand. In der Praxis bedeutet das, einen Commit zu erstellen. Ein Commit ist ein Snapshot von Änderungen an einer oder mehreren Dateien, die eine zusammenhängende Einheit bilden. Einen Speichern-Button hinzufügen? Der Commit sollte die Frontend-Änderung, den Backend-Endpunkt und alle zugehörigen Tests enthalten. Nicht die halbe Arbeit, nicht unzusammenhängende Änderungen gemischt.

Commits erzeugen einen Verlauf. Wenn später Code in der Produktion läuft und etwas kaputt geht, ist dieser Verlauf der erste Ort, an dem man nachsieht. Wer hat was geändert? Wann? Warum? Die Commit-Nachricht sollte diese Fragen beantworten.

Vom Lokalen zum Geteilten

Code in ein lokales Repository zu committen ist eine gute Gewohnheit, aber es reicht nicht. Der Code befindet sich immer noch auf einem Rechner. Wenn dieser Laptop stirbt, ist die Arbeit weg. Wenn ein anderer Entwickler die Änderungen überprüfen muss, kann er sie nicht sehen. Wenn ein automatisiertes System den Code bauen und testen muss, hat es keinen Zugriff.

Der nächste Schritt ist, den Code in ein gemeinsames Repository zu pushen. Dies ist ein zentraler Ort, auf den das Team zugreifen kann. Hier wird Code für andere sichtbar. Hier können automatisierte Tools ihn abholen. Hier beginnt die Reise von der Idee zur laufenden Software wirklich.

Sobald der Code das gemeinsame Repository erreicht hat, kann er überprüft, getestet und zu etwas gebaut werden, das auf einem Server läuft. Aber bevor das passiert, gibt es noch einen weiteren Schritt: Der Code muss überprüft werden. Entweder von einer anderen Person oder von einem automatisierten Tool. Diese Überprüfung fängt Fehler, die der ursprüngliche Entwickler übersehen hat. Sie stellt sicher, dass der Code den Standards des Teams entspricht. Sie bestätigt, dass die Änderung sinnvoll ist, bevor sie die Pipeline weiter durchläuft.

Die Lücke zwischen funktionierendem und auslieferbarem Code

Es gibt einen Unterschied zwischen Code, der auf deinem Rechner funktioniert, und Code, der bereit für die Auslieferung ist. Die Lücke hat nichts mit Können zu tun. Es geht um die Umgebung. Code, der nur auf einem Laptop gelaufen ist, wurde nicht gegen die Realität getestet. Er wurde nicht gegen die gemeinsame Umgebung validiert, in der er letztendlich laufen wird.

Diese Lücke zu schließen, erfordert Gewohnheiten, die sich zunächst nach Mehraufwand anfühlen. Abhängigkeiten aufzeichnen. Konfiguration trennen. Saubere Commits machen. In ein gemeinsames Repository pushen. Jeder Schritt scheint klein, aber zusammen verwandeln sie Code von etwas Persönlichem in etwas, das von jedem im Team gebaut, getestet und bereitgestellt werden kann.

Praktische Checkliste vor dem Pushen von Code

Bevor du deine nächste Änderung in das gemeinsame Repository pushst, gehe diese kurze Checkliste durch:

  • Sind alle Abhängigkeiten in einer Konfigurationsdatei festgehalten, die automatisch installiert werden kann?
  • Sind umgebungsspezifische Werte (Datenbank-URLs, API-Schlüssel, Pfade) vom Code getrennt?
  • Enthält der Commit eine logische Änderung mit allen zugehörigen Dateien?
  • Erklärt die Commit-Nachricht, was geändert wurde und warum?
  • Hast du den Code in einer sauberen Umgebung ausgeführt, die der des Servers entspricht?

Wenn du alle fünf Fragen mit Ja beantworten kannst, ist der Code bereit, deinen Laptop zu verlassen.

Was als Nächstes kommt

Code, der in ein gemeinsames Repository gepusht wurde, ist nicht mehr privat. Er ist für das Team sichtbar und für automatisierte Prozesse verfügbar. Aber es ist immer noch nur Code. Er wurde nicht gegen die reale Umgebung getestet. Er wurde nicht mit anderen Änderungen integriert. Er wurde nicht in ein auslieferbares Artefakt gebaut.

Der nächste Schritt ist zu überprüfen, ob dieser Code tatsächlich funktioniert, wenn er mit allem anderen kombiniert wird. Diese Überprüfung ist der Punkt, an dem Continuous Integration beginnt. Aber davor muss das Fundament solide sein: Code, der mit dem Bewusstsein geschrieben wurde, dass er den Entwicklerrechner verlassen und woanders laufen wird. Dieses Bewusstsein ist der erste wirkliche Schritt zu einer zuverlässigen Softwareauslieferung.