Javaメソッドのパラメータが多すぎる、パート6:メソッドの戻り値

Javaメソッドとコンストラクターを呼び出すために必要なパラメーターの数を減らすことについて書いている現在の一連の投稿では、これまで、パラメーター自体に直接影響するアプローチ(カスタム型、パラメーターオブジェクト、ビルダーパターン、メソッドのオーバーロード、およびメソッドの命名)。これを考えると、Javaメソッドが戻り値を提供する方法にこのシリーズの投稿を捧げることは私にとって驚くべきことのように思えるかもしれません。ただし、メソッドの戻り値は、開発者が従来のメソッドの戻りメカニズムではなく、またはそれに加えて、提供されたパラメーターを設定または変更することによって「戻り」値を提供することを選択した場合に、メソッドが受け入れるパラメーターに影響を与える可能性があります。

非コンストラクターメソッドが値を返す「従来の方法」は、両方ともメソッドシグネチャで指定できます。Javaメソッドから値を返すための最も一般的に認識されているアプローチは、宣言された戻り値の型を使用することです。これはよく機能しますが、最も一般的に発生するフラストレーションの1つは、Javaメソッドから1つの値のみを返すことが許可されていることです。

Javaの例外処理メカニズムは、メソッドの「結果」を呼び出し元に保持するためのもう1つのアプローチでもあります。特に、チェックされた例外は、throws句を介して呼び出し元にアドバタイズされます。実際、Jim Waldoは、彼の著書Java:The Good Partsで、Java例外を、Throwable型に限定された別のタイプのメソッドリターンと考えると、Java例外を理解しやすいと述べています。

メソッドの戻り値の型とスローされた例外は、メソッドが呼び出し元に情報を返すための主要なアプローチとして意図されていますが、メソッドに渡されたパラメーターを介してデータまたは状態を返したくなる場合があります。メソッドが複数の情報を返す必要がある場合、Javaメソッドの単一値の戻りは制限されているように見えることがあります。例外は発信者に通信する別の方法を提供しますが、例外は例外的な状況の報告にのみ使用し、「通常の」データの報告や制御フローでの使用には使用しないことにほぼ普遍的に同意しているようです。メソッドから返すことができるオブジェクトまたはプリミティブは1つだけであり、例外ではThrowable 例外的な状況を報告するためにのみ使用する必要があります。Java開発者にとって、データを呼び出し元に返すための代替ルートとしてパラメーターをハイジャックすることはますます魅力的になっています。

開発者が戻りデータのキャリアとしてメソッドパラメータを適用するために使用できる手法は、変更可能なパラメータを受け入れ、渡されたオブジェクトの状態を変更することです。これらの可変オブジェクトは、メソッドによって内容を変更できます。その後、呼び出し元は、提供されたオブジェクトにアクセスして、呼び出されたメソッドによって適用された新しい状態設定を判別できます。これは任意の可変オブジェクトで実行できますが、コレクションは、パラメーターを介して呼び出し元に値を返そうとする開発者にとって特に魅力的です。

提供されたパラメーターを介して呼び出された状態に状態を戻すことには、いくつかの欠点があります。ほとんどのJava開発者は、パラメータが送信ではなく受信であると期待しているため、このアプローチは驚き最小の原則に違反することがよくあります(Javaは違いを指定するためのコードサポートを提供していません)。ボブ・マーチンはそれを彼の著書「クリーンコード」の中でこのように述べています。「一般的に、出力引数は避けるべきです。」メソッドが呼び出し元に状態または出力を提供する手段として引数を使用することのもう1つの欠点は、これがメソッドに渡される引数の乱雑さを増すことです。これを念頭に置いて、この投稿の残りの部分では、渡されたパラメーターを介して複数の値を返す代わりの方法に焦点を当てます。

Javaメソッドは単一のオブジェクトまたはプリミティブしか返すことができませんが、オブジェクトが私たちが望むほぼすべてのものである可能性があることを考えると、これは実際にはそれほど制限ではありません。私が見たいくつかのアプローチがありますが、お勧めしません。これらの1つは、オブジェクトインスタンスの配列またはコレクションをそれぞれとともに返します。Object異種で明確で、しばしば無関係な「もの」であること。たとえば、このメソッドは、配列またはコレクションの3つの要素として3つの値を返す場合があります。このアプローチのバリエーションは、ペアタプルまたはnサイズのタプルを使用して、複数の関連する値を返すことです。このアプローチのもう1つのバリエーションは、任意のキーを関連する値にマップするJavaマップを返すことです。他のソリューションと同様に、このアプローチでは、これらのキーが何であるかを知り、それらのキーを介してマップ値にアクセスするために、クライアントに過度の負担がかかります。

