Являются ли жестко закодированные STRING когда-либо приемлемыми? - PullRequest
2 голосов
/ 28 января 2009

Похоже на Допустимы ли когда-нибудь жесткие литералы? , но я специально думаю о "магических строках" здесь.

В большом проекте у нас есть таблица параметров конфигурации:

Name         Value
----         -----
FOO_ENABLED  Y
BAR_ENABLED  N
...

(Сотни из них).

Обычной практикой является вызов универсальной функции для проверки такой опции:

if (config_options.value('FOO_ENABLED') == 'Y') ...

(Конечно, эту же опцию, возможно, нужно проверять во многих местах системного кода.)

При добавлении новой опции я подумывал добавить функцию, чтобы скрыть «волшебную строку», например:

if (config_options.foo_enabled()) ...

Однако коллеги думали, что я перешел за борт, и возражали против этого, предпочитая жесткое кодирование, потому что:

  • Это то, что мы обычно делаем
  • Это упрощает понимание того, что происходит при отладке кода

Беда в том, что я вижу их точку зрения! На самом деле, мы никогда не собираемся переименовывать опции по любой причине, поэтому единственное преимущество, которое я могу придумать для своей функции, заключается в том, что компилятор будет перехватывать любую опечатку, например, fo_enabled (), но не «FO_ENABLED».

Что ты думаешь? Я пропустил какие-либо другие преимущества / недостатки?

Ответы [ 9 ]

34 голосов
/ 28 января 2009

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

Если я дважды использую строку в коде, я рассмотрю , сделав ее постоянной.

Если я буду использовать строку три раза в коде, я почти наверняка сделаю ее константой.

9 голосов
/ 28 января 2009
if (config_options.isTrue('FOO_ENABLED')) {...
}

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

if (config_options.isFooEnabled()) {...
}

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

if (config_options.isTrue(ConfigKeys.FOO_ENABLED)) {...
}
6 голосов
/ 31 января 2011

Я понимаю, что вопрос старый, но он возник у меня на полях.

AFAIC, проблема здесь не была точно определена ни в вопросе, ни в ответах. Забудьте о «струнах кодирования» или нет, на мгновение.

  1. База данных имеет справочную таблицу, содержащую config_options. PK - это строка.

  2. Существует два типа ПК:

    • Значимые идентификаторы, которые пользователи (и разработчики) видят и используют. Эти PK должны быть стабильными, на них можно положиться.

    • Бессмысленные Id столбцы, которые пользователи никогда не должны видеть, о которых должны знать разработчики, и код вокруг. На них нельзя полагаться.

  3. Обычным, нормальным является написание кода с использованием абсолютного значения значащего PK IF CustomerCode = "IBM" ... или IF CountryCode = "AUS" и т. Д.

    • ссылка на абсолютное значение бессмысленного ПК недопустима (из-за автоинкремента; изменяются пробелы; значения заменяются оптом).
      ,
  4. Ваша справочная таблица использует значимые PK. Ссылка на эти буквенные строки в коде неизбежна. Сокрытие значения усложнит обслуживание; код больше не буквальный; ваши коллеги правы. Плюс есть дополнительная избыточная функция, которая жует циклы. Если в литерале есть опечатка, вы скоро обнаружите это во время тестирования Dev, задолго до UAT.

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

    • смысл в том, что попытка скрыть литерал не имеет значения.
      ,

  5. Это не может быть истолковано как «жесткое кодирование», это нечто совершенно иное. Я думаю, что именно в этом ваша проблема, идентифицируя эти конструкции как «жестко закодированные». Это просто ссылка на «Значение» в буквальном смысле.

  6. Теперь с точки зрения любого сегмента кода, если вы используете одно и то же значение несколько раз, вы можете улучшить код, захватив буквальную строку в переменной, а затем используя переменную в оставшейся части кодовый блок. Конечно, не функция. Но это вопрос эффективности и надлежащей практики. Даже это не меняет эффекта IF CountryCode = @cc_aus

5 голосов
/ 28 января 2009

По моему опыту, проблема такого рода маскирует более глубокую проблему: неспособность выполнить ООП и следовать принципу СУХОЙ.

В двух словах, запишите решение во время запуска соответствующим определением для каждого действия внутри операторов if, а затем отбросьте как config_options, так и тесты времени выполнения. 1007 *

Подробности ниже.

Пример использования:

if (config_options.value('FOO_ENABLED') == 'Y') ...

, который поднимает очевидный вопрос: «Что происходит в многоточии?», Особенно с учетом следующего утверждения:

(Конечно, эту же опцию, возможно, нужно проверять во многих местах системного кода.)

Предположим, что каждое из этих config_option значений действительно соответствует концепции одной проблемной области (или стратегии реализации).

Вместо этого (неоднократно, в разных местах кода):

  1. Взять строку (тег),
  2. Найдите соответствующую ему другую строку (значение),
  3. Проверьте это значение как логический эквивалент,
  4. На основании этого теста решить, следует ли выполнить какое-либо действие.

Я предлагаю инкапсулировать концепцию «настраиваемого действия».

