Всегда избегать входных и выходных параметров в Java? - PullRequest
4 голосов
/ 12 марта 2012

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

Итак, многие хорошие программисты говорят:

Избегайте входных-выходных параметров для изменения параметров изменяемого метода. Предпочитаю оставить параметры без изменений.

Для программиста-перфекциониста, который ожидает, что его код будет наиболее чистым и понятным, должно ли это «правило» применяться во всех случаях?

Например, предположим, что базовый метод добавления элементов в простой список существует двумя способами:

Первый способ (с параметром входа-выхода):

private void addElementsToExistingList(List<String> myList){
  myList.add("Foo");
  myList.add("Bar");
}

и вызывающий абонент:

List<String> myList = new ArrayList<String>();
//.......Several Instructions (or not) .....
addElementsToExistingList(myList);

Второй путь без параметра out:

private List<String> addElementsToExistingList(List<String> originalList){
      List<String> filledList = new ArrayList<String>(originalList); //add existing elements
      filledList.add("Foo");
      filledList.add("Bar");
      return filledList; 
    }

и вызывающий абонент:

List<String> myList = new ArrayList<String>();
//.......Several Instructions (or not) .....
myList.addAll(addElementsToExistingList(myList));

Плюсы второго пути:

Параметр не изменяется => нет риска непредвиденных побочных эффектов для новой программы чтения кода.

Минусы второго способа:

Очень многословный и очень менее читаемый ...

Конечно, вы сказали бы мне, что для такого простого кода первый способ действительно более удобен.

Но, если мы не учитываем сложность какой-либо концепции / кода, я считаю второй способ более логичным и очевидным для любых читателей (новичков или нет).

Тем не менее, он нарушает принцип CQS, который рассматривает методы «команды» с пустым возвратом с потенциальными (но разрешенными, поскольку это условно) побочными эффектами и методы «запроса», имеющие тип возврата и без побочных эффектов.

Итак, что должен принять мотивирующий программист? Смешение двух в соответствии с кодом дела? Или придерживайтесь «закона», ожидая всегда избегать параметров входа-выхода ...

(Конечно, метод для добавления Элемента назван для объяснения примера, и в реальном коде это будет неправильным выбором).

Ответы [ 5 ]

5 голосов
/ 12 марта 2012

Я думаю, что закон должен быть:

Используйте более простое, но всегда , всегда подробно документируйте поведение ваших методов.

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

3 голосов
/ 13 марта 2012

Есть третий способ. Оберните List<String> в класс, который знает, как добавлять элементы к себе:

class ElementList {
   private List<String> = new ArrayList<String>();

   public void addElements(Element... elements);
}

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

2 голосов
/ 12 июня 2012

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

Во втором примере элементы, которые уже присутствуют в myList, добавляются дважды. Фактически вы можете полностью удалить параметр метода addElementsToExistingList и переписать его как:

private List<String> getElements() {
   List<String> filledList = new ArrayList<String>();
   filledList.add("Foo");
   filledList.add("Bar");
   return filledList; 
}

List<String> myList = new ArrayList<String>();
//.......Several Instructions (or not) .....
myList.addAll(getElements());

Обратите внимание, что этот код не эквивалентен вашему второму примеру, потому что элементы добавляются только один раз, но я думаю, что это именно то, что вы хотели. Это стиль, который я обычно предпочитаю. Этот код проще для понимания и более гибкий, чем в первом примере, без добавления дополнительного кода (он может слегка снизить производительность, но обычно это не проблема). Клиент getElements () теперь может также делать другие вещи со списком элементов, кроме добавления его в существующую коллекцию.

1 голос
/ 12 марта 2012

Можно изменять / изменять параметры, если это задокументировано.И, конечно же, с именем метода "addElementsToExistingList", чего еще ожидать?Однако, как кто-то ранее указывал, ваша вторая реализация возвращает копию и не изменяет оригинал, поэтому имя метода теперь вводит в заблуждение.Ваш первый путь - это совершенно приемлемый способ ведения дел.Единственными дополнительными улучшениями является возможность добавления значения true / false к возвращаемому значению, если в список были добавлены только все элементы.

0 голосов
/ 12 марта 2012

В случае с вашим примером название проясняет: мне кажется, «addElementsToExistingList» довольно ясно указывает на то, что вы собираетесь… эээ… вы знаете. Но ваше беспокойство будет оправдано менее очевидным именем.

Например, в ruby ​​это обычно обрабатывается соглашениями об именах

"a".upcase => дает вам верхний регистр переменной, оставляя оригинал без изменений "a".upcase! => изменяет исходную переменную

...