Czy wartość stałej może być zmieniana w czasie?

28

Na etapie programowania istnieją pewne zmienne, które muszą być ustalone w tym samym przebiegu, ale z czasem mogą wymagać modyfikacji. Na przykład, booleanaby zasygnalizować tryb debugowania, więc robimy rzeczy w programie, których normalnie byśmy nie zrobili.

Czy źle jest przechowywać te wartości w stałej, tj. final static int CONSTANT = 0W Javie? Wiem, że stała pozostaje taka sama w czasie wykonywania, ale czy powinna być taka sama podczas całego rozwoju, z wyjątkiem oczywiście nieplanowanych zmian?

Szukałem podobnych pytań, ale nie znalazłem niczego, co pasowałoby dokładnie do mojego.

GregT
źródło
11
Jestem ciekawy, dlaczego uważasz, że zmiana ich była złym stylem?
Vincent Savard
36
O ile nie modelujesz właściwości fizycznych za pomocą stałych o znanych wartościach matematycznych, wszystko może się zmienić w pewnym momencie.
Berin Loritsch
19
oprogramowanie jest miękkie .
Erik Eidt
10
@GregT Nie zgadzam się. finaldaje wymuszoną przez kompilator gwarancję, że program nie zmodyfikuje wartości. Nie zrezygnowałbym z tego tylko dlatego, że programista może chcieć zmodyfikować wartość przypisaną w kodzie źródłowym.
Alexander - Przywróć Monikę
10
Nie ma dużo czasu na sformułowanie pełnej odpowiedzi, ale podejrzewam, że twoi koledzy martwią się nie tyle stałymi, ale wprowadzaniem kodu wartości konfiguracyjnych, które mogą przejawiać się jako stałe. ... Stałe chronią cię przed głupimi błędami, takimi jak przypadkowe przypisanie w trakcie gravitygry / biegu. Niekoniecznie oznaczają, że gravityjest taki sam na każdej planecie ... To powiedziawszy, zdrowym rozwiązaniem jest utworzenie gravitystałej, ale pobranie jej z planetpliku lub bazy danych na początku odpowiedniego zakresu.
svidgen

Odpowiedzi:

6

W Javie statyczne stałe końcowe mogą być kopiowane przez kompilator jako ich wartości do kodu, który ich używa . W wyniku tego, jeśli zwolnisz nową wersję kodu i istnieje pewna zależność niższego szczebla, która wykorzystała stałą, stała w tym kodzie nie zostanie zaktualizowana, chyba że kod dalszego przesyłania zostanie ponownie skompilowany. Może to stanowić problem, jeśli następnie wykorzystają tę stałą z kodem, który oczekuje nowej wartości, ponieważ nawet jeśli kod źródłowy jest poprawny, kod binarny nie jest.

Jest to brodawka w projektowaniu Java, ponieważ jest to jeden z niewielu przypadków (być może jedyny przypadek), w którym zgodność źródła i zgodność binarna nie są takie same. Z wyjątkiem tego przypadku można zamienić zależność na nową wersję zgodną z interfejsem API bez konieczności ponownej kompilacji przez użytkowników zależności. Jest to oczywiście niezwykle ważne, biorąc pod uwagę ogólny sposób zarządzania zależnościami Java.

Co gorsza, kod po prostu dyskretnie zrobi coś złego, zamiast generować przydatne błędy. Gdyby zastąpić zależność wersją o niekompatybilnych definicjach klas lub metod, otrzymalibyśmy błędy modułu ładującego lub wywołania, które przynajmniej dają dobre wskazówki co do tego, na czym polega problem. O ile nie zmieniłeś rodzaju wartości, ten problem będzie wyglądał jak tajemnicze złe zachowanie w czasie wykonywania.

Bardziej denerwujące jest to, że dzisiejsze maszyny JVM mogą łatwo wstawiać wszystkie stałe w środowisku wykonawczym bez obniżania wydajności (poza koniecznością ładowania klasy definiującej stałą, która i tak prawdopodobnie jest ładowana), niestety semantyka języka pochodzi z dni przed JIT . I nie mogą zmienić języka, ponieważ kod skompilowany z poprzednimi kompilatorami nie będzie poprawny. Kompatybilność z błędami uderza ponownie.

Z tego powodu niektórzy radzą, aby w ogóle nigdy nie zmieniać statycznej wartości końcowej. W przypadku bibliotek, które mogą być szeroko rozpowszechniane i aktualizowane w nieznany sposób w nieznanych czasach, jest to dobra praktyka.

