Zu viele Parameter in Java-Methoden, Teil 6: Methodenrückgabe

In der aktuellen Reihe von Beiträgen, die ich zum Reduzieren der Anzahl der zum Aufrufen von Java-Methoden und -Konstruktoren erforderlichen Parameter schreibe, habe ich mich bisher auf Ansätze konzentriert, die sich direkt auf die Parameter selbst auswirken (benutzerdefinierte Typen, Parameterobjekte, Builder-Muster, Methodenüberladung und Methodenbenennung). Angesichts dessen mag es für mich überraschend erscheinen, einen Beitrag in dieser Reihe darüber zu verfassen, wie Java-Methoden Rückgabewerte liefern. Die Rückgabewerte von Methoden können sich jedoch auf die Parameter auswirken, die die Methoden akzeptieren, wenn Entwickler "Rückgabewerte" bereitstellen, indem sie bereitgestellte Parameter anstelle oder zusätzlich zu herkömmlichen Methodenrückgabemechanismen festlegen oder ändern.

Die "traditionellen Methoden", mit denen eine Nicht-Konstruktor-Methode einen Wert zurückgibt, können beide in der Methodensignatur angegeben werden. Der am häufigsten erkannte Ansatz zum Zurückgeben eines Werts von einer Java-Methode ist der deklarierte Rückgabetyp. Dies funktioniert oft gut, aber eine der am häufigsten auftretenden Frustrationen besteht darin, dass nur ein Wert von einer Java-Methode zurückgegeben werden darf.

Der Ausnahmebehandlungsmechanismus von Java ist auch ein weiterer Ansatz, um ein "Ergebnis" einer Methode für Aufrufer beizubehalten. Insbesondere geprüfte Ausnahmen werden dem Anrufer über die Throws-Klausel angekündigt. Tatsächlich sagt Jim Waldo in seinem Buch Java: The Good Parts, dass es einfacher ist, Java-Ausnahmen zu verstehen, wenn man Java-Ausnahmen als eine andere Art von Methodenrückgabe betrachtet, die auf einen Throwable-Typ beschränkt ist.

Obwohl der Rückgabetyp und die ausgelösten Ausnahmen der Methode als primäre Ansätze für Methoden zur Rückgabe von Informationen an Aufrufer gedacht sind, ist es manchmal verlockend, Daten oder Status über die an die Methode übergebenen Parameter zurückzugeben. Wenn eine Methode mehr als eine Information zurückgeben muss, können die einwertigen Rückgaben von Java-Methoden einschränkend erscheinen. Obwohl Ausnahmen eine andere Möglichkeit bieten, mit dem Anrufer zu kommunizieren, scheint es fast allgemein anerkannt zu sein, dass Ausnahmen nur zur Meldung von Ausnahmesituationen und nicht zur Meldung "normaler" Daten oder zur Verwendung im Kontrollfluss verwendet werden sollten. Vorausgesetzt, dass nur ein Objekt oder Grundelement von einer Methode zurückgegeben werden kann und Ausnahmen nur die Rückgabe von a ermöglichenThrowable und sollte nur zum Melden von Ausnahmesituationen verwendet werden, wird es für den Java-Entwickler immer attraktiver, Parameter als alternative Route für die Rückgabe von Daten an den Anrufer zu missbrauchen.

Die Technik, mit der ein Entwickler Methodenparameter als Träger für Rückgabedaten anwenden kann, besteht darin, veränderbare Parameter zu akzeptieren und den Status der übergebenen Objekte zu ändern. Bei diesen veränderlichen Objekten kann der Inhalt durch die Methode geändert werden, und der Aufrufer kann dann auf das von ihm bereitgestellte Objekt zugreifen, um die neuen Statuseinstellungen zu ermitteln, die von der aufgerufenen Methode angewendet wurden. Obwohl dies mit jedem veränderlichen Objekt möglich ist, scheinen Sammlungen für den Entwickler besonders attraktiv zu sein, der versucht, Werte über Parameter an den Aufrufer zurückzugeben.

Es gibt einige Nachteile, wenn der Zustand über die bereitgestellten Parameter an den aufgerufenen zurückgegeben wird. Dieser Ansatz verstößt häufig gegen das Prinzip des geringsten Erstaunens, da die meisten Java-Entwickler wahrscheinlich erwarten, dass Parameter eher eingehend als ausgehend sind (und Java keine Codeunterstützung bietet, um den Unterschied anzugeben). Bob Martin drückt es in seinem Buch Clean Code so aus: "Im Allgemeinen sollten Ausgabeargumente vermieden werden." Ein weiterer Nachteil der Verwendung von Argumenten als Mittel für eine Methode zur Bereitstellung von Status oder Ausgabe für den Aufrufer besteht darin, dass dies die Unordnung der an eine Methode übergebenen Argumente erhöht. In diesem Sinne konzentriert sich der Rest dieses Beitrags auf Alternativen zur Rückgabe mehrerer Werte über übergebene Parameter.

