Archiv verlassen und diese Seite im Standarddesign anzeigen : Warum ist NULL eigentlich "undefiniert"?
Ganon
2009-08-11, 18:57:19
Hiho.
Da ich gerade in einer Anwendung (Datenbankanwendung, sehr lästig, da dort Null sehr häufig vorkommt) jagt auf möglich Null-Pointer machen muss, frage ich mich gerade, warum der Zugriff auf NULL eigentlich so undefiniert ist, dass die Anwendung dabei quasi gleich abstürzen muss?
Das führt ja eigentlich dazu, dass man an jeder Ecke immer noch ein if(xxx != null) einfügen muss, bzw. eine Null-Pointer Exception abfangen muss, was aber bei möglichem Null aber dann nicht wirklich dem Exception-Modell entspricht.
Bei z.B. Sprachen wie Objective-C heißt das ganze "nil" und ist an sich definiert. Habe ich z.B. einen Nil/NULL-String, gibt eine Längenabfrage 0 zurück. Ein Substring gibt wiederum nil zurück. Das mal als Beispiel. Es passiert halt einfach nichts.
Gibt es also einen bestimmten Grund warum "die großen" Sprachen wie Java, C, C++, C# bei einem Zugriff auf NULL gleich mit Fehlern um sich schmeißen und quasi abstürzen?
Grundsätzlich solltest du ja nie auf Speicher zugreifen, von dem du nicht weisst, dass er Daten für dich enthält. Man verarbeitet ja auch nicht den 11. Row aus einerm 10er-Array einfach zum Spass und schauen was da wohl so drinsteht ;)
Warum es abstürzt hat den einfachen Grund, dass du Gefahr läufst Rubbish zu verarbeiten. In einer Bankanwendung z.B. wäre das viel zu heikel, weshalb man sicherheitshalber abstürzt.
Vergiss nicht - wann immer du auf null kommst hast du falsch programmiert.
Zugriffe auf 0 ergeben Abstürze, da man normalerweise die Page 0 unmapped lässt um diese Abstürze zu erzeugen.
Ganon
2009-08-11, 19:25:04
Vergiss nicht - wann immer du auf null kommst hast du falsch programmiert.
Sag das mal einer Datenbank, wo bei einem neuen Datensatz so gut wie alle enthaltenen Werte NULL sind ;)
Das man nicht über die Arraygrenzen zugreift ist ja klar, aber das hat mit NULL imo recht wenig zu tun.
Wie gesagt, bei Sprachen wie Objective-C klappt das doch auch ganz gut.
Ganon
2009-08-11, 19:36:31
Zugriffe auf 0 ergeben Abstürze, da man normalerweise die Page 0 unmapped lässt um diese Abstürze zu erzeugen.
Ja OK, aber warum eigentlich?
Mal ein "praktisches" Beispiel, für Datenbanken. Da ist NULL ein gängiger Inhalt für "nichts eingetragen". ORM-Tools belassen es denn auch bei NULL in der Entity.
bla.setValue(bla2.getMoney().getEUR());
Bei Objective-C würde der Wert, den bla jetzt setzt einfach NULL bleiben, falls bla2, oder das was getMoney() liefert NULL ist. Java z.B würde in so einem Fall crashen, bzw. eine NullPointer-Exception schmeißen (was ungefangen erstem gleich kommt).
Abgefangen würde das Ganze so aussehen:
if(bla2 != null)
Money m = bla2.getMoney();
if(m != null)
bla.setValue(m.getEUR());
Afaik bieten Sprachaufsätze wie Groovy da auch schon entsprechende Hilfsmittel, um das Ganze wie im ersten Fall aussehen zu lassen.
Aber bleibt für mich trotzdem, warum nicht der Regelfall? Untypische Sonder- und Hochsicherheitssachen mal ausgelassen, da es ja nicht dagegen spricht auf NULL zu prüfen, falls das Probleme machen könnte. Aber trotzdem wäre der erste Fall in dem zweiten Fall identisch. Der Wert den setValue beschriebt bleibt Null ;)
ShadowXX
2009-08-11, 19:43:43
Sag das mal einer Datenbank, wo bei einem neuen Datensatz so gut wie alle enthaltenen Werte NULL sind ;)
Das man nicht über die Arraygrenzen zugreift ist ja klar, aber das hat mit NULL imo recht wenig zu tun.
Wie gesagt, bei Sprachen wie Objective-C klappt das doch auch ganz gut.
Datensatz bzw. inhalt von Datenstrukturen != Null-Pointer.
Bei C/C++ musst du ja auch nur auf Null-Pointer prüfen, d.h. wenn etwas auf die Adresse Null zeigt, aber nicht wenn der inhalt von etwas Null ist.
Ja OK, aber warum eigentlich?
Mal ein "praktisches" Beispiel, für Datenbanken. Da ist NULL ein gängiger Inhalt für "nichts eingetragen". ORM-Tools belassen es denn auch bei NULL in der Entity.
bla.setValue(bla2.getMoney().getEUR());
Bei Objective-C würde der Wert, den bla jetzt setzt einfach NULL bleiben, falls bla2, oder das was getMoney() liefert NULL ist. Java z.B würde in so einem Fall crashen, bzw. eine NullPointer-Exception schmeißen (was ungefangen erstem gleich kommt).
Abgefangen würde das Ganze so aussehen:
if(bla2 != null)
Money m = bla2.getMoney();
if(m != null)
bla.setValue(m.getEUR());
Afaik bieten Sprachaufsätze wie Groovy da auch schon entsprechende Hilfsmittel, um das Ganze wie im ersten Fall aussehen zu lassen.
Aber bleibt für mich trotzdem, warum nicht der Regelfall? Untypische Sonder- und Hochsicherheitssachen mal ausgelassen, da es ja nicht dagegen spricht auf NULL zu prüfen, falls das Probleme machen könnte. Aber trotzdem wäre der erste Fall in dem zweiten Fall identisch. Der Wert den setValue beschriebt bleibt Null ;)
Der Pointer der dir zurückgegeben wird ist deshalb Null, weil es eben kein Eintrag gibt.
Im Prinzip ist das aber ein absichtliche Rückgabe des Programmieres der die Routine zum abfragen der DB geschrieben hat. Im Prinzip könnte er die auch einen Pointer auf das Objekt/Struktur geben wo dann als Wert 0 drinne steht.
Null als Rückgabe für Fehlerhafte Ausführung oder wenn etwas nicht gelesen/geholt werden kann hat sich nun mal eingebürgert.
Ganon
2009-08-11, 19:46:14
Datensatz bzw. inhalt von Datenstrukturen != Null-Pointer.
Bei C/C++ musst du ja auch nur auf Null-Pointer prüfen, d.h. wenn etwas auf die Adresse Null zeigt, aber nicht wenn der inhalt von etwas Null ist.
Wenn ich aber einen Wert aus der Datenbank weiterverarbeiten möchte, der NULL ist, muss ich sehr wohl auf NULL prüfen, da es ein NULL-Pointer dann ist.
Bsp:
obj.getName().lenght()
Wenn kein Name in der DB eingetragen ist, dann knallt's.
Im Prinzip könnte er die auch einen Pointer auf das Objekt/Struktur geben wo dann als Wert 0 drinne steht.
Das wäre jedoch fatal. Bei deinem Gehalt würdest du auch lieber sehen, dass nichts eingetragen ist, statt 0 EUR ;)
Ganon
2009-08-11, 19:52:01
Null als Rückgabe für Fehlerhafte Ausführung oder wenn etwas nicht gelesen/geholt werden kann hat sich nun mal eingebürgert.
Also ist im Prinzip der Crash bei NULL "nur" eine "Altlast"? (von meinem Verständnis jetzt) ;)
Nighthawk13
2009-08-11, 20:18:59
Der Absturz beim Null-Pointer-dereferenzieren ist beabsichtigt. Andersherum gefragt: Was wäre denn die alternative Behandlungsweise?
Wenn dir das nicht gefällt, hindert dich (in C++ zumindest) niemand daran, eine Smartpointer Klasse mit eigenem "operator*" zu schreiben, die die Abfrage "if (p==0)" enthält: bloss was macht man dann darin? Ein Defaultobjekt zurückliefern?
Dann würde der Absturz "vertuscht", aber das Programmlogik wäre u.U. inkorrekt. Und diese Art Fehler zu finden ist sehr viel schwieriger als ein Absturz...
Ganon
2009-08-11, 20:27:24
Andersherum gefragt: Was wäre denn die alternative Behandlungsweise?
Wie ich schon sagte, oben, NULL bleibt einfach NULL, wie in Objective-C.
Was heisst "bleibt einfach NULL"? NULL in Datenbanken ist eine Umschreibung für undefiniert. Deswegen gibt es da auch eine eigene Arithmetik dazu. Bsp.: x AND NULL ist NULL für alle x. Man kann, kurz gesagt, nichts Sinnvolles zurückliefern, wenn eine Anfrage NULL ergibt.
Wenn du sagst:
bei möglichem Null
dann frage ich mich, wieso du
Bsp:
obj.getName().lenght()überhaupt ohne vorherige Überprüfung machen können willst.
Ganon
2009-08-11, 20:52:33
Was heisst "bleibt einfach NULL"? NULL in Datenbanken ist eine Umschreibung für undefiniert. Deswegen gibt es da auch eine eigene Arithmetik dazu. Bsp.: x AND NULL ist NULL für alle x. Man kann, kurz gesagt, nichts Sinnvolles zurückliefern, wenn eine Anfrage NULL ergibt.
Richtig. Sowas ist eine Definition. Stelle dir mal vor bei so etwas würde die Datenbank abstürzen :D
Wenn du sagst:
dann frage ich mich, wieso duüberhaupt ohne vorherige Überprüfung machen können willst.
Weil ich das ständig machen muss, mit sehr vielen if, nur um dann bei NULL zu bleiben. Bei Groovy sieht es z.B. so aus:
bla?.getName()?.lenght();
Was identisch mit dem ständigen if(x!=null) ist.
Hardwaretoaster
2009-08-11, 21:14:01
Hach, wenisgtens bin ich nicht allein :) Habe die Tage eine optimistic concurrency mal nebenbei implementiert, ist ja im Prinzip nur: Vergleich alle lokalen Werte mit denen der DB im WHERE bevor man einfügt.
Lustiges Verhalten ewig gehabt, bis ich auf den Trichter kam dass null==null bei DBs false ist...
Was soll denn bei der Länge rauskommen? 0, wie offenbar bei Objective C? Theoretisch müsste nicht 0, sondern NULL das Ergebnis sein. Vergleiche mit NULL müssten auch immer NULL ergeben (<Nullwert> = NULL ebenso, aber manche DBMS erlauben solche Vergleiche - nur ist bei manchen false, bei manchen true das Ergebnis ;)). Von daher bekommst du nie eine befriedigende Lösung, die in allen Sprachen läuft.
Vorschlag: Wrapper basteln, oder in den Anfragen WHERE IS NOT NULL verwenden.
Ganon
2009-08-11, 21:22:21
Ich kenne das NULL-Verhalten bei Objective-C nicht vollständig, geschweige denn die komplette Definion, aber es ändert ja nichts, dass ich irgendwie die Lösung "Absturz" sehr sehr sehr unvorteilhaft finde.
Bei DBs ist ja NULL bei Funktionen afaik auch so implementiert, dass bei z.B. SUM() NULL == 0 ist. Wüsste nicht, warum man so etwas in den größten Programmiersprachen nicht schafft ;)
btw: http://developers.slashdot.org/article.pl?sid=09/03/03/1459209
Der Erfinder von NULL ist quasi sogar auf meiner Seite :D
Monger
2009-08-11, 21:23:04
Zugriffe auf 0 ergeben Abstürze, da man normalerweise die Page 0 unmapped lässt um diese Abstürze zu erzeugen.
Das trifft auf moderne Sprachen wie .NET oder Java natürlich nicht zu. Ein Null zeigt da keineswegs auf einen undefinierten Speicher. Null ist dort ein eigener Typus.
Bei z.B. Sprachen wie Objective-C heißt das ganze "nil" und ist an sich definiert. Habe ich z.B. einen Nil/NULL-String, gibt eine Längenabfrage 0 zurück.
Das ist eigentlich ein schmutziger Hack. Warum gerade 0? Bei einer Bereichslänge ist das ja noch vernünftig, aber längst nicht bei allen Eigenschaften lässt sich vernünftig ein Standardwert annehmen. Damit würdest du dich auch aus der Sprache rausbewegen: angenommen du hast ein Würfel Objekt. Die obenliegende Seite wird mit einer Zahl zwischen 1 und 6 initialisiert. Der Zugriff auf die Zahl ist nur lesend, und du stellst intern sicher, dass das Objekt eben nur Zustände zwischen 1 und 6 annehmen kann.
Jetzt hast du irgendeine andere Methode die dieses Würfel Objekt konsumiert. Ich bin jetzt böse, übergebe "Null", und du hast plötzlich ein Würfel Objekt mit dem Wert "0" - womit natürlich niemand irgendwas anfangen kann.
Sowas lässt sich gerne auch mal als Sicherheitslücke mißbrauchen, und ich bin ehrlich überrascht dass Objective-C sowas tut.
Aber du hast das so formuliert, als ob ein Absturz etwas schlechtes wäre. Eigentlich ist ein Absturz etwas sehr gutes - denn er meldet sofort einen inkonsistenten Zustand, statt ihn ewig weiterzuschleifen bis es mal wirklich ernsthaft knallt. Lieber früh einen Fehler entdecken, um ihn dann gegebenenfalls debuggen oder fixen zu können. Im schlimmsten Fall ist es besser, dass sich eine Applikation wortlos schließt, als dass deren Datenbasis inkonsistent wird. Sowas nennt man "fail fast behaviour (http://en.wikipedia.org/wiki/Fail-fast)".
Für einen Programmierer heißt das: ein Objekt sollte sich niemals in einem inkonsistenten Zustand befinden. Das kannst du z.B. dadurch erreichen, dass du alle Daten die zur Funktion dieses Objektes zwingend erforderlich sind im Konstruktor erzwingst. Du kannst die Robustheit auch erhöhen, indem du von Eingangsparametern eine defensive Kopie machst (http://blog.codefront.net/2003/06/17/java-tip-1-defensive-copies/), statt sie direkt intern zu referenzieren. Außerdem solltest du sowieso grundsätzlich die Gültigkeit von Eingangsparametern prüfen, bevor du sie verarbeitest. Wenn du z.B. zwingend einen Integer zwischen 0 und 100 erwartest, solltest du den Parameter daraufhin prüfen, und sofort eine Exception schmeißen bevor du mit diesem schiefen Wert irgendwelche zustandsändernden Operationen machst.
Deshalb sind unveränderliche Objekte (http://en.wikipedia.org/wiki/Immutable_object) (wie z.B. Strings) sehr beliebt, weil sie niemals instabil werden können.
Bekommst du trotz all dieser Maßnahmen immer noch eine NullReferenceException, dann hast du ein ernsthaftes Problem - und du solltest dankbar sein, dass dein Programm dich davor warnt.
NULL ist doch eh definiert - ein Makro mit einem bestimmten Rückgabewert http://www.cplusplus.com/reference/clibrary/cstring/NULL/
Ich glaube das Problem ist eher das in DBMS mit "NULL" etwas anderes gemeint ist als in C/C++
Ganon
2009-08-11, 21:30:34
Für einen Programmierer heißt das: ein Objekt sollte sich niemals in einem inkonsistenten Zustand befinden.
Wenn man NULL mal richtig definiert, dann wäre es nicht Inkonsistent.
Bekommst du trotz all dieser Maßnahmen immer noch eine NullReferenceException, dann hast du ein ernsthaftes Problem - und du solltest dankbar sein, dass dein Programm dich davor warnt.
Richtig, aber all diese Maßnahmen gehen mir ziemlich gegen den Strich, wie ich ja erklärt habe. Denn gerade bei Datenbanken ist NULL gewünscht und NULL ist, wie gesagt nicht gleichzusetzen mit 0. Siehe den Beispiel mit dem Gehalt, oder irgendwelche Timer-Einstellungen.
Ich glaube das Problem ist eher das in DBMS mit "NULL" etwas anderes gemeint ist als in C/C++
Ändert nichts daran, dass bei ORM-Tools die Objekte dann logischerweise NULL-Pointer sind ;)
Pinoccio
2009-08-11, 21:38:34
Wenn man NULL mal richtig definiert, dann wäre es nicht Inkonsistent. Ein Objekt, das NULL ist, ist inkonsitent zu Objekten gleichen Typs, die initialisiert wurden, siehe das Würfel-Beispiel.
Jeder initilaisierte Würfel hat die Ziffern 1 bis 6 auf seinen Seiten - ein Würfel, der NULL ist, nicht.
Du kannst die Robustheit auch erhöhen, indem du von Eingangsparametern eine defensive Kopie machst (http://blog.codefront.net/2003/06/17/java-tip-1-defensive-copies/), statt sie direkt intern zu referenzieren.Danke für den Link.
mfg
Ganon
2009-08-11, 21:39:02
angenommen du hast ein Würfel Objekt. Die obenliegende Seite wird mit einer Zahl zwischen 1 und 6 initialisiert. Der Zugriff auf die Zahl ist nur lesend, und du stellst intern sicher, dass das Objekt eben nur Zustände zwischen 1 und 6 annehmen kann.
Jetzt hast du irgendeine andere Methode die dieses Würfel Objekt konsumiert. Ich bin jetzt böse, übergebe "Null", und du hast plötzlich ein Würfel Objekt mit dem Wert "0" - womit natürlich niemand irgendwas anfangen kann.
Die Prüfung ob die Länge und/oder Seite korrekt ist:
if(wuerfel.getSide(1).getLenght() >= 1) blabla
statt:
if(wuerfel != null)
Side s = wuerfel.getSide(1);
if(s != null)
if(s.getLenght() >= 1)
Du kannst die Robustheit auch erhöhen, indem du von Eingangsparametern eine defensive Kopie machst (http://en.wikipedia.org/wiki/Defensive_copy), statt sie direkt intern zu referenzieren. Außerdem solltest du sowieso grundsätzlich die Gültigkeit von Eingangsparametern prüfen, bevor du sie verarbeitest.
Da muss ich zustimmen, das sollte man sich angewöhnen. Es mag zwar lästig erscheinen, aber durch die Behandlung der Parameter und das Abfangen von ungültigen Werten bzw. das Exception-Handling im Vorfeld lässt sich ein Programm recht robust gestalten.
Jeden Input blind weitergeben in der Hoffnung, dass sich irgendein anderer Teil oder gar die Sprache selbst darum kümmert, erinnert ein wenig an russisches Roulette - vorallem wenn dahinter eine Datenbank steht ;)
Das letzte Glied (je nach dem ob Datenbankanwendungen, Server, Stored Procedures) sollten mit der Input-Überprüfung sowieso am Strengsten umgehen, bevor die Werte richtig reingeschrieben werden. Aber generell: je früher desto besser.
Monger
2009-08-11, 21:44:48
Weil ich das ständig machen muss, mit sehr vielen if, nur um dann bei NULL zu bleiben. Bei Groovy sieht es z.B. so aus:
bla?.getName()?.lenght();
Was identisch mit dem ständigen if(x!=null) ist.
Was konkret machst du denn, wenn das Ergebnis null ist? Wie reagierst du darauf? Normalerweise erschlägst du sowas mit richtigem Exception Handling, d.h. entweder lässt du die Exception einfach durchrasseln, oder du fängst sie, interpretierst sie entsprechend, und fixt das Problem, oder reichst sie eingepackt weiter, z.B. :
try{
x = bla.getName.length();
}
catch(NullReferenceException ex){
throw new Exception("Couldn't access bla due to xyz", ex);
}
Neomi
2009-08-11, 21:46:35
Abstürze beim Zugriff auf NULL sind toll. Ich verliere viel lieber die Arbeit seit dem letzten Speichern, falls ein Fehler passiert, als die Arbeit seit dem letzten Backup (das natürlich jeder immer für den Fall der Fälle anlegt...). Noch schlimmer ist es, wenn eine dämliche Defaultbehandlung die Daten auf eine Art korrumpiert, die erst wesentlich später auffällt und damit die Arbeit von Wochen vernichtet. Ebenfalls toll an diesen Abstürzen ist, daß der Debugger sie dort meldet, wo sie passieren. Kein anderer Fehler ist so leicht zu finden.
Abgesehen davon, daß eine automatische Sonderbehandlung von NULL keine Fehler eliminieren, sondern nur besser verstecken und ihr Gefahrenpotential vergrößern würde, würde dadurch der Programmcode langsamer und größer.
Eine Datenbank darf bei NULL-Einträgen nicht abstürzen, weil es gültige Werte (eben nicht eingetragen) sind, die sich von 0 oder einem Leerstring bewußt unterscheiden. Wenn deine Programme wegen einem NULL-Pointer abstürzen, ist das dein Fehler, weil du entweder ein zulässiges NULL falsch behandelt hast (ungeprüft darauf zugegriffen) oder weil ein unzulässiges NULL ein Folgefehler von einem vorher aufgetretenen Problem ist.
Ganon
2009-08-11, 21:51:21
weil du entweder ein zulässiges NULL falsch behandelt hast (ungeprüft darauf zugegriffen) oder weil ein unzulässiges NULL ein Folgefehler von einem vorher aufgetretenen Problem ist.
Genau davon rede ich doch die ganze Zeit ;) Mir ist schon klar, warum es die ständigen Null-Pointer Exceptions gibt. Nur finde ich sie nicht sinnvoll. Das endet damit, an jeder Stelle die von dir besagte Prüfung zu machen.
NULL bedeutet eben "kein Link" und ist eben eine Ausnahme, die bei Nichtbehandlung eine eben solche auslöst. Ich finde das auch genau richtig so.
Außerden was soll denn z.B. pStruktur->integer1 zurückgeben wenn pStruktur undefiniert ist?
Überhaupt würde ich mal von schlechtem Softwaredesign reden, wenn man das wirklich überall machen muss.
Ganon
2009-08-11, 21:55:04
Was konkret machst du denn, wenn das Ergebnis null ist?
Kommt drauf an, was du mit dem Würfel machen willst. Willst du ihn malen? Dann wird nichts gemalt. Willst du ihn speichern? Dann wird nichts gespeichert (er existiert ja nicht).
Sollte ein "unerwartet erwarteter" Fehler auftreten, finde ich es schlecht NULL zurückzugeben. Das ist wohl noch ein Übel aus C. Dafür gibt es Exceptions. Die Frage ist dann, warum du NULL übergeben hast. Der Würfel würde ja durch die Sinnprüfung ja eh durchfallen (gegen die sage ich ja ebenfalls nichts). Aber ohne noch die zusätzlichen if(x!=null)
Außerden was soll denn z.B. pStruktur->integer1 zurückgeben wenn pStruktur undefiniert ist?
NULL, denke ich mal. Wie gesagt, man kann es ja mal definieren ;)
Monger
2009-08-11, 21:57:05
Wenn man NULL mal richtig definiert, dann wäre es nicht Inkonsistent.
Was ich geschrieben habe, gilt ja nicht nur für Null. Es gilt für jede Art von invalidem Parameter. Und was eine richtige oder falsche Definition ist, hängt nunmal vom jeweiligen Objekt an. Du kannst einfach nicht annehmen, dass eine Initialisierung mit 0 der "Default" vom Objekt ist. Das kann fürchterlich in die Hose gehen.
Richtig, aber all diese Maßnahmen gehen mir ziemlich gegen den Strich, wie ich ja erklärt habe. Denn gerade bei Datenbanken ist NULL gewünscht und NULL ist, wie gesagt nicht gleichzusetzen mit 0.
NULL in einer Datenbank hat auch rein gar nichts mit dem Null in der Programmiersprache zu tun. Ich weiß ja nicht was du verwendest, aber wenn das eine saubere Datenbankverbindung ist, kennt die auch einen eigenen "NULL" Typus.
Die Prüfung ob die Länge und/oder Seite korrekt ist:
if(wuerfel.getSide(1).getLenght() >= 1) blabla
statt:
if(wuerfel != null)
Side s = wuerfel.getSide(1);
if(s != null)
if(s.getLenght() >= 1)
Bin ehrlich gesagt nicht sicher, was du mir mit dem Beispiel sagen willst.
NULL, denke ich mal. Wie gesagt, man kann es ja mal definieren ;)
Das geht aber bei nativen Datentypen nicht.
Pinoccio
2009-08-11, 21:58:44
NULL, denke ich mal. Wie gesagt, man kann es ja mal definieren ;)Wenn du eine Integer-Zahl erwartest, was soll NULL da sein? 0 kann es ja offenbar nicht sein, Inf gibt es nicht, NaN wäre evtl. sinnvoll, gibt es aber auch nicht. Und nun? ;-(
mfg
/edit: (Postinglänge Coda - Postinglänge Pinoccio) / (Zeitlicher Abstand) = 3,17 Zeichen pro Sekunde :-)
Und für Coda war es ein echt langer Beitrag. ;-)
Monger
2009-08-11, 22:00:53
Kommt drauf an, was du mit dem Würfel machen willst. Willst du ihn malen? Dann wird nichts gemalt. Willst du ihn speichern? Dann wird nichts gespeichert (er existiert ja nicht).
Damit verschiebst du das Problem nach außen. Du versuchst das schlechte Design des Würfels von außen zu kompensieren.
Eigentlich solltest du dich darauf verlassen, dass der Würfel gültig ist. Ist er es nicht, bekommst du früher oder später (hoffentlich früher) eine Exception - und dann musst du halt angemessen reagieren. Ist dein Würfel kaputt, sollte deine dringendste Frage sein wie er überhaupt kaputt gehen konnte - nicht, wie dein Programm trotz kaputten Würfels weiterlaufen kann.
Die Frage ist dann, warum du NULL übergeben hast.
Fremder Code ist grundsätzlich boshaft! ;)
Ganon
2009-08-11, 22:06:18
Das geht aber bei nativen Datentypen nicht.
Da ich heutige Sprachen nicht ändern kann, muss das ganze ja nicht unbedingt "hardwarenah" sein. Siehe die Wrapper für int, float, etc. bei Java oder C#. Also ist es wohl eine Altlast, für mein Empfinden ;)
Damit verschiebst du das Problem nach außen. Du versuchst das schlechte Design des Würfels von außen zu kompensieren.
Eigentlich solltest du dich darauf verlassen, dass der Würfel gültig ist. Ist er es nicht, bekommst du früher oder später (hoffentlich früher) eine Exception - und dann musst du halt angemessen reagieren.
Er würde eh durch die Gültigkeitsprüfung fallen, da die Werte nicht korrekt sind. Nur erspart man sich für jede Ebene (get().get().get() etc.) die Prüfung auf NULL.
Ist ja nicht so, als dass es solche Sprachen nicht gibt ;)
RattuS
2009-08-11, 22:07:25
Ich gebe Monger hier vollkommen recht. Eine Prüfung auf NULL darf bei einem ordentlichen Modell gar nicht notwendig sein.
Ganon
2009-08-11, 22:10:17
Fremder Code ist grundsätzlich boshaft! ;)
Das stimmt ;) Erzählte das mal meinen Kollegen die andauernd die NULL-Prüfung vergessen und man aus Zeitdruck die NULL-Prüfung quer im System verteilen muss. ;)
Monger
2009-08-11, 22:14:32
Er würde eh durch die Gültigkeitsprüfung fallen, da die Werte nicht korrekt sind. Nur erspart man sich für jede Ebene (get().get().get() etc.) die Prüfung auf NULL.
Ist ja nicht so, als dass es solche Sprachen nicht gibt ;)
Wieso prüfst du überhaupt auf Null?
Pack deine Get Konstrukte in ein Try/Catch, und gut ist. Natürlich nicht ohne im Catch was sinnvolles zu tun.
Würde ein Zugriff auf Null KEINE Exception werfen, hättest du hier ein ernsthaftes Problem! Dann müsstest du nämlich tatsächlich jeden einzelnen Zugriff auf Null prüfen. So kannst du die Exception locker großflächig fangen und verarbeiten.
Ein Null-Pointer-Exception wird unter C/C++ übrigens nicht mit try/catch gefangen.
Er würde eh durch die Gültigkeitsprüfung fallen, da die Werte nicht korrekt sind. Nur erspart man sich für jede Ebene (get().get().get() etc.) die Prüfung auf NULL.
Angenommen du würdest den erwarteten Würfel zeichnen wollen, dann würdest du in erster Linie keinen Würfel sehen, und du weißt zunächst überhaupt nicht warum.
Du hast nur eine Fehlerbeschreibung: "Ich sehe keinen Würfel", und das kann sehr viel bedeuten. Hängt er vielleicht in der Scene gar nicht drin? Stimmt die Kameraorientierung nicht? Falsche Materialeigenschaften? Wurde vielleicht sogar ein Draw-Call vergessen?
Eigentlich hätte dann die Aktion "zeichne Würfel x" nichts mit dem eigentlichen Zeichenvorgang zu tun, sondern mit den falschen Daten die dahinterliegen - von denen du anhand des leeren Bildschirms auch nicht wüsstest, auf welcher Ebene (get.().get().get()..) dieser Datenfehler passiert.
Das verschleiert das grundsätzliche Problem komplett.
Monger
2009-08-11, 22:21:48
Ein Null-Pointer-Exception wird unter C/C++ übrigens nicht mit try/catch gefangen.
Ach ja, richtig. Bereichslängenfehler auch nicht, nicht wahr?
Das ist dann natürlich äußerst, äußerst ungeschickt. Dann bleibt einem nix anderes übrig, als möglichst früh auf Null zu prüfen, und selber eine fangbare Exception zu werfen.
Ganon
2009-08-11, 22:28:18
Wieso prüfst du überhaupt auf Null?
Pack deine Get Konstrukte in ein Try/Catch, und gut ist. Natürlich nicht ohne im Catch was sinnvolles zu tun.
Das Problem ist hier die Definition. NULL ist ja nunmal Inkonsistent und entsprechende Zugriffe nicht definiert. Somit kann NULL ein gewünschter Wert gewesen sein (in/aus der Datenbank), oder ein Fehler (get() sagt ja nicht aus, dass es nur eine Member-Variable zurückgibt). Das hilft an der Stelle dann leider nicht.
Zumal, wenn man z.B. mal mit Hibernate gearbeitet hat, kann es sogar passieren, dass das genutzte Framework einfach Exceptions frisst und man ohne (fangbare) Exception NULL bekommt. Nobody is perfect ;)
Wenn es "nur" um Membervariablen geht, nutze ich natürlich try/catch...
Das verschleiert das grundsätzliche Problem komplett.
Du kannst in der sowieso notwendigen Validierung (jetzt mal von Null-Checks abgesehen) immer noch sinnvollere Exceptions werfen (oder Fehler loggen) und den Würfel ignorieren, anstatt einfach mit Nullpointer abzustürzen.
Ach ja, richtig. Bereichslängenfehler auch nicht, nicht wahr?
Da passiert wenn der Compiler nicht entsprechenden Zusatzcode einfügt einfach nur undefiniertes.
ScottManDeath
2009-08-11, 22:29:54
http://en.wikipedia.org/wiki/Null_Object_pattern wo es Sinn ergibt. Damit kann man sich viele der Null tests sparen, wenn es plausibles, neutrales verhalten gibt.
.NET hat auch typen die null sein koennen. http://msdn.microsoft.com/en-us/library/2cf62fcy.aspx
Monger
2009-08-11, 22:33:52
Das Problem ist hier die Definition. NULL ist ja nunmal Inkonsistent und entsprechende Zugriffe nicht definiert. Somit kann NULL ein gewünschter Wert gewesen sein (in/aus der Datenbank), oder ein Fehler (get() sagt ja nicht aus, dass es nur eine Member-Variable zurückgibt).
Dann ist das an der Stelle schlicht eine lausige Datenabstraktionsschicht.
In .NET gibt es da einen Typus namens System.DBNull (http://msdn.microsoft.com/en-us/library/system.dbnull.aspx). Das ist das Objekt, was zurückkommt wenn ich eine Datenbank abfrage und der Wert eben ein Datenbank "Null" ist. Das ist ein Immutable Objekt. Auf dem kann ich z.B. auch ToString() aufrufen, ohne eine NullReferenceException zu kriegen.
Diese Doppeldeutigkeit aus Null und DBNull die du bei dir offenbar hast, ist schlicht ein Design Bug.
Ganon
2009-08-11, 22:41:51
Dann ist das an der Stelle schlicht eine lausige Datenabstraktionsschicht.
In .NET gibt es da einen Typus namens System.DBNull (http://msdn.microsoft.com/en-us/library/system.dbnull.aspx). Das ist das Objekt, was zurückkommt wenn ich eine Datenbank abfrage und der Wert eben ein Datenbank "Null" ist. Das ist ein Immutable Objekt. Auf dem kann ich z.B. auch ToString() aufrufen, ohne eine NullReferenceException zu kriegen.
Diese Doppeldeutigkeit aus Null und DBNull die du bei dir offenbar hast, ist schlicht ein Design Bug.
Ich glaube nicht, dass ORM-Tools für .NET bei selbst definierten Klassen, oder sonstigen Entity-Membern einfach den Typ ändern. Das würde eine Classcast-Exception werfen, die auch nicht besser ist ;)
Monger
2009-08-11, 22:48:36
Das ist halt ein Design Problem. Keine Ahnung was für ORM Tools du genau verwendest, aber dem Entwickler kannst du dann gerne mal auf die Finger klopfen.
Zumindest das was im .NET Framework dabei ist (ADO.NET, und was da alles darunter fällt), funktioniert auf diese Weise wunderbar.
Ganon
2009-08-11, 22:53:12
Zumindest das was im .NET Framework dabei ist (ADO.NET, und was da alles darunter fällt), funktioniert auf diese Weise wunderbar.
Dann mal eine Frage:
Simples Beispiel, eine Klasse die nur Strings enthält. Jetzt lade ich mir aus der Datenbank eine die komplette Tabelle und habe jetzt eine Liste von Objekten. Jetzt nehme ich mir ein Objekt und greife auf einen String zu, der in der Datenbank NULL ist. Ist denn der String plötzlich DBNull und nicht mehr String?
Wie würde das dann funktionieren, wenn ich z.B. getName().substring() aufrufe, da DBNull ja Substr gar nicht hat?
Ich nutze übrigens Hibernate. Eigentlich so ziemlich das größte und umfangreichste ORM-Framework, was du kriegen kannst ;)
Abnaxos
2009-08-11, 23:23:43
Wieso prüfst du überhaupt auf Null?
Pack deine Get Konstrukte in ein Try/Catch, und gut ist. Natürlich nicht ohne im Catch was sinnvolles zu tun.
Bitte nicht! Exceptions, besonders RuntimeExceptions, sind Fehler. Es gibt wenig, was mir mehr auf den Keks geht, als Code, der solche Exceptions erwartet und fängt, statt sie zu verhindern. Ist besonders lustig bei gewissen XML-Parsern in Java: Da versucht man einer NullPointerException auf den Grund zu gehen und setzt einen Exception-Breakpoint darauf ─ und dann ist der XML-Parser so programmiert, dass vorher noch 100 “ganz normale” NPEs auftreten. Na danke …
Würde ein Zugriff auf Null KEINE Exception werfen, hättest du hier ein ernsthaftes Problem! Dann müsstest du nämlich tatsächlich jeden einzelnen Zugriff auf Null prüfen.
Richtig, und daher wäre es ungleich gefährlicher, bei Dereferenzierungen von Null irgend einen neutralen Wert zurück zu geben, als dem Programmierer einfach eine NPE um die Ohren zu knallen. Wenn Null ein zu erwartender Wert ist, soll er auch gesondert behandelt werden.
Ein wenig syntaktischer Zucker wäre allerdings schon nett, muss ich zugeben. Es wurde (oder wird immer noch) auch tatsächlich diskutiert, in Java 7 den Operator “?.” einzuführen. Damit hätte man für Standard-Fälle eine hübsche kurze Syntax, muss aber trotzdem explizit auf ein mögliches Null achten.
Mich hat es jedoch nie gross gestört.
http://en.wikipedia.org/wiki/Null_Object_pattern wo es Sinn ergibt. Damit kann man sich viele der Null tests sparen, wenn es plausibles, neutrales verhalten gibt.
Ein sehr schönes Pattern, das ich häufig verwende.
Dann mal eine Frage:
Simples Beispiel, eine Klasse die nur Strings enthält. Jetzt lade ich mir aus der Datenbank eine die komplette Tabelle und habe jetzt eine Liste von Objekten. Jetzt nehme ich mir ein Objekt und greife auf einen String zu, der in der Datenbank NULL ist. Ist denn der String plötzlich DBNull und nicht mehr String?
Nein, der ist dann null. Null bei .NET heißt eine Null Referenz. Referenzen haben nur Objekte, also Referenztypen. DbNull heißt nur null in der DB, das kann in der DB auch für int, bool etc. so definieren, die in .NET aber Valuetypen wären und somit gar kein null erlauben würden.
Wie würde das dann funktionieren, wenn ich z.B. getName().substring() aufrufe, da DBNull ja Substr gar nicht hat?
Diese Konvertierung übernimmt ADO.NET automatisch und ein String wäre dann eben null. Trotzdem ändert das nichts an der Tatsache, dass du auch bei einer Null Referenze eine Exception bekommst, wenn du auf die Methode Substring zugreifen würdest.
Ich nutze übrigens Hibernate. Eigentlich so ziemlich das größte und umfangreichste ORM-Framework, was du kriegen kannst ;)
Die Datenbank-Leute haben zu ORM-Framerworks ihre eigene Meinung: http://www.oreillynet.com/pub/e/1371
Ganon
2009-08-12, 08:20:58
@Gast
Eben, damit hätte man also das gleiche Problem auch in .NET und somit macht Hibernate wohl nichts falsches ;)
@Trap
Da geht es, soweit ich verstanden habe, nur darum, dass man das Datenbank-Design nicht dem ORM-Tool überlassen sollte. Ich kenne jetzt nur Hibernate, aber hier kann man das super steuern für "den Regelfall". Für Super-Sonder-Spezial-Designs kann man Hibernate auch komplett auf eine bestehende Datenbank anpassen. Selbst m:n Mapping kann Hibernate abbilden.
Das hat mit der NULL-Problematik nicht viel zu tun. Ohne ORM will ich eigentlich keine Datenbank mehr anfassen wollen ;) Zumal ORM eh IMMER da ist. Du kannst ja auf einer Oberfläche schlecht ein ResultSet anzeigen, man muss es immer erst Konvertieren. Sei es mit rs.getString("name") oder sonst wie.
Eben, damit hätte man also das gleiche Problem auch in .NET und somit macht Hibernate wohl nichts falsches ;)
Das ist doch aber kein Problem, sondern so gewollt. Wenn du DbNull nicht in der Anwendung behandeln willst, dann darfst du eben kein Null in der DB verwenden und musst stattdessen andere Defaultwerte nehmen (z.B. '', 0 etc.). Aber Null (ob nun in DB oder Anwendung) hat einen nicht definierten/uninitialisierten Charakter, das ist etwas anderes als ein Leerstring etc.
Bei ADO.NET hast du zumindest in Verbindung mit DataSets (oder beim neuen Entity Framework) die Möglichkeit, bei Null eine Konvertierung in einen Defaultwert vorzunehmen (also z.B. bei einem String '', bei einem int 0 etc.). Das legt man dann einfach zur Designzeit beim DataSet fest.
Monger
2009-08-12, 09:07:54
Bitte nicht! Exceptions, besonders RuntimeExceptions, sind Fehler.
Wenn ich sage "was sinnvolles im Catch tun", meine ich damit natürlich nicht, die Exception wortlos zu schlucken, oder als simple Signalisierung zu mißbrauchen. Exceptions sollten schließlich nur ausnahmsweise auftreten.
The_Invisible
2009-08-12, 09:42:12
Wenn ich sage "was sinnvolles im Catch tun", meine ich damit natürlich nicht, die Exception wortlos zu schlucken, oder als simple Signalisierung zu mißbrauchen. Exceptions sollten schließlich nur ausnahmsweise auftreten.
ich verwende exceptions gerne im zusammenhang mit datenbank rollbacks bei eingabefehlern, ka ob das auch unter "missbrauchen" fällt :D
mfg
Ganon
2009-08-12, 09:59:42
ich verwende exceptions gerne im zusammenhang mit datenbank rollbacks bei eingabefehlern, ka ob das auch unter "missbrauchen" fällt :D
Kundendummheit ist zwar ein Regelfall, aber ich denke man kann es aus der Programmierersicht ruhig als Ausnahme behandeln :D Dafür sind die Datenbank-Eigenschaften wie Eindeutigkeit etc. ja da.
SentinelBorg
2009-08-13, 15:08:11
Imho ist das genau richtig so. Null ist undefiniert und kann damit auch nicht normal behandelt werden. Einfach irgendwelche Default-Werte festlegen, wie scheinbar in Objective-C scheint mir da extrem gefährlich zu sein.
Das einzige was mich z.B. in .Net stört, dass es noch Value Types gibt, die nicht Null sein können. Da .Net eh automatisches Boxing beherrscht, hätte man das auch gleich von Anfang an unterstützen können. Jetzt hat man halt die Krücke der Nullable Generic Types, die eine unschöne Sonderbehandlung erfordern.
Novox
2009-08-17, 18:06:45
Das einzige was mich z.B. in .Net stört, dass es noch Value Types gibt, die nicht Null sein können.
Und ich bin froh, daß zumindest Value Types nicht null sein können.
Nighthawk13
2009-08-20, 19:03:07
Ein Null-Pointer-Exception wird unter C/C++ übrigens nicht mit try/catch gefangen.
<Semi-OT>
Unter Windows kann man die Exceptions mittels _se_translator_function() schon so "umbiegen", das structured exceptions(access violations etc.) zu C++ Execptions werden.
Allerdings ist das für das Null-Pointer-Problem auch keine brauchbare Lösung, weil Daten evtl. schon durch unkontrolliertes Überschreiben inkonsistent geworden sein könnten.
</Semi-OT>
Die Lösung liegt eher in einem entsprechend gekapselten Framework was die entsprechenden if-Abfragen versteckt und ggf. C++ Exceptions wirft.
vBulletin®, Copyright ©2000-2024, Jelsoft Enterprises Ltd.