Softwareentwicklung

Dein Wechsel ins Product Engineering diese 7 Fehler kosten Zeit und Nerven

Backend-Entwickler steigen oft in moderne und effiziente Softwareentwicklung ein, als ginge es um neue Rituale, bessere Tickets und mehr Tools. Meine Gegenposition: Die meisten Teamprobleme entstehen nicht durch zu wenig Methode, sondern durch zu spät sichtbare technische Kosten. Wer zuerst Prozesse optimiert, bezahlt mit Wartezeit, Fehlentscheidungen und wachsender Kopplung.

Die teuerste Verwechslung ist Tempo mit Durchsatz

Moderne Softwareentwicklung: Best Practices fuer Teams überschätzt aus meiner Sicht gemeinsame Rituale, weil ein Backend-Team mit undurchsichtigen Build-, Test- und Deployment-Grenzen auch im besten Daily nur schneller über Blockaden spricht. Das ist eine absichtlich streitbare Position: Ich würde nicht zuerst Scrum, SAFe oder ein Spotify-Modell einführen, weil diese Modelle Koordination sichtbarer machen, aber langsame Feedback-Loops nicht automatisch verkürzen.

Der häufigste Fehler ist, Geschwindigkeit an Aktivität zu messen. Viele Commits, viele Pull Requests und volle Sprint-Boards wirken produktiv, aber sie erhöhen die parallele Arbeit, wenn Reviews, CI und Deployments nicht mithalten. Der Preis ist Warteschlangenzeit: Ein Backend-Change, der technisch 40 Minuten dauert, aber zwei Tage auf Review, CI-Fix und Freigabe wartet, ist kein schneller Change.

Für ein neues Team würde ich zuerst vier Metriken messen: Lead Time for Changes, Deployment Frequency, Change Failure Rate und Failed Deployment Recovery Time, also die DORA-Metriken. Die von Google Cloud/DORA veröffentlichten Vergleichswerte sind keine Zielvorgaben für jedes Team, aber sie zeigen, dass Teams mit kurzen Wiederherstellungszeiten weniger Risiko pro Änderung tragen, weil Fehler schneller isoliert und zurückgerollt werden. Ein sinnvoller Startwert zum Tunen ist eine CI-p95-Dauer unter 10 Minuten, weil längere Pipelines Entwickler dazu verleiten, größere Batches zu bauen. Ein zweiter Richtwert zum Kalibrieren sind Pull Requests unter 300 netto geänderten Zeilen, weil Reviewer sonst eher Syntax scannen als Verhalten prüfen.

Die Werkzeuge sind dabei nicht exotisch: GitHub Actions mit timeout-minutes: 15, GitLab CI mit interruptible: true, Gradle 8.7 mit Build Cache, Maven 3.9 mit Surefire-Forking, JUnit 5.10, Testcontainers 1.19.8 und Docker BuildKit mit DOCKER_BUILDKIT=1 reichen für den Anfang. Der Fehler kostet konkret Zeit: Wenn eine lokal gemessene Pipeline von 8 auf 22 Minuten p95 steigt, erzeugen drei Fix-Versuche pro Ticket bereits rund 66 Minuten reine Wartezeit, bevor fachliche Diskussion überhaupt beginnt.

Ich würde auch keine Team-Auslastung über 80 Prozent planen, weil jedes ungeplante Produktionsproblem dann sofort geplante Arbeit verdrängt. Diese Zahl ist kein Naturgesetz, sondern ein Pufferwert zum Anpassen; sie zwingt das Team, Kapazität für Reviews, Security-Patches und Betriebsfeedback sichtbar zu halten.

Unscharfe Schnittstellen kosten mehr als schlechter Code

Backend-Entwickler unterschätzen beim Einstieg oft, wie teuer unausgesprochene Verträge werden. Ein schlechter Methodenname ist ärgerlich, aber ein nicht versioniertes JSON-Feld in einer öffentlichen API erzeugt Abhängigkeiten in Clients, Tests, Monitoring und Dokumentation. Der Preis ist Migrationsarbeit, weil jede Konsumentenänderung plötzlich zur Koordinationsaufgabe wird.

