Czy można wbrew nazewnicjom wielkimi literami wyliczeń, aby uprościć ich reprezentację ciągu?

14

Kilka razy widziałem, jak ludzie używają tytułowych lub nawet małych liter dla stałych enum, na przykład:

enum Color {
  red,
  yellow,
  green;
}

Dzięki temu praca z ich ciągiem znaków jest prosta i łatwa throw new IllegalStateException("Light should not be " + color + "."), na przykład , jeśli chcesz .

Wydaje się to bardziej akceptowalne, jeśli wyliczenie jest private, ale nadal mi się nie podoba. Wiem, że mogę utworzyć konstruktor enum z polem String, a następnie zastąpić toString, aby zwrócić tę nazwę, tak jak poniżej:

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; 
  }
}

Ale spójrz, jak długo to trwa. To irytujące, jeśli masz mnóstwo małych wyliczeń, które chcesz uprościć. Czy można tutaj używać niekonwencjonalnych formatów spraw?

łamacz kodów
źródło
4
To konwencja. Czy masz powód do przełamywania konwencji? To zależy od Ciebie.
9
Zastanawiam się, co jest nie tak z komunikatem o wyjątku Light should not be RED.. W końcu ta wiadomość jest dla programisty, użytkownik nigdy nie powinien jej zobaczyć.
Brandin
1
@Brandin, jeśli kolor jest bardziej szczegółem implementacji, nikt nie powinien go zobaczyć. Oczywiście myślę, że to pozostawia pytanie, dlaczego potrzebujemy ładnej formy do ciągnięcia.
codebreaker
8
Pod koniec dnia to zależy od ciebie. Gdybym debugował twój kod i zobaczył komunikat o wyjątku Light should not be YELLOW., zgaduję, że jest to jakaś stała, metoda toString () służy tylko do debugowania, więc nie rozumiem, dlaczego musisz zmienić wygląd tego komunikatu.
Brandin
1
Myślę, że odpowiedź jest bardziej oparta na twoim pomyśle „dobrze”. Świat najprawdopodobniej nie wybuchnie, jeśli to zrobisz, a nawet uda ci się napisać program. To jest użyteczne? meh zbyt subiektywne. Jeśli dostaniesz jakąś korzyść, czy jest to dla ciebie opłacalne i czy wpłynie na innych? To pytania, na które mi zależy.
Garet Claborn,

Odpowiedzi:

14

Krótka odpowiedź brzmi oczywiście, czy chcesz zerwać z konwencjami nazewnictwa dla zasadniczo stałych ... Cytując z JLS :

Stałe nazwy

Nazwy stałych w typach interfejsów powinny być, a końcowymi zmiennymi typów klasowych mogą być konwencjonalnie sekwencje jednego lub więcej słów, akronimów lub skrótów, wszystkie wielkie litery, z komponentami oddzielonymi znakami podkreślenia „_”. Nazwy stałe powinny być opisowe i nie powinny być niepotrzebnie skracane. Konwencjonalnie mogą być dowolną odpowiednią częścią mowy.

Długa odpowiedź, jeśli chodzi o użycie toString(), to zdecydowanie metoda zastępowania, jeśli chcesz bardziej czytelnej reprezentacji enumwartości. Cytowanie z Object.toString()(moje podkreślenie):

Zwraca ciąg znaków reprezentujący obiekt. Ogólnie toStringmetoda zwraca ciąg „tekstowo reprezentujący” ten obiekt . Rezultatem powinno być zwięzłe, ale pouczające przedstawienie , łatwe do odczytania przez osobę . Zaleca się, aby wszystkie podklasy zastąpiły tę metodę.

Teraz nie jestem pewien, dlaczego niektóre odpowiedzi skłaniały do ​​mówienia o konwersji enumsna Stringwartości z powrotem na wartości, ale ja też tutaj przedstawię swoje zdanie. Taką serializację enumwartości można łatwo rozwiązać za pomocą metod name()lub ordinal(). Oba są finali dlatego możesz być pewien zwracanych wartości , o ile nazwy lub położenie wartości nie ulegną zmianie . Dla mnie to wystarczająco wyraźny marker.

To, co zbieram z powyższego, brzmi: dzisiaj możesz chcieć opisać YELLOWjako po prostu „żółty”. Jutro możesz opisać to jako „Pantone Minion Yellow” . Opisy te powinny być zwrócone z wywołaniem toString(), a ja nie spodziewałbym albo name()lub ordinal()zmienić. Jeśli to zrobię, muszę to rozwiązać w ramach mojej bazy kodu lub mojego zespołu i staje się większym pytaniem niż tylko enumstylem nazewnictwa .

