Programmierrichtlinen (Uni-Paderborn)



  • otze schrieb:

    hats in C++ auch nicht. Die Compiler sind da schon intelligent genug 😉

    Das glaube ich nicht:

    class Obj {
    public:
       Obj() { sock.open("127.0.0.1"); }
       ~Obj() { sock.close(); }
       void write(char const * str) { sock.write(str); }
    private:
       SocketObj sock;
    };
    
    void func(int value) {
      Obj ob;
    
      if (value > 10) return;
    
      ob.write(value);
    }
    


  • Der Grund dafür das man Variablen in C++ nicht alle blockweise definiert ist
    doch ganz einfach und logisch nachvollziehbar.
    Man verbindet bei Objekten sehr häufig die Definition mit der Initialisierung.
    Da die Initailisierung sehr teuer werden kann "erstellt" man die Variablen
    natürlich erst dann wenn sie wirklich benötigt werden.
    Das ganze hat also wirklich mehr als nur formale Aspekte.



  • otze schrieb:

    Ponto schrieb:

    life schrieb:

    Gregorianer schrieb:

    Wenn alles am Anfang deklariert ist, kann man schneller nachgucken.

    wenn es direkt beim gebraucht definiert ist muss man aber garnicht nachgucken 💡

    Und es spart Codezeilen, wenn man dort definiert, wo auch gebraucht wird. In Java hat es wohl anders als in C++ keinen Overhead, wenn man alles am Anfang definiert. Man arbeitet ja nur mit Zeigern.

    hats in C++ auch nicht. Die Compiler sind da schon intelligent genug 😉

    Dann ist dein Compiler kaputt. Er sollte schon das machen, was du ihm sagst.

    {
       Foo foo;  // Default-C'tor
       ...
       foo = bar;  // Zuweisung
    }
    
    {
       ...
       Foo foo (bar);  // spezieller C'tor
    }
    

    Das gilt zwar für Java nicht. Dennoch wird bei mir alles da definiert, wo ich es brauche. Nach oben Springen 'ne Definition einfügen, wieder nach unten und weiterschreiben, wider nach oben, ... was für 'ne Arbeitsweise.



  • Helium schrieb:

    Dennoch wird bei mir alles da definiert, wo ich es brauche. Nach oben Springen 'ne Definition einfügen, wieder nach unten und weiterschreiben, wider nach oben, ... was für 'ne Arbeitsweise.

    Man hat ja gewöhnlich einen Plan der Funktion im Kopf und schreibst sie dann eben kochrezeptartig hin: Oben die Zutaten und unten, was damit passiert.



  • Daniel E. schrieb:

    Man hat ja gewöhnlich einen Plan der Funktion im Kopf und schreibst sie dann eben kochrezeptartig hin: Oben die Zutaten und unten, was damit passiert.

    Ist bei mir nicht so. Bei mir ergibt sich oft erst beim Schreiben eines konkreten Codesegments, was ich dazu eigentlich noch für Hilfsvariablen und so brauche.



  • Ponto: Was soll der Compiler denn deiner Meinung nach sonst machen? Ist doch klar, dass er den Konstruktoraufruf nicht einfach ignorieren kann. Das "Problem" hat man in Java auch, wenn man sich eine Instanz erstellt lange bevor sie gebraucht wird.



  • Walli schrieb:

    Ponto: Was soll der Compiler denn deiner Meinung nach sonst machen? Ist doch klar, dass er den Konstruktoraufruf nicht einfach ignorieren kann. Das "Problem" hat man in Java auch, wenn man sich eine Instanz erstellt lange bevor sie gebraucht wird.

    In Java ist es aber üblich ständig mit Zeigern zu arbeiten:

    Obj ob;
    
    ... ganz viel Code ...
    
    ob = new Obj();
    

    Da kostet es halt nichts vorher das ob anzulegen. Sowas kann man in C++ mit Zeigern ja genauso machen und dann gehe ich davon aus, dass der Compiler mir da nicht sofort Stackspeicher für alloziert, wenn es nicht notwendig ist.

    In C++ ist es aber eher unüblich mit Zeigern zu arbeiten und die Objekte werden direkt erstellt. Wenn man dann alle Variablen direkt definiert hat man das gleiche Problem, wie wenn man unter Java sofort instanziert.



  • Ja, aber da vergleichst du ja trotzdem Äpfel mit Birnen. Ich sehe nicht wo C++ da einen unnötigen Overhead haben sollte. In C++ ist es hier nicht üblich wie in Java mit Zeigern zu arbeiten, aber die Objekte zu früh zu instanziieren ist ja auch nicht üblich. Ich vermute die Compiler sind schon intelligent genug die Instanziierungen zur rechten Zeit zu machen, aber nur, wenn dadurch der Programmablauf nicht verändert wird. Ich denke bei PODs und builtins sollte es hier kein Problem geben.



  • Ich denke auch, dass man Variable moeglichst lokal halten soll. Meint ihr, man sollte soweit gehen, dazu extra Bloecke aufzumachen, damit temporaete Variablen wieder aus dem Scope verschwinden?

    ...
    int result;
    {
      int tmp = ...
      int tmp2 = ...
      result = tmp * (tmp + tmp2);
    }
    ...
    


  • Nur, wenn es einen Destruktor-Aufruf zur Folge hat, sonst kannst es dir schenken. Komisch, hier gibt es irgendwie ständig die Idee, extra-Blöcke einzufügen, anstatt die Funktion weiter aufzuteilen. 😕 Die Frage nach der Stelle, wo die Variable definiert wird, stellt sich doch gar nicht, wenn die Funktion nur 4 Zeilen lang ist. Also ist die Richtlinie sowieso nicht praxisrelevant.

    void foo() {
        {
            ...
        }
        {
            ...
        }
        {
            ...
        }
        {
            ...
        }
    }
    

    Oder zieht ihr so etwas tatsächlich einem

    void foo() {
        tuedies();
        tuedas();
        ...
        tuejenes();
    }
    

    vor? Nicht wirklich, oder?



  • Unterfunktionsaufrufe schliesst mein Codefetzen doch nicht aus. Aber Du hast recht, dass ist ein manuelles Inlinen einer Funktion. Aber wenn die Funktion so speziell ist, muss es sich nicht unbedingt lohnen, das ganze auseinanderzureissen, der Uebersichtlichkeit wegen.



  • Ich finde nicht, dass es was mit speziell-sein zu tun hat. Ich kann ne Funktion immer aufteilen, und irgendwann ist die Grenze des Sinnvollen erreicht. Beispielsweise wäre es nicht mehr sinnvoll, eine Funktion zu schreiben, die mir eine int-Variable initialisiert. Amsonsten lassen sich schon wirklich fast alle Arbeitsschritte weiter sinnvoll aufteilen. Damit wird die Frage, ob Variablen an den Anfang eines Blocks gehören, komplett bedeutungslos und sehr lokal sind die Variablen auch.
    Habe ich bedeutungslos gesagt? Ja stimmt auch, aber sie wird auch beantwortet. Ja, sie gehören an den Anfang des Blocks. Die Funktion ist zwei Zeilen lang, eine legt ne Variable an, die andere macht was damit. Vertauschen kann ich es nicht. Hach, wenn man nur alle Streitfragen so eindeutig lösen könnte.



  • komisch, dass dein Beispiel oben schon mehr als 2 codezeilen pro funktion enthält



  • Glaubst du nicht, ich könnte sie noch weiter zerlegen? Wenn es Sinn macht, tu ich es sofort. Kann man aber bei so Beispiel-Funktionen jetzt nicht entscheiden. Versuch mal, das im übertragenen Sinn zu sehen und nicht wortwörtlich zu nehmen.

    Dir würde ich nahelegen, dass du nicht die Frage, ob du Variablen nach oben tust oder nicht entscheiden sollst, sondern dass du die Funktionen so klein machst, dass es kein richtiges oben und unten mehr gibt. Ob es 2 oder 5 Zeilen sind, spielt doch keine Rolle. Kein Mensch verliert in so einer Funktion die Übersicht, außer sie ist in Ook! geschrieben. Tatsächlich hat man in einer kleinen Funktion automatisch mehr die Tendenz, Variablen nach oben zu tun. Im Extrembeispiel von 2 Anweisungen ist es eh klar. Konstruktoraufrufe spart man sich hier auch nicht mehr, weil da eh schon die ganze Funktion nicht aufgerufen würde.
    Solche Regeln wie "Variablen an den Anfang eines Blocks" sind für 200-Zeilen Monster geschaffen worden, um irgendwie den Anhauch von Übersichtlichkeit zu erhalten. Bei gescheiten Funktionen hast du nicht so die Wahl und machst einfach was gerade passt.



  • Was ist, wenn ich eine Funktion habe, wo an verschiedenen Stellen "returned" wird?
    Was, wenn ich mit Exceptions programmiere und meine Funktion eine solche wirft?
    Und was passiert, wenn ich in dieser Funktion am Anfang ein paar Objekte erstelle, die ich evtl. nur selten in dieser Funktion brauche, diese aber für ein Array mit 10.000 Elementen aufgerufen wird?

    Da werden die Objekte jedes Mal zu Beginn erstellt und am Ende wieder zerstört, obwohl ich gar nichts damit mache. Kann sein, dass ich das falsch sehe, aber wird da nicht u.U. ein enormer Performanceverlust in Kauf genommen?

    Ob ich jetzt ein

    int preis;
    ...
    preis = ...;
    

    habe oder ein

    ...
    int preis = ...;
    

    ist doch eigentlich völlig egal, weil ich die Variable (das Objekt) ja sowieso erst brauche, wenn ich das erste Mal etwas zuweise (bzw. erstelle und damit mache). Auch, wenn mein Compiler vielleicht viel optimiert, evtl. wird der Code auch mit einem anderen Compiler übersetzt, der nicht so dolle optimiert.

    Um's kurz zu machen: Ich finde diese Richtlinie nicht so dolle. 😃



  • Die meisten solcher Richtlinien sind bescheuert. Muß man ja einfach mal sagen. Genauso wie diese: "Alle Membervariablem müssen mit einem Unterstrich am Anfang versehen werden." Gibt es tatsächlich bei uns. Voll hohl!

    // in irgend einer Klasse:
    public void foo(int a) {
      _b = _b + a;
    }
    

    Ohne Unterstrichvorschrift:

    // in irgend einer Klasse:
    public void foo(int a) {
      b = b + a;
    }
    

    Kann mir jemand mal sagen, wo der Vorteil bei der ersten Variante sein soll? Wieso in Gottes Namen, soll ein Programmierer angeblich nicht an Variante 2 sehen können, das b eine Membervariable ist? Diesen Programmierer, der das nicht sieht, sollte nochmal über seinen Job nachdenken! 😡



  • Optimizer schrieb:

    Glaubst du nicht, ich könnte sie noch weiter zerlegen? Wenn es Sinn macht, tu ich es sofort. Kann man aber bei so Beispiel-Funktionen jetzt nicht entscheiden. Versuch mal, das im übertragenen Sinn zu sehen und nicht wortwörtlich zu nehmen.

    Dir würde ich nahelegen, dass du nicht die Frage, ob du Variablen nach oben tust oder nicht entscheiden sollst, sondern dass du die Funktionen so klein machst, dass es kein richtiges oben und unten mehr gibt. Ob es 2 oder 5 Zeilen sind, spielt doch keine Rolle. Kein Mensch verliert in so einer Funktion die Übersicht, außer sie ist in Ook! geschrieben. Tatsächlich hat man in einer kleinen Funktion automatisch mehr die Tendenz, Variablen nach oben zu tun. Im Extrembeispiel von 2 Anweisungen ist es eh klar. Konstruktoraufrufe spart man sich hier auch nicht mehr, weil da eh schon die ganze Funktion nicht aufgerufen würde.
    Solche Regeln wie "Variablen an den Anfang eines Blocks" sind für 200-Zeilen Monster geschaffen worden, um irgendwie den Anhauch von Übersichtlichkeit zu erhalten. Bei gescheiten Funktionen hast du nicht so die Wahl und machst einfach was gerade passt.

    es macht eben NICHT immer Sinn eine Funktion weiter zu zerlegen. Ja es ist sogar manchmal garnicht sinnvoll möglich, weil du dann ernorme schwierigkeiten mit variablen bekommst (grade bei java).. Auch wenn im Ansatz die Aussage richtig ist, übertreibst du es maßlos. Es KANN sinnvoll sein, aber zwanghaftes zerlegen von funktionen ist NICHT immer sinnvoll, sondern eben nur manchmal..

    nehmen wir doch einfach das Beispiel von der ersten Seite. Natürlich kann man das ganze auch so schreiben:

    public static void main (String [] args) {
        printDatumMauerfall();
     }
    
     static void printDatumMauerfall()
     {
       int tag = 9;
       int monat = 11;
       int jahr = 1989;
    
       System.out.println("Datum des Mauerfalls:");
       System.out.println(tag+" "+monat+" "+jahr);
       printAlterPreis(tag,monat,jahr);
     }
    
     static void printAlterPreis(int tag, int monat, int jahr)
     {   
       double preis = 3.29;
    
       System.out.println("Preis einer Flasche Sekt am Tag des Mauerfalls in DM:");
       System.out.println(preis); 
       printNeuerPreis(tag,monat,jahr,preis);
     }
    
     static void printNeuerPreis(int tag, int monat, int jahr, double preis)
     {
       double neuerpreis = 2 * preis;
    
        System.out.println("Preis einer Flasche Sekt heute in EUR:");
        System.out.println(neuerpreis);
     }
    

    Das ist aber eben NICHT sinnvoll (die namensgebung der funktionen ist absichtlich scheiße) 🙂



  • mantiz schrieb:

    Was ist, wenn ich eine Funktion habe, wo an verschiedenen Stellen "returned" wird?
    Was, wenn ich mit Exceptions programmiere und meine Funktion eine solche wirft?
    Und was passiert, wenn ich in dieser Funktion am Anfang ein paar Objekte erstelle, die ich evtl. nur selten in dieser Funktion brauche, diese aber für ein Array mit 10.000 Elementen aufgerufen wird?

    Was ist genau das Problem?

    Da werden die Objekte jedes Mal zu Beginn erstellt und am Ende wieder zerstört, obwohl ich gar nichts damit mache. Kann sein, dass ich das falsch sehe, aber wird da nicht u.U. ein enormer Performanceverlust in Kauf genommen?

    Richtig, wenn man nicht gescheit zerlegt. Ich konstruiere mit Sicherheit keine unnötigen Objekte.

    Ich definiere Variablen auch erst, wenn ich sie brauche und aus technischer Sicht hast du völlig Recht. Grundsätzlich sind Definitionen mit Initialisierung sowieso schöner. Aber die besseren Funktionen von mir sind so kurz, dass die Variablen automatisch am Anfang stehen und dort initialisiert werden. Die Regel krampfhaft einzuhalten ist Unsinn, habe ich auch gesagt.

    life schrieb:

    es macht eben NICHT immer Sinn eine Funktion weiter zu zerlegen. Ja es ist sogar manchmal garnicht sinnvoll möglich, weil du dann ernorme schwierigkeiten mit variablen bekommst (grade bei java).. Auch wenn im Ansatz die Aussage richtig ist, übertreibst du es maßlos. Es KANN sinnvoll sein, aber zwanghaftes zerlegen von funktionen ist NICHT immer sinnvoll, sondern eben nur manchmal..

    nehmen wir doch einfach das Beispiel von der ersten Seite. Natürlich kann man das ganze auch so schreiben:

    public static void main (String [] args) {
        printDatumMauerfall();
     }
    
     static void printDatumMauerfall()
     {
       int tag = 9;
       int monat = 11;
       int jahr = 1989;
    
       System.out.println("Datum des Mauerfalls:");
       System.out.println(tag+" "+monat+" "+jahr);
       printAlterPreis(tag,monat,jahr);
     }
     
     static void printAlterPreis(int tag, int monat, int jahr)
     {   
       double preis = 3.29;
    
       System.out.println("Preis einer Flasche Sekt am Tag des Mauerfalls in DM:");
       System.out.println(preis); 
       printNeuerPreis(tag,monat,jahr,preis);
     }
     
     static void printNeuerPreis(int tag, int monat, int jahr, double preis)
     {
       double neuerpreis = 2 * preis;
    
        System.out.println("Preis einer Flasche Sekt heute in EUR:");
        System.out.println(neuerpreis);
     }
    

    Das ist aber eben NICHT sinnvoll (die namensgebung der funktionen ist absichtlich scheiße) 🙂

    Du hast Recht, diese Zerlegung ist nicht sinnvoll. printAlterPreis(), printNeuerPreis()? Denk mal nach, was hier nicht stimmt. printAlterPreis druckt den neuen Preis mit? Denk mal nach, was hier nicht stimmt. Außerdem sind die Funktionen zu lang.

    Dass irgendwann eine Grenze des sinnvollen erreicht ist, habe ich selber bereits geschrieben. In diesem Beispiel ist das aber nicht der Fall, du zerlegst nur nicht richtig.



  • Optimizer schrieb:

    Du hast Recht, diese Zerlegung ist nicht sinnvoll. printAlterPreis(), printNeuerPreis()? Denk mal nach, was hier nicht stimmt. printAlterPreis druckt den neuen Preis mit? Denk mal nach, was hier nicht stimmt. Außerdem sind die Funktionen zu lang.

    Dass irgendwann eine Grenze des sinnvollen erreicht ist, habe ich selber bereits geschrieben. In diesem Beispiel ist das aber nicht der Fall, du zerlegst nur nicht richtig.

    du hast es wohl immernoch nicht verstanden :(. Dabei war das Beispiel so einfach 🙄. Nagut dann nochmal langsam zum mitschreiben: Die Namen sind absichtlich so gewählt, wie bereits geschrieben, um genau auf das Problem aufmerksam zu machen. Man kann es nunmal nicht die ganzen Aufrufe in die main packen, weil man eben noch die ganzen variablen braucht. Die alle zurückzugeben wäre zum einen sehr umständlich (referenzübergabe gibts nicht bei java oder?) und zum anderen hätteste dann dann genau das gleiche problem wie vorher, weil du ja nicht einfach schreiben darfst: int alterpreis = getAlterpreis();
    Um zu gewährleisten, dass das Programm sich genauso verhält wie das ausgangsprogramm bekommt man halt so einen sinnlosen code. Kannst mich aber gern vom gegenteil überzeugen (kannst ja noch mehr funktionen benutzen wie du gesagt hast). Bedenke aber, dass die sichtbarkeit von den ganzen variablen nicht verändert werden darf, damit sich das programm auch wirklich so wie das ausgangsprogramm verhält. Vielleicht motiviert dich das ja mal endlich dein gehirn einzuschalten, denn ich glaube nicht, dass du wirklich so schwer von begriff bist, wie du hier tust 🙂

    PS: und komm mir nicht mit irgendwelchen globalen variablen o.ä., wie getJahr() {return 1989;}



  • Optimizer: Stellst du dir das so in etwa vor? Oder würdest du noch weiter zerlegen oder anders? Vielleicht die Methode printDatumMauerfall noch ein bischen zerlegen? 🙂

    public static void main (String [] args) 
    {
       printDatumMauerfall();
       printAlterPreis(gibAlterPreis());
       printNeuerPreis(berechneNeuerPreis(gibAlterPreis()));
    }
    
    public static void printDatumMauerfall()
    {
       int tag = 9;
       int monat = 11;
       int jahr = 1989;
    
       System.out.println("Datum des Mauerfalls:");
       System.out.println(tag+" "+monat+" "+jahr);
    }
    
    public static void printAlterPreis(double preis)
    {   
       System.out.println("Preis einer Flasche Sekt am Tag des Mauerfalls in DM:");
       System.out.println(preis);
    }
    
    public static void printNeuerPreis(double neuerPreis)
    {
       System.out.println("Preis einer Flasche Sekt heute in EUR:");
       System.out.println(neuerPreis);
    }
    
    public static double gibAlterPreis()
    {
       return 3.29;
    }
    
    public static double berechneNeuerPreis(double alterPreis)
    {
       return 2.0 * alterpreis;
    }
    

Anmelden zum Antworten