Давайте возьмем в качестве примера (очевидно, такой же гипетический, как FOO_ENABLED ... ;-), что ваш код должен работать в английских или метрических единицах. Если METRIC_ENABLED равно «true», преобразуйте введенные пользователем данные из метрики в английский для внутренних вычислений и выполните обратное преобразование до отображения результатов.

Определить интерфейс:

public interface MetricConverter {
    double toInches(double length);
    double toCentimeters(double length);
    double toPounds(double weight);
    double toKilograms(double weight);
}

, который идентифицирует в одном месте все поведение, связанное с понятием METRIC_ENABLED.

Затем напишите конкретные реализации всех способов выполнения этих действий:

public class NullConv implements MetricConverter {
    double toInches(double length) {return length;}
    double toCentimeters(double length) {return length;}
    double toPounds(double weight)  {return weight;}
    double toKilograms(double weight)  {return weight;}
}
* * И тысяча сорок-девять
// lame implementation, just for illustration!!!!
public class MetricConv implements MetricConverter {
    public static final double LBS_PER_KG = 2.2D;
    public static final double CM_PER_IN = 2.54D
    double toInches(double length) {return length * CM_PER_IN;}
    double toCentimeters(double length) {return length / CM_PER_IN;}
    double toPounds(double weight)  {return weight * LBS_PER_KG;}
    double toKilograms(double weight)  {return weight / LBS_PER_KG;}
}

Во время запуска вместо загрузки набора config_options значений инициализируйте набор настраиваемых действий, например:

MetricConverter converter = (metricOption()) ? new MetricConv() : new NullConv();

(где приведенное выше выражение metricOption() является заменой для любой одноразовой проверки, которую вам нужно выполнить, включая просмотр значения METRIC_ENABLED; -)

Тогда везде, где код сказал бы:

double length = getLengthFromGui();
if (config_options.value('METRIC_ENABLED') == 'Y') {
    length = length / 2.54D;
}
// do some computation to produce result
// ...
if (config_options.value('METRIC_ENABLED') == 'Y') {
    result = result * 2.54D;
}
displayResultingLengthOnGui(result);

переписать его как:

double length = converter.toInches(getLengthFromGui());
// do some computation to produce result
// ...
displayResultingLengthOnGui(converter.toCentimeters(result));

Поскольку все детали реализации, относящиеся к этой единой концепции, теперь аккуратно упакованы, все последующее обслуживание, связанное с METRIC_ENABLED, может выполняться в одном месте. Кроме того, компромисс во время выполнения - это победа; «накладные расходы» на вызов метода тривиальны по сравнению с накладными расходами на извлечение значения String из Map и выполнение String # equals.

4 голосов
/ 28 января 2009

Я действительно должен использовать константы, а не жестко закодированные литералы.

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

4 голосов
/ 28 января 2009

Я полагаю, что две причины, о которых вы упомянули: возможная ошибка в строке, которая не может быть обнаружена до времени выполнения, и возможность (хотя и небольшая) изменения имени оправдают вашу идею.

Кроме того, вы можете получить типизированные функции, теперь кажется, что вы храните только логические значения, что если вам нужно хранить int, строку и т. Д. Я бы лучше использовал get_foo () с типом, чем get_string ("FOO ") или get_int (" FOO ").

3 голосов
/ 28 января 2009

Я думаю, что здесь есть две разные проблемы:

  • В текущем проекте соглашение об использовании жестко закодированных строк уже хорошо известно, поэтому все разработчики, работающие над проектом, знакомы с ним. Это может быть неоптимальным соглашением по всем перечисленным причинам, но каждый, кто знаком с кодом, может посмотреть на него и инстинктивно знает, что должен делать код. Изменение кода таким образом, чтобы в определенных частях он использовал «новую» функциональность, сделает код немного сложнее для чтения (потому что людям придется думать и помнить, что делает новое соглашение) и, следовательно, немного сложнее поддерживать. Но я полагаю, что переход на весь проект к новому соглашению может оказаться чрезмерно дорогим, если вы не сможете быстро написать сценарий преобразования.
  • В новом проекте символические константы являются способом IMO по всем перечисленным причинам. Особенно , потому что все, что заставляет компилятор ловить ошибки во время компиляции, которые в противном случае были бы обнаружены человеком во время выполнения, является очень полезным соглашением для установления.
1 голос
/ 11 сентября 2013

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

const string HELLO_WORLD = "Hello world!";
print(HELLO_WORLD);

Цель программиста ясна. Использование константы подразумевает, что эту строку не нужно локализовать. Теперь посмотрите на этот пример:

print("Hello world!");

Здесь мы не так уверены. Действительно ли программист не хотел, чтобы эта строка была локализована, или он забыл о локализации, когда писал этот код?

1 голос
/ 28 января 2009

Я тоже предпочитаю строго типизированный класс конфигурации, если он используется в коде. С правильно названными методами вы не потеряете читабельность. Если вам нужно выполнить преобразование из строк в другой тип данных (decimal / float / int), вам не нужно повторять код, который выполняет преобразование в нескольких местах и ​​может кэшировать результат, чтобы преобразование происходило только один раз. У вас уже есть основа для этого, поэтому я не думаю, что потребуется много времени, чтобы привыкнуть к новому способу ведения дел.

...