Home

04 Requirements Engineering: Von Tests zu Spezifikationen

lecture requirements specifications stakeholders user-stories agile

1. Einleitung: Ihre Tests sind grün, aber was bauen Sie eigentlich?

Wo Sie bisher stehen:

In Kapitel 03 (Testtheorie und Coverage) haben Sie etwas Unangenehmes entdeckt: Als Sie versucht haben, die Testabdeckung für find_intersection() zu verbessern, sind Sie immer wieder auf Fragen gestoßen wie:

Sie haben erkannt, dass Abdeckungslücken Anforderungslücken offenbaren. Sie können keine guten Tests schreiben, wenn Sie nicht wissen, was die Software tun soll.

Die unbequeme Wahrheit:

In diesem Kurs haben Sie bisher Tests gegen implizite Anforderungen geschrieben - Docstrings, Type Hints, Code-Kommentare und Ihre eigenen Annahmen. Aber echte Softwareprojekte brauchen explizite Anforderungen, auf die sich alle einigen.

Die heutige Frage: Wie erfassen, dokumentieren und validieren wir systematisch, was wir bauen - bevor wir die Tests schreiben?


2. Lernziele

Am Ende dieser Vorlesung werden Sie:

  1. Verstehen, was Anforderungen sind und warum sie für Softwarequalität wichtig sind
  2. Funktionale und nicht-funktionale Anforderungen unterscheiden mit konkreten Beispielen
  3. Stakeholder identifizieren und ihre unterschiedlichen Perspektiven verstehen
  4. Qualitätskriterien anwenden auf Anforderungen (testbar, messbar, eindeutig)
  5. Moderne Werkzeuge nutzen für Anforderungen: User Stories, GitHub Issues, Akzeptanzkriterien
  6. Anforderungen mit Tests verknüpfen für Nachverfolgbarkeit

Fortsetzung in Teil 2:

Was Sie noch NICHT lernen:


3. Anforderungen auf Code-Ebene: Wo Kapitel 03 aufgehört hat

Bevor wir auf Geschäftsanforderungen eingehen, beginnen wir dort, wo wir aufgehört haben - auf Code-Ebene.

3.1 Implizite Anforderungen im Code

In Kapitel 03 (Testtheorie und Coverage) haben Sie diese impliziten Anforderungen in find_intersection() entdeckt:

def find_intersection(
    x_road: NDArray[np.float64],  # Implizit: erwartet numpy Array von Floats
    y_road: NDArray[np.float64],  # Implizit: muss gleiche Länge wie x_road haben
    angle_degrees: float,          # Implizit: wahrscheinlich 0-360, aber nicht erzwungen
    camera_x: float = 0,
    camera_y: float = 1.5,
) -> tuple[float | None, float | None, float | None]:
    """
    Find the intersection point between the camera ray and the road profile.

    Returns:
        tuple of (x, y, distance) or (None, None, None) if no intersection
    """

Was fehlt?

3.2 Design by Contract: Anforderungen explizit machen

Design by Contract (DbC) ist eine Programmiermethodik, bei der Sie explizit angeben:

Beispiel: Explizite Anforderungen für find_intersection() (der Kommentar-Ansatz)

def find_intersection(
    x_road: NDArray[np.float64],
    y_road: NDArray[np.float64],
    angle_degrees: float,
    camera_x: float = 0,
    camera_y: float = 1.5,
) -> tuple[float | None, float | None, float | None]:
    """
    Find the intersection point between the camera ray and the road profile.

    Preconditions (Requirements on input):
        - len(x_road) == len(y_road) >= 2
        - x_road values are monotonically increasing (sorted left to right)
        - angle_degrees is in range [0, 360) degrees
        - camera_y > max(y_road) (camera is above the road)

    Postconditions (Guarantees on output):
        - If intersection exists: returns (x, y, dist) where:
            - x is in range [min(x_road), max(x_road)]
            - y is the interpolated road height at x
            - dist > 0 is the Euclidean distance from camera to intersection
        - If no intersection: returns (None, None, None)

    Special cases:
        - Vertical ray (angle = 90 or 270): returns (None, None, None)
        - All road segments behind camera: returns (None, None, None)
        - Ray parallel to road segment: uses segment start point
    """

Das ist Requirements Engineering auf Funktionsebene.

Jede Vor- und Nachbedingung ist eine testbare Anforderung. Jeder Spezialfall ist eine Äquivalenzklasse, die Sie in Kapitel 03 (Grundlagen des Testens) gelernt haben.

3.3 Das Problem mit Kommentaren: Sie lügen

Wichtiger Hinweis: Der oben gezeigte Docstring-Ansatz ist nicht, wie wir Design by Contract in modernem Code haben wollen. Wir zeigen es, weil:

  1. Sie werden diesem Muster in realen Codebasen begegnen, besonders in älteren
  2. Viele Lehrbücher und Tutorials lehren noch diesen Ansatz
  3. Das Verstehen seiner Probleme hilft Ihnen, bessere Alternativen zu schätzen

Übernimm dieses Muster nicht für neuen Code. Es gibt bessere Wege, wie wir gleich sehen werden.

Der Docstring-Ansatz oben sieht professionell aus, hat aber ernsthafte Probleme:

Problem 1: Kommentare machen Code schwerer lesbar

Jede Funktion beginnt jetzt mit 20+ Zeilen Dokumentation, bevor Sie echten Code sehen. In einer großen Codebasis wird das überwältigend - Sie schwimmen in Kommentaren, nur um zu verstehen, was der Code tut.

Problem 2: Kommentare veralten

Nichts zwingt den Docstring, mit dem Code synchron zu bleiben. Wenn Sie refaktorisieren oder Features hinzufügen, aktualisieren Sie immer den Docstring? Seien Sie ehrlich. Die meisten Entwickler tun es nicht - und jetzt ist Ihr “Vertrag” eine Lüge.

Problem 3: Kommentare verhindern keine Bugs

Der Docstring sagt len(x_road) == len(y_road), aber was passiert, wenn jemand Arrays unterschiedlicher Länge übergibt? Der Code wird irgendwo mit einem verwirrenden Fehler abstürzen, nicht an dem Punkt, wo der Vertrag verletzt wurde.

3.4 Besserer Ansatz: Das Typsystem Anforderungen durchsetzen lassen

Anstatt Anforderungen in Kommentaren zu dokumentieren, können wir sie durch bessere Datenrepräsentation durchsetzen.

Beispiel: Durchsetzen, dass x_road und y_road gleiche Länge haben

Die aktuelle Signatur erlaubt diesen Bug:

# BUG: Arrays haben unterschiedliche Längen - aber das Typsystem erlaubt es!
find_intersection(
    x_road=np.array([0, 10, 20]),
    y_road=np.array([0, 5])  # Oops, ein Element fehlt
)

Besseres Design: Verwende ein einzelnes Array von (x, y) Tupeln

import numpy as np
from numpy.typing import NDArray

# Straßenprofil ist ein Nx2 Array, wobei jede Zeile (x, y) ist
# x und y sind inhärent verknüpft - unmöglich, nicht übereinstimmende Längen zu haben!
RoadProfile = NDArray[np.float64]  # Shape: (N, 2)

def find_intersection(
    road: RoadProfile,  # Shape (N, 2) - jede Zeile ist (x, y)
    angle_degrees: float,
    camera_x: float = 0,
    camera_y: float = 1.5,
) -> tuple[float | None, float | None, float | None]:
    # Zugriff auf Punkte als road[i, 0] für x und road[i, 1] für y
    # Oder entpacken: x, y = road[i]
    ...

# Verwendung:
road = np.array([
    [0.0, 0.0],    # Punkt 1: x=0, y=0
    [10.0, 5.0],   # Punkt 2: x=10, y=5
    [20.0, 10.0],  # Punkt 3: x=20, y=10
])

Jetzt ist die Anforderung “x und y müssen gleiche Länge haben” unmöglich zu verletzen - jede Zeile enthält beide Koordinaten zusammen.

Beispiel: Gültige Winkelbereiche durchsetzen

Die aktuelle Signatur erlaubt diesen Bug:

# BUG: Was bedeutet angle=500 überhaupt? Ist es 500° oder 140° (500 mod 360)?
find_intersection(x_road, y_road, angle_degrees=500)

Besseres Design: Erstelle eine Angle-Klasse

from dataclasses import dataclass
import math

@dataclass
class Angle:
    """An angle in degrees, normalized to [0, 360)."""
    _degrees: float

    def __init__(self, degrees: float):
        # Normalize to [0, 360) on construction
        self._degrees = degrees % 360

    @property
    def degrees(self) -> float:
        return self._degrees

    @property
    def radians(self) -> float:
        return math.radians(self._degrees)

    def is_vertical(self) -> bool:
        """Returns True if angle is approximately 90° or 270°."""
        return abs(math.cos(self.radians)) < 1e-10

# Jetzt erzwingt die Funktionssignatur gültige Winkel:
def find_intersection(
    road: RoadProfile,
    angle: Angle,  # Immer normalisiert, hat is_vertical() Methode
    camera_x: float = 0,
    camera_y: float = 1.5,
) -> tuple[float | None, float | None, float | None]:
    if angle.is_vertical():
        return None, None, None
    ...

Was hat sich geändert?

3.5 Laufzeitprüfungen mit Asserts (Selten die richtige Wahl)

Einige Entwickler verwenden assert-Anweisungen, um Anforderungen zur Laufzeit zu prüfen. Sie werden das in manchen Codebasen sehen, also lassen Sie es uns verstehen - aber das ist selten der richtige Ansatz für Produktionscode:

def find_intersection(
    x_road: NDArray[np.float64],
    y_road: NDArray[np.float64],
    angle_degrees: float,
    ...
) -> tuple[float | None, float | None, float | None]:
    # Laufzeit-Anforderungsprüfungen
    assert len(x_road) == len(y_road), "x_road and y_road must have same length"
    assert len(x_road) >= 2, "Road must have at least 2 points"
    assert 0 <= angle_degrees < 360, f"Angle must be in [0, 360), got {angle_degrees}"

    # ... eigentliche Implementierung

Vorteile:

Nachteile (und diese sind ernst):

