Статьи

Слишком много параметров в методах Java, часть 6: возврат метода

В текущей серии публикаций, которые я пишу о сокращении количества параметров, необходимых для вызова методов и конструкторов Java, я до сих пор фокусировался на подходах, непосредственно влияющих на сами параметры ( пользовательские типы , объекты параметров , шаблон компоновщика , перегрузка методов и метод именования ). Учитывая это, мне может показаться удивительным посвятить пост в этой серии тому, как методы Java предоставляют возвращаемые значения. Однако возвращаемые значения методов могут влиять на параметры, принимаемые методами, когда разработчики выбирают предоставление «возвращаемых» значений путем установки или изменения предоставленных параметров, а не в дополнение к более традиционным механизмам возврата методов.

«Традиционные способы», что метод, не являющийся конструктором, возвращает значение, могут быть указаны в сигнатуре метода. Наиболее распространенный подход для возврата значения из метода Java — через объявленный тип возврата . Это часто работает хорошо, но одно из наиболее часто встречающихся разочарований — разрешено возвращать только одно значение из метода Java.

Механизм обработки исключений в Java также является еще одним подходом для сохранения «результата» метода для вызывающих. Проверенные исключения , в частности, объявляются вызывающей стороне через предложение throws . Фактически, Джим Уолдо в своей книге « Java: хорошие части» утверждает, что легче понять исключения Java, когда кто-то думает об исключениях Java как о другом типе возвращаемого метода, ограниченного типом Throwable .

Хотя тип возвращаемого метода и генерируемые исключения предназначены в качестве основных подходов к методам возврата информации вызывающим сторонам, иногда возникает соблазн вернуть данные или состояние через параметры, переданные в метод. Когда метод должен вернуть более одной части информации, возврат Java-методов с одним значением может показаться ограничивающим. Хотя исключения предоставляют еще один способ обратной связи с вызывающим абонентом, почти все согласны с тем, что исключения следует использовать только для сообщения об исключительных ситуациях, а не для сообщения «обычных» данных или использования в потоке управления. Учитывая, что из метода может быть возвращен только один объект или примитив, а исключения допускают только возврат Throwable и должны использоваться только для сообщения об исключительных ситуациях, для разработчика Java становится все более привлекательным использовать параметры в качестве альтернативного маршрута для возврата данных. для звонящего.

Техника, которую разработчик может использовать для применения параметров метода в качестве носителей для возвращаемых данных, заключается в том, чтобы принимать изменяемые параметры и изменять состояние переданных объектов. Эти изменяемые объекты могут изменять свое содержимое с помощью метода, и тогда вызывающая сторона может получить доступ к предоставленному объекту, чтобы определить его новые параметры состояния, которые были применены вызываемым методом. Хотя это можно сделать с любым изменяемым объектом, коллекции кажутся особенно привлекательными для разработчика, пытающегося передать значения вызывающей стороне через параметры.

Есть некоторые недостатки при передаче состояния обратно вызываемому через предоставленные параметры. Этот подход часто нарушает принцип наименьшего удивления, поскольку большинство разработчиков Java, вероятно, ожидают, что параметры будут входящими, а не OUTgoing (а Java не предоставляет никакой поддержки кода для определения различий). Боб Мартин говорит об этом в своей книге « Чистый код» : «В общем, выходных аргументов следует избегать». Другим недостатком использования аргументов в качестве средства для метода для предоставления состояния или вывода вызывающей стороне является то, что это добавляет к беспорядку аргументов, передаваемых методу. Имея это в виду, оставшаяся часть этого поста посвящена альтернативам возвращению нескольких значений через переданные параметры.

Хотя методы Java могут возвращать только один объект или примитив, на самом деле это не является большим ограничением, если учесть, что объект может быть практически любым, каким мы хотим его видеть. Есть несколько подходов, которые я видел, но не рекомендую. Одним из них является возвращение массива или коллекции экземпляров Объекта, причем каждый Object является разрозненной, отдельной и часто не связанной «вещью». Например, метод может возвращать три значения как три элемента массива или коллекции. Разновидностью этого подхода является использование парного кортежа или кортежа n-размера для возврата нескольких связанных значений. Еще одним вариантом этого подхода является возвращение карты Java, которая отображает произвольные ключи в их соответствующие значения. Как и в случае с другими решениями, этот подход возлагает чрезмерную нагрузку на клиента, чтобы узнать, что это за ключи, и получить доступ к значениям карты через эти ключи.

Следующий листинг кода содержит несколько из этих менее привлекательных подходов для возврата нескольких значений без перехвата параметров метода для возврата нескольких значений.