W swoim własnym kodzie, szczególnie na szczycie hierarchii zależności, zapewne ci się uda. Ale w takich przypadkach zastanów się, czy naprawdę potrzebujesz stałej, aby być publicznym (lub chronionym). Jeśli stałą jest tylko widoczność pakietu, uzasadnione jest, w zależności od okoliczności i standardów kodu, że cały pakiet zawsze zostanie ponownie skompilowany na raz, a problem zniknie. Jeśli stała jest prywatna, nie masz problemu i możesz ją zmienić w dowolnym momencie.

fluffysheap
źródło
85

Wszystko w kodzie źródłowym, w tym constzadeklarowane stałe globalne, może ulec zmianie wraz z nową wersją oprogramowania.

Słowa kluczowe const(lub finalw Javie) służą do sygnalizowania kompilatorowi, że ta zmienna nie zmieni się podczas działania tego wystąpienia programu . Nic więcej. Jeśli chcesz wysyłać wiadomości do następnego opiekuna, użyj komentarza w źródle, po to są.

// DO NOT CHANGE without consulting with the legal department!
// Get written consent form from them before release!
public const int LegalLimitInSeconds = ...

To zdecydowanie lepszy sposób komunikowania się z przyszłym sobą.

nvoigt
źródło
11
Naprawdę podobał mi się ten komentarz dla przyszłego siebie.
GregT,
4
TaxRatebycie publicdenerwuje mnie. Chciałbym mieć pewność, że zmiana ta dotyczy tylko działu sprzedaży, a nie naszych dostawców, którzy obciążają nas podatkiem. Kto wie, co wydarzyło się w bazie kodu od czasu napisania tego komentarza.
candied_orange
3
@IllusiveBrian nie krytykował użycia stałych. Ostrzegał przed zaufaniem, że komentarz będzie aktualny. Zawsze upewnij się, jak coś jest używane, zanim je zmienisz.
candied_orange
8
To dobra rada dla Javy . Może być inaczej w innych językach. Na przykład z powodu sposobu, w jaki wartości const są powiązane z witryną wywoływania w języku C #, public constpola powinny być używane tylko dla rzeczy, które nigdy się nie zmienią, takich jak Math.pi. Jeśli tworzysz bibliotekę, rzeczy, które mogą się zmienić podczas programowania lub nowej wersji, powinny być public static readonlytakie, aby nie powodować problemów z użytkownikami Twojej biblioteki.
GrandOpener
6
Powinieneś wybrać inny przykład ... wartości pieniężne nigdy nie powinny być zmiennoprzecinkowe!
corsiKa
13

Musimy rozróżnić dwa aspekty stałych:

  • nazwy wartości znanych w czasie opracowywania, które wprowadzamy dla lepszej konserwacji, oraz
  • wartości dostępne dla kompilatora.

A potem jest pokrewny trzeci rodzaj: zmienne, których wartość się nie zmienia, tj. Nazwy wartości. Różnica między tymi niezmiennymi zmiennymi a stałą polega na tym, że wartość jest określana / przypisywana / inicjalizowana: zmienna jest inicjalizowana w czasie wykonywania, ale wartość stałej jest znana podczas programowania. To rozróżnienie jest nieco mętne, ponieważ wartość może być znana podczas programowania, ale w rzeczywistości jest tworzona tylko podczas inicjalizacji.

Ale jeśli wartość stałej jest znana w czasie kompilacji, to kompilator może wykonywać obliczenia z tą wartością. Na przykład język Java ma pojęcie wyrażeń stałych . Stałe wyrażenie to dowolne wyrażenie, które składa się tylko z literałów pierwotnych lub łańcuchów, operacji na stałych wyrażeniach (takich jak rzutowanie, dodawanie, łączenie łańcuchów) i zmiennych stałych. [ JLS § 15.28 ] Zmienna stała jest finalzmienną, która jest inicjowana stałym wyrażeniem. [JLS §4.12.4] Tak więc dla Javy jest to stała czasowa kompilacji:

public static final int X = 7;

Staje się to interesujące, gdy zmienna stała jest używana w wielu jednostkach kompilacji, a następnie deklaracja jest zmieniana. Rozważać:

  • A.java:

    public class A { public static final int X = 7; }
  • B.java:

    public class B { public static final int Y = A.X + 2; }

Teraz, kiedy kompilujemy te pliki, B.classkod bajtowy zadeklaruje pole, Y = 9ponieważ B.Yjest zmienną stałą.

Ale kiedy zmienimy A.Xzmienną na inną wartość (powiedzmy X = 0) i ponownie skompilujemy tylko A.javaplik, wówczas B.Ynadal będzie się odnosić do starej wartości. Ten stan A.X = 0, B.Y = 9jest niezgodny z deklaracjami w kodzie źródłowym. Miłego debugowania!