OpenAPI 3.1 mit JSON Schema 2020-12 ist für HTTP-APIs meistens der bessere Startpunkt, weil Contracts dadurch reviewbar, generierbar und testbar werden. Pact v4 lohnt sich für Consumer-Driven Contracts, wenn mehrere Teams denselben Service konsumieren, weil der Provider dann nicht raten muss, welche Felder tatsächlich genutzt werden. gRPC 1.63 mit Protocol Buffers v3 gewinnt bei internen, stark typisierten Service-zu-Service-Kommunikationen, weil binäre Schemas und Codegenerierung Laufzeitfehler reduzieren. AsyncAPI 3.0 passt zu Kafka 3.7 oder RabbitMQ 3.13, weil Events sonst schnell zu impliziten Datenbanken werden.

Der klassische Fehler lautet: „Wir dokumentieren später.“ Das kostet, weil spätere Dokumentation meist den Ist-Zustand beschreibt, nicht den erlaubten Vertrag. Ein gemessener Grenzwert aus vielen API-Reviews ist banal, aber wirksam: Jede Breaking Change ohne Versionierung sollte als Produktionsrisiko behandelt werden, selbst wenn nur ein internes Team betroffen ist. Wer das übertrieben findet, unterschätzt die Kopplung, weil interne Clients oft weniger resilient gebaut sind als externe.

Die explizite Abwägung zwischen REST mit OpenAPI und gRPC ist wichtiger als die Geschmacksfrage. REST mit OpenAPI gewinnt, wenn externe Clients, Browser, API-Gateways und manuelle Debugbarkeit zählen; der Preis sind mehr Konventionen für Statuscodes, Pagination, Idempotency Keys und Fehlerformate. gRPC gewinnt, wenn niedrige Latenz, bidirektionales Streaming über HTTP/2 und generierter Code wichtiger sind; der Preis sind kompliziertere Browser-Unterstützung, weniger einfache Ad-hoc-Tests mit curl und zusätzliche Gateway-Schichten für öffentliche APIs.

Effiziente Softwareentwicklung: Best Practices fuer Teams würde ich nicht als Plan zur Auslastungsmaximierung verwenden, weil Effizienz im Backend zuerst aus stabilen Verträgen entsteht und nicht aus voller Kalenderbelegung. Ein Team, das OpenAPI-Spezifikationen versioniert, Pact-Tests in CI ausführt und Kafka-Topics mit Schema Registry schützt, wirkt kurzfristig langsamer; es ist mittelfristig schneller, weil weniger Änderungen heimlich Konsumenten brechen.

Observability nach dem Incident ist eine Steuer auf jede Änderung

Viele Teams behandeln Logs, Metriken und Traces als Betriebsaufgabe, bis der erste Incident beweist, dass niemand die Produktionsrealität kennt. Das ist besonders teuer für Backend-Entwickler, weil sie ohne Telemetrie nur zwischen Code-Vermutung, Datenbank-Vermutung und Netzwerk-Vermutung wechseln. Der Preis ist MTTR: Jede Minute ohne Hypothese verlängert die Wiederherstellung.

Ich würde Observability nicht als großes Plattformprogramm starten, weil ein monatelanges Projekt ohne konkrete Fragen meist Dashboards produziert, die niemand im Incident nutzt. Besser ist ein schmaler Pfad: OpenTelemetry 1.32.0 für Traces, Prometheus 2.52 für Metriken, Grafana 11 für Dashboards, Loki 3.0 für Logs und Jaeger 1.57 oder Tempo 2.5 für Trace-Suche. Der zu kalibrierende Sampling-Startwert OTEL_TRACES_SAMPLER_ARG=0.05 bedeutet 5 Prozent der Traces, weil ein neues Team zuerst Muster erkennen muss, ohne Storage-Kosten explodieren zu lassen.

Ein guter Service beantwortet drei Fragen ohne Deploy: Ist die Fehlerrate gestiegen, ist die Latenz gestiegen, und welche Abhängigkeit hat sich verändert? Dafür reichen wenige Standards: RED-Metriken für Request Rate, Errors und Duration; USE-Metriken für Utilization, Saturation und Errors; p95-Latenz statt Durchschnitt, weil Nutzer die langsamen Requests spüren; strukturierte Logs mit Trace-ID, weil grepbare Prosa bei Parallelität versagt.

Ein konkret zu tunender Zielwert kann p95 < 200 ms für einen synchronen Health- oder Lookup-Endpunkt sein, weil ein Backend sonst Lastspitzen zu spät erkennt. Dieser Wert ist kein universelles SLA; er ist ein bewusst enger Smoke-Test, der technische Schulden früh sichtbar macht.

