LimeSurvey und die Hungarian Notation

Wie PHP-Typisierung Variablenpräfixe obsolet macht.

Wer schon einmal einen Blick in den Quellcode von LimeSurvey geworfen hat, dem begegnet unweigerlich Folgendes:

$iSurveyId = 42;
$sLanguage  = 'de';
$oSurvey    = Survey::model()->findByPk($iSurveyId);
$aQuestions = $oSurvey->getQuestions();
$bActive    = true;

Das Präfix verrät den Typ: i für Integer, s für String, o für Object, a für Array, b für Boolean. Diese Konvention nennt sich Hungarian Notation – und sie stammt aus einer Zeit, in der PHP noch keinerlei Typisierung kannte. LimeSurvey existiert seit über 20 Jahren, und manche dieser Muster sind genauso alt.

Das Problem: PHP ist diese Zeit längst entwachsen. Die Notation aber blieb.


Was die Hungarian Notation eigentlich lösen wollte

Hungarian Notation wurde in den 1970ern von Charles Simonyi bei Microsoft entwickelt, ursprünglich für C. Die Idee: Da der Compiler den Typ nicht prüft, trägt der Entwickler die Information selbst in den Variablennamen hinein.

In frühen PHP-Versionen machte das durchaus Sinn. PHP 4 und 5 waren schwach typisiert, Typfehler wurden stillschweigend weggecastet, und eine IDE konnte kaum helfen. Der Präfix war ein menschlicher Typ-Hint.


PHP hat sich verändert - die Notation nicht

Seit PHP 7.0 hat sich die Sprache fundamental gewandelt:

Modernes PHP kann Typen erzwingen – nicht nur benennen. Was früher eine Konvention war, ist heute eine Sprachfunktion. Die Signatur sagt alles:

// Alt: Hungarian Notation
function activateSurvey($iSurveyId, $sLanguage, $bNotify) { ... }

// Modern: Typen in der Signatur
function activateSurvey(int $surveyId, string $language, bool $notify): void { ... }

Der Unterschied: Im ersten Fall ist $iSurveyId eine Hoffnung. Im zweiten ist int $surveyId ein Vertrag, den PHP zur Laufzeit (und PhpStorm schon beim Tippen) durchsetzt.


Die versteckte kognitive Last

Es klingt kleinlich – ein paar Buchstaben am Anfang eines Variablennamens. Aber kognitive Last summiert sich.

Wenn du $iSurveyId liest, laufen im Hintergrund mehrere Schritte ab:

  1. Präfix erkennen: „i“
  2. Präfix dekodieren: „i steht für Integer“
  3. Eigentliche Information extrahieren: „SurveyId“
  4. Mentale Verknüpfung herstellen: „also eine Survey-ID als Ganzzahl“

Bei int $surveyId (in einer Funktionssignatur oder mit Type-Hint) liest du direkt: Typ, Name, fertig. Zwei Informationen, klar getrennt, kein Dekodieraufwand.

Das mag pro Variable nach Millisekunden klingen. Aber ein typischer Code-Review umfasst Hunderte von Variablen. Und bei jedem neuen Teammitglied, das die Codebasis kennenlernt, zahlt sich dieser Zins neu aus.

Hinzu kommt das Lügenpotenzial der Notation: Niemand hindert dich daran, $iSurveyId einen String zuzuweisen. Der Präfix ist eine Konvention, keine Garantie. Ein typisierter Parameter ist eine Garantie.


Das $o-Problem: Wenn Präfixe nichts mehr aussagen

Besonders deutlich wird das Versagen der Hungarian Notation bei Objekten:

$oSurvey = Survey::model()->findByPk($iSurveyId);

o sagt: „das ist ein Objekt“. Aber findByPk() in Yii 1.1 liefert entweder ein ActiveRecord-Objekt oder null – wenn kein Datensatz gefunden wurde. Der Präfix o verschweigt das komplett. Er suggeriert, dass hier immer ein Objekt landet, und lädt damit zu fehlendem Null-Checking ein.

Modernes PHP macht den Rückgabetyp explizit und zwingt zur Auseinandersetzung mit dem Nicht-Fund-Fall:

// Rückgabetyp klar deklariert – der Aufrufer muss mit null umgehen
function findSurvey(SurveyId $surveyId): ?Survey
{
    return Survey::model()->findByPk($surveyId->value);
}

$survey = findSurvey($surveyId);

if ($survey === null) {
    throw new SurveyNotFoundException($surveyId);
}

// Ab hier ist $survey garantiert ein Survey-Objekt
doSomethingWith($survey);

Noch einen Schritt weiter geht ein explizites Null-Object oder ein Optional-Wrapper – aber schon der Nullable Type ?Survey ist ein gewaltiger Fortschritt gegenüber dem stummen $oSurvey.


Der nächste Schritt: Domänentypung statt primitiver Typen

Wer konsequent weiterdenkt, landet schnell bei einem noch ausdrucksstärkeren Ansatz: Value Objects für Domänentypen.

Statt:

int $surveyId

schreibt man:

SurveyId $surveyId

Auf den ersten Blick nur eine Umbenennung. Tatsächlich aber ein semantischer Quantensprung:

final class SurveyId
{
    public function __construct(
        public readonly int $value
    ) {
        if ($value <= 0) {
            throw new \InvalidArgumentException("SurveyId must be positive, got $value");
        }
    }
}

Was gewinnt man dadurch?

Typensicherheit auf Domänenebene. Eine Funktion, die SurveyId $id erwartet, akzeptiert keine QuestionId – auch wenn beide intern int sind. Primitive Typen wie int sind austauschbar; Domänentypen nicht.

// Mit int: Compiler beschwert sich nicht, Bug ist subtil
function getQuestion(int $surveyId, int $questionId): Question { ... }
getQuestion($questionId, $surveyId); // vertauscht – kein Fehler!

// Mit Value Objects: sofort erkennbar
function getQuestion(SurveyId $surveyId, QuestionId $questionId): Question { ... }
getQuestion($questionId, $surveyId); // TypeError – direkt zur Kompilierzeit

Validierungslogik gehört zum Typ. Eine SurveyId ist immer positiv. Dieses Wissen lebt im Value Object – und muss nicht in jedem Service, Controller und Repository neu geprüft werden.

Lesbarkeit durch Absicht. SurveyId $surveyId kommuniziert nicht nur einen technischen Typ, sondern eine fachliche Bedeutung. Der Code spricht die Sprache der Domäne.


Refactoring als weiteres Argument

Stell dir vor, du möchtest den Typ einer ID von int zu einem UUID-String migrieren. Mit Hungarian Notation und primitiven Typen bedeutet das:

  1. Alle $iSurveyId-Variablen umbenennen (wegen des nun falschen i-Präfixes)
  2. Alle Typ-Hints von int auf string ändern
  3. Alle Verwendungsstellen prüfen

Mit einem SurveyId-Value-Object änderst du eine Klasse. Der Rest des Codes bleibt unberührt.


Was das für LimeSurvey bedeutet

LimeSurvey ist ein gewachsenes Open-Source-Projekt mit einer langen Geschichte. Niemand erwartet, dass zwanzig Jahre Code von heute auf morgen umgeschrieben werden. Aber bei Neucode und bei Refactorings lohnt es sich, folgende Faustregel zu beherzigen:

Lass PHP den Typ kommunizieren – nicht den Variablennamen.

Konkret heißt das:

Früher Heute
$iSurveyId int $surveyId oder SurveyId $surveyId
$sLanguage string $language oder Language $language
$oSurvey Survey $survey
$aQuestions array $questions
$bActive bool $active

Fazit

Hungarian Notation war eine clevere Lösung für ein Problem, das PHP längst selbst löst. Wer sie heute noch einsetzt, zahlt einen unnötigen kognitiven Preis – für sich selbst, für Kollegen und für jeden, der den Code im Review oder in der Einarbeitung liest.

Moderne PHP-Typisierung, konsequent angewendet bis hin zu Domänentypen wie SurveyId, macht Präfixe nicht nur überflüssig. Sie macht den Code sicherer, ausdrucksstärker und wartbarer – ohne dass man dafür eine einzige Zeile Kommentar schreiben muss.

Der beste Typ-Hint ist der, den PHP selbst versteht.