Fazit: Asserts haben einen sehr engen Anwendungsfall - interne Invarianten während Entwicklung und Tests prüfen. Sie sind kein Mechanismus zur Durchsetzung von Anforderungen. Verlassen Sie sich nicht auf sie für Produktionscode. Wenn Sie Eingaben validieren müssen, verwenden Sie richtige Validierung mit sinnvoller Fehlerbehandlung (werfen Sie ValueError mit hilfreicher Nachricht, geben Sie ein Fehlerergebnis zurück, etc.).

3.6 Tests als ausführbare Anforderungen

Hier ist eine Schlüsselerkenntnis, die Anforderungen mit Tests verbindet:

Tests können als ausführbare Dokumentation von Anforderungen dienen.

Anstatt:

Können wir Tests schreiben, die jede Anforderung explizit verifizieren:

import numpy as np
import pytest
from road_profile_viewer.geometry import find_intersection, Angle

class TestFindIntersectionRequirements:
    """Tests documenting the requirements for find_intersection()."""

    def test_req_vertical_ray_returns_none(self):
        """REQ-GEOM-001: Vertical rays shall return None."""
        road = np.array([[0, 0], [10, 5]])
        result = find_intersection(road, Angle(90), camera_x=5, camera_y=10)
        assert result == (None, None, None)

    def test_req_intersection_distance_positive(self):
        """REQ-GEOM-002: When intersection exists, distance shall be > 0."""
        road = np.array([[0, 0], [10, 5], [20, 10]])
        x, y, dist = find_intersection(road, Angle(45), camera_x=0, camera_y=15)
        assert dist is not None
        assert dist > 0

    def test_req_intersection_on_road(self):
        """REQ-GEOM-003: Intersection point shall lie on the road profile."""
        road = np.array([[0, 0], [10, 5], [20, 10]])
        x, y, dist = find_intersection(road, Angle(45), camera_x=0, camera_y=15)
        assert x is not None
        assert 0 <= x <= 20  # Within road x-range

Beachte:

Wir werden das weiter in Abschnitt 8: Anforderungen mit Tests verknüpfen erkunden, wo wir sehen werden, wie man:

Aber zuerst, lass uns herauszoomen und verstehen, was Anforderungen auf Geschäftsebene sind.

3.7 Die Brücke: Von Code-Anforderungen zu Geschäftsanforderungen

Beachte, was passiert ist: Wir begannen mit Code-Abdeckungslücken und endeten mit besseren Datentypen und Tests, die Anforderungen verifizieren.

Der gleiche Prozess passiert auf jeder Ebene:

Code-Abdeckungslücke → Funktionsanforderung → Modulanforderung → Systemanforderung → Geschäftsbedarf

Jetzt lass uns herauszoomen und Anforderungen von oben nach unten betrachten.


4. Was sind Anforderungen?

4.1 Definition

Anforderung: Eine Bedingung oder Fähigkeit, die ein Benutzer benötigt, um ein Problem zu lösen oder ein Ziel zu erreichen. — IEEE Standard 610.12

Praktischer ausgedrückt:

Eine Anforderung beschreibt was das System tun soll (oder wie gut es es tun soll), ohne zu spezifizieren wie es implementiert werden soll.

4.2 Funktionale vs Nicht-Funktionale Anforderungen

Typ Definition Beispiel (Road Profile Viewer)
Funktional Was das System tut - spezifische Verhaltensweisen und Funktionen "Das System soll den Schnittpunkt anzeigen, wenn der Benutzer auf das Straßenprofil klickt"
Nicht-Funktional Wie gut das System es tut - Qualitätsattribute "Die Schnittpunktberechnung soll in weniger als 100ms abgeschlossen sein"

Kategorien nicht-funktionaler Anforderungen:

4.3 Beispiel: Road Profile Viewer Anforderungen

Funktionale Anforderungen:

Nicht-Funktionale Anforderungen:

4.4 Anforderungs-IDs: Namenskonventionen und ihre Bedeutung

Sie haben vielleicht bemerkt, dass wir hier IDs wie FR-1 und NFR-1 verwenden, aber früher in Abschnitt 3.6 haben wir REQ-GEOM-001 verwendet. Lassen Sie uns das klären.

Warum brauchen Anforderungen IDs?

Gängige ID-Benennungsschemata:

Schema Beispiel Wann verwenden
Typbasiert FR-1, NFR-2 Einfache Projekte, unterscheidet funktional vs nicht-funktional
Domänenbasiert REQ-GEOM-001, REQ-UI-005 Größere Projekte mit mehreren Modulen/Komponenten
Hierarchisch REQ-1.2.3 Anforderungen, die in Unteranforderungen zerlegt werden
Feature-basiert LOAD-001, CALC-002 Organisation nach benutzerorientierten Features

Beide Schemata beziehen sich auf das gleiche Konzept - es sind nur verschiedene Konventionen. FR-4 und REQ-GEOM-001 identifizieren beide eine spezifische Anforderung, die getestet werden kann.

Impliziert die Nummer Priorität oder Reihenfolge?

Nein! Die Nummer ist nur ein Identifikator, kein Prioritätsranking.

Wenn Sie Priorität angeben müssen, fügen Sie sie explizit hinzu:

FR-4 [Priorität: Hoch]: Das System soll den Schnittpunkt berechnen...
FR-5 [Priorität: Niedrig]: Das System soll Ergebnisse nach PDF exportieren...

