Exception Diskussion
 
StartSeite | Neues | TestSeite | ForumSeite | Teilnehmer | Kategorien | Index | Hilfe | Einstellungen | Ändern

Veränderung (letzte Korrektur) (Änderung, Normalansicht)

Hinzugefügt: 144a145,146


KategorieException

Siehe auch ExceptionsDiskussion.

Mit ein Paar kleinen Änderungen (die ich so einmal vorschlage) würde es mir besser gefallen. Eine einheitliche Art geschweifte Klammern zu setzen ist recht nützlich. Welche es ist ist dann ziemlich egal. Die misbräuchliche Verwendung einer Exception aus java.lang und die Wortwahl in der mitgegebenen Nachricht fand ich unglücklich. --kw

Ad Klammersetzung kann ich Dir nur zustimmen.

Bei der NullPointerException hingegen kann ich Dir nur teilweise zustimmen: die Wortwahl war tatsächlich unglücklich. Hingegen halte ich die Ersetzung der NullPointerException durch das Werfen einer unspezifizierten Exception im ersten Fall für einen deutlichen Rückschritt, im zweiten Fall für eine nur phyrrische Verbesserung.

Betrachten wir das einmal genauer:

Erster Fall:

// Original

public NumberCache(RandomNumberGenerator rng)
{
  if (rng == null)
  {
    throw new NullPointerException("The RandomNumberGenerator"
                                   "may not be null");
  }
  // ...

Hier wird die NPE als (Runtime-)Assert eingesetzt. Der NumberCache ist ohne einen RandomNumberGenerator in einem illegalen Zustand.

Wenn das so gemeint ist, dann würde mir RuntimeException als Typ der geworfenen Exception ganz gut gefallen, und zwar genau mit der oben angegebenen Nachricht. -- kw

Würde man (rng == null) nicht hier abfragen, würde bei der ersten Benutzung des NumberCaches eine NPE fliegen.

Was mir besser gefallen würde als das explizte Werfen einer NullPointerException, aber weniger gut als das Werfen einer RuntimeException (oder einer spezifischen RuntimeException)

Die NullPointerException ist eine RuntimeException.

Wie so häufig (wenn nicht sogar immer) beim Auftauchen von "Exceptions" reibe ich mir verduzt die Augen und frage: Was soll das? Wenn ich schon wieder diese Begründung höre, "Würde man (rng == null) nicht hier abfragen, würde bei der ersten Benutzung des NumberCaches eine NPE fliegen.". Was zwingt den jemanden, der ein NumberCache-Objekt anlegen möchte, dazu, später auf diesem Objekt die Funktion retrieveNext() aufzurufen? Z. B. könnte er ja lediglich ein "(new NumberCache(null)).toString()" machen wollen. Warum nicht und warum wollt ihr das verbieten? Was ich hier sehe, ist: der Code ist länger, häßlicher und auch noch falscher, als er es ohne das Exception-Geraffel gewesen wäre. Irgendwer muss endlich mal den Artikel ExceptionsConsideredHarmful schreiben. -- VolkerGlave (ich weiß, sorry, trägt hier nicht direkt zum Thema der Seite bei, trotzdem, ...)

Du hast recht, auch wenn ich "(new NumberCache(null)).toString()" nicht gerade als ausschlaggebend erachte, ist das größere Argument der Einschränkung der Benutzbarkeit durchaus zutreffend. Testhalber habe ich den Test jetzt in die retrieveNext() verschoben.

Ok, weiter geht's. Wieso wird in der Funktion main() umständlich ein "try ... catch (Exception ex) ..." gemacht? Erstens wird von Java-Runtime-Umgebungen üblicherweise eh so reagiert, wie hier nachprogrammiert (in "The Java Language Specification" heißt es dazu an einer Stelle "[...] after which the thread executing main [...] terminates because of an uncaught exception, which typically results in printing the exception name and a simple backtrace."). Zweitens mag - ein wenig weit hergeholt, aber auch nicht zu weit, weil diese Situation bei Exceptionbehandlungen so immer wieder anzutreffen ist - dieses main() durchaus gar nicht das Hauptprogramm sein, und es könnte sein, dass ein Aufrufer von main() die Exception auf irgendeine Weise verarbeiten wollte, nur kriegt er es leider nicht mit, weil das blöde main() ihm die Dinger vor der Nase wegschnappt. Ich plädiere also für das Weglassen des "try ... catch (Exception ex) ..." und statt dessen Ändern der Funktionssignatur in "public static void main(String argv[]) throws Exception". -- vgl

Ein Überbleibsel. Da die main() eigentlich nur zu Demonstrationszwecken da war, habe ich sie jetzt durch eine simplere Variante ersetzt.

Exkursion: Ich verstehe die bei den NumberCache-Instanzvariablen generator und index notierten Initialisierungen mit null bzw. 0 nicht. Das macht Java doch exakt genauso sowieso von selbst. Soll der Code damit C++-Programmiereren gefällig gemacht werden? IMO darf man beim Schreiben von Code davon ausgehen, dass potenzielle Leser der Sprache mächtig sind, und man darf also zur Verfügung stehende Sprachmittel einsetzen. Vielmehr sollte man es sogar, weil andernfalls nämlich bei tatsächlich der Sprache mächtigen Lesern der Eindruck entstehen muss, der ursprüngliche Autor wäre der Sprache selbst _nicht_ mächtig gewesen, wodurch der Leser unnötigerweise den gesamte Code mit mehr als dem normal üblichen Argwohn betrachtet, mit dem unschönen Effekt, den Code weniger schnell verstehen zu können. -- vgl

"[...]. Soll [...] C++-Programmiereren gefällig gemacht werden? [...] der ursprüngliche Autor wäre der Sprache selbst _nicht_ mächtig gewesen [...]": Beides. Ich programmiere sowohl Java als auch C++. Daher Ersteres _wegen_ Letzterem. Schlüsselwörter: Cross-Language-Konsistenz, AusnahmenVerringerung?, KISS, etc.

Geht mir ähnlich; ich nehme das mal als Erlaubnis, meinem Einwand entsprechend oben im Code die beiden Dinge zu verändern. Passt auch gut zu KISS - ist ja weniger Code -, und auf C++-Programmierer brauchen wir hier keine Rücksicht zu nehmen, wenn wir beide auch welche sind, ganz oben auf der Seite steht ja "Java". -/- Ich nehm auch gleich die bisher auf meinen ersten Hinweis hin von dir lediglich auskommentierten Teile ganz raus. -- vgl

Vollkommene Zustimmung.

Und die nächste Frage an unsere Exception-Süchtigen. [...]

Um in der öffentlichen NumberCache-Instanzmethode toString() den Inhalt des double[] cache ausgeben zu können, muß diese Ausgabe händisch erfolgen, anstatt sich auf die JRE-spezifischen Methoden (z.B.: result.append(cache)) zu verlassen, da letztere leider meist nur eine - an dieser Stelle sinnarme - Hex-Referenz ausgeben. Während das JRE bei result.append(cache) korrekt den null-Fall erkennen würde, muß das hier händisch abgeprüft werden um eine korrekte Ausgabe zu erzielen.

[...] Auffällig ist nun, dass in der zweiten öffentlichen Instanzfunktion retrieveNext() die erste Nutzung von cache nicht derart geschützt ist. Warum diese Ungleichbehandlung? Unter den vielen Möglichkeiten halte ich für nicht unwahrscheinlich, dass der Programmierer sich durch die an mehreren Stellen eingestreuten RandomNumberGeneratorException-throws-Spezifikationen und -throw-Anweisungen vor dieser Situation geschützt zu haben glaubt. [...]

Jein. Da keine vollständige Spezifikation des RandomNumberGenerator-Interfaces vorliegt, ist der NumberCache nach einem null Rückgabewert des generators in einem undefinierten Zustand. Da es in Java in Interfacespezifikationen keine a-posteriori Bedingungen ermöglicht, sehe ich auch nicht viel Chancen das zu verbessern.

  1. Status quo "legalisieren":
    1. Im Interface des RandomNumberGenerators einen Kommentar hinzufügen und "leere" Rückgabewerte verbieten.
    2. Im NumberCache festhalten, daß nach einr RNGException von retrieveNext() der NumberCache keinen legalen Status hat. Dies könnte man mit zusätzlicher Buchhaltung und einer IllegalStateException untermauern.
  2. Man könnte es nocheinmal versuchen und bei neuerlichem Mißerfolg wiederum eine RNGException werfen. Dabei kommt man aber möglicherweise in Endlosschleifen, die wesentlich unhandhabbarer sind als eine NPE oder eine ISE.
[...] Pustekuchen. Die Situation passiert so: Jemand schreibt sich eine Anwendung A und erzeugt sich dafür eine NumberCache-Instanz n unter Beigabe einer Instanz eines RandomNumberGenerator-Interfaces-Implementierers R. R sei so implementiert, dass bei nextBatch() auch die Rückgabe eines null-Feldes ("return null") ohne Werfen einer RandomNumberGeneratorException passieren kann (nennen wir diesen Fall "N"). Das mag hier vielleicht nicht gefallen, es ist aber völlig legal und erfüllt das Interface. In der Anwendung A wird nun des öfteren retrieveNext() auf der Instanz n aufgerufen. Diese Aufrufe seien durch try/catch-Blöcke geklammert, in denen auf RandomNumberGeneratorExceptions reagiert wird. Wie nun ganz genau, tut nichts zur Sache, aber sei es so, dass dabei die Instanz n nicht weggeschmissen wird. Auch das ist völlig legales, übliches und normales Gebaren. Zur Laufzeit passiere nun irgendwann der Fall "N". Innerhalb von retrieveNext() wird cache somit zu null - R.nextBatch() hat ja wie gesagt keine Exception geschmissen -, sofort danach schmeisst retrieveNext() aber selber eine. Die wird in der Anwendung A gefangen und behandelt (dazu sind Exceptions ja angeblich da), die Instanz n wird wie gesagt nicht weggeschmissen. So. Der nächste Aufruf von n.retrieveNext() knallt mit einer NullPointerException (an der Stelle "if (index >= cache.length) ..."). Genau so kennt man sie, unsere Exception-Freunde. Groß mit Spezial-Exceptions zaubern und dann doch mit NullPointerExceptions abschmieren. Und über die Vorzüge von Exceptions faseln.

Gegenvorschlag: Den gesamten Code völlig von "Exceptions" befreien. Das Interface so vorgeben, dass keine spezielle RandomNumberGeneratorException zugestanden ist. Funktion retrieveNext() 0.0 zurückgeben lassen, falls sie keinen Generator hat, oder falls der Generator ein null-Array oder ein leeres Array liefert. Zumal auf dieser Wiki-Seite hier Exceptions gar nicht das Kernthema sind. [Aber eine sehr interessante Diskussion!]

Leider gibt es keinen double Wert, der nicht in der Bildmenge der retrieveNext() Funktion liegt. Daher ist eine Fehlersignalisierung durch den Rückgabewert nicht möglich ohne die Signatur zu verändern (z.B.: boolean retrieveNext(Double)), was aber (imo) eine Vergewaltigung der SpracheJava wäre.

-- vgl (mit Umformulierungen und Antworten von DavidSchmitt)

Was denn nur immer für eine "Fehlersignalisierung"? Wir zurren die Chose so fest, dass es "Fehler" nicht mehr gibt. Das Interface schreiben wir so ...

public interface RandomNumberGenerator
{
  /**
   * Liefert ein Array von Zufallszahlen. Falls Implementierungen
   * hier null oder ein leeres Array zurückgeben, sind Nutzer
   * gehalten, dies als "new double[] { 0.0 }" zu interpretieren.
   */
  double[] nextBatch();
}

... und die NumberCache-Methode retrieveNext() so ...

public class NumberCache
{
  [...]
  public synchronized double retrieveNext()
  {
    if (index >= cache.length)
      pickNextBatch();
    return cache[index++];
  }

  private void pickNextBatch()
  {
    index = 0;
    if (generator != null)
      if ((cache = generator.nextBatch()) != null)
        if (cache.length > index)
          return;
    cache = new double[index + 1];
  }
  [...]
}

-- vgl

So ist des Zustand des Caches immer wohldefiniert.

Den Kommentar könnte man sogar noch durch einen Implementationstrick im Source festhalten:

public abstract class RandomNumberGenerator
{
  /**
   * Liefert ein Array von Zufallszahlen. retourniert nie null oder double[0]
   */
  public final double[] nextBatch()
  {
    double[] result;
    if ((result = calculateNextBatch()) != null)
      if (result.length > 0)
        return result;

    result = new double[] {0.0};

    return result;
   }

   /**
    * Override this method to provide customized random numbers
    */
   protected double[] calculateNextBatch();
}


KategorieException


StartSeite | Neues | TestSeite | ForumSeite | Teilnehmer | Kategorien | Index | Hilfe | Einstellungen | Ändern
Text dieser Seite ändern (zuletzt geändert: 21. Januar 2004 19:59 (diff))
Suchbegriff: gesucht wird
im Titel
im Text