Nie oznacza to, że stałe nigdy nie powinny być zmieniane. Stałe są zdecydowanie lepsze niż magiczne liczby, które pojawiają się bez wyjaśnienia w kodzie źródłowym. Jednak wartość stałych publicznych jest częścią publicznych API . Nie jest to specyficzne dla Javy, ale występuje również w C ++ i innych językach, które zawierają osobne jednostki kompilacji. Jeśli zmienisz te wartości, będziesz musiał ponownie skompilować cały zależny kod, tj. Wykonać czystą kompilację.

W zależności od charakteru stałych mogły one prowadzić do błędnych założeń programistów. Jeśli te wartości zostaną zmienione, mogą spowodować błąd. Na przykład można wybrać zestaw stałych, aby tworzyły pewne wzorce bitowe, np public static final int R = 4, W = 2, X = 1. Jeśli zostaną one zmienione w celu utworzenia innej struktury, takiej jak R = 0, W = 1, X = 2istniejący kod, taki jak, stanie boolean canRead = perms & Rsię niepoprawny. Pomyśl tylko o zabawie, która Integer.MAX_VALUEsię zmieni! Nie ma tutaj poprawki, należy tylko pamiętać, że wartość niektórych stałych jest naprawdę ważna i nie można jej po prostu zmienić.

Ale dla większości stałych zmiana ich będzie w porządku, o ile uwzględnione zostaną powyższe ograniczenia. Stałą można bezpiecznie zmienić, gdy znaczenie, a nie konkretna wartość, jest ważne. Dzieje się tak np. W przypadku tunerów takich jak BORDER_WIDTH = 2lub TIMEOUT = 60; // secondslub szablonów takich jak API_ENDPOINT = "https://api.example.com/v2/"- choć prawdopodobnie niektóre lub wszystkie z nich powinny być określone w plikach konfiguracyjnych, a nie w kodzie.

amon
źródło
5
Podoba mi się ta analiza. Czytam to jako: Możesz dowolnie zmieniać stałą, o ile tylko rozumiesz, jak jest używana.
candied_orange
+1 C # również „cierpi” na ten sam problem ze stałymi publicznymi.
Reginald Blue,
6

Stała gwarantuje się tylko jako stała przez cały czas działania środowiska wykonawczego aplikacji . Tak długo, jak to prawda, nie ma powodu, aby nie korzystać z funkcji języka. Musisz tylko wiedzieć, jakie są konsekwencje użycia flagi stała vs. kompilator w tym samym celu:

  • Stałe zajmują przestrzeń aplikacji
  • Flagi kompilatora nie
  • Kod wyłączony przez stałe może być aktualizowany i zmieniany za pomocą nowoczesnych narzędzi do refaktoryzacji
  • Kod wyłączony przez flagi kompilatora nie może

Po utrzymaniu aplikacji, która włącza rysowanie obwiedni dla kształtów, abyśmy mogli debugować sposób ich rysowania, napotkaliśmy problem. Po refaktoryzacji cały kod wyłączony przez flagi kompilatora nie zostałby skompilowany. Następnie celowo zmieniliśmy nasze flagi kompilatora na stałe aplikacji.

Mówię to, aby wykazać, że istnieją kompromisy. Waga kilku booleanów nie spowodowała, że ​​w aplikacji zabraknie pamięci lub nie zajmie zbyt wiele miejsca. Może to nie być prawdą, jeśli twoja stała jest naprawdę dużym obiektem, który zasadniczo ma uchwyt do wszystkiego w twoim kodzie. Jeśli nie zajmie się usunięciem wszystkich odniesień do obiektu, gdy nie jest już potrzebny, obiekt może być źródłem wycieku pamięci.

Musisz ocenić przypadek użycia i powód, dla którego chcesz zmienić stałe.

Nie jestem fanem prostych oświadczeń ogólnych , ale ogólnie twój starszy kolega ma rację. Jeśli coś musi się często zmieniać, może to wymagać elementu konfigurowalnego. Na przykład możesz prawdopodobnie przekonać swojego kolegę do stałej, IsInDebugMode = truegdy chcesz zabezpieczyć jakiś kod przed złamaniem. Jednak niektóre rzeczy mogą wymagać zmiany częściej niż wydanie aplikacji. W takim przypadku potrzebny jest sposób zmiany tej wartości w odpowiednim czasie. Możesz wziąć przykład TaxRate = .065. Może to być prawdą w momencie kompilacji kodu, ale z powodu nowych przepisów może on ulec zmianie przed wydaniem kolejnej wersji aplikacji. To jest coś, co wymaga aktualizacji albo z jakiegoś mechanizmu przechowywania (jak plik lub baza danych)