Oder verwenden Sie ein separates Prioritätsfeld in Ihrem Tracking-System.

4.5 Wo Anforderungen speichern: Formate und Werkzeuge

Anforderungen müssen irgendwo leben. Hier sind die Optionen, von informell bis formal:

Informell (für alles Ernsthafte vermeiden):

Semi-formal (gut für die meisten Projekte):

Formal (regulierte Branchen, große Projekte):

Was sollten Sie verwenden?

Für diesen Kurs und die meisten Softwareprojekte:

  1. Beginne mit GitHub Issues - ein Issue pro Anforderung oder User Story
  2. Verwende Labels zur Kategorisierung: requirement, FR, NFR, priority:high, etc.
  3. Verknüpfen Sie Issues mit PRs - wenn Sie eine Anforderung implementieren, referenzieren Sie das Issue
  4. Verknüpfe Issues mit Tests - erwähne die Anforderungs-ID in Test-Docstrings

Beispiel GitHub Issue für FR-4:

Title: FR-4: Calculate and highlight intersection point

## Description
The system shall calculate and highlight the intersection point
between the camera ray and the road profile.

## Acceptance Criteria
- [ ] Intersection point is calculated correctly
- [ ] Point is visually highlighted on the chart
- [ ] Coordinates are displayed to the user

## Priority
High - Core functionality

## Related
- Implements: REQ-GEOM-001, REQ-GEOM-002, REQ-GEOM-003
- Tests: test_geometry.py::TestFindIntersectionRequirements

Wir werden diesen Workflow detaillierter in Abschnitt 8: Anforderungen mit Tests verknüpfen erkunden.


5. Stakeholder: Wer kümmert sich um Ihre Software?

5.1 Was ist ein Stakeholder?

Stakeholder: Jede Person oder Organisation, die ein Interesse an dem System hat oder davon betroffen ist.

Verschiedene Stakeholder haben unterschiedliche - manchmal widersprüchliche - Anforderungen.

5.2 Arten von Stakeholdern

graph TD
    System[Software System] --- Users
    System --- Customers
    System --- Dev[Developers]
    System --- Ops[Operations]
    System --- Business
    System --- Reg[Regulators]

    Users --> EndUser[End Users]
    Users --> Admin[Administrators]

    Customers --> Buyer[Purchaser]
    Customers --> Sponsor[Project Sponsor]

    Dev --> Architect[Architect]
    Dev --> Programmer[Programmer]
    Dev --> Tester[QA/Tester]

    Business --> PM[Product Manager]
    Business --> Marketing
    Business --> Support

Wichtig: Dieses Diagramm ist nicht vollständig. Die Stakeholder-Rollen für Ihr Projekt hängen vollständig von der Domäne, dem Umfang und dem regulatorischen Umfeld ab.

Stakeholder-Komplexität variiert dramatisch nach Projekttyp:

Projekttyp Typische Stakeholder Warum?
Medizinprodukte-Software Endbenutzer, Patienten, Ärzte, Krankenhaus-IT, Klinische Forscher, FDA/Regulierungsbehörden, Qualitätssicherung, Risikomanagement, Cybersicherheitsbeauftragte, Datenschutzbeauftragte, Versicherungen, Rechts-/Compliance Leben auf dem Spiel → starke Regulierung (FDA, IEC 62304), Audit-Anforderungen, Haftungsbedenken
Kurzes Videospiel (Indie) Spieler, Entwickler, Publisher (falls vorhanden), Plattform (Steam, etc.) Unterhaltungsfokus, minimale Regulierung, kleinerer Umfang
Banking-Anwendung Kunden, Bankmitarbeiter, Regulierer (BaFin, EZB), Sicherheitsbeauftragte, Betrugserkennung, Compliance-Beauftragte, Prüfer Finanzregulierung, Sicherheitsanforderungen, Audit-Trails verpflichtend

Ihre erste Aufgabe in jedem Projekt: Identifizieren Sie wer ein Interesse an Ihrem System hat. Einen Stakeholder zu übersehen bedeutet, seine Anforderungen zu übersehen - was Sie schmerzhaft spät entdecken werden.

5.3 Stakeholder-Matrix: Road Profile Viewer

Stakeholder Rolle Hauptanliegen Beispielanforderung
Straßeningenieur Endbenutzer Genauigkeit, Benutzerfreundlichkeit "Schnittpunktgenauigkeit innerhalb 0,1m"
Laborleiter Kunde Kosten, Schulungszeit "In unter 1 Stunde erlernbar"
IT-Administrator Betrieb Installation, Wartung "Einzeldatei-Deployment"
Entwickler Intern Codequalität, Testbarkeit "Modulare Architektur für Tests"
Sicherheitsbeauftragter Regulierer Zuverlässigkeit, Audit-Trail "Alle Berechnungen protokolliert"

Schlüsselerkenntnis: Das gleiche Feature sieht für verschiedene Stakeholder unterschiedlich aus. Die “Schnittpunktberechnung” ist:


6. Was macht eine gute Anforderung aus?

Nicht alle Anforderungen sind gleich. Eine schlechte Anforderung führt zu:

6.1 Die INVEST-Kriterien (für User Stories)

