Allgemein: Programmiertipps - Public Variablen sind böse... aber wieso?



  • So lasset uns… ...etwas ausholen
    C++ ist eine objektorientierte Sprache. Das bedeutet in C++ wird mit einzelnen Objekten gearbeitet. Objekte sind im Prinzip nichts anderes als in sich abgeschlossene Programmmodule welche eine nach Aussen exakt definierte Schnittstelle besitzen. Die Innereien ein jeden Objekts (jeder Klasse) sind nach Aussen hin völlig unbekannt. Eine Art Black-Box welche einfach das macht, was man verlangt. Wie ist egal. Dieses Prinzip bildet die Grundlage des Konzepts der Objektorientierung.

    Worin liegt der Vorteil der Objektorientierung?
    Die Aufteilung der Software in unterschiedliche, in sich abgeschlossene Module ermöglicht es, dass man, ist die Schnittstelle einmal definiert, die Modulinterna umkrempeln kann wie es einem gefällt, ohne dass man den Code der sich um die Klasse herum gebildet hat umkrempeln muss.

    Bildet man zum Beispiel bei einer Software welche die RS232-Schnittstelle als Kommunikationsweg benutzt, eine Klasse um die ganzen Kommunikationsaufgaben, kann man – vorausgesetzt die Klasse hat ein passend universelles Interface – die Kommunikationsklasse welche über die nicht mehr zeitgemässe RS232-Schnittschtelle kommuniziert ersetzen durch eine Klasse die zum Beispiel die ganze Kommunikation über USB durchführt.

    Was hat das nun mit Public- Variablen zu tun?
    Nehmen wir an, wir haben ein Anzeigeelement, das immer den Aktuellen Wert welchen wir in die Public- Integervariable „ValueToShow_int“ schreiben. Funktioniert alles wunderbar. Später möchte man die Software so ändern, dass man eine History der zehn letzten angezeigten Werte abrufen kann. Zu diesem Zweck möchte man nun die Variable „ValueToShow_int“ in ein Objekt des Typs TList umwandeln um die History einfach realisieren zu können. Was passiert?
    Wo immer eine Zuweisung à la „Klasse->ValueToShow_int = x“ gemacht wurde, wird nun ein Compiler-Fehler auftreten. Wo immer diese Zuweisung stattfindet, muss der Code um die Klasse herum geändert werden.

    Ein anderes Beispiel wäre zum Beispiel eine Klasse die einen String verwaltet. Man stelle sich vor, AnsiString würde seinen char-Zeiger öffentlich zur Schau tragen. Es besteht unter anderem die Gefahr, dass nun irgendwer plötzlich den char-Zeiger freigibt oder vergrössert, ohne dass die Klasse davon was mitkriegt.

    Threadsicherheit ist ebenfalls ein Problem. Nehmen wir an, die Klasse aus dem obigen Beispiel wird von einem Thread verwendet. Alle 10s erfolgt ein Update der Anzeige. (ebenfalls ein eigener Thread) Nun kann es passieren, dass der Thread der die Aktualisierung vornimmt kurz bevor der neue Wert in den Speicher geschrieben wird unterbrochen wird und der Aktualisierungs-Thread der Anzeige käme zum Zug. Das Ergebnis wäre, dass der eigentlich bereits aktuelle Wert 10s zu spät angezeigt wird.
    Dies liesse sich ebenfalls dank Zugriffsmethoden und Synchronisations-Objekten wie Events, CriticalSections oder Semaphoren (Windows stellt eine ganze palette an Synchronisations Objekten zur Verfügung) vermeiden.
    Ganz abgesehen davon: woher soll das Anzeigeelement wissen, wann die Anzeige aktualisiert werden soll weil ein neuer Wert in der Variable steht?

    Lösung des Problems
    Vermeiden kann man das indem man Zugriffsmethoden schreibt. Hätte man beim obigen Beispiel Zugriffsmethoden definiert, wäre das Ganze gar kein Problem. Gerade auch im C++ Builder wo die Borländer ein Extra-Schlüsselwort namens „__property“ definiert haben (siehe FAQ) sind die Vorteile von Public-Variablen zum grössten Teil abgedeckt und trotzdem ist die Variable durch Zugriffsmethoden geschützt.

    Auch Forms sollten keine Public-Variablen besitzen
    Als diese Diskussion bereits mal im Gange war, entgegnete WebFritzi, dass man die Forms grundsätzlich ja eh nicht wieder verwende und deshalb public-Variablen kein Beinbruch seien. Hier muss ich sagen, dass dies schlicht falsch gedacht ist. Es gibt nichts schlimmeres als seinen Programmierstil im selben Projekt umzustellen. Eine Form ist eine Klasse wie jede andere auch. Wieso also anders behandeln als die anderen Klassen?

    Falsche Ausreden...
    Wer nun daher kommt mit "Es ist viel zu aufwendig, hier für jedes Attribut eine Zugriffsfunktion zu schreiben" liegt klar falsch...
    a) bietet die Borland IDE (>4) einen Assistenten an, mit dem Sich attribute erstellen lassen (inlusive Zugriffsfunktionen!)
    b) Gehören derartige Ausreden in die gleiche Schublade wie "ich hab keine Zeit kommentare zu schreiben..." oder ähnliches... (also unterste Schublade)

    Das sind so ein-zwei Gründe wieso keine Public-Variablen verwendet werden sollen. Berichtigungen oder Ergänzungen sind sehr willkommen.

    -junix

    <edit>HumeSikkins hat ebenfalls einen Artikel zu diesem Thema verfasst:
    http://fara.cs.uni-potsdam.de/~kaufmann/?page=GenCppFaqs&faq=ocp#Answ
    http://fara.cs.uni-potsdam.de/~kaufmann/?page=GenCppFaqs&faq=InstPriv#Answ
    Ausserdem finde man noch unter http://www.parashift.com/c++-faq-lite/ einige Hinweise dazu.
    </edit>

    [ Dieser Beitrag wurde am 20.11.2002 um 20:14 Uhr von junix editiert. ]

    [ Dieser Beitrag wurde am 12.02.2003 um 21:55 Uhr von Jansen editiert. ]



  • starker Beitrag und was die Objektorientierung angeht, vollkommen ok.

    Vielleicht sollte man noch den etwas unerfahreneren Lesern noch ein wichtiges Argument an die Hand geben, das ebenfalls extrem wichtig ist: die Widerverwendbarkeit! Wenn ich eine Klasse kreiere und versuche, nicht nur die aktuelle Aufgabe zu lösen, sondern die Sache etwas flexibler anzugehen, so daß man auch mit leicht abgewandelten Anforderungen an die Klasse noch mit ihr was anfangen kann, ohne die Klasse komplett umzuschreiben, so ist viel gewonnen.

    Das ist allerdings auch einer der schwierigsten Punkte und erfordert einiges an Gehirnschmalz und auch an Erfahrung. Aber es lohnt sich, wenn man nach ettlichen Monaten eine bereits geschriebene Klasse in einem neuen Projekt nutzen kann und diese auch noch wie gewünscht funktioniert, ohne das man das Rad neue erfinden müßte (mit all dem Testaufwand).

    Dies setzt aber auch eine gehörige Dokumentationsdisziplin voraus! Was nützen mir die schönsten Klassen, wenn ich nach 1 Jahr nicht mehr richtig weiß, wie ich sie bedienen muß (z.B. welche Datentypen verwende ich, welche Methoden nutze ich für welchen Zweck, was sagen die verschiedenen Eigenschaften aus etc.). Ich habe mir deshalb angewöhnt, alle Methoden einer Klasse in WORD zu dokumentieren, wobei ich immer erst (möglichst komplett) dokumentiere und dann erst codiere. Nachteil: das kann ziemlich frustig und etwas öde sein. Vorteil: man sieht schon sehr oft während des Beschreibens der diversen Methoden und Eigenschaften, daß bestimmte Sachen so nicht funktionieren und kann deshalb die Schnittstelle noch relativ unkompliziert anpassen, ohne gleich alles im Programm ändern zu müssen (dafür geht dann nämlich wirklich immens Zeit drauf!). Außerdem: wenn man erst mal codiert hat, dokumentiert man sowieso nicht mehr und vergißt in kurzer Zeit wieder alles. Wer's nicht glaubt, der soll man ein Programm von vor einem Jahr rauskramen und versuchen, sich selbst im Stillen zu erklären, warum man was wo macht. Da habe sicherlich nicht nur ich Schwierigkeiten!

    Und das tolle: wenn ich dann codiere, kann ich die Doku zu den Methoden in den Quellcode zur Methode hinzufügen (am Anfang der Methodendefinition) und somit hat man das auch im Quellcode stehen.

    Ups, ich schreib schon wieder zuviel... sorry... 🙄



  • junix...
    "Es ist viel zu aufwendig, hier für jedes Attribut eine Zugriffsfunktion zu
    schreiben"

    full ack. Wieviel Zeit verbring nochmal der Programmierer mit eintippen ?
    3% der Arbeitszeit ? 5% ?
    Und wie lange dauert das Debuggen eines Public-Pointers der nachfolgende Daten
    niedermäht ?

    Nehmen wir an, die Klasse aus dem obigen Beispiel wird von einem Thread
    verwendet

    Die Visualisierung ist eigentlich immer asynchron zur Datenerfassung und hat nichts mit Public-Variabeln zu tun...

    __property

    Kenne den BCB nur wenig, aber ich bin kein Fan von nicht-Std-Keywords...
    Ausserdem müsste man dann beim genannten Beispiel "Integer->TList" nicht
    trotzdem den Anwender-Code umschreiben? Denn woher soll die "__property-Funktion" wissen wie man aus einer TList einen integer kriegt?



  • Original erstellt von Solaris'dUKe:
    Die Visualisierung ist eigentlich immer asynchron zur Datenerfassung und hat nichts mit Public-Variabeln zu tun...

    Richtig, aber es ist war die einfachste Veranschaulichung die mir gerade eingefallen ist (:
    Man stelle sich zwei asynchron laufende Threads vor.

    Original erstellt von Solaris'dUKe:
    Kenne den BCB nur wenig, aber ich bin kein Fan von nicht-Std-Keywords...
    Ausserdem müsste man dann beim genannten Beispiel "Integer->TList" nicht
    trotzdem den Anwender-Code umschreiben? Denn woher soll die "__property-Funktion" wissen wie man aus einer TList einen integer kriegt?

    __property ist keine Funktion, sie ist ein Schlüsselwort, die implizit die passende Set- bzw. Getfunktion aufruft. daher braucht __property nichts zu wissen (: Selbstverständlich ist das Schlüsselwort nicht portabel, doch wer will denn schon den BCB/Kylix jemals wieder hergeben? (-;

    -junix



  • Nachfragen und weiterführende Erklärungen zum Thema gibt es vorläufig hier...


Anmelden zum Antworten