it-swarm.com.de

Warum ist Optional.get () ohne den Aufruf von isPresent () schlecht, aber nicht iterator.next ()?

Wenn ich die neue Java8-Streams-API verwende, um ein bestimmtes Element in einer Sammlung zu finden, schreibe ich Code wie folgt:

    String theFirstString = myCollection.stream()
            .findFirst()
            .get();

Hier warnt IntelliJ, dass get () aufgerufen wird, ohne zuerst isPresent () zu überprüfen.

Dieser Code jedoch:

    String theFirstString = myCollection.iterator().next();

... gibt keine Warnung aus.

Gibt es einen tiefgreifenden Unterschied zwischen den beiden Techniken, der den Streaming-Ansatz in irgendeiner Weise "gefährlicher" macht, dass es entscheidend ist, get () niemals aufzurufen, ohne zuerst isPresent () aufzurufen? Beim Googeln finde ich Artikel darüber, wie sorglos Programmierer mit Optional <> umgehen, und gehe davon aus, dass sie get () jederzeit aufrufen können.

Ich möchte, dass eine Ausnahme so nahe wie möglich an der Stelle ausgelöst wird, an der mein Code fehlerhaft ist. In diesem Fall rufe ich get () auf, wenn kein Element gefunden werden kann. Ich möchte keine nutzlosen Aufsätze schreiben müssen wie:

    Optional<String> firstStream = myCollection.stream()
            .findFirst();
    if (!firstStream.isPresent()) {
        throw new IllegalStateException("There is no first string even though I totally expected there to be! <sarcasm>This exception is much more useful than NoSuchElementException</sarcasm>");
    }
    String theFirstString = firstStream.get();

... es sei denn, es besteht die Gefahr, Ausnahmen von get () zu provozieren, von denen ich nichts weiß?


Nachdem ich die Antwort von Karl Bielefeldt und einen Kommentar von Hulk gelesen hatte, stellte ich fest, dass mein Ausnahmecode oben etwas ungeschickt war. Hier ist etwas Besseres:

    String errorString = "There is no first string even though I totally expected there to be! <sarcasm>This exception is much more useful than NoSuchElementException</sarcasm>";
    String firstString = myCollection.stream()
            .findFirst()
            .orElseThrow(() -> new IllegalStateException(errorString));

Dies sieht nützlicher aus und wird wahrscheinlich an vielen Stellen natürlich sein. Ich habe immer noch das Gefühl, dass ich in der Lage sein möchte, ein Element aus einer Liste auszuwählen, ohne mich mit all dem befassen zu müssen, aber das könnte nur sein, dass ich mich an diese Art von Konstrukten gewöhnen muss.

23
TV's Frank

Bedenken Sie zunächst, dass die Prüfung komplex und wahrscheinlich relativ zeitaufwändig ist. Es erfordert eine statische Analyse, und zwischen den beiden Aufrufen befindet sich möglicherweise einiges an Code.

Zweitens ist die idiomatische Verwendung der beiden Konstrukte sehr unterschiedlich. Ich programmiere Vollzeit in Scala, die Options viel häufiger verwendet als Java. Ich kann mich nicht an eine einzelne Instanz erinnern, in der ich eine .get() verwenden musste auf einem Option. Sie sollten fast immer das Optional von Ihrer Funktion zurückgeben oder stattdessen orElse() verwenden. Dies sind viel bessere Methoden, um fehlende Werte zu behandeln, als Ausnahmen auszulösen.

Da .get() bei normaler Verwendung selten aufgerufen werden sollte, ist es sinnvoll, dass eine IDE] eine komplexe Prüfung startet, wenn eine .get() angezeigt wird, um festzustellen, ob sie ordnungsgemäß verwendet wurde Im Gegensatz dazu ist iterator.next() die richtige und allgegenwärtige Verwendung eines Iterators.

Mit anderen Worten, es geht nicht darum, dass einer schlecht ist und der andere nicht, es geht darum, dass einer ein häufiger Fall ist, der für seinen Betrieb von grundlegender Bedeutung ist und bei Entwicklertests höchstwahrscheinlich eine Ausnahme auslöst, und der andere wird selten verwendet Fall, der möglicherweise nicht ausreichend ausgeübt wird, um beim Entwicklertest eine Ausnahme auszulösen. Dies sind die Fälle, vor denen IDEs Sie warnen sollen.

12
Karl Bielefeldt

