Problem mit Software Architektur (C++)



  • @Janjan
    Hehe, es waren dann schon ein wenig komplexere Anforderungsprüfungen 😉 resp. eben auch eine Liste (habe das da zu einfach dargestellt) Es war dann eine zur Kompilezeit unbekannte Anzahl von Überprüfungen.



  • Unser Lisp Professor behauptet, dass rein funktionale Konzepte (ausschliesslich Substitution, keine States) auf jeden Fall und immer besser und vor allem eleganter seien, als eine böse Statemachine (selbst ein von mir geschriebenes C Programm, welches durch dynamische Programmierung O(n) eine grosse Fibonacci Zahl in wenigen ms berechnen konnte, während der "rein funktionale" O(2^n) Approach in Lisp mehrere Stunden dafür benötigte, vermochte nicht zu überzeugen "Rekursiv ist trotzdem eleganter, bessär, eine Statemaschine ist trotzdem böse!!"

    Also Haskell als echt rein-funktionale Sprache kriegt das auch problemlos in O(n) hin... . Und ja, da ist das sogar ein 1-Zeiler der, wenn man ihn mal verstanden hat (was meiner Meinung nach nicht trivial ist), wesentlich eleganter ist als alles was du mit c hinkriegst.

    Ich kenne Lisp nicht, und weiß nicht ob es dort die eleganten Möglichkeiten von Haskell gab, wenn ja, dann war der Prof vielleicht nicht ganz so gut wie er meinte :). Wenn nicht, dann wars wahrscheinlich trotzdem eleganter 😉



  • Türlich geht das auch in Lisp schnell. Was ich nicht vestehe, ist wie man O(n^2) hinkriegt. O(1.61^n) klar. O(n) auch. (Addition gilt doch als O(1)?)


  • Administrator

    Ishildur schrieb:

    @Janjan
    Hehe, es waren dann schon ein wenig komplexere Anforderungsprüfungen 😉 resp. eben auch eine Liste (habe das da zu einfach dargestellt) Es war dann eine zur Kompilezeit unbekannte Anzahl von Überprüfungen.

    Dann mach es so:

    boolean checkRequirements(MyObject obj)
    {
      for(/* ... */)
      {
        if(!requirement)
        {
          return false;
        }
      }
    
      return true;
    }
    
    void foo()
    {
      // ...
      while(iter.hasNext())
      {
        MyObject obj = iter.next();
    
        if(checkRequirements(obj))
        {
          print(obj);
        }
      }
    
      // ...
    }
    

    Sieht doch besser aus, nicht?

    Grüssli



  • Ishildur schrieb:

    Ich habe folgende Struktur:

    class IService{
    public:
     virtual const char *GetName(void) const = 0;
    };
    
    class IResourceService:public IService{
     // einige zusätzliche Dinge...
    };
    
    class AbstractService:public IService{
     // implentiert gewisse Verwaltungsaufgaben die in den allermeisten Services anfallen...
    };
    
    class ResourceService:public AbstractService,public IResourceService{
     const char *GetName(void) const;
    };
    

    Ist diese Architektur Schrott oder was? 😞

    Ja, Schrott.



  • @Dravere
    Das ist auch nicht "strukturierte Programmierung", du wärst an dieser Prüfung auch durchgefallen :p Mehrere Returns... Pfui... :p (Ich brauche das auch oft aber eben, es gibt viele Leute, für die ist das "schlechter Stil")

    @volkard und otze
    Könnt ihr mir erklären, wie eine Fibonacci Zahl ohne das Speichern von Zwischenresultaten in O(n) berechnet werden kann? (Das ist eine ernstgemeinte Frage, ich kanns mir wirklich grad nicht vorstellen)
    Natürlich kriege ich das auch in Lisp problemlos in O(n) hin, aber IMHO nicht ohne das Zwischenspeichern von Werten. Es ging auch nicht um die Frage, ob man es in Lisp rekursiv definieren kann oder nicht, sondern ob es dann auch wirklich rein rekursiv berechnet wird (OHNE das Speichern von Zwischenresultaten, AUCH NICHT LOKAL oder so sondern rein durch Substitution wie beim Lambda Kalkül)
    Und Otze kannst du mir diesen 1 Zeiler mal zeigen, ich bin gespannt, da kann ich was lernen.

    @fallsesnochkeinergesathat
    Kannst du mir auch eine bessere Lösung zeigen oder ist "Ja, Schrott" dein ganzer Beitrag zu diesem Thema?



  • Ishildur schrieb:

    @volkard und otze
    Könnt ihr mir erklären, wie eine Fibonacci Zahl ohne das Speichern von Zwischenresultaten in O(n) berechnet werden kann?

    Das da http://en.literateprograms.org/Fibonacci_numbers_(Lisp) meinte ich wohl. Oder den http://stackoverflow.com/questions/627382/the-lisp-way-to-solve-fibonnaci mit fib_tr.



  • Ich lese mich schnell ein...



  • OK alles klar, wenn es Tailrekursiv ist, also vom Compiler in iterativen Code umgewandelt wird. Dabei werden doch vom Laufzeitsystem die Werte doch zwischengespeichert oder irre ich mich da?



  • @SeppJ

    Aber 0x00 ist bei pure virtual einfach falsch. Laut C++-Standard ist der pure-Specifier "=0;" und nichts anderes und entsprechend compiliert dir das auch nicht jeder Compiler (GCC beispielsweise nicht).

    Hab das mit dem gcc überprüft und ist tatsächlich so. Asche über mein Haupt...



  • Ishildur schrieb:

    OK alles klar, wenn es Tailrekursiv ist, also vom Compiler in iterativen Code umgewandelt wird. Dabei werden doch vom Laufzeitsystem die Werte doch zwischengespeichert oder irre ich mich da?

    Es macht optimierenderweise einfach eine Schleife draus. Aber auch ohne diese Optimierung mit ganz normalem rekursiven Absteig und nachher wieder Aufstieg wäre es noch O(n).



  • Hmm ich muss mir das morgen noch einmal genau genauer ansehen, jetzt bin ich zu müde dafür 😉 Ich suche mir dann auch noch ein Beispiel, wo man keine Tailrekursion mehr hinkriegt und dann schauen wir noch mal :p



  • Ishildur schrieb:

    Habe übrigens noch gerade ein IMHO gutes Beispiel, warum Interfaces keine member variablen haben sollten:

    class IVideoService{
    };
    
    class OpenGLVideoService:public IVideoService{
    };
    
    class Direct3DVideoService:public IVideoService{
    };
    

    Wetten der OpenGLVideoService hat nicht dieselben Members wie der Direct3DVideoService? Wetten eine Direct3D TextureResource hat komplett andere Members als eine OpenGl TextureResource?

    Was hat das mit Interface oder nicht zu tun? Wie in jeder Klasenhierarchie gilt auch hier:
    Wenn OpenGL/Direct3DVideoService ein VideoService ist, dann sind auch die Member aus dem VideoService im OpenGl/Direct3DVideoService sinnvoll.
    Alle Member, die speziell und nur was mit OpenGL oder Direct3D zutun haben, werden halt erst in den Ableitungen deklariert.


  • Administrator

    Ishildur schrieb:

    @Dravere
    Das ist auch nicht "strukturierte Programmierung", du wärst an dieser Prüfung auch durchgefallen :p Mehrere Returns... Pfui... :p (Ich brauche das auch oft aber eben, es gibt viele Leute, für die ist das "schlechter Stil")

    Dann war das ein Dogmatiker. Nur in der absolut ursprünglichen Version von strukturierten Programmierung hat man nur einen Ausgang zugelassen. Aber das kann man auch in meinem Beispiel problemlos erreichen:

    boolean checkRequirements(MyObject obj)
    {
      boolean result = true;
    
      for(/* init */; /* check */ && result; /* step */)
      {
        if(!requirement)
        {
          result = false;
        }
      }
    
      return result;
    }
    
    void foo()
    {
      // ...
      while(iter.hasNext())
      {
        MyObject obj = iter.next();
    
        if(checkRequirements(obj))
        {
          print(obj);
        }
      }
    
      // ...
    }
    

    Und schon sollte es diesem Dogmatiker genügen.

    Im übrigen zu deinem Problem mit dem Austauschen von Konzepten zwischen Programmiersprachen. Sowas sollte man aus meiner Erfahrung eher sein lassen. Es gibt Ausnahmen, aber in der Regel sollte man probieren, jede Programmiersprache einzeln zu behandeln, weil jede Programmiersprache auch ihre eigenen Konzepte und Wege mitbringt. Wenn du zum Beispiel Konzepte zwischen C++ und C#, Java austauschen möchtest, wirst du schnell mal in Java, C# an der fehlenden Kopiersemantik scheitern, während in C++ du alles auf den Heap wirfst und jegliche Kopien meidest 😉

    Grüssli



  • @Jockelx
    Ja wenn du von Anfang an damit rechnest, dass du beides implementieren willst, wirst du sicherlich keine Probleme bekommen (Weil du es eben beim Klassendesgin bereits berücksichtigst). Aber wenn du nun bspw. beim Design davon ausgehst, dass IVideoService einfach den Video Part der Applikation erledigt (ohne je daran zu denken, dass es ja noch was anderes als bspw. OpenGL) gibt, dann wirst du mit grosser Wahrscheinlichkeit die OpenGL members in den IVideoService reinpacken. Wenn dann später Direct3D dazu kommen soll must du in mühsamer Arbeit, sämtliche Members die in irgendeiner Verbindung mit OpenGL stehen rauspflücken und in eine abgleitete Klasse OpenGlVideoService umschaufeln (die halben Funktionen von IVideoServie laufen mal nicht mehr deswegen), kurz ein bis 2 Tage Refactoring bis der komplette OpenGL Teil aus IVideoService nach OpenGlVideoService ausgelagert wurde. Anschliessend kann man schliesslich die Direct3DVideoService Klasse machen.

    Hast du von Anfang an keine Members in IVideoService (einfach aus Prinzip, weil man ja nie wissen kann, was da noch kommt) wirst du solche Scenarien IMHO viel seltener bis gar nicht mehr erleben. OpenGLVideoService wurde (einfach aus Prinzip) als konkrete Implementierung von IVideoService gemacht (und nicht IVideoService selbst). Kommt später Direct3D dazu (woran niemand je gedacht hätte), hast du ein bis 2 Tage Refactoring gespart. 🙂

    @Dravere

    Dann war das ein Dogmatiker

    War er! 🙄 Weisst du es war ja nicht das Problem, dass man seine Erwartungen nicht hätte erfüllen können, sondern vielmehr darum, dass man ein völlig korrektes und fehlerfreies Resultat geliefert hat und dafür ein Ungenügend kassiert hat, weil dieser Person der Programmierstil nicht gefallen hat, bei einem anderen Lehrer hätte dieselbe Lösung vielleicht ein Hervorragend gegeben, während die Lösung mit den Booleans eine Ungenügend gegeben hätte mit der Begründing "Die zur Verfügung stehenden Sprachfeatures wurden nicht optimal ausgenutzt, dies wäre ein perfektes Beispiel für ein Continue gewesen und ich wollte sehen, ob ihr das kennt."

    Da kommt mir in den Sinn, ich bekam vor einem halben Jahr in einer Javaprüfung 5 von 25 möglichen Punkten abgezogen, weil ich in Funktionen kein return am Schluss machte... In Funktionen mit dem Rückgabetyp void... Er empfand das als schlechter Stil, eine Funktion müsse immer ein Return haben:

    void DoSomething(){
      // my code
      return;
     }
    

    Interessant ist, dass ca. 90% der Lehrer und Professoren, mit denen ich zu tun habe und die uns Studenten (auf eine teilweise äusserst dogmatische und missionarische) Art und Weise IHREN Stil als die einzige und letzte Wahrheit verkaufen, in ihrem Leben noch nicht ein einziges funktionierendes Programm geschrieben haben, dass mehr als ein paar 100 Zeilen umfasst. Aber dennoch ist einfach so gut wie alles falsch bös und schlechter Stil was Andere machen... 😉


  • Administrator

    Ishildur schrieb:

    Hast du von Anfang an keine Members in IVideoService (einfach aus Prinzip, weil man ja nie wissen kann, was da noch kommt) wirst du solche Scenarien IMHO viel seltener bis gar nicht mehr erleben. OpenGLVideoService wurde (einfach aus Prinzip) als konkrete Implementierung von IVideoService gemacht (und nicht IVideoService selbst). Kommt später Direct3D dazu (woran niemand je gedacht hätte), hast du ein bis 2 Tage Refactoring gespart. 🙂

    Man kann nicht ALLES voraussehen beim Programmieren. Wenn du so programmierst, erstellst du für die Aufgabe völlig untaugliche Programme. Programme werden auf einen gewissen Bereich zugeschnitten, dass hat durchaus einen Sinn. Es ist viel sinnvoller, wenn dann tatsächlich einmal Veränderungen kommen, ein gründliches Refactoring durchzuführen. Dass heisst allgemein gleich andere Fehlentscheide aufzuräumen und mit den neuen Vorgaben wieder einen neuen optimierten Weg finden.

    Klar, ein wenig in die Zukunft sehen schadet nicht, aber man kann es auch übertreiben. Habe schon des Öfteren gesehen, wie grossartig in die Zukunft geplant wurde mit Erweiterbarkeit und co, und als dann das wirkliche Problem kam, musste man es doch anders lösen, wodurch die ganze Arbeit davor für die Katz war und das Programm bisher leicht schlechter lief oder wartbar war für nichts.

    Zu den Professoren sag ich jetzt mal nichts. Bisher hatte ich zum Glück keine solchen krankhaften Dogmatiker, aber gerade an der Universität hatte man zum Teil echt das Gefühl, dass die Professoren 20 Jahre hinten nach sind. Ein allgemeines bekanntes Problem, sieht man auch oft bei Anfängern hier im Forum, dass hinter ihnen ein Professor ohne viel Ahnung steht.

    Grüssli



  • Man kann nicht ALLES voraussehen beim Programmieren.

    Hehe da bin ich ja absolut deiner Meinung, daher versuche ich ja auch so zu programmieren, dass unvorhergesehendes nicht ein Refactoring der halben Applikation zur Folge hat (siehe auch: http://en.wikipedia.org/wiki/Coupling_(computer_science))



  • Ishildur schrieb:

    Man kann nicht ALLES voraussehen beim Programmieren.

    Hehe da bin ich ja absolut deiner Meinung, daher versuche ich ja auch so zu programmieren, dass unvorhergesehendes nicht ein Refactoring der halben Applikation zur Folge hat...

    Die Wahrheit liegt irgendwo dazwischen.

    Ja, es kann nach hinten losgehen, wenn man Code ausschließlich auf einen Fall hin programmiert, ohne sich mögliche Änderung auch nur im Keim vorzustellen. Aber alles auf Änderbarkeit hin zu programmieren ist häufig auch der falsche Weg. Den in der Praxis können beide Extreme effektiv sehr viel Zeit kosten.

    Im ersten Fall weil ein Refactoring extrem aufwendig wird, im zweiten weil man vielleicht zuviel Zeit in etwas investiert hat, das später entweder nicht relevant ist, oder an einer anderen Stelle eingebaut werden muss.



  • @asc
    Wow! Dem kann ich nichts mehr hinzufügen. Da stimme ich dir zu 100% zu (habe auch beide Situationen bereits am eigenen Leib erfahren). Genau dieser Mittelweg ist halt eine riesen Gratwanderung und in meiner Erfahrung IMMER ein Streitpunkt innerhalb des Teams 😉


Anmelden zum Antworten