Статьи

Журнал непредвиденных параметров коммутатора

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

Прежде чем перейти к конкретике, я добавлю несколько предостережений. В некоторых случаях может не иметь смысла регистрировать включаемое значение, которое не было явно сопоставлено с case . Некоторые из них перечислены здесь.

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

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

Enum default Что регистрирует без switch Кандидат Значение

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
/**
 * Provides the Roman numeral equivalent of the
 * provided integer.
 *
 * @param integer Integer for which Roman numeral
 *    equivalent is desired.
 * @return Roman numeral equivalent of the provided
 *    integer or empty string ("") if I'm not aware of
 *    the Roman numeral equivalent.
 */
public String getRomanNumeralEquivalent(final int integer)
{
   String romanNumeral;
   switch (integer)
   {
      case 0:
         romanNumeral = "nulla";
         break;
      case 1:
         romanNumeral = "I";
         break;
      case 2:
         romanNumeral = "II";
         break;
      case 3:
         romanNumeral = "III";
         break;
      case 4:
         romanNumeral = "IV";
         break;
      case 5:
         romanNumeral = "V";
         break;
      case 6:
         romanNumeral = "VI";
         break;
      case 7:
         romanNumeral = "VII";
         break;
      case 8:
         romanNumeral = "VIII";
         break;
      case 9:
         romanNumeral = "IX";
         break;
      case 10:
         romanNumeral = "X";
         break;
      default:
         out.println("Unexpected integer was provided.");
         romanNumeral = "";
         break;
   }
   return romanNumeral;
}

Проблема здесь — действительно конкретный пример более общей проблемы, которую разработчики должны избегать: регистрация без достаточного контекста. В некоторых случаях может быть сложно или вычислительно дорого предоставить тип контекста, который делает сообщения журнала более полезными. Однако, как правило, это не относится к операторам switch , где мы можем легко записать значение, которое мы пытались switch . В приведенном выше листинге кода разработчикам, поддерживающим проблемы времени выполнения при развертывании, будет сказано только то, что было указано «неожиданное целое число» Без какого-либо контекста трудно узнать, что это было за целое число, и, не зная целое число кандидата, трудно отследить, что произошло, или даже воспроизвести его.

Требуется лишь очень небольшое усилие, чтобы сделать этот оператор ведения журнала по default полезным, и это показано в следующем листинге кода.

Построение лучшего оператора журнала по default

1
2
3
4
default:
   out.println("Unexpected integer (" + integer
      + ") was provided, so empty String being returned for Roman Numeral.");
   romanNumeral = "";

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

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

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

Часто оказывается, что наличие значения-кандидата для оператора switch без совпадения является исключительным обстоятельством. В таких случаях, вероятно, более целесообразно создать исключение, чем просто регистрировать исключительную ситуацию. Стандартное исключение, такое как IllegalArgumentException, хорошо работает для этого (это, в некотором смысле, является недопустимым аргументом для оператора switch ), но я иногда также написал специальное исключение, чтобы помочь с этим. Когда я решил реализовать и использовать это пользовательское исключение, одной из причин принятия этого решения было то, что выбрасывание этого исключения побуждает разработчиков предоставлять включаемый объект как часть конструктора исключения. Типичный пример этого типа пользовательского исключения показан ниже.

SwitchOptionNotExpectedException.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
package dustin.examples.switchdemo;
 
/**
 * Exception used to communicate a candidate value for
 * a {@code switch} statement not being matched by any
 * of the explicitly provided {@code case} blocks.
 */
public class SwitchOptionNotExpectedException extends RuntimeException
{
   /**
    * Object being switched on for which no matching
    * {@code case} clause existed.
    */
   private final Object switchedObject;
 
   /**
    * Constructor accepting exception message and the instance
    * upon which the {@code switch} was being attempted when no
    * matching {@code case} was found.
    *
    * @param newMessage Exception summary message.
    * @param newSwitchedObject Object being switched on for
    *    which there was no explicitly specifed {@code case}.
    */
   public SwitchOptionNotExpectedException(
      final String newMessage, final Object newSwitchedObject)
   {
      super(newMessage + " (unable to switch on '" + String.valueOf(newSwitchedObject) + "')");
      switchedObject = newSwitchedObject;
   }
 
   /**
    * Constructor accepting the instance upon which the {@code switch}
    * was being attempted when no matching {@code case} was found.
    *
    * @param newSwitchedObject Object being switched on for
    *    which there was no explicitly specified {@code case}.
    */
   public SwitchOptionNotExpectedException(final Object newSwitchedObject)
   {
      super(
           "Switch statement did not expect '" + String.valueOf(newSwitchedObject)
         + "'.");
      switchedObject = newSwitchedObject;
   }
 
   /**
    * Provides String representation of the object being
    * switched upon.
    *
    * @return String representation of the object being
    *    switched upon.
    */
   public String getSwitchedObjectString()
   {
      return String.valueOf(switchedObject);
   }
 
   /**
    * Provides type of object being switched upon.
    *
    * @return Type of the object being switched upon or
    *    {@code null} if that switched upon object is null.
    */
   public Class getSwitchedObjectType()
   {
      return switchedObject != null ? switchedObject.getClass() : null;
   }
}

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

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

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

Опубликовано на Java Code Geeks с разрешения Дастина Маркса, партнера нашей программы JCG . Посмотрите оригинальную статью здесь: Журнал Неожиданные Опции Переключателя

Мнения, высказанные участниками Java Code Geeks, являются их собственными.