Можно ли пойти против названий all-caps для перечислений, чтобы упростить их представление String?

14

Несколько раз я видел, как люди используют заглавные или даже строчные имена для перечисления констант, например:

enum Color {
  red,
  yellow,
  green;
}

Это делает работу с их строковой формой простой и легкой, например, если вы хотите throw new IllegalStateException("Light should not be " + color + ".").

Это кажется более приемлемым, если enum есть private, но мне все еще не нравится. Я знаю, что могу сделать конструктор enum с полем String, а затем переопределить toString, чтобы вернуть это имя, например:

enum Color {
  RED("red"),
  YELLOW("yellow"),
  GREEN("green");

  private final String name;

  private Color(String name) { 
    this.name = name 
  }

  @Override public String toString() {
    return name; 
  }
}

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

нарушитель закона
источник
4
Это соглашение. У вас есть причина нарушить соглашение? Это зависит от вас.
9
Просто интересно, что не так с сообщением об исключении, которое говорит Light should not be RED.. В конце концов, это сообщение для разработчика, пользователь никогда не должен его видеть.
Брандин
1
@ Брандин, если цвет - это скорее деталь реализации, то никто не должен его видеть. Конечно, тогда, я думаю, это оставляет вопрос о том, зачем нам нужна хорошая форма toString.
код взломщик
8
В конце дня это зависит от вас. Если бы я отлаживал ваш код и видел сообщение об исключении, Light should not be YELLOW.я бы предположил, что это какая-то константа, метод toString () предназначен только для целей отладки, поэтому я не понимаю, почему вам нужно изменить то, как он выглядит в этом сообщении.
Брандин
1
Я думаю, что ответ больше основан на вашей идее «хорошо». Мир, скорее всего, не загорится, если вы сделаете это, и вы даже сможете успешно написать программу. Это полезно? мне тоже субъективно. Если вы получаете какую-то выгоду, стоит ли это для вас и повлияет ли это на других? Это те вопросы, которые мне нужны.
Гарет Клаборн

Ответы:

14

Краткий ответ, конечно, заключается в том, хотите ли вы порвать с соглашениями об именах для того, что по существу является константой ... Цитата из JLS :

Постоянные имена

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

Длинный ответ, касающийся использования toString(), - это, безусловно, метод переопределения, если вы хотите более читаемое представление enumзначений. Цитирую из Object.toString()(выделено мое):

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

Теперь я не уверен, почему некоторые из ответов сместились к разговору о том, как конвертировать enumsтуда-сюда со Stringзначениями, но я просто дам здесь мой взгляд. О такой сериализации enumзначений можно легко позаботиться, используя методы name()или ordinal(). И то, finalи другое, и, таким образом, вы можете быть уверены в возвращаемых значениях, пока имена или расположение значений не меняются . Для меня это достаточно четкий маркер.

Из вышесказанного я понял следующее: сегодня вы можете описать его YELLOWпросто как «желтый». Завтра, возможно, вы захотите описать его как «Желтый цвет Pantone Minion» . Эти описания должны быть возвращены из вызова toString(), и я бы не стал ожидать либо name()или ordinal()изменения. Если я это сделаю, это то, что мне нужно решить в моей кодовой базе или моей команде, и это станет большим вопросом, чем просто enumстиль именования .

В заключение, если все, что вы намереваетесь сделать, это записать более удобочитаемое представление ваших enumзначений, я все равно предложу придерживаться соглашений и затем переопределить toString(). Если вы намерены сериализации их в файл данных, или в другие места , не Java, вы по- прежнему есть name()и ordinal()методы , чтобы поддержать вас, так что нет необходимости беспокоиться по поводу переопределение toString().

ХИК
источник
5

Не изменяйте ENUMэто просто плохой запах кода. Пусть enum будет enum, а string - строкой.

Вместо этого используйте конвертер CamelCase для строковых значений.

throw new IllegalStateException("Light should not be " + CamelCase(color) + ".");

Есть много библиотек с открытым исходным кодом, которые уже решают эту проблему для Java.

http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/base/CaseFormat.html

Reactgular
источник
Разве это не было бы недетерминированным в определенных крайних случаях? (не конкретное поведение конвертера, а общий принцип)
Panzercrisis
-1 Добавление сторонней библиотеки, которая может не подходить для базового форматирования строк, может быть излишним.
user949300
1
@ user949300 скопируйте код, напишите свой или что-либо еще. Вы упускаете суть.
Reactgular
Можете ли вы уточнить «суть»? «Пусть enum будет enum, а string - строка» звучит хорошо, но что это на самом деле означает?
user949300
1
Enum обеспечивает безопасность типов. Он не может передать "картофель" как цвет. И, возможно, он включает в перечисление другие данные, такие как значение RGB, просто не показывает их в коде. Есть много веских причин использовать перечисление вместо строки.
user949300
3

Отправляя перечисления между моим Java-кодом и базой данных или клиентским приложением, я часто заканчиваю тем, что читаю и записываю значения перечисления в виде строк. toString()вызывается неявно при конкатенации строк. Переопределение toString () в некоторых перечислениях означало, что иногда я мог просто

"<input type='checkbox' value='" + MY_CONST1 + "'>"

а иногда мне приходилось не забывать звонить

"<input type='checkbox' value='" + MY_CONST1.name() + "'>"

