Problem mit Software Architektur (C++)



  • @SeppJ

    dass C++ Programmierer in Java hinter sich sauber aufräumen, anstatt sich auf die Garbage-Collection zu verlassen.

    Hehe, ja in einem PHP Forum wurde ich mal zur Schnecke gemacht, weil ich ein Image Handles explizit freigegeben habe, nachdem ich es nicht mehr benutzt habe, anstatt dies vom Script automatisch erledigen zu lassen.

    Und stimmt jetzt fällt mir wieder das eine oder andere ein. In einem XNA Forum (Ich programmierte ein xbox 360 Spiel mit C# und XNA als Projektarbeit im 4. Semester) hatte ich die Frechheit, mich nach Referenz Counting im Resourcen Manager zu erkundigen. Da hiess es, ich solle endlich aufhören so komplett veraltete Konzepte zu verfolgen, das brauche heutzutage niemand mehr, dafür sei der Garbage Collector da, es müsse endlich ein Umdenken stattfinden usw...



  • 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?



  • Ishildur schrieb:

    Noch während der Berufslehre hatte ich einen beleidigend einfachen Javatest.
    Die Aufgabe war, eine Liste von Objekten auf eine Reihe von Anforderungen hin zu überprüfen und schliesslich all diejenigen Objekte, welche sämtliche Anforderungen erfüllten auf den Bildschirm auszugeben. Ich schrieb das etwa so:

    while HasNext begin
    if not Anforderung1 then continue;
    if not Anforderung2 then continue;
    if not Anforderung2 then continue;
    print Element
    end

    Das gabe ein "Ungenügend", das sei keine "strukturierte Programmierung", sonst solle ich ihm das mal in einem PAP oder in einem Nassi-Shneiderman darstellen.
    Korrekt wäre gewesen:

    boolean b1
    boolean b2
    boolean b3

    while HasNext begin
     b1 = false
     b2 = false
     b3 = false
    
     if Anforderung1 then b1 = true
     if Anforderung2 then b2 = true
     if Anforderung2 then b3 = true
     
     if b1 == true and b2 == true and b3 == true then print Element 
    end
    

    Auch die Argumentation dass es doch IMHO keinen Sinn macht, die letzten 2 Eigenschaften zu überprüfen, wenn bereits die erste nicht erfüllt ist, war nicht von interesse, es musste einfach "strukturierte Programmierung" sein, alles Andere war falsch (böse)!

    Besser:

    while hasNext
    begin
      if (Anforderung1) and (Anforderung2) and (Anforderun3)
        print Element
    end
    

    Keine ekligen continues.



  • @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))


Anmelden zum Antworten