次のコードリストには、メソッドパラメータを乗っ取って複数の値を返すことなく、複数の値を返すための魅力的でないアプローチがいくつか含まれています。

一般的なデータ構造を介して複数の値を返す

 // =============================================================== // 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; } 

上記のアプローチは、呼び出されたメソッドのパラメーターを介して呼び出し元にデータを返さないという意図を満たしていますが、返されたデータ構造の詳細を知るために呼び出し元に不必要な負担がかかります。メソッドのパラメーターの数を減らし、驚き最小の原則に違反しないのは良いことですが、複雑なデータ構造の複雑さをクライアントに知ってもらうのはそれほど良いことではありません。

複数の値を返す必要がある場合は、戻り値のカスタムオブジェクトを作成することを好みます。配列、コレクション、またはタプル構造を使用するよりも少し手間がかかりますが、ごくわずかな余分な作業(通常、最新のJava IDEでは数分)は、これらのより一般的なアプローチでは利用できない読みやすさと流暢さで報われます。 Javadocで説明したり、コードのユーザーにコードを注意深く読んで、配列またはコレクションでどのパラメーターがどの順序で提供されているか、またはタプルでどの値がどの値であるかを知るように要求するのではなく、カスタム戻りオブジェクトにメソッドを定義できます。彼らが提供しているものを正確にクライアントに伝えるそれら。

以下のコードスニペットは、Movie主にNetBeansによって生成された単純なクラスを示しています。このクラスは、より一般的で読みにくいデータ構造ではなく、そのクラスのインスタンスを返す可能性のあるコードとともに、戻り値の型として使用できます。

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 + '}'; } } 

単一のオブジェクトで複数の詳細を返す

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

の簡単な書き込み Movieクラスは私に約5分かかりました。 NetBeansクラス作成ウィザードを使用してクラス名とパッケージを選択し、クラスの4つの属性を入力しました。そこから、NetBeansの「コードの挿入」メカニズムを使用して、オーバーライドされたtoString()、hashCode()、およびequals(Object)メソッドとともに「get」アクセサメソッドを挿入しました。その一部が必要だと思わなかった場合は、クラスを単純に保つことができますが、そのまま作成するのは本当に簡単です。これで、はるかに使いやすい戻り値の型ができました。これは、クラスを使用するコードに反映されています。戻り値の型はそれ自体を表し、その「get」メソッドを使用してコンテンツをアドバタイズするため、戻り値の型に関するJavadocコメントはほとんど必要ありません。複数の値を返すためのこれらの単純なクラスを作成するためのわずかな追加の努力は、メソッドパラメータを介して状態を返す、またはより一般的で使いにくい戻りデータ構造を使用するなどの代替手段と比較すると、大きな利益をもたらすと感じています。

呼び出し元に返される複数の値を保持するカスタムタイプが魅力的なソリューションであることは、それほど驚くことではありません。結局のところ、これは、カスタムタイプとパラメーターオブジェクトを使用して、すべてを個別に渡すのではなく、複数の関連パラメーターを渡すことに関連して以前にブログで書いた概念と概念的に非常に似ています。Javaはオブジェクト指向言語であるため、Javaコードでパラメーターと戻り値を整理するために、素敵なパッケージでオブジェクトが頻繁に使用されているのを目にしないと、驚きます。

利点と利点

カスタムパラメータオブジェクトを使用して複数の戻り値を表現およびカプセル化することの利点は明らかです。すべての出力情報(例外メカニズムを介して伝達されるエラー情報を除く)は、メソッドによって返されるカスタムオブジェクトで提供できるため、メソッドへのパラメーターは「入力」パラメーターのままにすることができます。これは、一般的な配列、コレクション、マップ、タプル、またはその他の一般的なデータ構造を使用するよりもクリーンなアプローチです。これらの代替アプローチはすべて、開発作業をすべての潜在的なクライアントにシフトするためです。

コストとデメリット

Javaメソッドからの戻り値の型として使用される複数の値を持つカスタム型を作成することのデメリットはほとんどありません。おそらく最も頻繁に請求されるコストは、これらのクラスの作成とテストのコストですが、これらのクラスは単純である傾向があり、最新のIDEがほとんどの作業を行うため、そのコストはかなり低くなります。IDEはそれを自動的に行うため、コードは通常正しいです。クラスは非常に単純なので、コードレビューアが簡単に読み取れ、テストも簡単です。