Berin Loritsch
źródło
Co rozumiesz przez „flagi kompilatora”? Być może preprocesor C i podobne funkcje kompilatora obsługujące makra / definicje i #ifdefs? Ponieważ są one oparte na tekstowym podstawieniu kodu źródłowego, nie są częścią semantyki języka programowania. Pamiętaj, że Java nie ma preprocesora.
amon
@amon, Java może nie, ale robi to w kilku językach. Mam na myśli #ifdefflagi. Chociaż nie są one częścią semantyki języka C, są częścią języka C #. Pisałem dla szerszego kontekstu agnostycyzmu językowego.
Berin Loritsch
Myślę, że argument „marnowanie pamięci” jest dyskusyjny. Podszewka i wstrząsanie drzewem jest dość uniwersalnym krokiem w każdym optymalizatorze trybu zwolnienia.
Alexander - Przywróć Monikę
@Alexander, zgadzam się. Trzeba jednak pamiętać.
Berin Loritsch
1
„Stałe zajmują przestrzeń aplikacji” - chyba że opracowujesz wbudowaną aplikację dla mikrokontrolera z zaledwie kilobajtem lub dwoma pamięciami, nie powinieneś nawet myśleć o takich rzeczach.
vsz
2

const, #defineCzy finaljest to wskazówka kompilator (uwaga, że #definew rzeczywistości nie jest wskazówką, jego makro i znacznie bardziej wydajne). Wskazuje, że wartość nie zmieni się podczas wykonywania programu i można dokonać różnych optymalizacji.

Jednak, jako wskazówka kompilatora, kompilator robi rzeczy, których programista nie zawsze może oczekiwać. W szczególności, javac wstawi a, static final int FOO = 42;tak że gdziekolwiek FOOzostanie użyty, odczytany zostanie rzeczywisty skompilowany kod bajtowy 42.

Nie jest to zbyt dużym zaskoczeniem, dopóki ktoś nie zmieni wartości bez ponownej kompilacji drugiej jednostki kompilacji (pliku .java) - i 42resztek w kodzie bajtów ( czy możliwe jest wyłączenie wstawiania statycznych zmiennych końcowych przez javac? ).

Robienie czegoś static finaloznacza, że ​​tak właśnie będzie i na zawsze będzie to więcej, a zmiana tego jest naprawdę wielką sprawą - zwłaszcza jeśli coś takiego nie jest private.

Stałe dla rzeczy takich jak final static int ZERO = 0nie jest problemem. final static double TAX_RATE = 0.55(poza tym, że pieniądze i podwójne są złe i powinny używać BigDecimal, ale wtedy nie są prymitywne, a zatem nie uwzględniane) jest problemem i należy je dokładnie zbadać, gdzie są używane.

użytkownik292808
źródło
dla małych wartości ZERO.
3
is a problem and should be examined with great care for where it is used.Dlaczego to jest problem?
Alexander - przywróć Monikę
1

Jak sama nazwa wskazuje, stałe nie powinny się zmieniać w czasie działania i moim zdaniem stałe są zdefiniowane, aby nie ulegały zmianie przez dłuższy czas (możesz spojrzeć na to pytanie SO, aby uzyskać więcej informacji.

Jeśli chodzi o potrzebę użycia flag (np. W trybie programowania), należy zamiast tego użyć pliku konfiguracyjnego lub parametru startowego (wiele IDE obsługuje konfigurowanie parametru startowego dla poszczególnych projektów; zapoznaj się z odpowiednią dokumentacją), aby włączyć ten tryb - w ten sposób zachowujesz elastyczność korzystania z takiego trybu i nie możesz zapomnieć o zmianie go za każdym razem, gdy kod produktywny.

DMuenstermann
źródło
0

Możliwość zmiany między uruchomieniami jest jednym z najważniejszych punktów definiowania stałej w kodzie źródłowym!

Stała zapewnia dobrze zdefiniowaną i udokumentowaną (w pewnym sensie) lokalizację do zmiany wartości w dowolnym momencie przez cały czas istnienia kodu źródłowego. Jest to również obietnica, że ​​zmiana stałej w tym miejscu faktycznie zmieni wszystkie wystąpienia tego, co oznacza.

Jako negatywny przykład: nie ma sensu mieć stałej, TRUEktóra ewaluuje truew języku, który faktycznie ma truesłowo kluczowe. Nigdy, nigdy, ani razu nie zadeklarujesz, TRUE=falsechyba że okrutny żart.

Oczywiście istnieją inne zastosowania stałych, na przykład skracanie kodu ( CO_NAME = 'My Great World Unique ACME Company'), unikanie powielania ( PI=3.141), ustawianie konwencji ( TRUE=1) lub cokolwiek innego, ale posiadanie określonej pozycji do zmiany stałej jest z pewnością jednym z najważniejszych.

AnoE
źródło