Buchstabe Kriterium Beschreibung Schlechtes Beispiel Gutes Beispiel
I Independent Kann implementiert werden, ohne von anderen Stories abzuhängen "Nach Login zeige Dashboard" "Zeige Dashboard für authentifizierte Benutzer"
N Negotiable Details können diskutiert werden, nicht in Stein gemeißelt "Verwende Blau #0000FF für Buttons" "Buttons sollten visuell hervorstechen"
V Valuable Liefert Wert für Stakeholder "Refaktoriere Datenbankschicht" "Lade Profile 50% schneller"
E Estimable Team kann Aufwand schätzen "Mache das System besser" "Füge PDF-Export hinzu"
S Small Kann in einem Sprint abgeschlossen werden "Implementiere alle Berichte" "Generiere Einzelprofil-Bericht"
T Testable Klare Kriterien für "fertig" "System sollte schnell sein" "Antwortzeit < 200ms"

6.2 Die Testbarkeits-Verbindung

Erinnern Sie sich an Kapitel 03 (Grundlagen des Testens)-7? Das “Testable”-Kriterium ist, wo Requirements Engineering auf Testing trifft:

Nicht testbare Anforderung:

“Das System sollte benutzerfreundlich sein”

Wie schreiben Sie einen Test dafür? Was assertet der Test? “Benutzerfreundlich” bedeutet für verschiedene Personen verschiedene Dinge.

Testbare Anforderung:

“Das System soll ein Straßenprofil laden und einen Schnittpunkt in unter 2 Sekunden berechnen”

Jetzt können Sie einen Test schreiben:

def test_load_and_calculate_performance():
    """
    Performance test: Core workflow completes within time limit.

    Requirement: NFR-PERF-001
    """
    start_time = time.time()

    app.load_profile("sample_road.csv")
    app.set_camera_angle(45.0)
    result = app.calculate_intersection()

    elapsed = time.time() - start_time
    assert elapsed < 2.0, f"Workflow took {elapsed:.2f}s, exceeds 2 second limit"
    assert result is not None, "Calculation should return a result"

Die Schlüsselerkenntnis: Wenn Sie keinen Test dafür schreiben können, ist es keine gute Anforderung. Vage Anforderungen wie “benutzerfreundlich” müssen in spezifische, messbare Kriterien verfeinert werden, bevor sie nützlich sind.

6.3 Anforderungen und die Test-Pyramide

6.3.1 Welche Art von Test haben wir gerade geschrieben?

Schau zurück auf den Test, den wir oben geschrieben haben:

def test_load_and_calculate_performance():
    app.load_profile("sample_road.csv")
    app.set_camera_angle(45.0)
    result = app.calculate_intersection()
    assert elapsed < 2.0

Das ist KEIN Unit-Test! Er testet mehrere Komponenten, die zusammenarbeiten:

Erinnern Sie sich an die Test-Pyramide aus Kapitel 03 (Grundlagen des Testens)?

       /   \
      / E2E \       ← Wenige, langsam, teuer
     /-------\
    / Module  \     ← Einige, mittlere Geschwindigkeit  ◄── Unser Test ist HIER
   /-----------\
  /    Unit     \   ← Viele, schnell, günstig
 /---------------\

Unser Performance-Test ist ein Modul-Test (oder Integrationstest) - er testet Komponenten, die zusammenarbeiten. Das wirft eine Frage auf: Wenn Stakeholder-Anforderungen auf Modul/E2E-Ebene getestet werden, wofür sind dann all diese Unit-Tests?

6.3.2 Warum höherstufige Tests für Stakeholder-Anforderungen?

Die Stakeholder-Anforderung war:

“Das System soll ein Straßenprofil laden und einen Schnittpunkt in unter 2 Sekunden berechnen”

Das ist fundamental eine Systemebenen-Anforderung - sie erstreckt sich über mehrere Komponenten. Sie können das nicht mit einem Unit-Test von find_intersection() allein testen, weil:

Das ist warum die Test-Pyramide existiert:

6.3.3 Die Implementierungslücke: Woher abgeleitete Anforderungen kommen

Bei der Implementierung von FR-CALC-001: "Schnittpunkt berechnen und Ergebnis anzeigen" treffen Entwickler Entscheidungen:

  1. Datenrepräsentation: NDArray[(N,2)] oder separate x_road, y_road verwenden?
  2. Algorithmuswahl: Ray-Casting? Interpolation?
  3. Edge-Case-Behandlung: Was ist mit vertikalen Strahlen? Kamera unter der Straße?
  4. API-Design: Angle-Klasse oder rohe Floats verwenden?

Den Kunden kümmert es nicht, welchen Ansatz Sie wählen - beide können FR-CALC-001 erfüllen.

Aber hier ist der Haken: Diese Entscheidungen hängen oft implizit von Anforderungen ab - besonders nicht-funktionalen. Betrachte:

Die expliziten Anforderungen des Kunden (“Schnittpunkt berechnen”) diktieren diese Entscheidungen nicht, aber ihre impliziten Anforderungen (schnell, speichereffizient, wartbar) tun es.

6.3.3.1 Design Decision Documents (DDDs)

