Information Hiding

Zusammenhänge versteckter Information

Montag, 17. Februar 2025

Allgemein

Das Geheimhaltungsprinzip ist sicherlich so alt wie die Menschheit selbst. Und wird auf Basis des need to know zum Herrschaftsprinzip. IT bezogen ist die gängige Praxis, Informationen in Objekte zu kapseln und diese hinter einer Schnittstelle zu verbergen. Vor der Objekt-Orientierung sprach man von Modularisierung - immer noch ein gängiger Begriff.

Durch verstecken der Informationen hinter einer Grenze – der Schnittstelle (Interface) – wird die Struktur in ein Innen und Außen unterteilt. Die ins innere gewanderten Implementierungsdetails somit verborgen, jedoch über die gezielte Beschreibung des Interfaces in abstrahierter Form wieder sichtbar gemacht.

Wird im Inneren auf versteckte (nicht per Schnittstelle übergebene) und sich zeitlich ändernde Informationen zugegriffen, spricht man von hidden state.

Das Innen und Außen

Wichtig zu erwähnen ist, dass fehlende Details einer nach außen gerichteten Abstraktion im Inneren der strukturellen Einheit sichbar sind. Die Wartung kleinerer Einheiten sollte nicht mit teuren Ausgrabungen der Details verbunden sein.

Architektur und Struktur

Mit Strukturierung der Software in Einheiten verschiedener Granularität – etwa Substysteme, Schichten, Module, Komponenten usw. – bauen wir eine Architektur, die Informationen nach dem Prinzip Separation of Concerns in selbst festgelegte Bereiche gliedert. Die tragende, das gesamte System verbindende Struktur fügt Informationen hinzu, die Details der in die verschiedenen Organi­sa­tions­einheiten verschobenen Informationen werden verborgen.

Sichtbarmachung

Damit hinzugefügte Information nicht selbst wieder während der Wartung teuer wiederentdeckt werden muss, ist auf gute Sichtbarkeit zu achten, erreichbar mittels Dokumentation oder spezieller Auszeichnung im Quellcode.

Versteckte räumliche und zeitliche Zusammenhänge

Wird etwa das DRY Prinzip (Don't Repeat Yourself) verletzt, indem die Berechnung, ob ein Student ein Examen bestanden hat oder nicht, mehrfach im Source steht, z.B. in der Be­nutzer­schnittstelle sowie im Code zur PDF Generierung,

boolean studentPassedExam = exam.getPoints() > 40;

entsteht eine nicht offensichtliche, versteckte Kopplung zwischen Komponenten. Soll nachträglich die Grenze, ob ein Test bestanden wurde, geändert werden, sind alle betroffenen Komponenten zu ändern. Entweder wird das boolesche Prädikat gemäß DRY in den Examenstyp verschoben oder zumindest die Konstante 40 extrahiert. Durch Bezugnahme auf eine Konstante wird der räumliche Zusammenhang offensichtlich.

class Exam { static int pointsToPassExam = 40; }
boolean studentPassedExam = exam.getPoints() > Exam.pointsToPassExam

Zudem existiert noch eine zeitlich gesehen versteckte Abhängigkeit bei Änderung der Punktegrenze. Nach Änderung des Wertes werden auch ältere, schon bewertete Examen bei Neuauswertung mit der neuen Formel berechnet. Der sich zeitlich ändernde Wert pointsToPassExam ist demnach entweder als zusätzlicher Zustand im Examenstyp zu speichern oder das davon abhängige Ergebnis der Auswertung.

class Exam { int pointsToPassExam = 40;
              /* keeps same for already evaluated exams
                 but differs between exams over time */ }
boolean studentPassedExam = exam.getPoints() > exam.pointsToPassExam

Abstraktionen

Werden Implementierungsdetails in Komponenten abstrahiert, entstehen möglicherweise versteckte Abhängigkeiten und damit eine leaky abstraction. Man verschiebt sozusagen die Komplexität von einer Phase auf eine zeitlich spätere.

Beispiele sind Zugriffe auf Standardbibliotheksfunktionen, auf das Dateisystem, Services etc.

   void method1() { ... malloc(sizeof(LIST_HEAD)) ... }
   Config method2() { ... open("/config-path", O_RDONLY) ... }
   function method3() { ... await fetch("/webservice") ... }

Möchte man die erste Komponente (method1) in einem embedded Projekt mit eigener statischer Speicherverwaltung wiederverwenden, scheitert man. Verwendet man die zweite Komponente (method2), so ist der Administrator des Systems gefordert, den Dateizugriff auf die Konfigurationsdatei bei der Installation abzusichern, die Abstraktion verursacht also einen Security-Leakage, aber auch noch weitere. Komponente drei (method3) verursacht eine Laufzeitabhängigkeit zu einem speziellen Service und verursacht Probleme während der Testphase.

Um diese Abhängigkeiten nicht nur zu verschieben, sondern sichtbar zu machen, wird mittels Dependency Injection (DI) die Abstraktion gezielt durchbrochen und bei Erzeugung der Komponente der verwendete Speicher-Allokator, Persistenz- bzw. Web-Service übergeben.

Problematisch wird es nur, wenn das verwendete DI-Framework kompliziert zu bedienen ist und daher das eigentliche Problem in eine Konfigurations-Hölle umgewandelt wird. Dokumentiert werden müssen die Umgebungs-Abhängigkeiten für die DevOps-Teams in jedem Fall.

Zusammenhänge über Abstraktionsebenen (Grenzen) hinweg sichtbar zu machen und zu verwalten ist mit unseren heutigen Entwicklungsumgebungen noch nicht möglich und muss daher statisch dokumentiert werden. Das wird aber oft nicht gemacht bzw. die Dokumentation vergessen anzupassen. Oder die Zusammenhänge werden in kryptischen Konfigurationsdateien dargestellt. Eine anderer Weg ist die Verwendung domänenspezifischer Sprachen (Konfigurationssyntax ist eine solche). Das Problem mit diesen Ansätzen ist die Separation, die fehlende Integration. Lösungen dazu zeichnen sich erst am Horizont ab.

Abschlussworte

Informationen zu kapseln, um die Auswirkungen bei Änderung zu begrenzen, ist ein probates Mittel, um Komplexität zu beherrschen. Im Gegenzug (Yin und Yang) ist darauf zu achten, dass Informationen offensichtlich gemacht werden, so denn darauf zurückgegriffen werden muss.

Dinge offensichtlich zu machen ist effizient und wirtschaftlich, widerspricht aber dem Herrschaftsprinzip. Der Softwareentwickler macht sich somit entbehrlich und schützt sich nicht vor möglicher Arbeits­losigkeit.