Softwarequalität, Testen, Entwurfsmuster und Benutzungsoberflächen
Nach einem Update stürzt die Bank-App bei manchen Nutzern ab, und eine Fahrplan-App zeigt Züge, die es nicht gibt. In beiden Fällen lief das Programm, nur nicht so, wie man es braucht. Wann ist Software also gut? Wie findet man Fehler, bevor Nutzer sie finden? Und wie baut man ein Programm, das man später noch ändern kann, ohne dass es auseinanderfällt? Darum geht es hier.
Softwarequalität, Gütekriterien und Verantwortlichkeit
Softwarequalität ist, wie gut eine Software ihre Anforderungen erfüllt. Gebräuchliche Gütekriterien:
- Korrektheit: Sie tut, was verlangt wurde.
- Zuverlässigkeit: Sie kommt mit Fehleingaben und Ausfällen zurecht (Robustheit).
- Effizienz: Sie braucht angemessen wenig Zeit und Speicher.
- Sicherheit: Sie schützt Daten und widersteht Angriffen.
- Benutzbarkeit: Nutzer erreichen ihre Ziele leicht.
- Wartbarkeit: Sie lässt sich verstehen, ändern und erweitern.
- Portabilität: Sie läuft auf verschiedenen Systemen.
Die Kriterien können einander widersprechen, etwa Effizienz und Wartbarkeit. Wer Software entwickelt, trägt Verantwortung für ihre Folgen: Fehler in Fahrzeugen, Medizingeräten oder Banken können Menschen gefährden oder Geld kosten. Dazu gehören Qualitätssicherung, offener Umgang mit Risiken und der Schutz personenbezogener Daten.
Softwareergonomie, Benutzungsfreundlichkeit und Barrierefreiheit
Softwareergonomie gestaltet Oberflächen so, dass sie zu Menschen und Aufgaben passen. Benutzungsfreundlichkeit heißt: Nutzer erreichen ihre Ziele wirksam, effizient und zufrieden. Grundsätze der Dialoggestaltung (aus der Norm ISO 9241-110) sind unter anderem:
- Aufgabenangemessenheit: Die Software unterstützt die Aufgabe ohne unnötigen Aufwand.
- Selbstbeschreibungsfähigkeit: Nutzer erkennen, wo sie sind und was als Nächstes möglich ist.
- Erwartungskonformität: Die Software verhält sich so, wie man es von ähnlichen Programmen gewohnt ist.
- Steuerbarkeit: Nutzer bestimmen Tempo und können abbrechen oder zurückgehen.
- Fehlertoleranz: Fehleingaben richten keinen Schaden an, und Meldungen sagen, wie man den Fehler behebt.
Barrierefreiheit heißt, dass auch Menschen mit Einschränkungen die Software nutzen können, etwa bei Blindheit, Farbsehschwäche, Schwerhörigkeit oder eingeschränkter Beweglichkeit. Dazu dienen Alternativtexte für Bilder (für Screenreader), ausreichender Kontrast, Bedienung allein mit der Tastatur, skalierbare Schrift, Untertitel und der Grundsatz, Informationen nie nur durch Farbe zu zeigen. Für Webseiten gibt es dazu die Web Content Accessibility Guidelines (WCAG).
Teststrategien, Unit- und Integrationstests, testgetriebene Entwicklung
Ein Test führt die Software mit gewählten Eingaben aus und vergleicht das Ergebnis mit dem erwarteten. Tests können Fehler finden, aber nicht beweisen, dass keine mehr vorhanden sind. Man testet in Stufen:
- Unit-Test: prüft eine kleine Einheit, etwa eine Funktion oder Klasse, für sich allein.
- Integrationstest: prüft, ob Einheiten über ihre Schnittstellen zusammenarbeiten.
- Systemtest: prüft das ganze System gegen die Anforderungen.
Testfälle wählt man systematisch. Eine Äquivalenzklasse fasst Eingaben zusammen, die gleich behandelt werden, ein Vertreter genügt. Grenzwerte liegen an den Rändern der Klassen, dort sind Fehler häufig. Bei Black-Box-Tests kennt man nur die Spezifikation, bei White-Box-Tests auch den Code. Bei der testgetriebenen Entwicklung (TDD) schreibst du zuerst einen Test, der fehlschlägt, dann gerade so viel Code, dass er besteht, und räumst danach auf. Alle Tests laufen nach jeder Änderung erneut, damit neue Fehler auffallen.
Unit-Tests für ein Schaltjahr
Unit-Tests für ein Schaltjahr
Die Funktion soll prüfen, ob ein Jahr ein Schaltjahr ist: durch 4 teilbar, aber nicht durch 100, es sei denn, es ist durch 400 teilbar. Eine erste Version:
| Zeile | Einrückung | Anweisung |
|---|---|---|
| 1 | 0 | |
| 2 | 1 |
Welche Testfälle decken den Fehler auf?
Man wählt je einen Vertreter pro Äquivalenzklasse: normales Jahr, durch 4 teilbar, Jahrhundert ohne 400, Jahrhundert mit 400.
| Jahr | Sollwert | Version 1 liefert | Ergebnis |
|---|---|---|---|
| 2023 | False | False | bestanden |
| 2024 | True | True | bestanden |
| 1900 | False | True | durchgefallen |
| 2000 | True | True | bestanden |
Nur der Test mit 1900 deckt den Fehler auf. Die Korrektur ist . Danach laufen alle vier Tests erneut.
Refaktorierung und Dokumentation
Refaktorierung verbessert die innere Struktur des Codes, ohne sein Verhalten zu ändern: bessere Namen, doppelten Code in eine Methode ziehen, lange Methoden teilen. Die Tests zeigen, dass nach dem Umbau alles noch funktioniert. Dokumentation besteht aus Kommentaren, die das Warum erklären, aus Beschreibungen der Schnittstellen, einer Übersicht über den Aufbau und einem Handbuch für Nutzer. Sie muss zusammen mit dem Code gepflegt werden, sonst führt sie in die Irre.
Entwurfsmuster: Observer und Model-View-Controller
Ein Entwurfsmuster ist eine bewährte Lösung für ein wiederkehrendes Entwurfsproblem.
- Observer: Ein Objekt, das Subjekt, führt eine Liste von Beobachtern. Ändert sich sein Zustand, ruft es bei jedem Beobachter eine Methode wie auf. Beobachter melden sich an und ab. Das Subjekt kennt nur die Schnittstelle der Beobachter, nicht deren Klassen, die Kopplung ist lose.
- Model-View-Controller (MVC): Das Model hält Daten und Fachlogik, die View stellt sie dar, der Controller nimmt Eingaben entgegen und ruft das Model auf. Die View beobachtet das Model. So lassen sich Darstellung und Logik getrennt ändern und testen.
MVC bei einer Zähler-App
MVC bei einer Zähler-App
Eine App zählt Besucher. Auf dem Bildschirm steht der Zählerstand und eine Taste „+“. Welche Teile arbeiten, wenn jemand auf „+“ tippt?
- Der Controller bekommt den Tastendruck und ruft am Model auf.
- Das Model erhöht den Zählerstand und meldet die Änderung allen Beobachtern (Observer).
- Die View ist als Beobachter angemeldet, wird aktualisiert und zeigt den neuen Stand.
Das Model weiß nichts von Bildschirm oder Tasten. Man kann es also ohne Oberfläche mit Unit-Tests prüfen und später eine zweite View anmelden, etwa eine Anzeige am Eingang.
Versionsverwaltung, Bugtracking, Schnittstellen und Modularisierung
Versionsverwaltung (zum Beispiel Git) speichert jede Änderung als Version, den Commit, mit Autor, Zeit und Nachricht. So kann man zu früheren Ständen zurückgehen. In Branches entwickelt man Linien parallel und führt sie per Merge zusammen. Das Repository enthält die ganze Geschichte. Bugtracking verwaltet Fehler als Tickets mit Beschreibung, Schritten zum Nachstellen, Priorität, Zuständigem und Status.
Eine Schnittstelle (API) legt fest, wie man einen Teil von außen benutzt (Methoden, Parameter, Rückgaben), ohne seine Umsetzung zu kennen. Modularisierung zerlegt ein System in Module mit hoher Kohäsion (ein Modul, eine Aufgabe) und loser Kopplung (wenige Abhängigkeiten), die nur über Schnittstellen zusammenarbeiten. Module lassen sich einzeln testen, austauschen und im Team verteilen.
Alle Tests bestanden heißt fehlerfrei
Alle Tests bestanden heißt fehlerfrei
„Alle Tests laufen grün, also hat die Software keine Fehler.“
Tests zeigen nur, dass die geprüften Fälle richtig sind. Im Beispiel hätten 2023, 2024 und 2000 allein den Fehler nicht gefunden. Gute Tests decken Äquivalenzklassen und Grenzwerte ab, bleiben aber eine Stichprobe.
Refaktorierung heißt neue Funktionen einbauen
Refaktorierung heißt neue Funktionen einbauen
„Beim Refaktorieren baue ich gleich noch die gewünschte Suchfunktion ein.“
Refaktorierung ändert nur die Struktur, nicht das Verhalten. Eine neue Funktion ist eine Erweiterung. Trenne beides, damit ein Test, der nach dem Umbau fehlschlägt, eindeutig auf den Umbau zeigt.
Zusammenfassung
Softwarequalität zeigt sich in Kriterien wie Korrektheit, Zuverlässigkeit, Sicherheit, Benutzbarkeit und Wartbarkeit. Entwicklerinnen und Entwickler tragen Verantwortung für die Folgen.
Softwareergonomie fragt nach Aufgabenangemessenheit, Selbstbeschreibung, Erwartungskonformität, Steuerbarkeit und Fehlertoleranz. Barrierefreiheit ermöglicht Menschen mit Einschränkungen die Nutzung.
Unit-Tests prüfen Einheiten, Integrationstests ihr Zusammenspiel. Testfälle wählt man nach Äquivalenzklassen und Grenzwerten. Bei TDD steht der Test vor dem Code.
Refaktorierung verbessert die Struktur ohne Änderung des Verhaltens, Tests sichern sie ab. Dokumentation erklärt Schnittstellen, Aufbau und Gebrauch.
Der Observer meldet Zustandsänderungen an angemeldete Beobachter. MVC trennt Daten (Model), Darstellung (View) und Eingabe (Controller).
Versionsverwaltung, Bugtracking, Schnittstellen und Modularisierung machen Teamarbeit und spätere Änderungen beherrschbar.
Übungen
Welche Testart prüft, ob zwei Klassen über ihre Schnittstellen richtig zusammenarbeiten?
Eine Funktion soll Alterswerte von 14 bis 18 (einschließlich) akzeptieren. Welche Testwerte sind Grenzwerte?
In einer App tippt jemand auf „Löschen“. Welcher Teil des MVC-Musters nimmt diese Eingabe entgegen?
Ein Sensor hat zuerst 3 angemeldete Beobachter. Er ändert seinen Wert zweimal. Dann meldet sich ein vierter Beobachter an, und der Sensor ändert seinen Wert noch dreimal. Wie oft wird insgesamt die Methode aktualisieren aufgerufen?
Eine Lern-App zeigt falsche Antworten nur durch einen roten Rahmen. Nenne zwei Gründe, warum das nicht barrierefrei ist, und eine bessere Lösung.