Frage

Ich bin gerade dabei, das Ausgezeichnete zu lesen Sauberer Code

Eine Diskussion betrifft die Übergabe von Nullen an eine Methode.

public class MetricsCalculator {
    public double xProjection(Point p1, Point p2) {
        return (p2.x - p1.x) * 1.5;
    }
}
...
calculator.xProjection(null, new Point(12,13));

Es stellt verschiedene Möglichkeiten dar, damit umzugehen:

public double xProjection(Point p1, Point p2) {
    if (p1 == null || p2 == null) {
        throw new IllegalArgumentException("Invalid argument for xProjection");
    }
    return (p2.x - p1.x) * 1.5;
}

public double xProjection(Point p1, Point p2) {
    assert p1 != null : "p1 should not be null";
    assert p2 != null : "p2 should not be null";
    return (p2.x - p1.x) * 1.5;
}

Ich bevorzuge die Behauptungen Ansatz, aber mir gefällt die Tatsache nicht, dass Behauptungen standardmäßig deaktiviert sind.

Im Buch heißt es abschließend:

In den meisten Programmiersprachen gibt es keine gute Möglichkeit, mit einer Null umzugehen, die von einem Aufrufer versehentlich übergeben wird.Da dies der Fall ist, besteht der rationale Ansatz darin, die Übergabe von null standardmäßig zu verbieten.

Es geht nicht wirklich darum, wie Sie diese Einschränkung durchsetzen würden?

Hat einer von euch eine klare Meinung dazu?

War es hilfreich?

Lösung

Sowohl die Verwendung von Behauptungen als auch das Auslösen von Ausnahmen sind hier gültige Ansätze.Beide Mechanismen können verwendet werden, um einen Programmierfehler anzuzeigen, nicht einen Laufzeitfehler, wie es hier der Fall ist.

  • Behauptungen bieten einen Leistungsvorteil, da sie auf Produktionssystemen normalerweise deaktiviert sind.
  • Ausnahmen haben den Vorteil der Sicherheit, da die Prüfung immer durchgeführt wird.

Die Wahl hängt wirklich von den Entwicklungspraktiken des Projekts ab.Das Projekt als Ganzes muss über eine Behauptungsrichtlinie entscheiden:Wenn die Wahl darin besteht, Zusicherungen während der gesamten Entwicklung zu aktivieren, dann würde ich sagen, Zusicherungen zu verwenden, um diese Art von ungültigen Parametern zu überprüfen – in einem Produktionssystem ist es unwahrscheinlich, dass eine aufgrund eines Programmierfehlers ausgelöste NullPointerException abgefangen und behandelt werden kann jedenfalls auf sinnvolle Weise und verhält sich daher wie eine Behauptung.

In der Praxis kenne ich jedoch viele Entwickler, die nicht darauf vertrauen, dass Zusicherungen bei Bedarf aktiviert werden, und sich daher für die Sicherheit des Auslösens einer NullPointerException entscheiden.

Wenn Sie für Ihren Code keine Richtlinie erzwingen können (wenn Sie beispielsweise eine Bibliothek erstellen und daher davon abhängig sind, wie andere Entwickler Ihren Code ausführen), sollten Sie sich natürlich für den sicheren Ansatz entscheiden, NullPointerException für diese auszulösen Methoden, die Teil der API der Bibliothek sind.

Andere Tipps

Die allgemeine Regel lautet: Wenn Ihre Methode dies nicht erwartet null Argumente, die Sie dann werfen sollten System.ArgumentNullException.Richtig werfen Exception Schützt Sie nicht nur vor Ressourcenbeschädigung und anderen schädlichen Dingen, sondern dient auch als Leitfaden für Benutzer Ihres Codes und spart Zeit, die für das Debuggen Ihres Codes aufgewendet wird.

Lesen Sie auch einen Artikel über Defensive Programmierung

Auch nicht von unmittelbarem Nutzen, aber im Zusammenhang mit der Erwähnung von Spec#...Es gibt einen Vorschlag, „Null-sichere Typen“ zu einer zukünftigen Java-Version hinzuzufügen: „Verbesserte Null-Behandlung – Null-sichere Typen“.

Nach dem Vorschlag würde Ihre Methode werden

public class MetricsCalculator {
    public double xProjection(#Point p1, #Point p2) {
        return (p2.x - p1.x) * 1.5;
    }
}

Wo #Point ist die Art von Nicht-null Verweise auf Objekte des Typs Point.

Es geht nicht wirklich darum, wie Sie diese Einschränkung durchsetzen würden?