что привело к ошибкам, поэтому я больше так не делаю. На самом деле, я не переопределяю никакие методы в Enum, потому что, если вы добавите их к достаточному количеству клиентского кода, вы в конечном итоге нарушите чьи-то ожидания.

Создайте свое собственное имя нового метода, например public String text()или toEnglish()или как угодно.

Вот небольшая вспомогательная функция, которая поможет вам сэкономить при наборе, если у вас много перечислений, как указано выше:

public static String ucFirstLowerRest(String s) {
    if ( (s == null) || (s.length() < 1) ) {
        return s;
    } else if (s.length() == 1) {
        return s.toUpperCase();
    } else {
        return s.substring(0, 1).toUpperCase() + s.substring(1).toLowerCase();
    }
}

Всегда легко вызывать .toUpperCase () или .toLowerCase (), но вернуть смешанный регистр может быть сложно. Рассмотрим цвет "bleu de France". Франция всегда пишется с большой буквы, поэтому вы можете захотеть добавить метод textLower () к своему перечислению, если столкнетесь с этим. Когда вы используете этот текст в начале предложения по сравнению с серединой предложения или в заголовке, вы можете увидеть, как один toString()метод не справится с задачей. И это даже не касается символов, которые являются недопустимыми в идентификаторах Java, или которые неудобно набирать, потому что они не представлены на стандартных клавиатурах, или символы, которые не имеют регистра (кандзи и т. Д.).

enum Color {
  BLEU_DE_FRANCE {
    @Override public String textTc() { return "Bleu De France"; }
    @Override public String textLc() { return "bleu de France"; }
  }
  CAFE_NOIR {
    @Override public String textTc() { return "Café Noir"; }
  }
  RED,
  YELLOW,
  GREEN;

  // The text in title case
  private final String textTc;

  private Color() { 
    textTc = ucFirstLowerRest(this.toString());
  }

  // Title case
  public String textTc() { return textTc; }

  // For the middle of a sentence
  public String textLc() { return textTc().toLowerCase(); }

  // For the start of a sentence
  public String textUcFirst() {
    String lc = textLc();
    return lc.substring(0, 1).toUpperCase() + lc.substring(1);
  }
}

Это не так сложно использовать правильно:

IllegalStateException(color1.textUcFirst() + " clashes horribly with " +
                      color2.textLc() + "!")

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

GlenPeterson
источник
3
Предложение никогда не отменять toString()перечисление не имеет смысла для меня. Если вы хотите гарантированное поведение по умолчанию имен enum, используйте name(). Я не нахожу заманчивым полагаться на то, toString()чтобы предоставить правильный ключ valueOf(String). Я думаю, если вы хотите идиотизировать ваши перечисления, это одно, но я не думаю, что это достаточно веская причина, чтобы рекомендовать вам никогда не отменять toString().
код взломщик
1
Кроме того, я обнаружил это, что рекомендует (как мне поверили), что переопределение toString в перечислениях на самом деле хорошая вещь: stackoverflow.com/questions/13291076/…
codebreaker
@codebreaker toString () вызывается неявно при конкатенации строк. Переопределение toString () в некоторых перечислениях означало, что иногда я мог просто <input type='checkbox' value=" + MY_CONST1 + ">, а иногда мне приходилось не забывать вызывать <input type='checkbox' value=" + MY_CONST1.name() + ">, что приводило к ошибкам.
ГленПетерсон
Хорошо, это хороший момент, но это действительно имеет значение, когда вы «сериализуете» перечисления с их именами (это не то, о чем мне нужно беспокоиться в моем примере).
код взломщик
0

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

Что делать , если в один прекрасный день вы решили переименовать Color.Whiteв Color.TitaniumWhiteто время как есть данные файлы из там содержащих «Белых»?

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

Сказав это, вы, конечно, можете пойти дальше и сделать то, о чем вы думали, а затем реорганизовать свой enum позже, чтобы передать имена конструктору, когда (и если) у вас возникнут проблемы.

Вы можете убрать пару строк из вашего enum-with-constructor, объявив nameполе public finalи потеряв геттер. (Что это за любовь к Java-программистам?)

Майк Накис
источник
Публичная финальная статика компилируется в код, который использует ее как фактическую константу, а не как ссылку на класс, из которого она исходит. Это может вызвать ряд других забавных ошибок при частичной перекомпиляции кода (или замене jar-файла). Если вы предлагаете public final static int white = 1;или public final static String white = "white";(кроме нарушения соглашения снова), это теряет безопасность типов, которую обеспечивает enum.
@MichaelT Enum поля не являются статическими. Экземпляр создается для каждой константы перечисления, а члены перечисления являются переменными-членами этого экземпляра. public finalПоле экземпляра , который инициализируется из конструкторы ведет себя так же , как неконечное поле, за исключение того, что вы предотвращены во время компиляции от присвоения ему. Вы можете изменить его с помощью отражения, и весь код, который ссылается на него, немедленно начнет видеть новое значение.
Майк Накис
0

Возможно, следование соглашению об именах UPPERCASE нарушает DRY . (И ЯГНИ ). Например, в вашем примере кода # 2: «КРАСНЫЙ» и «красный» повторяются.

Итак, вы следуете официальному соглашению или следите за DRY? Ваш звонок.

В моем коде я буду следовать DRY. Тем не менее, я добавлю комментарий к классу Enum, сказав, что «не все в верхнем регистре, потому что (объяснение здесь)»

user949300
источник