cat > smoke.js <<'EOF'
import http from 'k6/http';
import { check, sleep } from 'k6';
export const options = { vus: 5, duration: '30s', thresholds: { http_req_duration: ['p(95)<200'] } };
export default function () {
  const res = http.get('http://host.docker.internal:8080/health');
  check(res, { 'status 200': r => r.status === 200 });
  sleep(1);
}
EOF
docker run --rm -i grafana/k6:0.49.0 run - < smoke.js

Der Fehler wäre, dieses Skript als Lasttest zu verkaufen, weil fünf virtuelle Nutzer über 30 Sekunden keine Kapazitätsaussage liefern. Sein Wert liegt darin, dass es lokal und in CI dieselbe Frage stellt: Hat der Service unter minimaler paralleler Last ein Latenz- oder Verfügbarkeitsproblem?

Die falsche Plattformentscheidung macht Entwickler zu Teilzeit-SREs

Backend-Teams greifen zu früh zu Kubernetes, weil es wie der Standard für professionelle Softwareentwicklung aussieht. Das kostet, weil Kubernetes 1.30 nicht nur Deployments ausführt, sondern auch neue Fehlerflächen über Ingress, Service Discovery, RBAC, NetworkPolicies, Ressourcenlimits und Rollout-Strategien einführt. Wer diese Fehlerflächen nicht betreiben kann, verschiebt Komplexität aus dem Code in YAML.

Docker Compose v2.27 gewinnt, wenn ein kleines Team eine Postgres-16-Datenbank, Redis 7, einen Worker und eine API reproduzierbar lokal starten will; der Preis ist schwächere Produktionsnähe bei Scheduling, Autoscaling und Netzwerkpolicies. Kubernetes 1.30 gewinnt, wenn mehrere Services unabhängig deployt werden, horizontale Skalierung gebraucht wird und Plattformkompetenz vorhanden ist; der Preis sind Cluster-Betrieb, Helm-Charts, CRD-Versionen und Debugging über mehrere Abstraktionen.

Das ist keine Anti-Kubernetes-Position, weil Kubernetes bei klaren Betriebsanforderungen sehr stark ist. Es ist eine Anti-Autopilot-Position, weil ein neues Backend-Team sonst kubectl describe pod lernt, bevor es seine Failure Modes verstanden hat. Der Kubernetes-Default für Rolling Updates liegt bei maxUnavailable: 25% und maxSurge: 25%; diese vendor-publizierten Defaults sind vernünftig für viele Web-Workloads, aber falsch für Jobs, Singletons oder Services mit teuren Warm-ups.

Ein weiterer teurer Fehler ist, Ressourcenlimits zu raten. Ein JVM-Service mit -XX:MaxRAMPercentage=75 in einem Container mit 512 MiB Limit kann unter Last durch Metaspace, Native Memory oder Thread-Stacks sterben, weil Heap nicht der gesamte Prozessspeicher ist. PostgreSQL 16 mit falsch gesetztem max_connections erzeugt ähnliche Kosten, weil zu viele Verbindungen Speicher binden und zu wenige Verbindungen Warteschlangen vor der Anwendung erzeugen. HikariCP mit maximumPoolSize=10 ist ein zu testender Startwert, nicht eine Best Practice, weil die richtige Poolgröße von Query-Zeit, CPU-Kernen und Datenbanklimits abhängt.

Ich würde keine Microservice-Aufteilung als ersten Modernisierungsschritt wählen, weil getrennte Deployments ohne klare Team- und Datenbesitzgrenzen nur verteilte Transaktionen, verteiltes Debugging und verteilte Schuldzuweisung erzeugen. Ein modularer Monolith mit Spring Boot 3.3, ArchUnit 1.3 und klaren Package-Regeln ist oft die bessere Zwischenform, weil Architekturgrenzen im Code überprüfbar bleiben, ohne Netzwerkfehler einzuführen.

Der erste Schritt muss eine messbare Reibung entfernen

Beginne nicht mit einem neuen Prozessnamen. Wähle morgen einen Service, miss CI-p95, Review-Wartezeit, p95-Latenz und Change-Failure-Rate, und lösche danach genau eine Reibung: einen langsamen Test, einen unversionierten Contract oder ein fehlendes Rollback. Moderne und effiziente Softwareentwicklung wird für Backend-Teams erst real, wenn jede Verbesserung eine beobachtbare Kostenstelle senkt.