Статьи

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

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

С точки зрения сокращения количества параметров, требуемых при вызовах методов и конструкторов, некоторые из наиболее значительных проблем, связанных с перегрузкой методов, заключаются в ограничении того, сколько раз одно и то же имя метода может быть перегружено для большого набора параметров, особенно если некоторые параметров имеют один и тот же тип данных. Например, если у меня есть класс с тремя атрибутами String и я хочу написать три конструктора, каждый из которых принимает только один из этих атрибутов, я действительно не могу сделать это с перегрузкой метода. Если бы я попытался, конструктор, принимающий одну строку, должен был бы использоваться для одного из трех атрибутов, и только комментарий Javadoc мог бы объяснить, какой из трех атрибутов устанавливает конструктор с одним аргументом. Освободившись от ограничения использования одного и того же имени для всех методов и методов построения, можно быть более выразительным в коде относительно ожидаемых и предполагаемых параметров.

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

Примеры методов экземпляров, названных для описания того, что они делают

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public Person createPersonWithFirstAndLastNameOnly(final String firstName, final String lastName)
   {
      // implementation goes here ...
   }
 
   public Person createEmployedHomeOwningFemale(final FullName name, final Address address)
   {
      // implementation goes here ...
   }
 
   public Person createEmployedHomeOwningMale(final FullName name, final Address address)
   {
      // implementation goes here ...
   }
 
   public Person createUnemployedHomeOwningFemale(final FullName name, final Address address)
   {
      // implementation goes here ...
   }
 
   public Person createEmployedRentingMale(final FullName name, final Address address)
   {
      // implementation goes here ...
   }

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

Приведенные выше примеры кода продемонстрировали предоставление различных и описательных имен для методов экземпляра, чтобы указывать, какие параметры передавать, и даже в некоторых случаях указывать, какие параметры не нужно указывать, поскольку они подразумеваются в имени метода. Те, кто плохо знаком с Java, могут подумать, что этот подход не может быть использован с созданием / созданием объекта, потому что конструкторы класса Java должны быть названы с тем же именем, что и класс. Это означает, что конструкторы могут быть перегружены только на основе сигнатуры метода. К счастью, Джош Блох говорил об этом в самом первом выпуске обоих выпусков Effective Java . Как описывает Блох , мы можем использовать методы фабрики статической инициализации для предоставления экземпляров наших классов. Одно из преимуществ, которое цитирует Блох в этом пункте № 1, — это возможность называть эти методы по своему усмотрению.

Следующий листинг кода демонстрирует мощь этих статических фабрик инициализации. Когда я их реализую, мне нравится реализовывать один или очень небольшое количество private конструкторов (не могут быть созданы внешними классами), которые вызываются только моими фабриками статической инициализации. Это позволяет мне оставить сигнатуру метода конструктора как потенциально чуть менее желательную, потому что ее должен использовать только мой класс, а другие статические фабрики инициализации проще в использовании и скрывают уродство конструктора многих параметров. Более конкретно, если конструктор принимает значение NULL для необязательных параметров, я могу написать различные статические фабрики инициализации, чтобы моим клиентам не нужно было передавать значение NULL, но вместо этого мои фабричные методы могут передавать значение NULL в конструктор. Короче говоря, методы фабрики статической инициализации позволяют мне представить клиентам более чистый и приятный интерфейс и скрыть уродство во внутреннем пространстве моего класса. Я не могу так легко обеспечить это несколькими конструкторами напрямую из-за невозможности дать им что-либо с объяснением. Другое преимущество этих статических методов инициализации состоит в том, что они могут принимать «необработанные» типы при желании и встроить их в пользовательские типы и объекты параметров внутри. Все эти случаи показаны в следующем листинге кода.

Статические фабрики инициализации продемонстрировали

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
/**
    * Parameterized constructor can be private because only my internal builder
    * needs to call me to provide an instance to clients.
    *
    * @param newName Name of this person.
    * @param newAddress Address of this person.
    * @param newGender Gender of this person.
    * @param newEmployment Employment status of this person.
    * @param newHomeOwner Home ownership status of this person.
    */
   private Person(
      final FullName newName, final Address newAddress,
      final Gender newGender, final EmploymentStatus newEmployment,
      final HomeownerStatus newHomeOwner)
   {
      this.name = newName;
      this.address = newAddress;
      this.gender = newGender;
      this.employment = newEmployment;
      this.homeOwnerStatus = newHomeOwner;
   }
 
   public static Person createInstanceWithNameAndAddressOnly(
      final FullName newName, final Address newAddress)
   {
      return new Person(newName, newAddress, null, null, null);
   }
 
   public static Person createEmployedHomeOwningFemale(
      final FullName newName, final Address newAddress)
   {
      return new Person(
         newName, newAddress, Gender.FEMALE, EmploymentStatus.EMPLOYED, HomeownerStatus.HOME_OWNER);
   }
 
   public static Person createEmployedHomeowningMale(
      final FullName newName, final Address newAddress)
   {
      return new Person(
          newName, newAddress, Gender.MALE, EmploymentStatus.EMPLOYED, HomeownerStatus.HOME_OWNER);
   }
 
   public static Person createUnemployedMaleRenter(
      final FullName newName, final Address newAddress)
   {
      return new Person(
         newName, newAddress, Gender.MALE, EmploymentStatus.NOT_EMPLOYED, HomeownerStatus.RENTER);
   }
 
   public static Person createPersonWithFirstNameLastNameAndAddress(
      final Name newFirstName, final Name newLastName, final Address newAddress)
   {
      return new Person(
         new FullName.FullNameBuilder(newLastName, newFirstName).createFullName(),
         newAddress, null, null, null);
   }
 
   public static Person createPersonWithFirstNameLastNameAndAddress(
      final String newFirstName, final String newLastName, final Address newAddress)
   {
      return new Person(
         new FullName.FullNameBuilder(new Name(newLastName), new Name(newFirstName)).createFullName(),
         newAddress, null, null, null);
   }

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

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

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

Я не стал подробно останавливаться на этом здесь, но еще одно преимущество тщательно выбранных имен методов перед простой перегрузкой методов — это возможность включать единицы измерения или другую контекстную информацию в имя метода. Например, вместо методов setLength() которые принимают int и double , я мог бы предоставить такие методы, как setWholeLengthInMeters(int) и setFractionalLengthInFeet(double) .

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

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

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

Вывод

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