qualitativ hochwertiger Programmcode...



  • shoat schrieb:

    volkard schrieb:

    die von programmen gemessenen metriken wie "zeilen kommentar/zeilen code" oder "2 punkte für ein if, 5 für ein while und 6 für ein for und dann punkte pro funktion" taugen nix.

    Ein Schwerpunkt meine Arbeit soll evtl. Metriken sein!

    ich weiß. das steht bereits recht offen im titel der arbeit.

    Soweit ich es bisher verstanden habe, dann Metriken eine Möglichkeit eine Programmcode mit Hilfe von bestimmten Berechnungsverfahren zu überprüfen. (Ich habe mich noch nicht sehr damit befaßt!)

    ja.

    Warum taugen sie nix? Wie soll ich meinem Auftraggeber begründen, dass die Mühe hier nicht lohnt?

    beispiel: kommentare pro zeile

    double* find(double toFind,double* arr,int size)
    {
       for(int i=0;i<size;++i)
          if(arr[i]==toFind)
             return &arr[i];
       return 0;
    }
    

    diese funktion krieg offensichlich null punkte, obgleich sie fast perfekt ist.
    dafür bekommt aber dieser humbug total viele punkte:

    /****************************************************************************
    FUNKTION find
    Parameter: toFind - Zahl, die gesucht wird
    Parameter: arr - Array, das durchsucht wird
    Parameter: size - Größe des Arrays
    Rückgabewert: Adresse der gefundenen Zahl
    Zweck: Diese Funktion such eine Zahl und gibt deren Adresse zurück. Sie gibt 
    NULL zurück, wenn keine gefunden wurde.
    ****************************************************************************/
    double* find(double toFind,double* arr,int size)
    {
       int i,*result=NULL;
       for(i=0;i<size&&result;++i)
          if(arr[i]==toFind)
             result=arr[i]+i;
       return result;
    }
    

    die funktion deklariert i und *result in einer zeile, was unfug ist, weil a) i innerhalb des for lokaler wäre, b) result=NULL ein eigener bedeutungsinhalt ist, der ne eigene zeile verdient, c) result=NULL eh weg sollte, weils umständlich ist. sie hat ne zu große laufbedingung. sie will die adresse des i-ten elements und schreibt iteratorensyntax statt "adresse von a[i]". also einfach nur schlecht lesbar, die funktion.

    Und dann soll es noch leute geben, die normalerweise verdammt wenig code erzeugen. die es halt oft schaffen, mit einem zehntel dessen auszukommen, was die kollegen verbraten. allein die kleinheit ist offensichlicher gewaltiger wartungsvorteil. den erkennt aber keine metrik. es werden ja nicht zwei lösungen zum selben problem verglichen und kein bot (und nicht einer von 100 chefs) kann wissen, wieviel code man wirklich braucht.

    void Mobile::go(int richtung)
    {
       switch(richtung)
       {
          case 0: goNorthWest(); break;
          case 1: goNorth(); break;
          case 2: goNorthEast(); break;
          case 3: goWest(); break;
          case 4: ; break;
          case 5: East(); break;
          case 6: goSouthWest(); break;
          case 7: goSouth(); break;
          case 8: goSouthEast(); break;
       }
    }
    void goNorthWest()
    {
       x-=1;
       y-=1;
    }
    //..und noch sieben funktionen
    //(1*10+8*2)/9==2.9 zeilen pro funktion
    

    zwerfällt bei langem anstarren zu
    void Mobile::go(int richtung)
    {
    int rx=richtung%3-1;
    int ry=richting/3-1;
    x+=rx;
    y+=ry;
    }
    //4 zeilen pro funktion, schlechter gemetirkt.
    [/cpp]

    die metriken helfen meiner meinung nach, um zu bewerten, ob idiot a besser codet als idiot b, aber sie sperren reihenweise die sehr guten lösungen aus.

    was ist, wenn der, der das MVC-konzept verwendet, um das modell von der oberfläche abzukoppeln, um das modell sogar plattformunabhängig zu haben, das mit einem if pro modellklasse bezahlen muß? die blinden metriken sehen überhaupt nicht, obs was taugt. haben keine ahnung von exceptionsicherheit. geben keinen minuspunkt, weil obige find-funktion, obwohl sie danach schreit, nicht zum template wurde. geben nix auf schlechte kommentare und erst recht nix auf schlechten bezeichner.

    deinem chef kannste eigentlich nicht begründen, daß metriken, außer in seltenen spezialfällen nix taugen. immerhin hat er schon davon gehört. und metriken begeistern chefs. endlich ein werkzeug, um den entwicklern da unten mal auf die finger zu schauen.
    aber ich kenne deinen chef nicht. er könnte auch der eine von hundert sein.



  • Marc++us schrieb:

    Das mag ich aus psychologischen Gründen nicht. Damit schafft man von Anbeginn an eine Rechtfertigung für Fehler, nach dem Motto "das wird sowieso nicht laufen, das weiß ja jeder, war schon immer so".

    Du bemerkst vielleicht an meiner Zögerlichen Art, vorzuschlagen, dass die Wartbarkeit stärker bewertet werden sollte, dass ich diesen Gedanken natürlich auch shcon hatte. Wartbarkeit beinhaltet allerdings nicht nur die Behebung von Fehlern sondern auch ggf. die Änderung oder das Hinzufügen von Verhalten und Funktionsweisen, da der Kunde erfahrungsgemäss erst exakt weiss was er will, nachdem er den ersten Kontakt mit dem Entwurf hatte.

    Es sollte selbstverständlich nicht als Rechtfertigung für Fehler dargestellt werden. Das macht keinen Sinnn und ist auch blöd. Aber das sollte eigentlich jedem auch klar sein. Ausserdem ist eine fehlerfreie Anwendung ja irgendwo durch auch eine Visitenkarte. "Ah der Kerl schreibt software die im ersten Release bestimmt nicht läuft!" oder "Ah, die Software des Typen läuft immer schon im ersten Release den er in die Hand gibt". Mir persönlich wäre letzteres Bild lieber (auch wenn ich leider weit davon entfernt bin. aber da bin ich selberschuld.)

    Fehler sind immer ärgerlich (sowohl für den Produzenten als auch den Konsumenten) und müssen daher immer möglichst vermieden werden. Trotzdem halte ich jeden der behauptet auf Anhieb fehlerfreie Software (komplexer als ein Hello World selbstverständlich (o; ) zu produzieren für einen Aufschneider... Was nicht heissen muss, dass er sich nicht bemüht möglichst fehlerfrei zu arbeiten. Aber wir sind alles menschen. UNd kein System, egal wie gut, sützt vor der Dummheit und Bequemlichkeit der Menschen die es anwenden (sollten). Naja lange rede kurzer sinn: Ich denke nicht, dass man die Heraufstufung der Wartbarkeit als Entschuldigung bzw. Rechtfertigung für Fehler interpretieren sollte... Das wäre ja eine Interpretation ala "ich hab ja nen Pneuhändler, also fahr ich doch einfach über das Nagelbrett drüber" (wobei gilt Pneuhändler = Wartbarkeit bzw. Möglichkeit Probleme zu fixen und Nagelbrett = potentieller Bug) und wer bitteschön denkt schon so?

    -junix



  • @ volkard / Metriken

    Ich habe das Problem verstanden. Deswegen werde ich mich mal in das Thema selber kurz einarbeiten, um eine eigene Meinung zu haben. Dabei werde ich immer Deine Warnung im Hinterkopf behalten! In jedem Fall werde ich danach meinem "Chef" die Probleme darlegen. Mal schauen, was er dazu sagt.
    (Vielleicht ist er ja der eine von hundert.)

    Zum Glück soll mein Hauptschwerpunkt ja auch "automatisertes Testen" sein. Hoffentlich hast Du dagegen nicht auch einen berechtigten starken Einwand.

    Vielen Dank für Deinen Beitrag!
    Und viele Grüße!

    Ciao
    shoat



  • shoat schrieb:

    Zum Glück soll mein Hauptschwerpunkt ja auch "automatisertes Testen" sein. Hoffentlich hast Du dagegen nicht auch einen berechtigten starken Einwand.

    automatisiertes testen ist klasse.



    1. kurze Entwicklungszeit
      Wenn die Sache zu lange dauert, bekomme ich kein Geld, Leute werden ungeduldig - dann komme ich nie zur Wartung oder Wiederverwendung

    Genau das ist der Punkt. Ich als Hobbyprogrammierer stehe unter keinerlei Zeitdruck und kann soviel Zeit mit einem wiederverwendbaren Desing verbrauchen, wie ich will.

    Wie zuvor ausgeführt, beruht Wartbarkeit auch auf gutem Design des Programms.

    Ich würde das "auch" durch "größten Teils" ersetzen.

    Wie stellst Du sicher, dass Programmcode möglichst fehlerfrei ist?

    Unit-Tests. Unglaublich hilfreich. Ansonten Pre-Konditionen. Postkonditionen sind In C++ meistens nicht ganz so leicht umzusetzen.
    Ansonten sind es immer sehr Situatonsabhängige Dinge.



  • shoat schrieb:

    Vielleicht als Anregung hier gleich mal ein paar Dinge, die mir spontan dazu eingefallen sind.
    Qualitativ hochwertiger Code:

    ist (möglichst) fehlerfrei -> er funktioniert -> verschiedene Testverfahren anwenden.

    Fehlerfrei sollte jeder Code sein, das mit der Funktion ist doppelt gemoppelt.

    shoat schrieb:

    ist einfach und leichtverständlich geschrieben -> damit er wartbar und von anderen verstanden werden kann -> erreichbar durch gute Kommentierung und Einhaltung von Programmierrichtlinien.

    Ich würde einfach sagen: Es sollten so viele Zeilen kommentiert sein, wie möglich und die Variablen sollten sinnvolle Namen haben, z.B. statt Button1 sollte man OK_Button nehmen usw.

    shoat schrieb:

    wird nur einmal geschrieben und kann dann beliebig oft wiederverwendet werden -> write once use anywhere -> durch Objektorientierung.

    Und vor allem sollte es so wenig wie möglich code sein, d.h. kürzen, kürzen, kürzen.

    Du solltest nicht so allgemein denken, sondern wirklich ins Detail gehen, denn we willst du sonst 100 Seiten voll kriegen?



  • Windoof schrieb:

    Fehlerfrei sollte jeder Code sein, das mit der Funktion ist doppelt gemoppelt.

    Nö, wieso? Ich kann fehlerfreien Code schreiben, dessen Funktion bestimmt nicht dem entspricht, was ich eigentlich wollte... Nur dass der compiler drüberrauscht ohne zu meckern, bedeutet nicht, dass der Code richtig ist.

    Windoof schrieb:

    Ich würde einfach sagen: Es sollten so viele Zeilen kommentiert sein, wie möglich

    Soviel Kommentar wie nötig, dünkt mich die bessere Formulierung.

    Windoof schrieb:

    und die Variablen sollten sinnvolle Namen haben, z.B. statt Button1 sollte man OK_Button nehmen usw.

    Selbstredend. denn damit ersparst du dir wieder Kommentare -> Soviel Kommentar wie nötig bzw. selbstdokumentierende Software

    Windoof schrieb:

    Und vor allem sollte es so wenig wie möglich code sein, d.h. kürzen, kürzen, kürzen.

    Kann ich so nicht unterstützen. Wieso extra kryptisch codieren, und dafür 5 Zeilen kommentar schreiben, wenn ich mit ein paar Zeilen mehr code und vielleicht noch ein paar Zwischenvariablen den Code mehr oder weniger selbsterklärend gestalten kann? Es ist ein Abwägen, was wann wieviel sinn macht. Bedenke: Der compiler optimiert auch selber einen grossen Teil des codes...

    Windoof schrieb:

    Du solltest nicht so allgemein denken, sondern wirklich ins Detail gehen, denn we willst du sonst 100 Seiten voll kriegen?

    Es geht wohl nicht darum 100 Seiten zu füllen, sondern um qualitativ guten Inhalt abzuliefern. Und da ist ein Aufbau mit einer Überleitung von Allgmeinen Apsekten zu detailierteren Betrachtungen gar nicht mal so schlecht. Nur kann er die gut selber detailierter betrachten, denn er soll ja die Arbeit selber schreiben. Was er allerdings im Moment macht ist sehrgut: Er holt sich aspekte aus einem grossen Wissens und Ideenpool...

    -junix



  • Hallo Helium!

    Helium schrieb:

    Wie stellst Du sicher, dass Programmcode möglichst fehlerfrei ist?

    Unit-Tests. Unglaublich hilfreich. Ansonten Pre-Konditionen. Postkonditionen sind In C++ meistens nicht ganz so leicht umzusetzen.

    Wäre toll, wenn Du mir kurz erklären könntest, was es mit Pre- und Postkonditionen auf sich hat. Ich habe davon noch nie was gehört und auch in den Büchern, die ich lese, gibt es darauf bisher keinen Hinweis. Ich bin auch für einen Link sehr dankbar!

    Natürlich habe ich mal auch mal kurz gegooglt, aber unter den deutschen Begriffen gibt es nur zwei Treffer, wo ich den geistreichen Hinweis bekomme, dass es sich um Vor- / Nachbedingungen handelt.
    (Einmal im Zusammenhang mit UML-Diagrammen und einmal im Zusammenhang mit dem Compiler.)

    Bei den englischen Begriffen ist es uferlos (~ 185.000 Treffer).

    Danke für Deine Hilfe!

    Ciao
    shoat



  • shoat schrieb:

    Wäre toll, wenn Du mir kurz erklären könntest, was es mit Pre- und Postkonditionen auf sich hat. Ich habe davon noch nie was gehört und auch in den Büchern, die ich lese, gibt es darauf bisher keinen Hinweis. Ich bin auch für einen Link sehr dankbar!

    Change books.

    Auszug:

    *Beschreibung von Vor- und Nachbedingungen
    Ein üblicher und sehr hilfreicher Ansatz bei der Dokumentation von Verhalten jeglicher Art ist die Einführung von Vor- und Nachbedingungen. Dieser Ansatz kann zusammen mit anderen textbasierten Methoden, aber auch mit grafischen Ansätzen verwendet werden.

     Vorbedingung: eine Vorbedingung ist eine Aussage, die das Gesamtgebilde erfüllen muss bevor ein Verhalten abläuft. Die Existenz dient dazu sicherzustellen, dass das Verhalten wie geplant ablaufen kann. Zum Beispiel muss zur Absage einer Zimmerreservierung zunächst eine erfolgreiche Zimmerreservierung erfolgt sein, und Sie müssen dabei die Person sein, die das Zimmer reserviert hatte.

     Nachbedingung: eine Nachbedingung ist eine Aussage, die erfüllt sein muss nachdem ein Verhalten erfolgreich abgelaufen ist. Zum Beispiel ist nach erfolgreicher Stornierung einer Zimmerreservierung der Raum als frei gekennzeichnet und die Belastung der Kreditkarte wurde annulliert.

     Invariante: abgesehen von Vor- und Nachbedingungen müssen Sie Invarianten («Unveränderliche») garantieren - Bedingungen die vor und nach der Ausführung eines Verhaltens erfüllt sind. Zum Beispiel ist die Anzahl der Personen pro Raum an einem bestimmten Tag niemals kleiner 0.

    Invarianten sind echte Bedingungen, die immer wenn ein anders Objekt auf das ausführende Objekt schaut erfüllt sind. Bei Multithreading-Systemen, wo ein Objekt mehr als eine Operation gleichzeitig ausführen kann, ist es möglich dass ein Objekt eine Operation ausführt und gleichzeitig sein Status abgefragt wird. Das bedeutet, dass die Invariante niemals, nicht einmal kurzfristig, verletzt werden darf.

    Wenn Sie einen vollständigen Satz an Vor- und Nachbedingungen für ein Verhalten abliefern, definieren Sie das Verhalten ohne Annahmen über das Design zu treffen. Jeder Aufrufer des Verhaltens oder der Operation versucht sicher zu stellen, dass die Vorbedingungen erfüllt sind bevor er das Verhalten aufruft. Danach garantiert das aufgerufene Objekt, dass die Nachbedingungen erfüllt werden - nachdem das Verhalten beendet ist. Dieser Ansatz wird manchmal auch als Design-by-Contract (nicht wörtlich übersetzbar, sinngemäß ein Design nach Absprache) bezeichnet. Hiermit wird dem Designer erlaubt, dass er tut was er will, solange er die Absprachen einhält.*



  • Äh Marcus: hast du zu meinem Obigen Statement (26 Aug 2003 10:59) noch irgendwelche korrigierende Kommentare, oder kann man das so stehenlassen? Würde mich wirklich interessieren, denn ich stell mir die Frage "Was ist qualitativ guter code" auch desöfteren.

    -junix



  • Hallo volkard!

    volkard schrieb:

    void Mobile::go(int richtung)
    {
       switch(richtung)
       {
          case 0: goNorthWest(); break;
          case 1: goNorth(); break;
          case 2: goNorthEast(); break;
          case 3: goWest(); break;
          case 4: ; break;
          case 5: East(); break;
          case 6: goSouthWest(); break;
          case 7: goSouth(); break;
          case 8: goSouthEast(); break;
       }
    }
    void goNorthWest()
    {
       x-=1;
       y-=1;
    }
    //..und noch sieben funktionen
    //(1*10+8*2)/9==2.9 zeilen pro funktion
    

    zerfällt bei langem anstarren zu

    void Mobile::go(int richtung)
    {
       int rx=richtung%3-1;
       int ry=richting/3-1;
       x+=rx;
       y+=ry;
    }
    //4 zeilen pro funktion, schlechter gemetirkt.
    

    Du hast wirklich vollkommen recht, dass es die obere Lösung vollkommener Schwachsinn ist. Und natürlich ist die untere Lösung viel besser. Aber in der Form kann man sie meiner Meinung nach unmöglich belassen. So würde ich sie einstellen.

    public class Mobile
    {
        protected int posX, posY ;
    
        ...
    
        /**
         * Versetzt das Mobile in die angegebene "richtung".
         * @param 	richtung    Gibt die Richtung an. Dabei bedeutet richtung%9 
         *                        gleich 0 linksunten, 1 unten, 2 rechtsunten, 
         *                        3 links, ...
         */
        public void go (int richtung)
        {
            this.posX += richtung%3-1 ;
            this.posY += richtung/3-1 ;
        }
    }
    

    So finde ich es besser. Damit schneidet die Funktion (denke ich?) auch bei Metriken gut ab und nichts von Deiner guten Lösung geht verloren.

    double* find(double toFind,double* arr,int size) 
    { 
       for(int i=0;i<size;++i) 
          if(arr[i]==toFind) 
             return &arr[i]; 
       return 0; 
    }
    

    Guter Algorithmus, aber auch zu dieser Funktion (zumindest, wenn sie public ist) gehört meinesachtens eine Schnittstellenbeschreibung!

    Warum? Ganz einfach. Wenn Dritte Deinen Code nutzen sollen, dann fällt ihnen dies viel leichter, wenn die nutzbaren Schnittstellen verständlich beschrieben sind. I. d. R. dauert es viel länger, wenn man erst den Quell-Code anschauen und verstehen muss.

    Oder was meinst Du dazu?

    Ciao
    shoat



  • junix schrieb:

    Äh Marcus: hast du zu meinem Obigen Statement (26 Aug 2003 10:59) noch irgendwelche korrigierende Kommentare, oder kann man das so stehenlassen? Würde mich wirklich interessieren, denn ich stell mir die Frage "Was ist qualitativ guter code" auch desöfteren.

    Du stellst am Schluß die Frage "wer denkt schon so?" - und da muß ich sagen: "zu viele".

    Du hast natürlich bzgl. der Wartbarkeit recht, trotzdem gibt es ja eine Wertigkeit, irgendwann muß man mal eine Entscheidung "entweder so oder so" treffen - und da wichte ich die Fehlerfreiheit als Ziel immer höher.

    Es hat noch so einen Nebeneffekt, weil die Punkte nicht wirklich getrennt sind: schlanke und übersichtliche Designs ermöglichen oft weitgehend fehlerfreie Programme (also: Algorithmus und Gesamtidee funktioniert wie gewünscht, aber es gibt hier und da mal kleine Lücken, oftmals im Zusammenhang mit der GUI). Und gleichzeitig sind diese Programme auch gut wartbar. Und Wartbarkeit (auch im Sinne von Erweiterungen) geht auf dann auch Hand in Hand mit der Wiederverwendung vorhandener Klassen/Objekte der Applikation.

    Also könnte man die Punkte gleich wichten? Und da sage ich vor meiner Erfahrung mit anderen Entwicklern ganz klar "Nein". Wenn mich einer fragt, sage ich immer "Fehlerfreiheit geht vor".

    Weißt Du warum? Softwareentwickler sind in ihrem Herzen alles wahnsinnige Spielkinder. Wenn ich einem Entwickler als wichtiges Ziel zu erkennen gebe, daß mir Wartbarkeit und/oder Wiederverwertbarkeit wichtige Ziele sind, so baut er mir ein riesiges Programm mit Klassen und Mustern und Interfaces und Komponenten, das von Mondflügen bis zur Waschmaschine alle Anwendungen abdecken kann, so eine Art Framework. Der eigentliche Auftrag des Programms wird dann als "Konfigurations- und Anpassproblem" definiert, "das machen wir noch". Natürlich ist es interessant so ein Framework zu entwickeln, elegante Lösungen zu definieren, usw. Macht mir auch mehr Spaß also so ein doofes Programm zu schreiben. Wenn 2 oder 3 Leute aber so arbeiten, treibt es einen in den Wahnsinn und man muß es rigoros unterbinden.

    Ich finde

    http://www.c-plusplus.net/titelanzeige.php?ISBN=3826613260

    ist dazu ein sehr erhellendes Buch, das meine Erfahrungen auch in einigen Punkten bestätigt hat.



  • Zu den Metriken:
    Ich teile volkards Meinung, daß sie nichts taugen. Die mir bekannten Metriken messen bloß, wie geschwätzig ein Entwickler ist. In Bezug auf die Qualität seines Codes können sie keine Aussage treffen.

    Ich glaube, Metriken sind eher ein Ausdruck der Hilflosigkeit (oder meinetwegen auch Überfroderung) des Führungspersonals, wenn es darum geht, die Arbeit der Entwickler zu beurteilen. Man frage sich selbst: Wenn bekannt ist, daß die "Qualität" meiner Arbeit anhand von Metriken ermittelt wird - wovon dann vielleicht meine Aufstiegschancen, mein Gehalt o.ä. abhängen - wie werde ich arbeiten, gut oder mit möglichst viel Code? So ein Quatsch!

    Guter Algorithmus, aber auch zu dieser Funktion (zumindest, wenn sie public ist) gehört meinesachtens eine Schnittstellenbeschreibung!

    Warum? Ganz einfach. Wenn Dritte Deinen Code nutzen sollen, dann fällt ihnen dies viel leichter, wenn die nutzbaren Schnittstellen verständlich beschrieben sind. I. d. R. dauert es viel länger, wenn man erst den Quell-Code anschauen und verstehen muss.

    Das sehe ich nicht ganz so. Ich finde es oft leichter, einen guten(!) Code zu verstehen, als die Schnittstellenbeschreibung zu lesen. Das trifft auf find() ebenso wie auf Mobile::go() zu. Allerdings ist der Code in beiden Fällen nicht optimal. Im Falle von find() fehlt eine Fehlerbehandlung (arr darf nicht 0 sein). BTW: In einem C++-Projekt gehört sich etwas wie find() natürliche ohnehin nicht.

    Im Falle von Mobile::go() sollte der Parameter nicht vom Typ int sondern Mobile::Richtung sein, der natürlich ein passender enum wäre. Damit beschreibt der Code die Funktion schon verdammt gut.

    Trotzdem gehört für mich eine Schnittstellenbeschreibung dazu. Diese allerdings sollte nichts enthalten, was man aus der Signatur der Funktionen entnehmen kann. Und dann bleibt bei den Beispielen wohl nicht allzu viel übrig. Natürlich ist eine Schnittstellenbeschreibung für jede Funktion angemessen, nicht nur für die public-Schnittstelle.

    Stefan.



  • Wobei das auch relativ ist und von der Anwendung (bzw. vom Kontext) abhängt.

    Methoden wie

    bool Sprite::checkForCollision(const Sprite& otherSprite)
    void Customer::loadFromXMLFile(const std::string& filename) throw IOErrors::LoadException;
    

    sind nur noch mit wirklich zusätzlichen Kommentaren trivial kommentierbar, d.h. ein

    /* Prüft ob eine Kollision zwischen dem aktuellen Spriteobjekt und otherSprite
       aufgetreten ist, liefert true im Kollisionsfall */
    /* Lädt Kundenobjekt aus der XML-Datei, deren Name in filename übergeben wird.
       Bei einem Parsingerror wird eine Exception geworfen */
    

    liefert definitiv keinen Zusatznutzen.



  • DStefan schrieb:

    Trotzdem gehört für mich eine Schnittstellenbeschreibung dazu. Diese allerdings sollte nichts enthalten, was man aus der Signatur der Funktionen entnehmen kann. Und dann bleibt bei den Beispielen wohl nicht allzu viel übrig. Natürlich ist eine Schnittstellenbeschreibung für jede Funktion angemessen, nicht nur für die public-Schnittstelle.

    Liest diese Schnittstellenbeschreibung wirklich jemand?
    Aus der Sicht desjenigen, der den Code an- und verwenden möchte ist Fließtext in einer gesprochenen Sprache selten semantisch so schön eindeutig, wie der Code einer Programmiersprache. Um die Missverständlichkeiten aus dem Weg zu räumen liest er den Code.
    Und der, der den (im Idealfall kaputten) Code zu warten oder wiederherzustellen hat, wittert sowieso hinter jeder Zeile Betrug und glaubt gar nichts.



  • Hallo Helium!

    Wäre toll, wenn Du mir kurz erklären könntest, was es mit Pre- und Postkonditionen auf sich hat. Ich habe davon noch nie was gehört und auch in den Büchern, die ich lese, gibt es darauf bisher keinen Hinweis. Ich bin auch für einen Link sehr dankbar!

    Nachdem Markus mit seinem Zitat alles erklärt hat kann ich wohl nichtmehr viel beitragen.

    C++, du scheinst scheinbar Java zu verwendent, da ist es genauso, bitet keine sprachseitige Unterstützung für solche Tests.

    Vorbedingungen werden in C++ deshalb meist so formuliert:

    double wurzel (double radiant)
    {
       assert (radiant >= 0);  // Vorbedingung
       // algorithmus, der die wurzel berechnet und zurückgibt
    }
    

    In diesem Fall ließ sich sogar eine Nachbedingung formulieren, wie

    assert(ergebnis * ergebnis == radiant);
    

    Das problem ist, das man häufig mehrere Austrittspunkte hat und deshalb an verschiedenen Stellen die Bedingungen stellen müsste, was selbst wiederum fehleranfällig ist. Man verändert den Code und vergisst dann mögliche neue Kondidtionen an einigen Stellen.

    Invarianten Funktionieren in etwa so: Angenommen du hast eine Klasse Datum dann könnte eine Methode invariant so aussehen:

    void invariant ()
    {
       assert (m_monat >= 1 && m_monat <= 12);
       assert (m_tag >= 1 && m_tag <= 31);
    }
    

    Invarianten werden für gewöhnlich nach jedem Aufruf einer Methode ausgeführt. Einige Sprachen automatisieren das, weshalb es in solchen Sprachen auch üblich ist.

    Wie schon erwähnt heißt das ganze Design-by-Contract. Der Vertrag mit dem Aufrufer besteht quasi darin, dass man sagt, wenn er korrekte Daten liefert, liefert der Aufgerufene ein korrektes Ergebnis.

    Da du nicht nach Unit-Tests gefragt hast scheinst du dazu genug Material zu haben. Sowas wie Test-Driven-Design.



  • DStefan schrieb:

    Zu den Metriken:
    Ich teile volkards Meinung, daß sie nichts taugen.

    Ich habe auch nie behauptet, dass ich Metriken gut finde, denn ich habe nicht genug Ahnung, um mir schon eine eigene Meinung zu erlauben. Und ich nehme zur Kenntnis, dass die Praxis (damit meine ich Euch) nicht sehr begeistert ist!

    Später komme ich bestimmt noch darauf zurück. Solange müßt ihr euch leider noch gedulden! 😉

    Es ist aber sehr gut, dass ihr mich schon so früh auf die Probleme gestoßen habt. Danke dafür!

    shoat schrieb:

    Wenn Dritte Deinen Code nutzen sollen, dann fällt ihnen dies viel leichter, wenn die nutzbaren Schnittstellen verständlich beschrieben sind. I. d. R. dauert es viel länger, wenn man erst den Quell-Code anschauen und verstehen muss.

    DStefan schrieb:

    Das sehe ich nicht ganz so. Ich finde es oft leichter, einen guten(!) Code zu verstehen, als die Schnittstellenbeschreibung zu lesen. Das trifft auf find() ebenso wie auf Mobile::go() zu.

    Ok! Da Du diese Aussage auf die beziehst, nehme ich sie so hin. Mir geht es manchmal auch so!

    Bei Volkards Mobile::go habe ich zum Verstehen jedenfalls ca. 1-2 min gebracht habe. Wäre allerdings in einem Kommentar irgendwie soetwas wie:

    // aktueller Standort des Mobile = X. 
    // Wähle eine Richtung (==Ziffer) zum Verändern des Standorts aus, wobei gilt:
    // 6 7 8
    // 3 X 5
    // 0 1 2
    

    gestanden, hätte nur durch hinschauen, dass System sofort verstanden. Ich hätte es zwar nicht verstanden wie die Funktion funktioniert, aber ich hätte sie nutzen können!

    Genau dass ist der Punkt! Uns (Studenten) wird von allen Seiten beigeracht, wenn wir von Dritten implementiere Funktionalitäten nur nutzen wollen: "Schau nicht den Code an, sondern nur die Beschreibung der Schnittstelle." Was soll ich machen, wenn es keine (gute) Schnittstellenbeschreibung gibt?

    Wenn man allerdings einen Code "warten" soll, Weiterentwicklen will oder auf Fehlersuche ist, dann sieht es natürlich ganz anders aus! Dann muss man sich den Code sowieso genau anschauen, deswegen wäre es vielleicht sehr gut, wenn man eine Unterscheidung zwischen:
    Kommentare, die auf Nutzer der Funktionalität des Codes abzielen
    Kommentare, die auf Prüfer/Weiterentwickler des Codes abzielen

    Natürlich ist es ganz klar, dass für letzter ganz andere Sachen wichtig sind. Zu qualitativ hochwertigem Code gehört aber beides dazu!

    Allgemein muss ich aber sagen, dass - meiner subjektiven Meinung nach - viel zu wenig kommentiert wird. Ich weiß nicht, wie es bei Euch in der Praxis aussieht. Aber alle, die ich kenne, sind mit den Kommentierungen von fremden Code (z. B. von Libs) oft unzufrieden. Da wird z. B. Wissen vorausgesetz, dass man einfach nicht hat oder es kommen Pseudo-Kommentare raus, wie:

    ...
    /** ...
     * @param xyz  dies ist der Parameter xyz der Funktion hasteNichtGesehen
     */
    public void hasteNichtGesehen (MysticUnknown xyz) { ... }
    ...
    

    DStefan schrieb:

    Allerdings ist der Code in beiden Fällen nicht optimal. Im Falle von find() fehlt eine Fehlerbehandlung (arr darf nicht 0 sein). BTW: In einem C++-Projekt gehört sich etwas wie find() natürliche ohnehin nicht.

    Die Fehlerbehandlung vergesse ich doch immer zu gern. Du hast natürlich vollkommen recht.

    DStefan schrieb:

    Im Falle von Mobile::go() sollte der Parameter nicht vom Typ int sondern Mobile::Richtung sein, der natürlich ein passender enum wäre. Damit beschreibt der Code die Funktion schon verdammt gut.

    Ich weiß zwar nicht, was ein enum ist, aber ich glaube Dir, dass es möglich ist, durch den Code die Funktion noch besser zu beschreiben.
    (Vielleicht bekomme ich ja ein kleines Quellcode-Fragment im Bezug auf Mobile::Richtung? Nur, wenn Du Zeit und Lust hast!!!)

    DStefan schrieb:

    Trotzdem gehört für mich eine Schnittstellenbeschreibung dazu.

    Dann sind wir ja einer Meinung!

    DStefan schrieb:

    Diese allerdings sollte nichts enthalten, was man aus der Signatur der Funktionen entnehmen kann. Und dann bleibt bei den Beispielen wohl nicht allzu viel übrig.

    Bin mir hier nicht ganz sicher, was Du mit Signatur meinst - den Prototypen?
    Da ich keine Ahnung hab, was man dem "enum" alles entnehmen kann, würde ich vorerst auf meine Fassung besstehen, da ich mir den Quellcode als reiner Nutzer des Codes nicht anschauen soll. (siehe auch unten)

    DStefan schrieb:

    Natürlich ist eine Schnittstellenbeschreibung für jede Funktion angemessen, nicht nur für die public-Schnittstelle.

    Denn Einwand mit dem public hatte ich nur deswegen gebracht, weil für mich als Javaer alles eine Klasse ist. Damit habe ich von außen nur Zugriff auf den public-Bereich. Alles andere interessiert mich in diesem Zusammenhang erst einmal nicht.

    Für die interne Wartung einer Klasse sind natürlich auch alle anderen Schnittstellen sauber zu beschreiben.

    Was hälst Du davon?
    Kommentare, die auf Nutzer der Funktionalität des Codes abzielen im public-Bereich und der allgemeinen Programmbeschreibung (meint Klassenbeschreibung)
    == ziemlich ausführlich / dummy-tauglich
    (in Java sind dass die /** */ Kommentare fürs javadoc)

    Kommentare, die auf Prüfer/Weiterentwickler des Codes abzielen überall
    == aber nur da, wo man es auch wirklich braucht / insider-tauglich
    das meiste sollte dem Code selber entnommen werden können)

    Vielen Dank für den Beitrag!

    Ciao
    shoat



  • shoat schrieb:

    (Vielleicht bekomme ich ja ein kleines Quellcode-Fragment im Bezug auf Mobile::Richtung? Nur, wenn Du Zeit und Lust hast!!!)

    ein enum ist nur eine aufzaehlung.

    statt
    m.go(3);
    schreibt man
    m.go(GO_RIGHT);
    oder so.

    ein enum ist ganz simpel:

    enum RICHTUNG
    {
      GO_RIGHT=1,
      GO_LEFT=2,
      GO_UP=3,
      .
      .
      .
    };
    

    natuerlich muessen die zahlen stimmn (und ich bin jetzt zufaul mir den code anzusehen um die richtigen zahlen herauszulesen. aber das prinzip sollte klar sein 😉



  • <@Marcus>
    Danke, damit hast du bestätigt, was ich mir bereits gedacht habe nach deiner Andeutung "aus psychologischen Gründen". Das Buch werd ich mir mal zu Gemüte führen, sieht interessant aus (auch wenn ich nicht Projektleiterpositionen einnehmen kann/will *g*).

    Ich glaube dir zwar, dass zu viele so denken wie ich es angetönt hatte, aber ich denke, man sollte nicht die "Regeln" an denen Anpassen sondern ein Umdenken lancieren. Klar. Man kann alte Wölfe nicht dazu bringen, aus der Hand zu fressen, aber den Nachwuchs müsste man doch eigentlich mit neuem Denken aufziehen können. Wobei das bestimmt ein Prozess ist, der länger dauert.

    Das Problem ist, beim Senken der Priorität der Wartbarkeit, dass dies auch misverstanden werden könnte...
    /@Marcus

    (zu Shades Beitrag)
    Das schöne an Shades Enum-Lösung sind eigentlich 2 Nebeneffekte:

    1. Das programm wird wieder selbstdokumentierende
    2. Durch das enum-Feld kann man eine Typensicherheit erstellen, was die Fehleranfälligkeit des Codes wieder verkleinert.

    -junix



  • Marc++us schrieb:

    Wobei das auch relativ ist und von der Anwendung (bzw. vom Kontext) abhängt.

    Methoden wie

    bool Sprite::checkForCollision(const Sprite& otherSprite)
    void Customer::loadFromXMLFile(const std::string& filename) throw IOErrors::LoadException;
    

    sind nur noch mit wirklich zusätzlichen Kommentaren trivial kommentierbar, d.h. ein

    /* Prüft ob eine Kollision zwischen dem aktuellen Spriteobjekt und otherSprite
       aufgetreten ist, liefert true im Kollisionsfall */
    /* Lädt Kundenobjekt aus der XML-Datei, deren Name in filename übergeben wird.
       Bei einem Parsingerror wird eine Exception geworfen */
    

    liefert definitiv keinen Zusatznutzen.

    Ich persönlich würde es trotzdem dazuschreiben, wenn es sich um public-Funktionen (s. o.) handelt.
    Denn eine Schnittstellenbeschreibung sollte unbedingt immer gemacht werden!

    Außerdem kann ich ja nicht wirklich wissen, was die Funktion macht, wenn ich mir den Code nicht genau anschaue. In diesen Fall ist es zwar unwahrscheinlich, aber könnte ja etwas fehlinterpretieren - man hat ja mit fremden Code schon genug erlebt. Also bin ich erst mal unsicher (zumindest, wenn es nicht gleich funktioniert). Wenn jetzt ein Fehler auftritt, dann weiß ich nicht woran es liegt, habe ich etwas falsch gemacht (eigener Fehler) oder habe ich nur etwas falsch verstanden (Verständnisfehler) oder ist der Fremd-Code einfach fehlerhaft (Fremd Fehler). Und so suche ich vor mich hin. Was wieder Zeit kostet. Vielleicht vertue ich mich auch noch beim Testen und dann ... (am Schluß komme ich dann vielleicht zu dem Schluß, dass es am Besten ist doch alles Selber zu programmieren.)

    Ich denke Du hast verstanden worauf ich hinaus will!

    Wenn allerdings genau dasteht was ein Funktion machen soll (== Schnittstellenbeschreibung), dann verlasse ich mich erst mal darauf - ich bin nicht unsicher. Und wenn dann Fehler auftreten, die nicht auftreten dürften, dann suche ich den Fehler wenigstens nicht bei mir!

    ( Ich bin mir natürlich auch dessen bewußt, dass es auch ein großes Problem darstellt leicht verständliche Kommentare zu schreiben! )

    Naja! Mal schauen, ob ich alles so auf Blatt gebracht habe, wie ich es meine. Wenn nicht muss ich es halt nochmal kommentieren!

    Vielen Dank für den Beitrag!

    Ciao
    shoat


Anmelden zum Antworten