Может ли значение постоянной меняться со временем?

28

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

Это плохой стиль, чтобы содержать эти значения в константе, то есть final static int CONSTANT = 0в Java? Я знаю, что константа остается неизменной во время выполнения, но должна ли она быть одинаковой на протяжении всей разработки, за исключением, конечно, незапланированных изменений?

Я искал похожие вопросы, но не нашел ничего, что точно соответствовало бы моему.

GregT
источник
11
Мне любопытно, почему вы считаете это плохим стилем, чтобы изменить это?
Винсент Савард
36
Если вы не моделируете физические свойства с помощью констант, которые имеют известные математические значения, все может измениться в какой-то момент времени.
Берин Лорич
19
Программное обеспечение мягкое .
Эрик Эйдт
10
@GregT Я бы не согласился. finalдает гарантию компилятора, что программа не будет изменять значение. Я бы не отказался от этого только потому, что программист может захотеть изменить значение, назначенное в исходном коде.
Александр - Восстановить Монику
10
Не так много времени, чтобы сформулировать полный ответ, но я подозреваю, что ваши коллеги беспокоятся не столько о константах, сколько о вставке кода в значения конфигурации, которые могут проявляться как константы. ... Константы защищают вас от глупых ошибок, таких как случайное назначение в gravityсередине игры / пробега. Они не обязательно означают, gravityчто это одинаково на каждой планете ... Тем не менее, здоровое решение - создать gravityконстанту, но извлечь ее из planetфайла или базы данных в начале соответствующей области.
svidgen

Ответы:

6

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

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

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

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

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

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

fluffysheap
источник
85

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

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

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

Это лучший способ общения с вашим будущим я.

nvoigt
источник
11
Мне очень понравился этот комментарий к будущему себе.
GregT
4
TaxRateБытие publicзаставляет меня нервничать. Я хотел бы знать наверняка, что это изменение затронуло только отдел продаж, а не наших поставщиков, которые взимают с нас налог. Кто знает, что произошло в базе кода с момента написания этого комментария.
candied_orange
3
@IllusiveBrian не критиковал использование констант. Был предупреждением против доверия комментарию, чтобы быть в курсе. Всегда будьте уверены в том, как что-то используется, прежде чем менять это.
candied_orange
8
Это хороший совет для Java . Это может отличаться на других языках. Например, из-за того, как значения const связаны с сайтом вызовов в C #, public constполя должны использоваться только для вещей, которые никогда не изменятся, например Math.pi. Если вы создаете библиотеку, вещи, которые могут измениться во время разработки или с новой версией, должны быть public static readonlyтакими, чтобы не вызывать проблем у пользователей вашей библиотеки.
GrandOpener
6
Вы должны выбрать другой пример ... денежные значения никогда не должны быть плавающей точкой!
CorsiKa
13

Нам нужно выделить два аспекта констант:

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

И затем есть связанный третий тип: переменные, значение которых не изменяется, то есть имена для значения. Разница между этими неизменяемыми переменными и константой заключается в том, что значение определяется / присваивается / инициализируется: переменная инициализируется во время выполнения, но значение константы известно во время разработки. Это различие немного мутное, так как значение может быть известно во время разработки, но фактически создается только во время инициализации.

Но если значение константы известно во время компиляции, то компилятор может выполнять вычисления с этим значением. Например, язык Java имеет концепцию константных выражений . Константное выражение - это любое выражение, состоящее только из литералов примитивов или строк, операций с константными выражениями (таких как приведение, сложение, конкатенация строк) и константных переменных. [ JLS §15.28 ] Постоянная переменная - это finalпеременная, которая инициализируется постоянным выражением. [JLS §4.12.4] Итак, для Java это константа времени компиляции:

public static final int X = 7;

Это становится интересным, когда постоянная переменная используется в нескольких единицах компиляции, а затем объявление изменяется. Рассмотреть возможность:

  • A.java:

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

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

Теперь, когда мы скомпилируем эти файлы, B.classбайт-код объявит поле, Y = 9потому что B.Yэто постоянная переменная.

Но когда мы A.Xизменяем переменную на другое значение (скажем, X = 0) и перекомпилируем только A.javaфайл, тогда B.Yвсе равно ссылается на старое значение. Это состояние A.X = 0, B.Y = 9несовместимо с объявлениями в исходном коде. Удачной отладки!

Это не значит, что константы никогда не должны изменяться. Константы определенно лучше магических чисел, которые появляются без объяснения в исходном коде. Тем не менее, значение общественных констант является частью вашего общественного API . Это не относится к Java, но также встречается в C ++ и других языках, которые имеют отдельные модули компиляции. Если вы измените эти значения, вам нужно будет перекомпилировать весь зависимый код, т.е. выполнить чистую компиляцию.