Podsumowując, jeśli wszystko, co zamierzasz zrobić, to zapisać bardziej czytelną reprezentację swoich enumwartości, nadal sugeruję trzymanie się konwencji, a następnie zastępowanie toString(). Jeśli zamierzasz również serializować je do pliku danych lub do innych miejsc docelowych innych niż Java, nadal masz metody name()i ordinal(), aby utworzyć kopię zapasową, więc nie musisz się martwić o zastąpienie toString().

hjk
źródło
5

Nie modyfikuj ENUM, to po prostu nieprzyjemny zapach kodu. Niech wyliczenie będzie wyliczeniem, a łańcuch ciągiem.

Zamiast tego użyj wartości konwertera CamelCase.

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

Istnieje wiele bibliotek typu open source, które już rozwiązują ten problem dla Java.

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

Reactgular
źródło
Czy nie byłoby to niedeterministyczne w niektórych przypadkach skrajnych? (nie zachowanie konkretnego konwertera, ale ogólna zasada)
Panzercrisis
-1 Dodanie biblioteki innej firmy, która może nie być tym, czego potrzebujesz w przypadku podstawowego formatowania ciągów, może być nadmierne.
user949300
1
@ user949300 skopiuj kod, napisz własny lub cokolwiek innego. Nie rozumiesz sedna sprawy.
Reactgular
Czy potrafisz opracować „punkt”? „Niech enum będzie enum, a sznurek być sznurkiem” brzmi dobrze, ale co to tak naprawdę znaczy?
user949300
1
Enum zapewnia bezpieczeństwo typu. Nie może przekazać „potatoe” jako koloru. I być może zawiera on inne dane w wyliczeniu, takie jak wartość thr RGB, po prostu nie pokazuje tego w kodzie. Istnieje wiele dobrych powodów, aby używać wyliczenia zamiast łańcucha.
user949300
3

Wysyłając wyliczenia między moim kodem Java a bazą danych lub aplikacją klienta, często kończę na czytaniu i zapisywaniu wartości wyliczania jako ciągów. toString()nazywa się niejawnie podczas łączenia łańcuchów. Przesłonięcie metody toString () w niektórych wyliczeniach oznaczało, że czasami mogłem po prostu

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

i czasami musiałem pamiętać, żeby zadzwonić

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

co doprowadziło do błędów, więc już tego nie robię. Właściwie nie zastępuję żadnych metod na Enum, ponieważ jeśli podrzucisz je do wystarczającej liczby kodów klienta, ostatecznie złamiesz czyjeś oczekiwania.

Zrobić własną nową nazwę metody, jak public String text()i toEnglish()czy cokolwiek innego.

Oto mała funkcja pomocnika, która może zaoszczędzić trochę pisania, jeśli masz wiele wyliczeń takich jak powyżej:

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();
    }
}

Zawsze łatwo jest wywołać .toUpperCase () lub .toLowerCase (), ale odzyskanie mieszanych liter może być trudne. Rozważ kolor „bleu de France”. Francja jest zawsze pisana wielkimi literami, więc jeśli chcesz, możesz dodać metodę textLower () do swojego wyliczenia. Kiedy użyjesz tego tekstu na początku zdania, w środku zdania, a w tytule, zobaczysz, jak jedna toString()metoda może się nie udać. I to nawet nie dotyka znaków, które są niedozwolone w identyfikatorach Java, lub których pisanie jest trudne, ponieważ nie są reprezentowane na standardowych klawiaturach, ani znaków, które nie mają wielkości liter (Kanji itp.).

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);
  }
}

Prawidłowe korzystanie z nich nie jest takie trudne:

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

Mam nadzieję, że to również pokazuje, dlaczego stosowanie wartości wyliczania mieszanych wielkości liter również Cię rozczaruje. Ostatnim powodem, dla którego warto trzymać się wielkich liter ze stałymi enum podkreślenia, jest to, że postępuje to zgodnie z zasadą najmniejszego zdziwienia. Ludzie tego oczekują, więc jeśli zrobisz coś innego, zawsze będziesz musiał wyjaśniać siebie lub radzić sobie z ludźmi niewłaściwie używającymi twojego kodu.

