Die API-Tests der Kladde laufen seit Lektion 2.7.3 gegen einen Speicher im Arbeitsspeicher. Das macht sie schnell, und sie brauchen keinen SQL Server. Im Betrieb läuft die Kladde aber mit SQL Server. Ein grüner Test sagt also: „Mit dem Speicher im Arbeitsspeicher funktioniert die API.“ Er sagt nichts darüber, ob sie mit SQL Server funktioniert, es sei denn, beide Speicher verhalten sich gleich. Genau darum geht es beim L in SOLID, dem Liskovschen Substitutionsprinzip: Jede Umsetzung einer Schnittstelle muss überall dort einsetzbar sein, wo die Schnittstelle verlangt wird, ohne dass sich etwas ändert, worauf sich ein Aufrufer verlassen darf.
In dieser Lektion schreiben Sie Tests, die genau das prüfen, und finden zwei Stellen, an denen die Kladde es bisher nicht hielt.
Der Vertrag einer Schnittstelle
Eine Schnittstelle in C# legt Namen, Parameter und Rückgabetypen fest. Das ist der kleinere Teil des Vertrags. Der größere steht nicht im Typsystem:
- Vorbedingungen: was der Aufrufer liefern muss. „Der Name ist nicht leer.“
- Nachbedingungen: was die Umsetzung danach zusichert. „Ein angelegter Kontakt wird von der Suche gefunden.“ „Die Ergebnisse sind nach Namen sortiert.“
- Ausnahmen und Fehler: wie sie scheitert. „Eine schon vergebene Adresse gibt ein
Resultmitcontact.email.taken, keine Ausnahme.“
Das Prinzip heißt in dieser Sprache: Eine Umsetzung darf die Vorbedingungen nicht verschärfen und die Nachbedingungen nicht abschwächen. Sie darf nicht mehr verlangen und nicht weniger zusichern als der Vertrag. Und sie darf nicht anders scheitern, als der Vertrag sagt.
Für die Kontakte der Kladde sieht der Vertrag so aus, src/Kladde.Core/IContactStore.cs:
namespace Kladde.Core;
/// <summary>Where the office keeps its contacts. What every store must do stands here and, better, in ContactStoreContract.</summary>
public interface IContactStore
{
/// <summary>Adds a contact. An address that another contact has, ignoring case, is refused with contact.email.taken.</summary>
Task<Result<Contact>> AddAsync(string name, string email, CancellationToken cancellationToken);
/// <summary>The contacts whose name contains the term, ignoring case, sorted by name.</summary>
Task<IReadOnlyList<Contact>> SearchAsync(string term, CancellationToken cancellationToken);
}
Heißt dieser Port bei Ihnen anders, oder sprechen Ihre Endpunkte noch direkt mit KladdeBook und ContactDirectory, ist das der erste
kleine Umbau dieser Lektion: ein Port mit den Methoden, die die Endpunkte der Kontakte brauchen, und die beiden Speicher dahinter. Die
Kommentare an den Methoden sind der Vertrag in Worten. Sie haben einen Nachteil: Niemand prüft sie.