В зависимости от природы констант, они могли привести к неверным предположениям разработчиков. Если эти значения изменены, они могут вызвать ошибку. Например, набор констант может быть выбран так, чтобы они формировали определенные битовые комбинации, например public static final int R = 4, W = 2, X = 1. Если они изменены, чтобы сформировать другую структуру, как R = 0, W = 1, X = 2тогда, существующий код, такой как, boolean canRead = perms & Rстановится неправильным. И просто подумайте о том, что веселье должно было Integer.MAX_VALUEизмениться! Здесь нет никакого исправления, просто важно помнить, что значение некоторых констант действительно важно и не может быть изменено просто.

Но для большинства констант их изменение будет нормальным, если учесть вышеупомянутые ограничения. Константу безопасно менять, когда важно значение, а не конкретное значение. Это, например, относится к настраиваемым элементам, таким как BORDER_WIDTH = 2или, TIMEOUT = 60; // secondsили шаблонам, таким как, API_ENDPOINT = "https://api.example.com/v2/"хотя, возможно, некоторые или все из них должны быть указаны в файлах конфигурации, а не в коде.

Амон
источник
5
Мне нравится этот анализ. Я читаю это так: вы можете изменять константу, если вы понимаете, как она используется.
candied_orange
+1 C # также «страдает» от той же проблемы с публичными константами.
Реджинальд Блю,
6

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

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

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

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

Вам нужно оценить вариант использования и то, почему вы хотите изменить константы.

Я не фанат простых общих заявлений, но в целом ваш старший коллега прав. Если что-то часто меняется, возможно, это должен быть настраиваемый элемент. Например, вы можете убедить своего коллегу в константе, IsInDebugMode = trueесли вы хотите защитить некоторый код от взлома. Однако некоторые вещи, возможно, придется менять чаще, чем вы выпускаете приложение. Если это так, вам нужен способ изменить это значение в соответствующее время. Вы можете взять пример TaxRate = .065. Это может быть правдой во время компиляции кода, но из-за новых законов он может измениться до того, как вы выпустите следующую версию своего приложения. Это то, что должно быть обновлено с помощью какого-либо механизма хранения (например, файла или базы данных)

Берин Лорич
источник
Что вы подразумеваете под «флагами компилятора»? Возможно, препроцессор C и аналогичные функции компилятора, которые поддерживают макросы, определения и #ifdefs? Поскольку они основаны на текстовой подстановке исходного кода, они не являются частью семантики языка программирования. Обратите внимание, что в Java нет препроцессора.
Амон
@amon, Java может и не быть, но есть несколько языков. Я имею в виду #ifdefфлаги. Хотя они не являются частью семантики C, они являются частью C #. Я писал для более широкого контекста языкового агностицизма.
Берин Лорич
Я думаю, что аргумент "потеря памяти" спорный. Подкладка и встряхивание дерева - довольно универсальный шаг в любом оптимизаторе режима выпуска.
Александр - Восстановить Монику
@ Александр, я согласен. Это то, что нужно знать, хотя.
Берин Лорич
1
«Константы занимают пространство приложения» - если вы не разрабатываете встроенное приложение для микроконтроллера с объемом памяти в один или два килобайта, вам даже не следует думать о таких вещах.
vsz
2

const, #defineИли finalэто компилятор намек (обратите внимание , что #defineэто на самом деле не намек, его макро и значительно более мощный). Это указывает на то, что значение не изменится во время выполнения программы, и могут быть выполнены различные оптимизации.

Однако, как подсказка компилятора, компилятор делает то, чего не всегда может ожидать программист. В частности, javac будет встроен static final int FOO = 42;так, что где бы он ни FOOиспользовался, будет считываться фактический скомпилированный байт-код 42.

Это не является большим сюрпризом, пока кто-то не изменит значение, не перекомпилировав другой модуль компиляции (файл .java) - и 42остатки в байт-коде (см. Можно ли отключить встроенные в javac статические конечные переменные? ).

Создание чего-либо static finalозначает, что это так и будет всегда, и изменение этого - действительно большое дело - особенно если это что-то еще private.

Константы для таких вещей, как final static int ZERO = 0не проблема. final static double TAX_RATE = 0.55(кроме того, что деньги и double вредны и должны использовать BigDecimal, но тогда это не примитив и, следовательно, не ограничен), это проблема, и ее следует внимательно изучить, где она используется.

user292808
источник
для малых значений НУЛЯ.
3
is a problem and should be examined with great care for where it is used.Почему это проблема?
Александр - Восстановить Монику
1

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

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

DMuenstermann
источник
0

Возможность изменения между запусками является одним из наиболее важных моментов определения константы в вашем исходном коде!

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

В качестве негативного примера: она не имеет смысла иметь константу , TRUEкоторая расценивается как trueна языке , который на самом деле имеет trueключевое слово. Вы никогда, никогда, ни разу не объявите, TRUE=falseкроме как жестокая шутка.

Конечно, есть и другие способы использования констант, например, сокращение кода ( CO_NAME = 'My Great World Unique ACME Company'), избегание дублирования ( PI=3.141), установка соглашений ( TRUE=1) или чего-то еще, но наличие определенной позиции для изменения константы, безусловно, является одним из наиболее заметных.

Anoe
источник