Sie erzwingen es, indem Sie eine werfen ArgumentException wenn sie null übergeben.

if (p1 == null || p2 == null) {
    throw new IllegalArgumentException("Invalid argument for xProjection");
}

Ich bevorzuge die Verwendung von Behauptungen.

Ich habe die Regel, dass ich Behauptungen nur in öffentlichen und geschützten Methoden verwende.Das liegt daran, dass ich glaube, dass die aufrufende Methode sicherstellen sollte, dass sie gültige Argumente an private Methoden übergibt.

Spec# sieht sehr interessant aus!

Wenn so etwas nicht verfügbar ist, teste ich im Allgemeinen nicht private Methoden mit einer Laufzeit-Nullprüfung und Zusicherungen für interne Methoden.Anstatt die Nullprüfung explizit in jeder Methode zu codieren, delegiere ich sie an eine Dienstprogrammklasse mit einer Nullprüfungsmethode:

/**
 * Checks to see if an object is null, and if so 
 * generates an IllegalArgumentException with a fitting message.
 * 
 * @param o The object to check against null.
 * @param name The name of the object, used to format the exception message
 *
 * @throws IllegalArgumentException if o is null.
 */
public static void checkNull(Object o, String name) 
    throws IllegalArgumentException {
   if (null == o)
      throw new IllegalArgumentException(name + " must not be null");
}

public static void checkNull(Object o) throws IllegalArgumentException {
   checkNull(o, "object");
} 

// untested:
public static void checkNull(Object... os) throws IllegalArgumentException {
   for(Object o in os) checkNull(o);  
}

Dann wird aus der Überprüfung:

public void someFun(String val1, String val2) throws IllegalArgumentException {
   ExceptionUtilities.checkNull(val1, "val1");
   ExceptionUtilities.checkNull(val2, "val2");

   /** alternatively:
   ExceptionUtilities.checkNull(val1, val2);
   **/

   /** ... **/
} 

Das kann mit Editor-Makros oder einem Codeverarbeitungsskript hinzugefügt werden.Bearbeiten: Die ausführliche Prüfung könnte auch auf diese Weise hinzugefügt werden, aber ich denke, dass es wesentlich einfacher ist, das Hinzufügen einer einzelnen Zeile zu automatisieren.

In den meisten Programmiersprachen gibt es keine gute Möglichkeit, mit einer Null umzugehen, die von einem Aufrufer versehentlich übergeben wird.Da dies der Fall ist, besteht der rationale Ansatz darin, die Übergabe von null standardmäßig zu verbieten.

ich fand JetBrains' @Nullable Und @NotNull Annotations-Ansatz für den Umgang damit ist bisher der genialste.Es ist leider IDE-spezifisch, aber meiner Meinung nach wirklich sauber und leistungsstark.

http://www.jetbrains.com/idea/documentation/howto.html

Es wäre wirklich schön, dies (oder etwas Ähnliches) als Java-Standard zu haben.

Auch wenn es nicht direkt damit zusammenhängt, sollten Sie doch einen Blick darauf werfen Spezifikation#.

Ich denke, es befindet sich noch in der Entwicklung (von Microsoft), aber einige CTP sind verfügbar und es sieht vielversprechend aus.Im Grunde ermöglicht es Ihnen Folgendes:

  public static int Divide(int x, int y)
    requires y != 0 otherwise ArgumentException; 
  {
  }

oder

  public static int Subtract(int x, int y)
    requires x > y;
    ensures result > y;
  {
    return x - y;
  } 

Es bietet auch weitere Funktionen wie Notnull-Typen.Es basiert auf .NET Framework 2.0 und ist vollständig kompatibel.Die Syntax ist, wie Sie vielleicht sehen, C#.

@Chris Karcher Ich würde sagen, absolut richtig.Das Einzige, was ich sagen würde, ist, die Parameter separat zu überprüfen und die Ausnahme den Parameter melden zu lassen, der null war, da dies auch die Verfolgung, woher die Null kommt, viel einfacher macht.

@wvdschel wow!Wenn Ihnen das Schreiben des Codes zu aufwändig ist, sollten Sie sich mit etwas wie „ PostSharp (oder ein Java-Äquivalent, falls verfügbar), das Ihre Assemblys nachbearbeiten und Parameterprüfungen für Sie einfügen kann.

Da Off-Topic zum Thema geworden zu sein scheint, verfolgt Scala einen interessanten Ansatz.Es wird davon ausgegangen, dass alle Typen nicht null sind, es sei denn, Sie schließen sie explizit in eine ein Option um anzuzeigen, dass es möglicherweise null ist.Also:

