Gibt es so etwas wie einen typsicheren Stack/Member Store (oder bessere Alternativen?) (Konzeption))



  • Hallo,

    für TL;DR: Langes Thema 😕

    ein wenig viel Hintergrund:
    Ein Projekt besitzt recht umständlichen und unübersichtlichen Code. Es ist abzusehen, dass in diesem Code noch weitere Änderungen rein müssen.
    Das Niveau dieser Änderungen bewegt sich auf "das kann nicht so kompliziert sein, da muss nur ein if rein".
    So sieht aktuell der Code auch aus, voller If-Beulen und Sonderfälle, dadurch ist er nicht sehr wartbar.

    Was der Code abstrakt macht: Er führt Operationen aus, wenn eine Reihe von If-Bedingungen true sind. (Das trifft wohl so ziemlich auf jeden Code zu 🙂 )

    Im besten Falle gibt es in diesem Code atomare Operationen, die sequentiell ausgeführt werden. In komplexeren Fällen sind dort auch Bedingungen vorhanden.

    Um das Ganze zu abstrahieren, versuche ich eine Art Operation-Engine zu schreiben, die eine Kette von Operationen ausführt.

    Sagen wir die Operation Klasse sieht so aus (pseudo pseudo Code 🙂 )

    class Operation
    {
    public:
       void Do();
    // Continue with wäre natürlich protected und durch ctor oder setter gesetzt
       Operation * m_pContinueWith;
    };
    

    Wenn ich eine Verkettung von Operationen habe, muss ich nur das Do der Ersten ausführen. Nach der Logik in Do, müsste der Code dass Do ihrer continuation aufrufen, usw.

    Habe ich zwei Operationen SayHello und SayWorld, würde ich so etwas machen:

    SayHello a;
    SayWorld b(&a);
    
    a.Do();
    

    Das jeweilige Do würde print("Hallo "); und print("Welt!"); ausführen

    Das Ganze weitergedacht funktioniert auch mit Bedingungen, eine ConditionalOperation hat mehrere Continuations: eine für true und eine für false. Abhängig davon, was die Bedingung ergibt, ruft man die eine oder andere Continuation auf.
    So kann man einen ganzen Entscheidungsbaum abbilden.

    Die Idee ist, die Operationen so atomar wie möglich zu lassen. Das spätere Zusammenstecken erzeugt dann den Kontext, also die Programmlogik.
    Ein Beispiel wäre eine ConditionalOperation die "WichtigesObjekt != null" prüft, und eine normale Operation "GreifAufWichtigesObjektZu", die etwas mit dem wichtigen Objekt macht. Nur durch das Zusammenstecken der beiden Operationen…

    GreifAufWichtigesObjekt g;
    WichtigesObjektUngleichNull n;
    n.ContinueOnTrue(&g);
    n.Do();
    

    … wird korrekter Code ausgeführt, also kein Null-Pointerzugriff, wenn "WichtigesObjekt" nicht vorhanden ist.

    Jetzt bin ich aber an dem Punkt gekommen, an dem ein derartiger Code dadurch nicht sehr gut abgebildet wird (der obige Code nutzt auch bereits ein „Wichtiges Objekt“, aber die Herkunft dieses Objekts ist nicht klar):

    KontextVariable kv;
    
    if(a == 42)
    {
      kv.MacheEtwas();
    }
    else if (a = 43)
    {
      kv.MacheEtwasAnderes();
    }
    
    kv.Execute();
    

    Wenn ich nun obigen Code als eine Reihe von Operationen die an ConditionalOperations geknüpft sind, umsetze, habe ich das Problem, dass ich eine Variable kv angelegt habe, die als Kontext in die ConditionalOperations reinfliesen müsste, damit sie an die ContinueOnTrue-Operation "kv.MachEtwas()" oder "kv.MachEtwasAnderes"() weiter gereicht und benutzt werden kann.
    (Alternativ könnte ich den obigen Code als eine eigene Operation implementieren, diese wäre dann nicht mehr atomar, beinhaltet aber alles, was sie braucht. Der Nachteil davon ist, dass wenn ich Operationen so grob zusammenfasse, ich letztendlich bei fast dem gleichen Code lande, den ich refaktoriseren will, nur eben einmal abstrahiert in einer Methode/Klasse.)

    Wenn ich den Ansatz mit den atomaren Operationen verfolge, war mein erster Gedanke ein "Kontext"-Objekt, was alle benötigten Informationen einfach an alle Operationen weitergibt. Der Nachteil davon: Wenn viele Operationen „Zwischenoperationen“ ausführen, die variblen benötigen, so bläht das Kontext enorm auf. Wenn tief verschachtelte Operationen eine Variable brauchen, muss diese im Kontext vorgesehen werden, auch wenn die meisten Operationen diese nicht brauchen. Das führt mit der Zeit dazu, dass dieses Kontext-Objekt die ganze Welt beinhalten wird und dazu noch temp Variablen, die mehrere Operationen überspannen. Dann kann ich auch genauso gut globale Variablen nehmen. Da Operationen die gleiche Signatur haben, kann ich den Kontext zwischenzeitlich nicht ändern. Darf ich ja auch gar nicht, weil dieser eventuell für folgende Operationen benötigt wird.

    Hier ist der Punkt, bei dem ich mich gefragt habe, ob es so etwas wie ein typsicheren Stack oder ein eher einen "variable-store" gibt.
    Wenn ich zwei Operationen habe, von denen eine, eine Variable aus dem Store holt, so obliegt es demjenigen, der den Kontext kennt, die Reihenfolge der Operationen so zu wählen, dass im Store diese Variable schon vorhanden ist (zum Beispiel durch die andere Operation, die vorweggeschaltet wird).

    Stack wäre hier bei näherer Betrachtung nicht so gut geeignet, da man immer einen korrekten push/pop count benötigen würde.
    Ein Store, in dem ich Daten ablege wäre da schon passender. Mir ist nur nicht klar, wie ich diesen typsicher mache. (Wenn ich anfange void* Pointer hin und her zu casten, habe ich so ziemlich garnichts gewonnen).

    Je länger ich darüber nachdenke, desto komplexer wird die Sache: Was ist mit Scopes, kann man sicherstellen, dass nur Operationen tiefer im Baum an die Variablen kommen? Das klingt wieder nach einem Kellerautomaten. Was passiert wenn mehrere Operationen in diesem Store eine Variable mit dem gleichen Typen benötigen? Oder mit dem gleichen Namen anlegen?

    (Mir ist bewusst, dass es gerade keinen Mehrwert gegenüber dem direkten Code hat, es ist so etwas wie ein in C++ gebauter Syntaxbaum. Ich will das aber nur als Startpunkt benutzen. Wenn man solche Bäume zusammensteckt, kann man zur Laufzeit die Operationen anzeigen und Analysen am Produktivsystem vornehmen, es visualisieren, delays zwischen Operationen einsetzen, das Zusammenstöpseln nicht im C++ code machen, usw.)



  • Wasauchimmer Du machst, es werden Dir Sachen fehlen. Die baust Du nach und nach dazu. else, variablen, schleifen, scopes, ..., klassen, templates, exceptions, ...


Anmelden zum Antworten