GlenPeterson
źródło
3
Sugestia, by nigdy nie zastępować toString()wyliczenia, nie ma dla mnie sensu. Jeśli chcesz zagwarantować domyślne zachowanie nazw wyliczeń, użyj name(). Nie wydaje mi się, by „kusiło”, aby polegać na toString()zapewnieniu właściwego klucza valueOf(String). Sądzę, że jeśli chcesz dowodzić idiotyzmem swoich wyliczeń, to jedno, ale nie sądzę, żeby to był wystarczający powód, aby zalecać, abyś nigdy nie zastępował toString().
codebreaker
1
Co więcej, znalazłem to, co sugeruje (jak mnie przekonano), że przesłonięcie metody toString na wyliczeniach jest w rzeczywistości dobrą rzeczą: stackoverflow.com/questions/13291076/...
codebreaker
@codebreaker toString () jest wywoływany niejawnie podczas łączenia łańcuchów. Przesłonięcie metody toString () w niektórych wyliczeniach oznaczało, że czasami mogłem po prostu <input type='checkbox' value=" + MY_CONST1 + ">, a czasem musiałem pamiętać o wywołaniu <input type='checkbox' value=" + MY_CONST1.name() + ">, co prowadziło do błędów.
GlenPeterson
Dobra, to dobra uwaga, ale to naprawdę ma znaczenie tylko wtedy, gdy „serializujesz” wyliczenia z ich nazwami (co nie jest tym, o co muszę się martwić w moim przykładzie).
codebreaker
0

Jeśli istnieje najmniejsza szansa, że ​​te reprezentacje ciągów mogą być przechowywane w miejscach takich jak bazy danych lub pliki tekstowe, to powiązanie ich z rzeczywistymi stałymi wyliczania stanie się bardzo problematyczne, jeśli kiedykolwiek będziesz musiał zmienić swoje wyliczenie.

Co jeśli kiedyś zdecydujesz się zmienić nazwę Color.White, aby Color.TitaniumWhitepodczas gdy istnieją dane pliki tam zawierające „białe”?

Ponadto, gdy zaczniesz konwertować stałe wyliczeniowe na ciągi, następnym krokiem w dalszej części będzie wygenerowanie ciągów, które użytkownik będzie mógł zobaczyć, a problemem jest, jak jestem pewien, że już wiesz, że składnia java nie zezwalaj na spacje w obrębie identyfikatorów. (Nigdy nie będziesz miał Color.Titanium White.) Tak więc, ponieważ prawdopodobnie będziesz potrzebować odpowiedniego mechanizmu do generowania nazw wyliczeń, aby pokazać je użytkownikowi, najlepiej unikać niepotrzebnego komplikowania wyliczenia.

To powiedziawszy, możesz oczywiście iść naprzód i zrobić to, o czym myślisz, a następnie przefakturować swój wyliczenie później, aby przekazać nazwy konstruktorowi, kiedy (i jeśli) napotkasz kłopoty.

Możesz ogolić kilka linii ze swojego enum-z-konstruktorem, deklarując namepole public finali tracąc gettera. (Jaką miłość mają programiści Java w stosunku do getterów?)

Mike Nakis
źródło
Publiczny końcowy statyczny kompiluje się w kod, który wykorzystuje go jako rzeczywistą stałą, a nie jako odwołanie do klasy, z której pochodzi. Może to powodować wiele innych zabawnych błędów podczas częściowej rekompilacji kodu (lub wymiany słoika). Jeśli sugerujesz public final static int white = 1;lub public final static String white = "white";(oprócz ponownego zerwania konwencji), traci to bezpieczeństwo typu, które zapewnia wyliczenie.
@MichaelT Pola Enum nie są statyczne. Instancja jest tworzona dla każdej stałej wyliczania, a członkowie wyliczenia są zmiennymi składowymi tego wystąpienia. Pole public finalinstancji inicjowane z konstruktora zachowuje się dokładnie tak samo, jak pole nie-końcowe, z tym wyjątkiem, że podczas kompilacji nie można przypisać do niego pola . Możesz go zmodyfikować poprzez odbicie, a cały odnoszący się do niego kod natychmiast zacznie widzieć nową wartość.
Mike Nakis
0

Prawdopodobnie przestrzeganie konwencji NAZWY DUŻYCH narusza OSUSZANIE . (I YAGNI ). np. w twoim kodzie przykład 2: „RED” i „red” są powtarzane.

Czy przestrzegasz oficjalnej konwencji, czy postępujesz zgodnie z zasadą DRY? Twoja decyzja.

W moim kodzie będę śledzić DRY. Zamieszczę jednak komentarz do klasy Enum, mówiąc: „nie wszystkie wielkie litery, ponieważ (wyjaśnienie tutaj)”

użytkownik949300
źródło