Возврат нескольких значений через общие структуры данных

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// ===============================================================
   // 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<Object> getMovieDetails()
   {
      return Arrays.<Object>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<String, Object> getMovieDetailsMap()
   {
      final HashMap<String, Object> 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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
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 + '}';
   }
}

Возврат нескольких деталей в одном объекте

1
2
3
4
5
6
7
8
9
/**
    * Provide movie information.
    *
    * @return Movie information.
    */
   public Movie getMovieInfo()
   {
      return new Movie("Oblivion", 2013, "Joseph Kosinski", "PG-13");
   }

Простое написание урока Movie заняло у меня около 5 минут. Я использовал мастер создания класса NetBeans, чтобы выбрать имя класса и пакет, а затем набрал четыре атрибута класса. После этого я просто использовал механизм вставки кода NetBeans для вставки методов доступа «get» вместе с переопределенными методами toString () , hashCode () и equals (Object) . Если бы я не думал, что мне нужно что-то из этого, я мог бы сделать класс проще, но его действительно легко создать как есть. Теперь у меня есть гораздо более удобный тип возвращаемого значения, и это отражено в коде, который использует класс. Ему не нужно почти столько же комментариев Javadoc к возвращаемому типу, потому что этот тип говорит сам за себя и рекламирует свой контент с помощью методов «get». Я чувствую, что небольшое количество дополнительных усилий по созданию этих простых классов для возврата нескольких значений окупается огромными дивидендами по сравнению с альтернативами, такими как возвращение состояния через параметры метода или использование более общих и более сложных для использования структур возвращаемых данных.

Не слишком удивительно, что пользовательский тип для хранения нескольких значений, возвращаемых вызывающей стороне, является привлекательным решением. В конце концов, это концептуально очень похоже на концепции, о которых я писал ранее, связанные с использованием пользовательских типов и объектов параметров для передачи нескольких связанных параметров, а не передачи их всех по отдельности. Java является объектно-ориентированным языком, и поэтому меня удивляет, когда я не вижу объектов, которые чаще всего используются в коде Java для организации параметров и возврата значений в хорошем пакете.

Преимущества и преимущества

Преимущества использования пользовательских объектов параметров для представления и инкапсуляции нескольких возвращаемых значений очевидны. Параметры метода могут оставаться «входными» параметрами, поскольку вся выходная информация (за исключением информации об ошибках, передаваемой через механизм исключений) может быть предоставлена ​​в пользовательском объекте, возвращаемом методом. Это более чистый подход, чем использование универсальных массивов, коллекций, карт, кортежей или других общих структур данных, поскольку все эти альтернативные подходы переносят усилия по разработке на всех потенциальных клиентов.

Затраты и недостатки

Я вижу очень небольшой недостаток в написании пользовательских типов с несколькими значениями, которые будут использоваться в качестве типов возврата из методов Java. Возможно, наиболее часто запрашиваемая стоимость — это стоимость написания и тестирования этих классов, но эта стоимость довольно мала, потому что эти классы, как правило, просты и потому что современные IDE делают большую часть работы за нас. Поскольку IDE делают это автоматически, код, как правило, правильный. Классы настолько просты, что они легко читаются рецензентами кода, и их легко тестировать.

Стремясь найти другие издержки и недостатки, можно утверждать, что эти классы могут раздуть базы кода и пакеты, но я не вижу в этом веского аргумента. Хотя риск плохо реализованного пользовательского класса может быть очень небольшим, я думаю, что вероятность того, что клиентский код испортит интерпретацию более общего типа возвращаемого значения, более вероятна. Еще один небольшой риск заключается в том, что разработчики могут бросить много несвязанных вещей в один и тот же класс, при этом единственная связь между этими элементами заключается в том, что один и тот же метод должен возвращать их. Даже при этом единственная лучшая альтернатива, которую я вижу, — это изменить код, чтобы не нужно было возвращать несколько значений. Возврат иным образом не связанных между собой элементов в пользовательском объекте все еще кажется лучше, чем возврат этого набора несвязанных данных в общей структуре данных. Фактически, эти общие структуры данных становятся более громоздкими и трудными в использовании, поскольку значения, которые они содержат, становятся менее связанными.

Вывод

Пользовательские типы и объекты параметров помогают нам напрямую решать проблему слишком большого количества параметров для метода Java. К счастью, эти пользовательские типы и объекты возвращаемых значений могут также помочь нам косвенно сократить количество обязательных параметров, позволяя нам возвращать несколько значений через объекты пользовательских типов и возвращаемых значений, а не добавлять дополнительные параметры, предназначенные только для передачи выходной информации обратно. звонящий.