Obwohl Java-Methoden nur ein einzelnes Objekt oder Grundelement zurückgeben können, ist dies keine große Einschränkung, wenn man bedenkt, dass ein Objekt so ziemlich alles sein kann, was wir wollen. Es gibt verschiedene Ansätze, die ich gesehen, aber nicht empfohlen habe. Eine davon ist die Rückgabe eines Arrays oder einer Sammlung von Objektinstanzen mit jederObjectein ungleiches und unterschiedliches und oft nicht verwandtes "Ding" zu sein. Beispielsweise kann die Methode drei Werte als drei Elemente eines Arrays oder einer Sammlung zurückgeben. Eine Variation dieses Ansatzes besteht darin, ein Paartupel oder ein Tupel der Größe n zu verwenden, um mehrere zugeordnete Werte zurückzugeben. Eine weitere Variante dieses Ansatzes besteht darin, eine Java-Map zurückzugeben, die beliebige Schlüssel dem zugehörigen Wert zuordnet. Wie bei den anderen Lösungen belastet dieser Ansatz den Client übermäßig mit der Kenntnis dieser Schlüssel und dem Zugriff auf die Kartenwerte über diese Schlüssel.

Die nächste Codeliste enthält mehrere dieser weniger attraktiven Ansätze zum Zurückgeben mehrerer Werte, ohne die Methodenparameter zu missbrauchen, um mehrere Werte zurückzugeben.

Rückgabe mehrerer Werte über generische Datenstrukturen

 // =============================================================== // NOTE: These examples are intended solely to illustrate a point // and are NOT recommended for production code. // =============================================================== /** * Provide movie information. * * @return Movie information in form of an array where details are mapped to * elements with the following indexes in the array: * 0 : Movie Title * 1 : Year Released * 2 : Director * 3 : Rating */ public Object[] getMovieInformation() { final Object[] movieDetails = {"World War Z", 2013, "Marc Forster", "PG-13"}; return movieDetails; } /** * Provide movie information. * * @return Movie information in form of a List where details are provided * in this order: Movie Title, Year Released, Director, Rating. */ public List getMovieDetails() { return Arrays.asList("Ender's Game", 2013, "Gavin Hood", "PG-13"); } /** * Provide movie information. * * @return Movie information in Map form. Characteristics of the movie can * be acquired by looking in the map for these key elements: "Title", "Year", * "Director", and "Rating"./ */ public Map getMovieDetailsMap() { final HashMap map = new HashMap(); map.put("Title", "Despicable Me 2"); map.put("Year", 2013); map.put("Director", "Pierre Coffin and Chris Renaud"); map.put("Rating", "PG"); return map; } 

Die oben gezeigten Ansätze erfüllen die Absicht, keine Daten über die Parameter der aufgerufenen Methoden an den Aufrufer zurückzugeben, aber es besteht immer noch eine unnötige Belastung für den Aufrufer, vertraute Details der zurückgegebenen Datenstruktur zu kennen. Es ist schön, die Anzahl der Parameter auf die Methode zu reduzieren und nicht gegen das Prinzip der geringsten Überraschung zu verstoßen, aber es ist nicht so schön, vom Client zu verlangen, dass er die Feinheiten einer komplexen Datenstruktur kennt.

Ich bevorzuge es, benutzerdefinierte Objekte für meine Retouren zu schreiben, wenn ich mehr als einen Wert zurückgeben muss. Es ist etwas mehr Arbeit als die Verwendung eines Arrays, einer Sammlung oder einer Tupelstruktur, aber die sehr geringe zusätzliche Arbeit (normalerweise einige Minuten mit modernen Java-IDEs) zahlt sich durch Lesbarkeit und Sprachkompetenz aus, die mit diesen allgemeineren Ansätzen nicht verfügbar sind. Anstatt mit Javadoc erklären zu müssen oder Benutzer meines Codes zu verpflichten, meinen Code sorgfältig zu lesen, um zu wissen, welche Parameter in welcher Reihenfolge im Array oder in der Sammlung bereitgestellt werden oder welcher Wert welcher im Tupel ist, können für meine benutzerdefinierten Rückgabeobjekte Methoden definiert werden diejenigen, die dem Kunden genau sagen, was sie bereitstellen.

Die folgenden Codeausschnitte veranschaulichen eine einfache MovieKlasse, die größtenteils von NetBeans generiert wird und als Rückgabetyp zusammen mit dem Code verwendet werden kann, der eine Instanz dieser Klasse anstelle einer allgemeineren und weniger lesbaren Datenstruktur zurückgeben könnte.

Movie.java