//  allocate null
var name : Option[String]
name = None

//  allocate a value
name = Any["Hello"]

//  print the value if we can
name match {
  Any[x] => print x
  _ => print "Nothing at all"
}

Generell ziehe ich es vor, beides nicht zu tun, da es die Dinge nur verlangsamt.NullPointerExceptions werden später sowieso ausgelöst, was den Benutzer schnell zu der Entdeckung führt, dass er null an die Methode übergibt.Früher habe ich nachgeschaut, aber am Ende waren 40 % meines Codes Prüfcode, und an diesem Punkt kam ich zu dem Schluss, dass sich die netten Behauptungsmeldungen einfach nicht lohnen.

Ich stimme zu oder nicht wvdschels Beitrag, es kommt darauf an, was er konkret sagt.

In diesem Fall wird diese Methode sicher abstürzen null Daher ist die explizite Prüfung hier wahrscheinlich nicht erforderlich.

Wenn die Methode jedoch lediglich die übergebenen Daten speichert und Sie später eine andere Methode aufrufen, die damit umgeht, Schlechte Eingaben so früh wie möglich zu entdecken ist der Schlüssel zur schnelleren Behebung von Fehlern.Zu diesem späteren Zeitpunkt könnte es unzählige Möglichkeiten geben, dass fehlerhafte Daten an Ihre Klasse weitergegeben werden.Man versucht irgendwie herauszufinden, wie die Ratten nachträglich in Ihr Haus gekommen sind, und versucht, das Loch irgendwo zu finden.

Etwas abseits des Themas, aber ein Merkmal von Findbugs Ich halte es für sehr nützlich, die Parameter von Methoden mit Anmerkungen versehen zu können, um zu beschreiben, welchen Parametern kein Nullwert übergeben werden sollte.

Mithilfe der statischen Analyse Ihres Codes Findbugs kann dann auf Orte hinweisen, an denen die Methode mit einem potenziell Nullwert aufgerufen wird.

Dies hat zwei Vorteile:

  1. Die Anmerkung beschreibt Ihre Absicht, wie die Methode aufgerufen werden soll, und hilft so bei der Dokumentation
  2. FindBugs kann auf potenziell problematische Aufrufer der Methode verweisen, sodass Sie potenzielle Fehler aufspüren können.

Nur nützlich, wenn Sie Zugriff auf den Code haben, der Ihre Methoden aufruft, aber das ist normalerweise der Fall.

C# werfen ArgumentException, oder Java IllegalArgumentException Gleich zu Beginn scheint mir die Methode die klarste Lösung zu sein.

Bei Laufzeitausnahmen – Ausnahmen, die nicht in der Methodensignatur deklariert sind – sollte man immer vorsichtig sein.Da der Compiler Sie nicht dazu zwingt, diese abzufangen, kann es sehr leicht passieren, dass Sie sie vergessen.Stellen Sie sicher, dass Sie über eine Art „Catch-All“-Ausnahmebehandlung verfügen, um zu verhindern, dass die Software abrupt anhält.Das ist der wichtigste Teil Ihrer Benutzererfahrung.

Der beste Weg, damit umzugehen, wäre die Verwendung von Ausnahmen.Letztendlich werden die Behauptungen zu einem Ergebnis führen ähnlich Dem Endbenutzer wird zwar ein Erlebnis angezeigt, aber der Entwickler, der Ihren Code aufruft, hat keine Möglichkeit, mit der Situation umzugehen, bevor dem Endbenutzer eine Ausnahme angezeigt wird.Letztendlich möchten Sie sicherstellen, dass Sie so früh wie möglich auf ungültige Eingaben testen (insbesondere bei öffentlich zugänglichem Code) und die entsprechenden Ausnahmen bereitstellen, die der aufrufende Code abfangen kann.

In Java geht man davon aus, dass die Null auf einen Programmierfehler zurückzuführen ist (d. h.sollte nie außerhalb der Testphase gehen), dann verlassen Sie das System und werfen es aus, oder wenn es Nebenwirkungen gibt, die diesen Punkt erreichen, prüfen Sie zu Beginn auf Null und lösen Sie entweder IllegalArgumentException oder NullPointerException aus.

Wenn die Null von einem tatsächlichen Wert stammen könnte AusnahmeIn jedem Fall, aber Sie möchten dafür keine geprüfte Ausnahme verwenden, dann möchten Sie auf jeden Fall die IllegalArgumentException-Route am Anfang der Methode wählen.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top