Umbau von Compilerschaltern

    Umbau von Compilerschaltern

    Hallo,

    ​ich hab eine Frage zu Compilerschaltern. Und zwar werden die immer mal wieder in unserem Code benutzt.
    ​Allerdings haben die eben auch ihre Nachteile. Die größten bei uns sind: Compilefehler treten nicht auf, weil nicht alle Schalterstellungen kompiliert wurden. Und: Vergisst man die Unit mit der Konstante für den Compilerschalter einzubinden kriegt mans evtl. nicht mit und das Programm biegt anders ab als es soll.

    ​Von daher wollt ich mal schaun die Konstante direkt im Code zu benutzen, also

    Delphi-Code

    1. ​if KONSTANTE then
    statt einem Compilerschalter. Das würd die 2 Punkte oben elemenieren. Allerdings bekomm ich dann Warnings wie z.B. "Variable wird nicht benutzt", da der Compiler hier "zu" schlau ist.

    ​Gibt's da einen Weg drumrum?
    Mahlzeit,

    Äußerst unverständlich.
    Meinst Du mit "Compilerschaltern" bedingte Kompilation ? (Variante 1)
    Oder das alte Zeugs wie {$R+} u.a. ? (Variante 2)
    Eine Konstante ist eine unveränderliche Größe im Programm, sie hilft Speicherpatz sparen und erhöht die Programmiersicherheit, weil kein Unsinn auf ihr landen kann. Variante 3)
    Hier scheint also einiges durcheinanderzugehen, überleg nochmal genau, welche der Varianten gebraucht werden.
    ism
    Morgen ist Heute schon Gestern
    Compilerschalter der Art:

    Delphi-Code

    1. var
    2. {$IF BOOL_KONSTANTE}
    3. i: Integer;
    4. {$ENDIF}
    5. begin
    6. {$IF BOOL_KONSTANTE}
    7. i := 1;
    8. ...
    9. {$ENDIF}


    Der Vorteil bei einem "normalen" if wär, dass immer alle Blöcke kompiliert und damit besser gecheckt werden und ich auch mitkrieg, wenn z.B. BOOL_KONSTANTE nicht vorhanden ist.

    Allerdings muss ich i dann immer anlegen und der Kompiler meckert bei BOOL_KONSTANTE = False, dass i nie verwendet wird.

    Notlösung wär sowas wie

    Delphi-Code

    1. if BOOL_KONSTANTE.ToInteger.ToBoolean then


    Aber schön ist anders

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „Klausens“ ()

    Meinst du sowas?

    Delphi-Code

    1. {$DEFINE DEBUG}
    2. {$IFDEF DEBUG}
    3. Writeln('Debugging aktiviert.'); // Dieser Code wird ausgeführt
    4. {$ELSE}
    5. Writeln('Debugging deaktiviert.'); // Dieser Code wird nicht ausgeführt
    6. {$ENDIF}
    7. {$UNDEF DEBUG}
    8. {$IFNDEF DEBUG}
    9. Writeln('Debugging deaktiviert.'); // Dieser Code wird ausgeführt
    10. {$ENDIF}


    Schaust du einfach in der Delphi-Hilfe (Compiler-Direktiven) nach...

    cckloud

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „haentschman“ ()

    Compiler-Defines für bedingte Compilierung zu verwenden sollte man eigentlich nur in einem Fall wirklich machen: Wenn man Code hat, der unter keinen Umständen im endgültigen Programm erscheinen soll. Gründe, die ich mir dafür vorstellen kann:
    • Wie von cckLoud erwähnt: Der Debug-Modus. Du willst zu Testzwecken sicherstellen, dass irgendwelche Überprüfungen gemacht werden. Diese sind aber u.U. relativ langwierig und sollten "wenn alles so läuft, wie man es sich beim Codeschreiben vorgestellt hat" immer erfüllt sein. Deshalb sollen diese Tests nicht im Release-Modus compiliert werden.
      Wenn sich das allerdings - und das sollte meistens so sein - schreiben lässt als eine simple If-Abfrage, dann ist es ratsamer, Assertions zu verwenden. Assert wird ebenfalls über einen globalen Compilerschalter gesteuert und entweder als Abfrage compiliert oder vollständig aus dem Code entfernt.
      Niemals sollte Code, der nur im Debug-Modus ausgeführt wird, Seiteneffekte haben, die sich auch auf den Release-Modus auswirken. Sprich: Überprüfen OK, Ändern böse.
    • Du willst z.B. eine Demoversion deines Programms herausbringen, die eine bestimmte Funktion - etwa Speichern oder Drucken - nicht unterstützt. Dann gibt es die Möglichkeit, ein gewöhnliches If zu verwenden - auf diese Weise kann man dem Benutzer dann z.B. unter Eingabe eines Lizenzschlüssels ermöglichen, die Funktion freizuschalten. Allerdings ist es dann möglich, im Disassembly die Sprungbedingung der If-Abfrage einfach umzukehren, und schon ist die Funktion so freigeschaltet. Die Alternative ist es, einen Compilerschalter zu verwenden, so dass der Code für die Funktion gar nicht erst in der Endanwendung landet - dann musst du allerdings zwei Versionen deines Programms vertreiben.
    • Architekturabhängige (x86-32/-64, ARM, Linux) oder compilerabhängige (Delphi-Version) Funktionen
    Es mag natürlich noch andere Gründe geben, aber wenn bedingte Compilierung "immer mal wieder" verwendet wird, würde ich spontan sagen, dass das zu häufig ist.

    Wenn du außerdem in einer Funktion häufig bedingte Blöcke mischt, dann weiß man am Ende überhaupt nicht mehr, was diese Funktion eigentlich tun soll. Dann ist es ratsam, die gesamte Funktion in den bedingten Block zu packen:

    Delphi-Code

    1. {$IFDEF Demo}
    2. Procedure Print;
    3. Begin
    4. ShowMessage('not supported');
    5. End;
    6. {$ELSE}
    7. Procedure Print;
    8. Var
    9. // Lots of variables
    10. Begin
    11. // Code
    12. End;
    13. {$ENDIF}


    Und schließlich noch zu der Frage, was passiert, wenn man die Unit mit der Konstanten vergisst einzubinden. Wenn du ein ganz einfaches Rezept befolgst, sollte das nicht passieren:
    • Compiler-Defines sind entweder global für das gesamte Projekt. Du kannst sie in den Projektoptionen unter Delphi-Compiler > Bedingungen festlegen. Dabei handelt es sich dann eben um Symbole, die entweder "definiert" oder "nicht definiert" sind, nicht aber spezielle Werte haben.
    • Oder sie gelten für die aktuelle Datei, werden also am Anfang der Datei definiert und nur innerhalb dieser Datei verwendet.
    • Wenn du tatsächlich mal $IF verwenden solltest, dann sollte sich das ebenso auf Konstanten beschränken, die innerhalb der aktuellen Datei definiert wurden, nicht auf externe Symbole. Ich habe mal kurz einen GREP im Delphi-Quellcode durchgeführt. $IF wird bei XE3 in 116 Dateien verwendet - und zwar meist in der Form {$IF Defined(abc) Or Defined(def)}, weil IFDEF keine logische Verknüpfung erlaubt. Als Konstanten wird eigentlich nur zugegriffen auf RTLVersion und CompilerVersion (beide in der System.pas definiert, also überall verfügbar) oder aber auf Konstanten, die in derselben Unit definiert wurden (aber auch das nur selten).
    Master of the EDH ;)
    Naja, du kannst die Warnung unterdrücken entweder in den Projektoptionen global oder in der Datei - oder sogar nur in der Abfrage, was das ganze aber ziemlich unansehnlich macht:

    Delphi-Code

    1. {$WARN COMPARISON_FALSE OFF}
    2. {$WARN COMPARISON_TRUE OFF}
    3. If InDebugMode Then ...
    4. {$WARN COMPARISON_FALSE DEFAULT}
    5. {$WARN COMPARISON_TRUE DEFAULT}


    Ansonsten würde ich eine Abfrage im Debug-Modus tatsächlich als bedingten Block compilieren. Und die Programmentwicklung findet ja grundsätzlich im Debug-Modus statt, d.h. Fehler, die nur in diesem Block auftreten, sind auch immer echte Compilierfehler. [Es sollte keine bedingten Blöcke geben, die im Release-, nicht aber im Debug-Modus ausgeführt werden, es sei denn, du hast solche Zusatzblöcke nur drin, damit du dir, wie ich oben beschrieben habe, tausend Unterblöcke innerhalb einer Funktion ersparst. Dann aber auf jeden Fall den Code so kommentieren, dass die beiden Blöcke immer synchron gehalten werden.]
    Die Notwendigkeit, eigene Variablen für den Debug-Modus zu definieren, sollte eher nicht vorhanden sein. Lässt sich die Prüfung auslagern in eine Funktion, die du nur im Debug-Modus reincompilierst?

    Noch ein bisschen Meta-Antwort, da du bisher relativ unkonkret bist, auch ziemlich allgemein:
    Ich schreibe hier viel mit "sollte" - das heißt, dass das eben das übliche Szenario ist, für das sich solche zusätzliche Abfragen auch komfortabel implementieren lassen. Nun gibt es aber leider Theorie, in der alles schön ist und die Praxis, bei der man Einzelfallentscheidungen treffen muss. Und es ist immer möglich, solche "sollte-Regeln" zu brechen, wenn man sich das gründlich überlegt hat und zu dem Schluss gekommen ist, dass alles andere nur noch schlimmer ist. Wenn du also das Problem vieler solcher komplexer Blöcke mit verschiedenen Kontrollflüssen in unterschiedlichen Kompilaten hast, dann kann das zwei Gründe haben:
    • Das Programm ist schlecht designed. Das hört man natürlich nicht gerne. Aber es kann z.B. sein, dass man mit irgendetwas "gutem" anfing, was sich im Laufe der Zeit wesentlich weiterentwickelt hat. Jede der dabei vorgenommenen Anpassungen war für sich genommen nicht schlimm und man hat sich vielleicht gedacht, dass so ein bedingter Block ja eigentlich eine ganz geschickte Art der Code-Wiederverwendung ist. Aber in Summe ist das Ganze durch seine enorme "Flexibilität" extrem unhandlich geworden. Man steht nun vor der schmerzlichen Aufgabe, entweder die Dinge so zu akzeptieren wie sie sind oder aber eine Menge Zeit und Aufwand in ein Neudesign der Schnittstellen zu investieren, wo man wirklich alle Fälle durchdenkt, die man bisher hat und sich auch überlegt, was in Zukunft noch hinzukommen könnte. Das ist schwer und vielleicht nicht mal erfolgreich - es kann sein, dass in einem Jahr etwas erforderlich ist, mit dem niemand gerechnet hat und was einfach nicht so geht. Aber mit einem kleinen Compilerschalter würde sich das ganze Problem..... Das ist eben eine niemals endende Geschichte.
    • Das Programm ist schlecht designed. Das hört man natürlich nicht gerne. Aber es ist ja nun einmal offensichtlich: Wenn man sich verbiegen muss, um irgend etwas hinzubekommen, ist das Design mangelhaft. Es kann aber vielleicht tatsächlich sein, dass jegliche denkbare Alternative noch viel schlechter wäre. Oder man wäre in der Lage, das mit verschiedenen Abstraktionsschichten zwar wesentlich schöner zu machen (so dass die Informatik-Theoretiker zufrieden sind), aber dass dann notgedrungen die Performance in den Keller geht. Das ist ein übliches Problem: Häufig ist die Verwendung vieler beliebter Softwarepattern und -konzepte etwas, was für das Verständnis und die "Codeschönheit" hilft, aber einen Todesstoß für Performance bedeutet. Den Enduser interessiert aber mein Code nicht, sondern nur wie schnell er zum richtigen Ergebnis kommt. Es gibt also tatsächlich Situationen, in denen ein "schlechtes Design" unumgänglich ist - weil man nicht alles gleichzeitig optimieren kann. Aber in einem solchen Fall muss man sich einfach klarmachen, wo die Prioritäten liegen, und dass man Unschönheiten in Kauf nehmen muss.
    Master of the EDH ;)