Wenn Sie signifikante Implementierungsentscheidungen treffen, sollten Sie sie dokumentieren. Ein Design Decision Document (oder Architectural Decision Record, ADR) erfasst:

  1. Kontext: Welches Problem lösen wir?
  2. Entscheidung: Was haben wir gewählt und warum?
  3. Konsequenzen: Was sind die Trade-offs?
  4. Betrachtete Alternativen: Was haben wir sonst evaluiert?

Warum Entscheidungen dokumentieren?

Wo DDDs speichern:

Option 1: GitHub Issues (empfohlen für diskussionsintensive Entscheidungen)

Beispiel GitHub Issue:

Title: DDD-001: Ray-casting algorithm for intersection calculation

## Context
FR-CALC-001 requires calculating intersection between camera ray and road profile.
NFR-PERF-001 requires < 100ms response time.

## Decision
Use ray-casting with early exit when intersection found.

## Rationale
- Benchmarked at 15ms for 10,000-point profiles (meets NFR-PERF-001)
- Early exit optimization reduces average case to O(n/2)
- Well-documented algorithm, easy to test

## Alternatives Considered
- **Interpolation search**: Faster for sorted data but complex edge cases
- **Binary search on segments**: Requires preprocessing, adds complexity

## Consequences
- Vertical rays (90°, 270°) require special handling → REQ-GEOM-001
- Performance degrades linearly with profile size

## Related
- Implements: FR-CALC-001, constrained by NFR-PERF-001
- Derived requirements: REQ-GEOM-001, REQ-GEOM-002

Option 2: Markdown-Dateien im Repository (empfohlen für stabile, genehmigte Entscheidungen)

Praktischer Workflow:

  1. Öffne ein GitHub Issue, um die Entscheidung mit dem Team zu diskutieren
  2. Wenn Konsens erreicht ist, erstelle eine Markdown-Datei, um sie zu dokumentieren
  3. Referenziere das Issue in der Markdown-Datei für die Diskussionshistorie
  4. Verknüpfe das Entscheidungsdokument vom relevanten Code (Docstrings, Kommentare)

Aber sobald Sie entscheiden, erstellen Sie neue Anforderungen, die Ihr Code erfüllen muss. Diese werden abgeleitete Anforderungen genannt.

6.3.4 Abgeleitete Anforderungen: Die offizielle Definition

Der Begriff “abgeleitete Anforderung” ist in der Industrie standardisiert:

Abgeleitete Anforderung (ISO/IEC/IEEE 29148:2018): “Eine Anforderung, die aus der Sammlung und Organisation von Anforderungen in eine bestimmte Systemhierarchie abgeleitet oder gefolgert wird.”

Der Standard verwendet Eltern/Kind-Terminologie:

NASAs Systems Engineering Handbook bietet eine praktische Definition:

“Abgeleitete Anforderungen entstehen aus Einschränkungen, Berücksichtigung von Themen, die impliziert aber nicht explizit in der High-Level-Richtung angegeben sind, oder Faktoren, die durch die gewählte Architektur und das Design eingeführt werden.”

NASA verwendet auch den Begriff selbst-abgeleitete Anforderungen für Anforderungen, die rein aus Designentscheidungen entstehen - genau das, was wir hier diskutieren.

6.3.5 Vollständiges durchgearbeitetes Beispiel: Vom Stakeholder zum Unit-Test

Lass uns die vollständige Kette verfolgen:

Elternanforderung (Stakeholder):

FR-CALC-001: Benutzer soll Schnittpunkt auf Straßenprofil-Diagramm angezeigt sehen

Modul-Test (Testet die Stakeholder-Anforderung):

def test_fr_calc_001_intersection_displayed():
    """
    Acceptance test for FR-CALC-001.
    Tests the full workflow from user perspective.
    """
    app = RoadProfileViewer()
    app.load_profile("test_road.csv")
    app.set_camera_angle(45.0)

    result = app.calculate_and_display()

    assert result.intersection_point is not None
    assert result.chart_shows_marker == True

Implementierungsentscheidung:

“Wir implementieren find_intersection() mit Ray-Casting und numpy Arrays. Vertikale Strahlen sind undefiniert und sollen None zurückgeben.”

Abgeleitete Anforderungen (aus dieser Entscheidung):

REQ-GEOM-001: find_intersection() soll (None, None, None) für vertikale Strahlen (90°, 270°) zurückgeben
REQ-GEOM-002: Wenn Schnittpunkt existiert, soll zurückgegebene Entfernung > 0 sein
REQ-GEOM-003: Zurückgegebener Schnittpunkt soll innerhalb des Straßen-x-Bereichs liegen
REQ-GEOM-004: Funktion soll Straßenprofil als NDArray mit Shape (N, 2) akzeptieren

Unit-Tests (Testen die abgeleiteten Anforderungen):

class TestFindIntersectionDerivedRequirements:
    """Unit tests for derived requirements of find_intersection()."""

    def test_req_geom_001_vertical_ray(self):
        """REQ-GEOM-001: Vertical rays return None."""
        road = np.array([[0, 0], [10, 5]])
        result = find_intersection(road, Angle(90), camera_x=5, camera_y=10)
        assert result == (None, None, None)

    def test_req_geom_002_positive_distance(self):
        """REQ-GEOM-002: Distance is positive when intersection exists."""
        road = np.array([[0, 0], [10, 5], [20, 10]])
        x, y, dist = find_intersection(road, Angle(45), camera_x=0, camera_y=15)
        assert dist is not None and dist > 0

    def test_req_geom_003_on_road(self):
        """REQ-GEOM-003: Intersection lies within road bounds."""
        road = np.array([[0, 0], [10, 5], [20, 10]])
        x, y, dist = find_intersection(road, Angle(45), camera_x=0, camera_y=15)
        assert 0 <= x <= 20

