Was ist für euch guter Programmcode?



  • volkard schrieb:

    Das heißt, dass man Parameter auf ihre Gültigkeit überprüft und nicht einfach nach irgendwohin weiterreicht oder mit ihnen rechnet: Wenn du in einem Setter die Gültigkeit des Parameters nicht überprüfst und die Membervariable einfach so auf den gegebenen Wert setzt, dann brauchst du dich nicht zu wundern, wenn irgendwo im Programm ein Fehler auftaucht, den du dir nicht erklären kannst.

    ok, das ist ein problem von java. in c++ muss man anders vorgehen. da ist generell der aufrufer dafür zuständig, sich an die spezifikationen zu halten.

    Kann ich jetzt nicht nachvollziehen. Wenn ich einer C++ Funktion nen ungültigen Parameter übergebe habe ich genauso ein Problem. Wieso sollte das bitte Java-spezifisch sein?

    volkard schrieb:

    Defensiver Programmierstil beschränkt sich aber natürlich nicht auf Fehlerchecks, sondern kann auch andere Aspekte beinhalten. Beispielsweise kann es manchmal sinnvoll sein, Kopien von Parametern oder Rückgabewerten anzulegen, damit nicht plötzlich jemand unbeabsichtig außerhalb des Objekts dieses verändert.

    auch ein privates java-problem.

    Ja, fehlende const-correctness ist vielleicht manchmal schade, aber manchmal sind immutable classes sinnvoll möglich und dann ist das unglaublich godlike. Da kann kein const dagegen anstinken. Insgesamt ist das System von Java aber unsicherer, ja.

    volkard schrieb:

    Weiterhin gehört ein sinnvolles Exception Handling natürlich auch zu einem defensiven Programmierstil.

    auch ein privates java-problem.

    Bitte? Kannst du das näher ausführen? Seit wann ist Exception-handling ein Java-Problem? Da kann doch Java nichts dafür, wenn die Stream I/O von C++ auf Wunsch auch Fehlercodes liefert. Und ich sehe auch keinen Vorteil darin. 🙄 Du musst in C++ genauso auf bestimmte Miserfolge reagieren.
    Und falls du auf die checked exceptions anspielst: Ich behaupte, dass die meisten Leute, die sie kritisieren sie einfach nur nicht verstehen. Java hat ein mächtiges Exceptionhandling und mit nested Exceptions kann man einiges machen. Wenn man das verstanden hat und richtig benutzt hat man nicht das geringste Problem damit, sondern ist dankbar.
    Falls du was anderes als Java-spezifisches Problem darstellen willst, dann kannst du das vielleicht nochmal auf den Punkt bringen?

    ok. die mittel sind anders. du willst so viele fehler wie möglich zur laufzeit fangen (und manchmal sogar einen vermeiden) und wir möchten so viele fehler wie möglich zu compilezeit vermeiden.

    Das konnte ich aus Gregor's Post nicht rauslesen. Du kannst Sachen wie ungültige Parameter nicht zur compile-zeit feststellen.

    Die meisten deiner Kritikpunkte an Gregor's Code finde ich als Java-Programmierer ok. Vor allem der Test, ob das Element schon drinsteckt ist nicht sinnvoll. In einer Map dürfen doppelte Elemente durchaus vorkommen und wenn es ein Set wäre, dann würde das interne Set das auch schon prüfen. null sollte auch erlaubt sein.

    @Gregor:

    /** 
        * This constructor constructs a new DirectedGraph with the initial capacity of a 
        * HashMap constructed by the default constructor. 
        */ 
       public DirectedGraph () 
       { 
          nodeMap = new HashMap<ElementType,Node> (); 
       }
    
    /** 
        * This constructor constructs a new DirectedGraph with the given initial capacity. 
        * @param initialCapacity This is initial capacity of the new DirectedGraph. The 
        *                        intitialCapacity may not be negative. 
        */ 
       public DirectedGraph (final int initialCapacity) 
       { 
          if (initialCapacity < 0) throw new IllegalArgumentException("InitialCapacity is negative."); 
          nodeMap = new HashMap<ElementType,Node> (initialCapacity); 
       }
    

    Ich würde die Konstruktoren sich gegenseitig aufrufen lassen. (Auch ne tolle Gelegenheit ein nettes Feature von Java zu demonstrieren 😉 ) Also:

    public DirectedGraph()
    {
        this(DEFAULT_CAPACITY);   // ruft DirectedGraph(DEFAULT_CAPACITY) auf.
    }
    


  • Optimizer schrieb:

    volkard schrieb:

    Weiterhin gehört ein sinnvolles Exception Handling natürlich auch zu einem defensiven Programmierstil.

    auch ein privates java-problem.

    Bitte? Kannst du das näher ausführen? Seit wann ist Exception-handling ein Java-Problem? Da kann doch Java nichts dafür, wenn die Stream I/O von C++ auf Wunsch auch Fehlercodes liefert. Und ich sehe auch keinen Vorteil darin. 🙄 Du musst in C++ genauso auf bestimmte Miserfolge reagieren.

    Nur dass du gutes Exceptionhandling in C++ daran erkennst, dass du es nicht siehst 😉
    Das ist der kleine Unterschied.
    Gutes Exceptionhandling in C++ braucht idR keinen oder besser kaum Code, weil du es gratis bekommst.



  • volkard schrieb:

    also wäre NodeType korrekt und nicht ElementType? merke: wenn man kommentieren muss, was ein name beschreibt, und das in einem satz schafft, dann ist dringend zu überlgenen, oder der name überhaupt klasse war.

    Da ist was dran. Der Name ist hier nicht ideal gewählt. Gebe ich zu.

    volkard schrieb:

    hier gehört übrigens in der tat eine beschreibung hin, was für ne klasse das ist. und was sehe ich? einen scheiss. ok, ein gerichteter graph also. aber wie implkementiert? als inzidentmatrix? die matrix gepackt (zum beisliel die zeilen als double linked lists, oder die ganze matrix als hashtable?) voll verzeigert (sorry, verreferenzt natürlich). ich muss nen groben überblick haben, wie die sache innen aussieht, damit ich sofort weiß, ob ich diese klasse für mein problem einsetzen kann. ist nicht gegeben.

    Ich sehe ein, dass ich vielleicht bestimmte Zusagen bezüglich der Komplexität von Methoden angeben sollte. Ich denke aber nicht, dass ich die Implementierung komplett verraten sollte. Dann würde ich mich diesbezüglich festlegen und darf das nie wieder ändern. Das wäre ja gegen das Design des Codes gerichtet. Naja, ok: Inwiefern es überhaupt realistisch ist, dass sich an dieser Klasse jemals etwas grundlegendes ändert, ist eine ganz andere Sache. Das gebe ich zu.

    volkard schrieb:

    typen und attribute am anfang. das ist gut. so kann ich mir wenigstens einigermaßen schnell einen überblick erahnen, was los sein wird. allerdings schon unfair gegen die, die weniger als 5 jahre erfahrung im graphengeschäft haben, fürchte ich.

    Willst du hier Kommentare haben? Das sind hier Dinge, die nicht zur öffentlichen Schnittstelle der Klasse gehören und einen Nutzer der Klasse nichts angehen. Wer sich damit auseinandersetzt, der will die Klasse selbst verändern und sollte dann auch das nötige Wissen hierzu mitbringen und den Code der Klasse komplett verstehen, bevor er etwas ändert. Ich denke, dass Kommentare da nicht allzu wichtig sind. Kann man aber natürlich auch anders sehen.

    volkard schrieb:

    /** 
        * This constructor constructs a new DirectedGraph with the given initial capacity. 
        * @param initialCapacity This is initial capacity of the new DirectedGraph. The 
        *                        intitialCapacity may not be negative. 
        */ 
       public DirectedGraph (final int initialCapacity) 
       { 
          if (initialCapacity < 0) throw new IllegalArgumentException("InitialCapacity is negative."); 
          nodeMap = new HashMap<ElementType,Node> (initialCapacity); 
       }
    

    obernutzlos. das alles.
    was macht dein if hier. heilige mantra "ich muss jedes argument prüfen". warum? new HashMap<ElementType,Node> (initialCapacity); wird schon prüfen.

    1. Kommentar: Nicht überflüssig, da Parameternamen in class-Dateien nicht vorhanden sind und man manchmal nur class-Dateien (und vielleicht Doku) zur Verfügung hat. Da hast du dann als Signatur dieses Construktors nur folgendes public DirectedGraph(int). Sehe ich als wirkliche Schwachstelle von Java an. In dem Fall ist es zumindest gut, wenn man in der Doku einen Hinweis darauf hat, um was es sich bei dem int handeln könnte. Bei mehreren Parametern gibt es da natürlich auch Probleme, ist aber besser als gar nichts. Abgesehen davon ist die Doku ohne diese Beschreibung einfach nicht komplett. 😃 Ist ne Frage der Ästhetik: Ein Javadoc-html-File mit leeren Kästchen ist einfach nicht angebracht. Ein Hinweis darauf, dass der Parameter nicht negativ sein darf ist auch angebracht. Ich könnte mit negativen Werten ja auch anders umgehen und sie zum Beispiel einfach auf 0 setzen oder so.
    2. Genau: Das heilige Mantra. 🙂 Ich prüfe grundsätzlich alles. Ich möchte nicht erst durch 5 Klassen navigieren, um zur Fehlerquelle zu gelangen. Die soll gefälligst möglichst nah an der Stelle liegen, an der ich zuerst nachgucke. BTW: Vielleicht geht die Hashmap ja auch ganz anders mit negativen Anfangskapazitäten um und schmeißt dann keine Exception. Ich weiß das gar nicht, aber ich möchte mich nicht darauf verlassen, zumal ich auf die Idee kommen könnte, die Implementierung der Klasse zu ändern und die Hashmap durch etwas anderes zu ersetzen. Dann habe ich da plötzlich etwas anderes stehen, habe aber nicht daran gedacht, den Check wieder einzubauen.

    volkard schrieb:

    ideal wäre es natürlich, einen typen zu haben, der aussagen kann, daß nur positive werte möglich sind. also cardinal oder unsigned oder so.

    Ja, hast Recht. Es gibt in jeder Sprache Dinge, an denen man vorbeidesignen muss.

    volkard schrieb:

    warum darf man keine null reinstecken?

    Weil ich es verbiete. Abgesehen davon funktioniert null nicht als Schlüssel einer HashMap in Java. ...AFAIK.

    volkard schrieb:

    aber das ist ja gar nicht das schlimme. das schlimme ist der test, ob das element schon da ist.

    Der ist absolut wichtig, weil ohne den Test jede Kante von und zu dem Knoten durch das "new Node()" gelöscht werden würde, wenn ein schon vorhandener Knoten nochmal hinzugefügt wird. Ich gebe dir aber Recht, dass man da auch eine Exception schmeißen könnte. Ich habe mich in diesem Fall anders entschieden. Nicht zuletzt aus dem Grund, dass dieses Verhalten mehr oder weniger durch die Schnittstelle vorgeschrieben wird. Gleiches gilt für die remove-Methode.

    volkard schrieb:

    was ist denn hier schief gelaufen?

    😃 Steck deinen salzigen Finger hier am Besten mal richtig tief in die Wunde. 😃 Was du da siehst ist neben einem Umgehen einer Java-Eigenart pures Unvermögen, vernünftigen Code produzieren zu können. 🙄 Ich habe bei dieser Methode echt ein schlechtes Gewissen.

    volkard schrieb:

    neighbors = new HashMap<NeighborType,LinkedList<Node>> (2,1.0f);
    

    verstehe ich nicht. wie hast due diese magic numbers bestimmt?

    Die sind eigentlich komplett überflüssig und dienen nur dazu, den Speicher etwas zu schonen. Durch die 1.0 gebe ich an, dass die HashMap voll sei soll, bevor sie erweitert wird, durch die 2 gebe ich an, dass voll=2 Elemente bedeutet. In diesem Fall sind es 2 Listen: Eine für die Vorgänger, eine für die Nachfolger des Knotens. Hätte ich aber benennen können und sollen. Gebe ich zu.

    volkard schrieb:

    public ElementType getValue() 
          { 
             assert(value != null); 
             return value; 
          }
    

    aha. nur mal zur neugier: was würde ohne das assert passieren?

    Erstmal genau das Gleiche. Assert muss man in Java extra anschalten. Sonst existiert es einfach nicht. ...kostet dann auch keine Performance. Checks, die nicht die öffentliche Schnittstelle der Klasse betreffen, sich also im inneren von Methoden bzw. im inneren der Klasse befinden, macht man in Java normalerweise mit assert. Ist eigentlich nur zum debuggen gedacht, um bei einem Fehler innerhalb der Klasse näher an die Fehlerquelle heranzukommen. Ich habe bereits gute Erfahrungen damit gemacht. Also, als Faustregel gilt in Java: Mit Exceptions reagiert man auf Fehler anderer, mit assert auf die eigenen.



  • Optimizer schrieb:

    Ja, fehlende const-correctness ist vielleicht manchmal schade, aber manchmal sind immutable classes sinnvoll möglich und dann ist das unglaublich godlike. Da kann kein const dagegen anstinken. Insgesamt ist das System von Java aber unsicherer, ja.

    Kann dir hier nur zustimmen, mit der Ergänzung, dass es in Java leider auch für immutable Klassen kein Sprachmittel gibt. Vielleicht denkt das jemand, der deinen Text liest. Man muss beim Programmieren mit Java schon aufpassen, damit eine immutable Klasse auch tatsächlich immutable wird.

    Optimizer schrieb:

    @Gregor:
    [...]
    Ich würde die Konstruktoren sich gegenseitig aufrufen lassen. (Auch ne tolle Gelegenheit ein nettes Feature von Java zu demonstrieren 😉 ) :

    Ja, könnte ich machen, ist aber kein Muss. ...zumal das hier nicht als Demonstration von Java Sprachmitteln gedacht war. Ich müßte mir mal Gedanken machen, inwiefern das hier angebracht ist.



  • Shade Of Mine schrieb:

    Nur dass du gutes Exceptionhandling in C++ daran erkennst, dass du es nicht siehst 😉
    Das ist der kleine Unterschied.
    Gutes Exceptionhandling in C++ braucht idR keinen oder besser kaum Code, weil du es gratis bekommst.

    Nur, falls hier ein Mißverständnis besteht: Die Exceptions in meiner Klasse da oben werden normalerweise alle nicht gefangen und müssen auch nicht gefangen werden. Das sind alles "RuntimeExceptions", die man normalerweise für solche Fehlerchecks bezüglich der Nutzung einer Klasse macht. Exceptions, auf die man mit einem catch reagieren muss, nutzt man in Java für andere Dinge: Nicht für Programmierfehler, sondern für Ausnahmesituationen, deren Ursache nicht im Code zu finden ist. Ein catch findet man auch in Java recht selten und beschränkt sich auf bestimmte Bereiche des Codes: IO und so.



  • In C++ returned er eine Kopie. Und eine Reference zurück geben, macht man nicht (auch wenn es möglich ist).

    liefere nie eine Referenz auf interne Daten zurück
    heißt hier der Merksatz

    Hmm, Kopien sind mir immer viel zu zeitraubend. Deswegen gebe ich immer Referenzen zurück, wenn es geht. Kennt ihr 'const'?

    da ist generell der aufrufer dafür zuständig, sich an die spezifikationen zu halten.

    Hmm, also ich halte die Annahmen, die ich über die übergebenen Parameter mache schon gerne in Form von Assertions fest.

    Das sind alles "RuntimeExceptions", die man normalerweise für solche Fehlerchecks bezüglich der Nutzung einer Klasse macht. Exceptions, auf die man mit einem catch reagieren muss, nutzt man in Java für andere Dinge: Nicht für Programmierfehler, sondern für Ausnahmesituationen, deren Ursache nicht im Code zu finden ist.

    Für Programmierfehler verwende ich keine Exceptions. Die gibt's nur in Ausnahmesituationen.



  • Helium schrieb:

    Für Programmierfehler verwende ich keine Exceptions. Die gibt's nur in Ausnahmesituationen.

    Was benutzt du? assert, oder?
    Dann fliegt halt ein AssertionError, abgeleitet von Error, abgeleitet von Throwable, wovon auch Exception abgeleitet ist.
    Und was passiert, wenn ein Throwable nicht gefangen wird? Genau das, was bei jeder Exception passiert, alle finallys werden ausgeführt, du kriegst nen schönen Stack Trace usw.
    Es ist also gehupft wie gesprungen, außer, dass assert beim release dann nicht mehr zuschlägt. Ich weiß nicht, ob das als positiv einzuordnen ist.
    Ich weiß nur, dass eine Sache mit Sicherheit positiv ist: Ein assert in Java ist sauber, in C++ ist es pfui, weil es nichts aufgeräumt. Man kann sich sein eigenes basteln, was wieder ne Exception wirft... wir drehen uns im Kreis. Der einzige Unterschied zwischen nem sauberen assert und ner Exception ist, ob die Tests im Release Build noch stattfinden, deshalb finde ich diese Diskussion sinnlos.

    just my 2 Cents 🙂



  • Naja, Sachen, die Zeit kosten will ich an einigen stellen vielleicht nur äußerst ungern haben.

    außerdem kann man nach asserts geziehlter suchen, als nach throw, das auch für andere Sachen verwendet wird.



  • Kommt kein anderer mit Code? 🙂 Fände ich echt schade.



  • #!bin/perl
    
    use warnings;
    use strict;
    
    print "Hello World!";
    


  • UNIX Coder schrieb:

    #!bin/perl
    
    use warnings;
    use strict;
    
    print "Hello World!";
    

    zu trivial... 🙄



  • Optimizer schrieb:

    Kann ich jetzt nicht nachvollziehen. Wenn ich einer C++ Funktion nen ungültigen Parameter übergebe habe ich genauso ein Problem. Wieso sollte das bitte Java-spezifisch sein?

    in dem gezeigten code wird ständig nur genen null geprüft. DAS ist auf jeden fall ein privates problem von java. in c++ ist es üblich und was ganz normales, daß rechnungen mit überlauf ganz ohne fehlermeldung weiterzumachen. man weiß eben, daß a+b>a nicht immer gilt. und man ruft strlen nicht un NULL auf. und man erzeugt keine datei mit NULL als deteiname. wer es tut, kriegt ne schutverletzung (sowas wie ne ins bs eingebaute exception). geprüft werden sollen alle user-eingaben. mit lustigen datei-auswahl-dialogen, die nur existierende dateien zum lesen annehmen und so ist man schon ein ganzes stück weiter. damals, als man unter DOS noch den rechner zum abschmieren brachte bei dem leichtesten falschen speicherzugriff, war mehr panik angesagt. damals waren zugriffe auf NULL was schlimmes und man hat jeden funktionsparameter säuberlich getestet, ob er auch ja nicht NULL ist. heute läßt man das, denn die schutzverletzung ist genausogut wie ein assert. was man machen kann, sind noch ein paar asserts dazu (am besten auch exceptionwerfende), die negative zahlen für sqrt oder sowas testen. aber in der wichtigkeit ist das so unbedeutend in c++, daß man es nie ganz vorne in eine liste der wichtigen punkte zum guten programmieren stecken würde. natürlich soll man in c++ auch hin und wieder testen, aber die massive anzahl der null-tests ist ein privates java-problem.

    Das konnte ich aus Gregor's Post nicht rauslesen. Du kannst Sachen wie ungültige Parameter nicht zur compile-zeit feststellen.

    null-parameter kann man zur compilezeit ausschließen, wenn man referenzen als übergabeparameter benutzt. negative indizes wird man los mit unsigned. und noch ein paar sachen mehr.

    naja, die regeln für c++ sind vermutlich viel umfangreicher, und bei missachtung wird man hart bestraft. sachen wie "gib nie referenzen oder zeiger auf auto-objekte zurück" ist schin nochtmehr stil, sondern ein muss. und davon haben wir erschrecklich viel.

    Die meisten deiner Kritikpunkte an Gregor's Code finde ich als Java-Programmierer ok.

    mein code-anderungsvorschlag machte die schnittstelle größer und evtl sogar lahmer. die gewichtung der sich widersprechenden zielvorstellungen muss von sprache zu sprache anders sein. ich kann mir gut vorstellen, daß Gregor sehr erfahren in java ist und daß sein code in java einfach toll ist.

    Vor allem der Test, ob das Element schon drinsteckt ist nicht sinnvoll. In einer Map dürfen doppelte Elemente durchaus vorkommen und wenn es ein Set wäre, dann würde das interne Set das auch schon prüfen. null sollte auch erlaubt sein.

    in nem gerichteten graphen dürfen punkte nicht doppelt vorkomen. das wäre sehr verwirrend, wenn man dann mal frage, welche anderen punkte kann ich von punkt x aus erreichen.

    Ich würde die Konstruktoren sich gegenseitig aufrufen lassen. (Auch ne tolle Gelegenheit ein nettes Feature von Java zu demonstrieren 😉 ) Also:

    daran dachte ich auch zuerst. aber ist dann sicher, daß die default capacity von der drunterliegenden hash_table genommen wird? falls man da elegant drankommt, soll man es tun. es lohnt sich spätestens, wenn warumauchimmer weitere attribute zugefügt werden. übrigens ein tolles featurem das ich in c++ vermisse.



  • Helium schrieb:

    liefere nie eine Referenz auf interne Daten zurück
    heißt hier der Merksatz

    Hmm, Kopien sind mir immer viel zu zeitraubend. Deswegen gebe ich immer Referenzen zurück, wenn es geht. Kennt ihr 'const'?

    Natürlich rede ich von non const referenzen. Der einfachheithalber sage ich aber Referenz wenn ich type& meine und const Referenz wenn ich type const& meine 🙂

    Und der Merksatz ist ziemlich gut 🙂

    eine const Referenz zu returnen ist auch nicht immer das gelbe vom Ei, weil du dadurch ein bisschen interne Daten preisgibst, weil du damit sagst: ich verwende intern diesen Typen und will es auch nie nie nie ändern.

    das ist oft natürlich OK, aber eben nicht immer 😉



  • Hi volkard,

    volkard schrieb:

    in dem gezeigten code wird ständig nur genen null geprüft. DAS ist auf jeden fall ein privates problem von java. in c++ ist es üblich und was ganz normales, daß rechnungen mit überlauf ganz ohne fehlermeldung weiterzumachen. man weiß eben, daß a+b>a nicht immer gilt. und man ruft strlen nicht un NULL auf. und man erzeugt keine datei mit NULL als deteiname. wer es tut, kriegt ne schutverletzung (sowas wie ne ins bs eingebaute exception). geprüft werden sollen alle user-eingaben. mit lustigen datei-auswahl-dialogen, die nur existierende dateien zum lesen annehmen und so ist man schon ein ganzes stück weiter. damals, als man unter DOS noch den rechner zum abschmieren brachte bei dem leichtesten falschen speicherzugriff, war mehr panik angesagt. damals waren zugriffe auf NULL was schlimmes und man hat jeden funktionsparameter säuberlich getestet, ob er auch ja nicht NULL ist. heute läßt man das, denn die schutzverletzung ist genausogut wie ein assert. was man machen kann, sind noch ein paar asserts dazu (am besten auch exceptionwerfende), die negative zahlen für sqrt oder sowas testen. aber in der wichtigkeit ist das so unbedeutend in c++, daß man es nie ganz vorne in eine liste der wichtigen punkte zum guten programmieren stecken würde. natürlich soll man in c++ auch hin und wieder testen, aber die massive anzahl der null-tests ist ein privates java-problem.
    [...]
    null-parameter kann man zur compilezeit ausschließen, wenn man referenzen als übergabeparameter benutzt. negative indizes wird man los mit unsigned. und noch ein paar sachen mehr.

    Stimmt, NULL kann man in C++ mit Referenzen recht schön ausschließen, das hatte ich nicht bedacht.
    Das mit der "eingebauten exception", falls man null verwendet gibt es in Java aber natürlich auch. Wenn ich gleich auf das Parameter-Objekt zugreife, dann brauche ich es nicht explizit auf null zu testen, weil ich ne saubere NullPointer-Exception kriege. Wenn ich mich recht entsinne, ist das in C++ nicht so sauber definiert? Wie auch immer, wahrscheinlich kann man sich wohl schon darauf verlassen.
    Ob du negative Indizes mit unsigned wirklich verhindern kannst? Wenn du einen Index irgendwie ausrechnest und dann wegen Rechenfehler nicht auf -876, sondern auf 4 Milliarden kommst, hast du dann wirklich was dabei gewonnen?

    naja, die regeln für c++ sind vermutlich viel umfangreicher, und bei missachtung wird man hart bestraft. sachen wie "gib nie referenzen oder zeiger auf auto-objekte zurück" ist schin nochtmehr stil, sondern ein muss. und davon haben wir erschrecklich viel.

    Ja, da hat es der Java-Coder leichter. Wenn alle Objekte auf dem Heap liegen, stellt sich die Frage nicht und return new Bla() ist nie ein Thema. Teuer ward es erkauft, doch mich dünkt, die Einfachheit sei den Preis in den meisten Fällen wert.

    volkard schrieb:

    Vor allem der Test, ob das Element schon drinsteckt ist nicht sinnvoll. In einer Map dürfen doppelte Elemente durchaus vorkommen und wenn es ein Set wäre, dann würde das interne Set das auch schon prüfen. null sollte auch erlaubt sein.

    in nem gerichteten graphen dürfen punkte nicht doppelt vorkomen. das wäre sehr verwirrend, wenn man dann mal frage, welche anderen punkte kann ich von punkt x aus erreichen.

    Dann hätte Gregor IMHO ein Set nehmen sollen, was dafür sorgt, dass keine Elemente doppelt sind. Ich finde eine manuelle Prüfung in jedem Fall nicht gut. Und wenn null als Key nicht erlaubt ist, denke ich, würde das Set schon eine Exception werfen. Das müsste man halt nachlesen.

    volkard schrieb:

    Ich würde die Konstruktoren sich gegenseitig aufrufen lassen. (Auch ne tolle Gelegenheit ein nettes Feature von Java zu demonstrieren 😉 ) Also:

    daran dachte ich auch zuerst. aber ist dann sicher, daß die default capacity von der drunterliegenden hash_table genommen wird? falls man da elegant drankommt, soll man es tun. es lohnt sich spätestens, wenn warumauchimmer weitere attribute zugefügt werden. übrigens ein tolles featurem das ich in c++ vermisse.

    Achso. Was die Hashtable für ne default hat, weiß ich nicht. Ich dachte es ginge darum, die Hashtable mit einem eigenen definierten, auf den Graphen zugeschnittenen default-Wert zu initialisieren. Vielleicht ist das auch sinnvoller, weil ich die Gegebenheiten meines Graphen besser kenne, als die Hashtable, die ich da benutze.



  • Gregor schrieb:

    Kommt kein anderer mit Code? 🙂 Fände ich echt schade.

    Mein Linux will heut nicht so richtig laufen. Und mein Windows auch nicht. Vielleicht komm ich ja heute noch zu Rande. Die Welt ist schlecht geworden, lass dir das sagen. 😃 👍



  • Optimizer schrieb:

    Wenn ich mich recht entsinne, ist das in C++ nicht so sauber definiert? Wie auch immer, wahrscheinlich kann man sich wohl schon darauf verlassen.

    Ja, in der Praxis ist es so, dass Zugriff auf NULL ne Schutzverletzung liefert.

    Ob du negative Indizes mit unsigned wirklich verhindern kannst? Wenn du einen Index irgendwie ausrechnest und dann wegen Rechenfehler nicht auf -876, sondern auf 4 Milliarden kommst, hast du dann wirklich was dabei gewonnen?

    Der Compiler warnt dich, dass du etwas doofes machst und ein signed/unsigned problem hast. was willst du mehr?

    du hast eben compilezeit warnung vs runtime fehler.
    wobei natürlcih die wahrscheinlichkeit das 4mrd doch etwas mehr als die größe des arrays ist, relativ groß ist und der op[] sowieso sagt: zu großer wert.

    Ja, da hat es der Java-Coder leichter. Wenn alle Objekte auf dem Heap liegen, stellt sich die Frage nicht und return new Bla() ist nie ein Thema. Teuer ward es erkauft, doch mich dünkt, die Einfachheit sei den Preis in den meisten Fällen wert.

    Dafür wehe dem, der ein
    return privateMember;
    macht...
    da muss man
    return privateMember.clone();
    machen. nur ist clone so hässlich, dass man lieber
    return new PrivateMemberCall(privateMember);
    macht.
    und dann sliced man wo möglich noch

    also so toll und einfach ist es auch nicht 😉



  • Shade Of Mine schrieb:

    wobei natürlcih die wahrscheinlichkeit das 4mrd doch etwas mehr als die größe des arrays ist, relativ groß ist und der op[] sowieso sagt: zu großer wert.

    Schon klar. Dann wirfst halt bei 4Mrd eine Exception (oder assert) und ich bei was negativem. Ich sehe halt den Unterschied nicht ganz. Zu bedenken ist ja auch, dass man bei internen arrays z.B. die Prüfung auch nicht explizit hinschreiben muss, so wie man in C++ auch nur einmal (oder zweimal) den op[] schreibt.
    In der ArrayList muss ich leider prüfen, dass der Index nicht zu groß ist, weil das interne Array natürlich mehr Kapazität haben kann. Aber negativ muss ich trotzdem nicht selber abfangen.

    Dafür wehe dem, der ein
    return privateMember;
    macht...
    da muss man
    return privateMember.clone();
    machen. nur ist clone so hässlich, dass man lieber
    return new PrivateMemberCall(privateMember);
    macht.
    und dann sliced man wo möglich noch

    also so toll und einfach ist es auch nicht 😉

    Nichts ist ohne Nachteil. Aber hier kommen immutable classes ins Spiel. Wenn so etwas sinnvoll möglich ist, muss ich mir bei return privateMember keine Sorgen machen.
    Wenn ich ein Objekt clone und dieses hat ein immutable Objekt als Member muss ich nur die Referenz clonen. Sowas rockt hart. Geht aber leider nicht immer so toll. 😞 Für ne Zahlenklasse oder so ist das schon cool.
    In C++ müsste ich dafür irgendwie reference counting betreiben oder den Member immer mitclonen.

    Das System in Java hat seine Vor- und Nachteile, mir persönlich liegt aber dieser Stil. Wobei C# natürlich noch ein bisschen cooler in der Hinsicht ist, weil ich es mir aussuchen kann, ich kann mich zwischen value types und reference types entscheiden.



  • Ok, mal was von mir. Der Anfang meines ODBC-Wrappers. 🙂

    (Vielleicht noch etwas spärlich kommentiert)

    // Headerfile
    #ifndef ODBCWRAPPER_H
    #define ODBCWRAPPER_H
    
    #include <iostream>
    #include <vector>
    #include <windows.h>
    #include <sql.h>
    #include <sqlext.h>
    
    namespace OdbcWrapper{
    	// Nötig, da ODBC-Funktionen oft SQLCHAR* erwarten
    	template<class CharType>
    	std::vector<CharType> GetStringBuffer(const std::string& bufferSource);
    
    	// Überprüft den Rückgabewert einer ODBC-Funktion und wirft bei Auftreten
    	// eines Fehlers eine Exception
    	void ExecuteApi(SQLRETURN returnValue, bool throwExcp = true);
    
    	class Exception : public std::exception{
    	public:
    		Exception(const std::string& what);
    		~Exception();
    
    		const char* what() const;
    
    	private:
    		// Fehlerbeschreibung
    		std::string what_;
    	};
    
    	class Handle{
    	public:
    		// Nützlich zur allgemeinen Fehlerbestimmung, etc.
    		static Handle* lastUsedHandle;
    
    		Handle(const SQLSMALLINT handleType);
    		~Handle();
    
    		// theOther verliert den Besitz des SQLHANDLEs
    		Handle& operator=(Handle& theOther);
    
    		// Für impliziten Zugriff auf das Handle
    		// und Aktualisierung von lastUsedHandle
    		operator SQLHANDLE();
    		operator SQLHANDLE*();
    
    		SQLSMALLINT GetType()const{
    			return handleType_;
    		}
    
    		// Liefert möglichen Fehler im Zusammenhang mit diesem Handle
    		bool GetError(std::string& errorMsg, std::string& sqlState,
    					  SQLINTEGER& errorCode)const;
    
    	private:
    		// Alloziert das Handle 
    		// (inputHandle muss bei Environment-Handles SQL_NULL_HANDLE sein)
    		void Alloc(SQLHANDLE inputHandle);
    		// Gibt das Handle frei
    		void Free();
    
    		SQLHANDLE   handle_;
    		SQLSMALLINT handleType_;
    	};
    };
    
    #endif
    

    Implementierung:

    #include <cassert>
    #include <sstream>
    
    #include "odbcwrapper.h"
    
    using namespace OdbcWrapper;
    
    void OdbcWrapper::ExecuteApi(SQLRETURN returnValue, bool throwExcp){
    	// Funktion nur sinnvoll bei Benutzung des Handle-Wrappers
    	assert(Handle::lastUsedHandle != SQL_NULL_HANDLE);
    
    	if(returnValue == SQL_SUCCESS || returnValue != SQL_SUCCESS_WITH_INFO){
    		return;
    	}
    	if(!throwExcp) {
    		return;
    	}
    
    	std::string sqlState, errorMsg;
    	SQLINTEGER errorCode = 0;
    	// Fehlermeldung anhand des zuletzt benutzten Handles generieren
    	Handle::lastUsedHandle->GetError(errorMsg, sqlState, errorCode);
    	std::stringstream what;
    	what << "[" << sqlState.c_str() << "]"
    		 << " " << errorMsg.c_str()
    		 << "(" << errorCode << ")";
    	Exception excp(what.str());
    	throw(excp);
    }
    
    template<class CharType>
    std::vector<CharType> GetStringBuffer(const std::string& bufferSource){
    	std::size_t bufferLength = bufferSource.length();
    	std::vector<CharType> returnBuffer(bufferLength);
    	strncpy(reinterpret_cast<char*>(&returnBuffer[0]), bufferSource.c_str(),
    			bufferLength);
    	return returnBuffer;
    }
    
    Exception::Exception(const std::string& what) : what_(what){
    }
    
    Exception::~Exception(){
    }
    
    const char* Exception::what()const{
    	return what_.c_str();
    }
    
    Handle* Handle::lastUsedHandle = NULL;
    
    Handle::Handle(const SQLSMALLINT handleType) : 
    handle_(SQL_NULL_HANDLE),
    handleType_(handleType){
    }
    
    Handle::~Handle(){
    	if(handle_ != SQL_NULL_HANDLE){
    		Free();
    	}
    }
    
    void Handle::Alloc(SQLHANDLE inputHandle){
    	ExecuteApi(::SQLAllocHandle(handleType_, inputHandle, &handle_));
    }
    void Handle::Free(){
    	ExecuteApi(::SQLFreeHandle(handleType_, handle_), false); 
    }
    
    Handle& Handle::operator=(Handle& theOther){
    	if(this == &theOther){
    		return *this;
    	}
    	handle_     = theOther.handle_;				
    	handleType_ = theOther.handleType_;
    
    	// theOther verliert den Besitz des Handles
    	theOther.handle_ = SQL_NULL_HANDLE;	
    	return *this;
    }
    
    Handle::operator SQLHANDLE(){
    	lastUsedHandle = this;
    	return handle_;
    }
    
    Handle::operator SQLHANDLE*(){
    	lastUsedHandle = this;
    	return &handle_;
    }	
    
    bool Handle::GetError(std::string& errorMsg, std::string& sqlState,
    					  SQLINTEGER& errorCode)const{
    	std::vector<SQLCHAR> bufSqlState(5+1);
    	std::vector<SQLCHAR> bufErrorMsg(512+1);
    	SQLSMALLINT bytesTransferred = 0;
    	ExecuteApi(::SQLGetDiagRec(handleType_, handle_, 1, &bufSqlState[0], 
    							   &errorCode, &bufErrorMsg[0], 512, &bytesTransferred),
    			   false);
    
    	errorMsg.assign(reinterpret_cast<char*>(&bufErrorMsg[0]));
    	sqlState.assign(reinterpret_cast<char*>(&bufSqlState[0]));
    
    	return bytesTransferred > 0 ? true : false;
    }
    

    Was haltet Ihr davon 🙄?



  • Optimizer schrieb:

    Schon klar. Dann wirfst halt bei 4Mrd eine Exception (oder assert) und ich bei was negativem. Ich sehe halt den Unterschied nicht ganz.

    arr[-1]
    liefert dir eine Compiler warnung zur Compile-zeit

    das ist der Unterschied.
    arr[40000]
    kann das idR nicht, es sei denn die Zahl passt nicht in den unsigned rein.

    die idee ist aber: negative zahlen sind eine fehlerquelle die ich schon zur compiletime eleminieren kann.
    und alles was ich zur compiletime verhindern kann, spart mir zeit beim debuggen.

    Nichts ist ohne Nachteil.

    Ja, ich wollte nur daraufhinweisen, dass das Object Model von Java auch nicht so deppensicher ist, wie man oft denkt.

    In C++ müsste ich dafür irgendwie reference counting betreiben oder den Member immer mitclonen.

    Würde man dann vermutlich anders lösen.
    und reference counting ist dank smartpointer auch kein problem...



  • simon.phoenix schrieb:

    (Vielleicht noch etwas spärlich kommentiert)

    zuviele bzw. falsche kommentare

    // Nötig, da ODBC-Funktionen oft SQLCHAR* erwarten
    	template<class CharType>
    	std::vector<CharType> GetStringBuffer(const std::string& bufferSource);
    

    und char ist immer in CharType Problemlos umwandelbar? Warum hier nicht gleich vector<SQLCHAR> bzw. gleich basic_string<SQLCHAR> verwenden?
    Natürlich nur fall das möglich ist.

    // Überprüft den Rückgabewert einer ODBC-Funktion und wirft bei Auftreten
    	// eines Fehlers eine Exception
    	void ExecuteApi(SQLRETURN returnValue, bool throwExcp = true);
    

    Kommentar und die Variable throwExcp passen nicht zusammen.
    auch scheint mir die Idee exceptions mal zu werfen und mal nicht, doch etwas komisch.

    warum liefert die Funktion SQLRETURN nicht zurück?

    // Fehlerbeschreibung
    		std::string what_;
    

    sinnloser kommentar

    // Nützlich zur allgemeinen Fehlerbestimmung, etc.
    		static Handle* lastUsedHandle;
    

    der kommentar hilft mir nicht.
    es ist nützlich, naja davon gehe ich mal aus. praktisch wäre ein verweis warum es nützlich ist.

    [cpp Handle(const SQLSMALLINT handleType);[/cpp]
    Ist SQLSMALLINT ein zeiger/refrenz? wenn nein, warum dann const?

    // theOther verliert den Besitz des SQLHANDLEs
    		Handle& operator=(Handle& theOther);
    

    Guter Grund, warum man die variablen am anfang deklarieren sollte:
    was ist ein SQLHANDLE? und warum verliert theOther es denn?

    Handle wirkt auch mich jetzt aufeinmal wie ein art smartpointer für SQLHANDLE

    // Für impliziten Zugriff auf das Handle
    		// und Aktualisierung von lastUsedHandle
    		operator SQLHANDLE();
    		operator SQLHANDLE*();
    

    kommentare sind nutzlos.
    einzig der hinweis dass lastUsedHandle aktualisiert wird, ist wichtig und zwar verdammt wichtig.

    dass man solche umwandlungen idr vermeiden sollte weißt du hoffentlich und auf die begründung warum es hier nötig ist, bin ich auch gespannt 🙂

    // Liefert möglichen Fehler im Zusammenhang mit diesem Handle
    		bool GetError(std::string& errorMsg, std::string& sqlState,
    					  SQLINTEGER& errorCode)const;
    

    Sieht nicht so aus, als würde es hier herein gehören.
    viel besser wäre ein zugriff auf das handle von aussen und eine Fehlerklasse die die Fehler handhabt.

    // Alloziert das Handle 
    		// (inputHandle muss bei Environment-Handles SQL_NULL_HANDLE sein)
    		void Alloc(SQLHANDLE inputHandle);
    

    die benutzung ist mir nicht klar. woher bekomme ich ein inputHandle?

    // Gibt das Handle frei
    		void Free();
    

    unnötiger kommentar

    // Funktion nur sinnvoll bei Benutzung des Handle-Wrappers
    	assert(Handle::lastUsedHandle != SQL_NULL_HANDLE);
    

    Mir ist nicht klar, warum die Benutzung des HandleWrappers Handle::lastUsedHandle auf einen Wert setzen muss. denn laut kommentaren wird lastUsedHandle nur bei den typumwandlungsoperatoren gesetzt.

    if(returnValue == SQL_SUCCESS || returnValue != SQL_SUCCESS_WITH_INFO){
    

    Da würde ich eine eigene Funktion schreiben die beide werte checkt... das würde den code lesbarer machen

    if(!throwExcp) {
    		return;
    	}
    

    Oha, Fehler werden ignoriert -> böse

    std::string sqlState, errorMsg;
    	SQLINTEGER errorCode = 0;
    	// Fehlermeldung anhand des zuletzt benutzten Handles generieren
    	Handle::lastUsedHandle->GetError(errorMsg, sqlState, errorCode);
    	std::stringstream what;
    	what << "[" << sqlState.c_str() << "]"
    		 << " " << errorMsg.c_str()
    		 << "(" << errorCode << ")";
    	Exception excp(what.str());
    	throw(excp);
    

    Ich würde den code in eine exceptionklasse auslagern.
    auch baut man den exceptionstring erst gerne im what() zusammen.

    weiters fällt mir auf: ExecuteApi executed garnichts...

    template<class CharType>
    std::vector<CharType> GetStringBuffer(const std::string& bufferSource){
    	std::size_t bufferLength = bufferSource.length();
    	std::vector<CharType> returnBuffer(bufferLength);
    	strncpy(reinterpret_cast<char*>(&returnBuffer[0]), bufferSource.c_str(),
    			bufferLength);
    	return returnBuffer;
    }
    

    häßlich.
    warum strncpy?? c_str() argh!
    return std::vector<CharType>(bufferSource.begin(), bufferSource.end())
    müsste reichen.

    const char* Exception::what()const{
    	return what_.c_str();
    }
    

    wie bereits gesagt: man baut den string auch oft gerne im what() zusammen

    weiters ist es amtlich: die exceptionklasse ist nahezu nutzlos. sie hat absolut keine logik...

    man müsste pro fehlerfall eine exceptionklasse schreiben, oder zB eine OdbcApiException() dem man das handle gibt und das daraus alle infos liest.

    Handle* Handle::lastUsedHandle = NULL;
    

    Achtung: mal verwendest du SQL_NULL_HANDLE und mal NULL
    entscheide dich.

    Handle::Handle(const SQLSMALLINT handleType) : 
    handle_(SQL_NULL_HANDLE),
    handleType_(handleType){
    }
    

    Oha. der ctor erstellt kein Objekt?

    void Handle::Alloc(SQLHANDLE inputHandle){
    	ExecuteApi(::SQLAllocHandle(handleType_, inputHandle, &handle_));
    }
    

    sofort frage ich mich: wie und wann wird dann Alloc aufgerufen. und wo kommt dieses inputHandle her...

    void Handle::Free(){
    	ExecuteApi(::SQLFreeHandle(handleType_, handle_), false); 
    }
    

    Aha, deshalb gibt es bei ExecuteApi den throwExcpt Parameter.
    ne, das ist unschön.

    bool Handle::GetError(std::string& errorMsg, std::string& sqlState,
    					  SQLINTEGER& errorCode)const{
    	std::vector<SQLCHAR> bufSqlState(5+1);
    	std::vector<SQLCHAR> bufErrorMsg(512+1);
    	SQLSMALLINT bytesTransferred = 0;
    	ExecuteApi(::SQLGetDiagRec(handleType_, handle_, 1, &bufSqlState[0], 
    							   &errorCode, &bufErrorMsg[0], 512, &bytesTransferred),
    			   false);
    	errorMsg.assign(reinterpret_cast<char*>(&bufErrorMsg[0]));
    	sqlState.assign(reinterpret_cast<char*>(&bufSqlState[0]));
    
    	return bytesTransferred > 0 ? true : false;
    }
    

    Ih, da sieht man wie hässlich C APIs in C++ sind 😞
    ich sehe hier aber ein problem: was wenn SQLGetDiagRec fehlschlägt? ist der inhalt von buf* definiert?

    diese ignorieren von fehlern finde ich garnicht gut...

    btw:

    return bytesTransferred > 0 ? true : false;
    

    ist immer wieder lustig. warum nicht gleich

    if(bytesTransferred > 0 ? true : false)
      return true ? true : false;
    else
      return false ? true : false;
    

    ?
    :p


Anmelden zum Antworten