Die optionale Klasse soll verwendet werden, wenn nicht bekannt ist, ob das darin enthaltene Element vorhanden ist oder nicht. Die Warnung dient dazu, Programmierer darüber zu informieren, dass es einen zusätzlichen möglichen Codepfad gibt, den sie möglicherweise ordnungsgemäß verarbeiten können. Die Idee ist, zu vermeiden, dass Ausnahmen überhaupt ausgelöst werden. Der Anwendungsfall, den Sie präsentieren, ist nicht das, wofür er entwickelt wurde, und daher ist die Warnung für Sie irrelevant. Wenn Sie tatsächlich möchten, dass eine Ausnahme ausgelöst wird, deaktivieren Sie sie (obwohl dies nur für diese Zeile und nicht global gilt) Wenn Sie die Entscheidung treffen, ob der nicht vorhandene Fall instanzweise behandelt werden soll oder nicht, werden Sie durch die Warnung daran erinnert, dies zu tun.

Möglicherweise sollte eine ähnliche Warnung für Iteratoren generiert werden. Es wurde jedoch einfach nie durchgeführt, und das Hinzufügen einer jetzt würde wahrscheinlich dazu führen, dass zu viele Warnungen in bestehenden Projekten eine gute Idee sind.

Beachten Sie auch, dass das Auslösen einer eigenen Ausnahme nützlicher sein kann als nur eine Standardausnahme, da eine Beschreibung des erwarteten Elements enthalten sein kann, aber beim Debuggen zu einem späteren Zeitpunkt nicht sehr hilfreich sein kann.

15
Jules

Ich bin damit einverstanden, dass es keinen inhärenten Unterschied gibt, möchte jedoch auf einen größeren Fehler hinweisen.

In beiden Fällen haben Sie einen Typ, der eine Methode bereitstellt, deren Funktion nicht garantiert ist, und Sie sollten vorher eine andere Methode aufrufen. Ob Ihre IDE/Compiler/etc Sie vor dem einen oder anderen Fall warnt, hängt nur davon ab, ob dieser Fall unterstützt wird und/oder ob Sie ihn dafür konfiguriert haben.

Davon abgesehen sind beide Codeteile Code-Gerüche. Diese Ausdrücke weisen auf zugrunde liegende Konstruktionsfehler hin und führen zu sprödem Code.

Sie haben natürlich Recht, wenn Sie schnell scheitern wollen, aber die Lösung kann zumindest für mich nicht darin bestehen, es einfach abzuschütteln und Ihre Hände in die Luft zu werfen (oder eine Ausnahme auf den Stapel).

Es ist bekannt, dass Ihre Sammlung vorerst nicht leer ist, aber Sie können sich nur auf den Typ verlassen: Collection<T> - und das garantiert Ihnen nicht, dass Sie nicht leer sind. Obwohl Sie jetzt wissen, dass es nicht leer ist, kann eine geänderte Anforderung dazu führen, dass der Code im Voraus geändert wird und Ihr Code plötzlich von einer leeren Sammlung getroffen wird.

Jetzt können Sie zurückschieben und anderen mitteilen, dass Sie eine nicht leere Liste erhalten sollten. Sie können froh sein, dass Sie diesen "Fehler" schnell finden. Trotzdem haben Sie eine kaputte Software und müssen Ihren Code ändern.

Vergleichen Sie dies mit einem pragmatischeren Ansatz: Da Sie eine Sammlung erhalten und diese möglicherweise leer sein kann, zwingen Sie sich, diesen Fall mit der optionalen # map, #orElse oder einer anderen dieser Methoden außer #get zu behandeln.

Der Compiler erledigt so viel Arbeit wie möglich für Sie, sodass Sie sich so weit wie möglich auf den statischen Vertrag verlassen können, einen Collection<T> Zu erhalten. Wenn Ihr Code niemals gegen diesen Vertrag verstößt (z. B. über eine dieser beiden stinkenden Anrufketten), ist Ihr Code für sich ändernde Umgebungsbedingungen viel weniger spröde.

Im obigen Szenario hat eine Änderung, die zu einer leeren Eingabesammlung führt, keinerlei Auswirkungen auf Ihren Code. Es ist nur eine weitere gültige Eingabe und wird daher immer noch korrekt behandelt.

Die Kosten hierfür sind, dass Sie Zeit in bessere Designs investieren müssen, anstatt a) spröden Code zu riskieren oder b) Dinge unnötig zu komplizieren, indem Sie Ausnahmen herumwerfen.

Abschließend: Da nicht alle IDEs/Compiler ohne vorherige Überprüfung vor iterator (). Next () - oder optional.get () -Aufrufen warnen, wird dieser Code in der Regel in Überprüfungen markiert. Für das, was es wert ist, würde ich nicht zulassen, dass ein solcher Code eine Überprüfung besteht und stattdessen eine saubere Lösung fordert.

6
Frank