Die Nachverfolgungskette:

FR-CALC-001 (Stakeholder)
    │
    ├──► test_fr_calc_001_intersection_displayed() [Modul-Test]
    │
    └──► find_intersection() [Implementierung]
              │
              ├──► REQ-GEOM-001 ──► test_req_geom_001_vertical_ray() [Unit-Test]
              ├──► REQ-GEOM-002 ──► test_req_geom_002_positive_distance() [Unit-Test]
              └──► REQ-GEOM-003 ──► test_req_geom_003_on_road() [Unit-Test]

6.3.6 Wo abgeleitete Anforderungen dokumentieren?

Anforderungstyp Zielgruppe Wo dokumentieren
Stakeholder-Anforderungen Alle GitHub Issues, User Stories
Abgeleitete Anforderungen Entwickler Code (Docstrings), Test-Docstrings, interne Docs

Für diesen Kurs, der pragmatische Ansatz:

  1. Stakeholder-Anforderungen → GitHub Issues mit FR-/NFR- IDs
  2. Abgeleitete Anforderungen → Im Code dokumentieren:
    • Funktions-Docstrings (Vor- und Nachbedingungen)
    • Test-Klassen/Methoden-Docstrings
    • Interne REQ-* Kommentare in Tests

Warum NICHT abgeleitete Anforderungen in GitHub Issues?

6.3.7 Die Test-Pyramide ergibt jetzt Sinn

Test-Ebene Was sie testet Anforderungstyp Ausführungshäufigkeit
E2E Vollständige Benutzer-Workflows Stakeholder (FR-*) Vor Release
Modul Komponenten-Integration Stakeholder (FR-*, NFR-*) Bei PR-Merge
Unit Implementierungskorrektheit Abgeleitet (REQ-*) Jeder Commit

Die Erkenntnis:

Unit-Tests testen nicht direkt Stakeholder-Anforderungen - sie testen, dass unsere Implementierung dieser Anforderungen korrekt ist. Die Modul/E2E-Tests verifizieren die eigentliche Anforderung.

6.3.8 “Verwaiste” Anforderungen vermeiden

NASAs Software Engineering Handbook warnt vor verwaisten Anforderungen - Code oder Tests, die nicht zu einer Elternanforderung zurückverfolgt werden können.

Anzeichen von Waisen:

Prävention: Jede abgeleitete Anforderung sollte zu einer Stakeholder-Anforderung zurückführen. Wenn Sie nicht erklären können, wie test_vertical_ray_returns_none() hilft, ein Stakeholder-Bedürfnis zu erfüllen, hinterfragen Sie, ob Sie es brauchen.

Bidirektionale Nachverfolgbarkeit bedeutet, Sie können verfolgen:

  • Vorwärts: Stakeholder-Anforderung → abgeleitete Anforderungen → Unit-Tests
  • Rückwärts: Unit-Test → abgeleitete Anforderung → Stakeholder-Anforderung

Wenn eine Richtung bricht, haben Sie ein Problem.


7. Werkzeuge zur Anforderungsdokumentation

7.1 Das Spektrum der Formalität

Informell                                                              Formal
   |                                                                     |
   v                                                                     v
E-Mails → Haftnotizen → User Stories → Use Cases → IEEE 830 → Formale Specs (Z)

(IEEE 830 und sein Nachfolger ISO/IEC/IEEE 29148 definieren standardisierte Formate für Anforderungsspezifikationen - siehe Abschnitt 4.5 für Details)

Die meiste moderne Softwareentwicklung verwendet semi-formale Ansätze: strukturiert genug, um klar zu sein, flexibel genug, um sich zu ändern.

7.2 User Stories

Format:

Als ein [Rolle], möchte ich [Feature], damit [Nutzen].

Beispiel:

Als ein Straßeningenieur möchte ich den Schnittpunkt im Diagramm hervorgehoben sehen, damit ich meine Messungen schnell verifizieren kann.

Warum dieses Format funktioniert:

7.3 Akzeptanzkriterien: Given-When-Then

Jede User Story braucht Akzeptanzkriterien - spezifische Bedingungen, die erfüllt sein müssen, damit die Story “fertig” ist.

Format (Gherkin-Syntax):

Given [initial context]
When [action occurs]
Then [expected outcome]

Beispiel:

Feature: Intersection Calculation

  Scenario: Normal intersection with road
    Given a road profile is loaded with points [(0,0), (10,5), (20,10)]
    And the camera is at position (0, 15)
    When I set the viewing angle to 45 degrees
    Then the system should display an intersection point
    And the intersection should be between x=0 and x=20
    And the distance should be greater than 0

  Scenario: Vertical ray (edge case)
    Given a road profile is loaded
    And the camera is at position (5, 15)
    When I set the viewing angle to 90 degrees
    Then the system should display "No intersection"