package dustin.examples; import java.util.Objects; /** * Simple Movie class to demonstrate how easy it is to provide multiple values * in a single Java method return and provide readability to the client. * * @author Dustin */ public class Movie { private final String movieTitle; private final int yearReleased; private final String movieDirectorName; private final String movieRating; public Movie(String movieTitle, int yearReleased, String movieDirectorName, String movieRating) { this.movieTitle = movieTitle; this.yearReleased = yearReleased; this.movieDirectorName = movieDirectorName; this.movieRating = movieRating; } public String getMovieTitle() { return movieTitle; } public int getYearReleased() { return yearReleased; } public String getMovieDirectorName() { return movieDirectorName; } public String getMovieRating() { return movieRating; } @Override public int hashCode() { int hash = 3; hash = 89 * hash + Objects.hashCode(this.movieTitle); hash = 89 * hash + this.yearReleased; hash = 89 * hash + Objects.hashCode(this.movieDirectorName); hash = 89 * hash + Objects.hashCode(this.movieRating); return hash; } @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final Movie other = (Movie) obj; if (!Objects.equals(this.movieTitle, other.movieTitle)) { return false; } if (this.yearReleased != other.yearReleased) { return false; } if (!Objects.equals(this.movieDirectorName, other.movieDirectorName)) { return false; } if (!Objects.equals(this.movieRating, other.movieRating)) { return false; } return true; } @Override public String toString() { return "Movie{" + "movieTitle=" + movieTitle + ", yearReleased=" + yearReleased + ", movieDirectorName=" + movieDirectorName + ", movieRating=" + movieRating + '}'; } } 

Rückgabe mehrerer Details in einem einzelnen Objekt

 /** * Provide movie information. * * @return Movie information. */ public Movie getMovieInfo() { return new Movie("Oblivion", 2013, "Joseph Kosinski", "PG-13"); } 

Das einfache Schreiben der MovieDer Unterricht dauerte ungefähr 5 Minuten. Ich habe den NetBeans-Klassenerstellungsassistenten verwendet, um den Klassennamen und das Paket auszuwählen, und dann die vier Attribute der Klasse eingegeben. Von dort aus habe ich einfach den NetBeans-Mechanismus "Code einfügen" verwendet, um Zugriffsmethoden "get" zusammen mit überschriebenen Methoden toString (), hashCode () und equals (Object) einzufügen. Wenn ich nicht gedacht hätte, dass ich etwas davon brauche, könnte ich die Klasse einfacher halten, aber es ist wirklich einfach, so wie es ist zu erstellen. Jetzt habe ich einen viel besser verwendbaren Rückgabetyp, der sich in dem Code widerspiegelt, der die Klasse verwendet. Es werden nicht annähernd so viele Javadoc-Kommentare zum Rückgabetyp benötigt, da dieser Typ für sich selbst spricht und seinen Inhalt mit seinen "get" -Methoden ankündigt.Ich bin der Meinung, dass sich der geringe zusätzliche Aufwand für die Erstellung dieser einfachen Klassen für die Rückgabe mehrerer Werte im Vergleich zu Alternativen wie der Rückgabe des Status über Methodenparameter oder der Verwendung allgemeinerer und schwieriger zu verwendender Rückgabedatenstrukturen mit enormen Dividenden auszahlt.

Es ist nicht allzu überraschend, dass ein benutzerdefinierter Typ, der die mehreren Werte enthält, die an einen Anrufer zurückgegeben werden sollen, eine attraktive Lösung ist. Schließlich ist dies konzeptionell sehr ähnlich zu den Konzepten, über die ich zuvor gebloggt habe, um benutzerdefinierte Typen und Parameterobjekte für die Übergabe mehrerer verwandter Parameter zu verwenden, anstatt sie alle einzeln zu übergeben. Java ist eine objektorientierte Sprache und daher überrascht es mich, wenn ich keine Objekte sehe, die im Java-Code häufiger zum Organisieren von Parametern verwendet werden UND Werte in einem schönen Paket zurückgeben.

Vor- und Nachteile

Die Vorteile der Verwendung benutzerdefinierter Parameterobjekte zur Darstellung und Kapselung mehrerer Rückgabewerte liegen auf der Hand. Parameter für die Methode können "Eingabeparameter" bleiben, da alle Ausgabeinformationen (mit Ausnahme der über den Ausnahmemechanismus übermittelten Fehlerinformationen) in dem von der Methode zurückgegebenen benutzerdefinierten Objekt bereitgestellt werden können. Dies ist ein sauberer Ansatz als die Verwendung generischer Arrays, Sammlungen, Karten, Tupel oder anderer generischer Datenstrukturen, da all diese alternativen Ansätze den Entwicklungsaufwand auf alle potenziellen Clients verlagern.

Kosten und Nachteile

Ich sehe sehr wenig Nachteile beim Schreiben von benutzerdefinierten Typen mit mehreren Werten, die als Rückgabetypen von Java-Methoden verwendet werden sollen. Vielleicht sind die am häufigsten beanspruchten Kosten der Preis für das Schreiben und Testen dieser Klassen, aber diese Kosten sind ziemlich gering, da diese Klassen in der Regel einfach sind und moderne IDEs den größten Teil der Arbeit für uns erledigen. Da die IDEs dies automatisch tun, ist der Code normalerweise korrekt. Die Klassen sind so einfach, dass sie für Codeprüfer leicht lesbar und leicht zu testen sind.