Das ist direkt testbar! Tools wie pytest-bdd können diese als automatisierte Tests ausführen.

7.4 GitHub Issues als Anforderungen

Moderne Entwicklung verwendet oft GitHub Issues, um Anforderungen zu verfolgen:

## User Story
As a road engineer, I want to export my results to PDF so that I can include them in reports.

## Acceptance Criteria
- [ ] Export button visible on main screen
- [ ] PDF includes road profile chart
- [ ] PDF includes intersection coordinates
- [ ] PDF includes calculation parameters
- [ ] File name defaults to "road_profile_YYYY-MM-DD.pdf"

## Technical Notes
- Use `reportlab` library for PDF generation
- Follow existing chart styling

## Links
- Related to #42 (Export feature epic)
- Blocks #45 (Reporting milestone)

Warum GitHub Issues für Anforderungen hervorragend sind

Erinnern Sie sich an die INVEST-Kriterien aus Abschnitt 6? Begriffe wie “Independent”, “Small” und “Valuable” sind inhärent subjektiv. Was ein Entwickler als “klein genug” betrachtet, könnte einem anderen zu groß erscheinen. Was der Product Owner als “wertvoll” betrachtet, stimmt möglicherweise nicht mit Engineering-Prioritäten überein.

Hier glänzen GitHub Issues - sie sind für kollaborative Diskussion gebaut:

Beispieldiskussion zu einem Issue:

@project-manager: Ich denke, das ist klein genug für eine Woche.

@dev-alice: Eigentlich sind allein die PDF-Generierung 3 Tage. Können wir in “einfacher Export” und “gestylter Export” aufteilen?

@dev-bob: Einverstanden. Außerdem, ist das Datumsformat im Dateinamen testbar? Welche Zeitzone?

@project-manager: Gute Punkte. Akzeptanzkriterien aktualisiert, um UTC anzugeben und das Feature aufzuteilen.

Diese kollaborative Verfeinerung ist genau das, was Sie brauchen für subjektive Kriterien - die Anforderung verbessert sich durch Teamdiskussion, und das Gespräch wird für zukünftige Referenz bewahrt.

Zusätzliche Vorteile:


8. Anforderungen mit Tests verknüpfen: Nachverfolgbarkeit

8.1 Was ist Nachverfolgbarkeit?

Nachverfolgbarkeit: Die Fähigkeit, Anforderungen → Tests → Code in beide Richtungen zu verknüpfen.

Warum es wichtig ist:

8.2 Nachverfolgbarkeit mit pytest implementieren

Marker verwenden, um Tests mit Anforderungen zu verknüpfen:

import pytest

# Define custom markers for requirements
pytestmark = pytest.mark.requirements

@pytest.mark.requirement("FR-4")
def test_intersection_found_for_normal_case():
    """
    Requirement FR-4: The system shall calculate and highlight
    the intersection point between ray and road.
    """
    result = find_intersection(x_road, y_road, angle=45.0, camera_x=0, camera_y=10)
    assert result[0] is not None, "Should find intersection"

@pytest.mark.requirement("FR-4")
@pytest.mark.requirement("NFR-2")
def test_intersection_performance():
    """
    Requirements:
    - FR-4: Calculate intersection
    - NFR-2: Complete within 100ms
    """
    import time
    start = time.time()
    result = find_intersection(x_road, y_road, angle=45.0, camera_x=0, camera_y=10)
    elapsed = (time.time() - start) * 1000

    assert result[0] is not None
    assert elapsed < 100, f"Took {elapsed}ms, exceeds 100ms requirement"

8.3 Anforderungsabdeckung vs Code-Abdeckung

Code-Abdeckung (Kapitel 03 (TDD und CI)-7): Haben wir den gesamten Code ausgeführt?

Anforderungsabdeckung: Haben wir alle Anforderungen getestet?

Metrik Was sie misst Einschränkung
Code-Abdeckung % der Code-Zeilen/Branches, die von Tests ausgeführt werden 100% Abdeckung ≠ alle Anforderungen getestet
Anforderungsabdeckung % der Anforderungen mit mindestens einem Test 100% Abdeckung ≠ Anforderungen korrekt

Sie brauchen beides:


9. Zusammenfassung

Konzept Kernpunkt Verbindung zu Testing
Anforderungen Was das System tun soll (oder wie gut) Tests verifizieren, dass Anforderungen erfüllt sind
Funktional vs Nicht-Funktional Was es tut vs wie gut es es tut Verschiedene Testtypen für jeden
Stakeholder Verschiedene Perspektiven, verschiedene Anforderungen Verschiedene Akzeptanzkriterien
INVEST-Kriterien Gute Anforderungen sind testbar Testbarkeit ermöglicht Testdesign
User Stories Als ein [Rolle], möchte ich [Feature], damit [Nutzen] Akzeptanzkriterien = Testfälle
Nachverfolgbarkeit Anforderungen ↔ Tests ↔ Code Anforderungsabdeckungs-Metrik

Was wir behandelt haben:


10. Was kommt als Nächstes: Teil 2

In Teil 2 werden wir behandeln:

Weiter zu Teil 2 → Requirements Engineering - Von Prozess zu Praxis

© 2026 Dominik Mueller   •  Powered by Soopr   •  Theme  Moonwalk