Это плохая практика для хранения определенных значений в виде строк?

19

Это очень расплывчатое название, но я не мог придумать лучшего способа выразить это. Но, в качестве примера, подумайте о направлении, в котором движется персонаж в игре. Просто кажется неправильным использовать строку, а затем делать что-то подобное if(character.direction == "left"). Мне кажется , что она оставляет слишком много места для глупых ошибок, вроде случайного использования Leftили lили что - то вместо left. Мои подозрения верны? Если да, то каков предпочтительный способ достижения чего-то подобного?

Cal7
источник
10
Вы имеете в виду, кроме перечислений, если ваш язык поддерживает те?
hoffmale
12
Вы имеете в виду Stringly Typed ?
RubberDuck
Некоторые языки не могут хранить значения, отличные от строк, например bash.
Мувисиэль
не знаю почему, но я вспомнил «определить» в C. Вы можете сделать такие вещи, как: #define false true
linuxunil

Ответы:

28

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

public enum Direction {
    LEFT,
    RIGHT,
    UP,
    DOWN
}
Maybe_Factor
источник
2
Этот ответ не способствует пониманию проблемы ОП; Я не вижу здесь никаких рассуждений, только мнение, ограниченное по объему языками с enumтакими классными, как java. Многие языки (например, VBA) имеют enum, но это, возможно, не лучший вариант, так как в нем отсутствует такая же перспектива на будущее. См. Мой ответ для некоторого обсуждения того, почему enumодно это неполное, часто хрупкое и специфичное для языка решение.
sqykly
12

Страшная практика - использовать буквенные строки (или магические числа) в коде.

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

const string LEFT = "left"
if (someThing == LEFT) { doStuff(); }

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

Вот один быстрый реф, я уверен, что есть еще сотни: /programming/47882/what-is-a-magic-number-and-why-is-it-bad

jleach
источник
1
Проблема, с которой я столкнулся при таком подходе, заключается в том, что вы получаете опечатки типа --const string LEFT = "letf", тогда как если вы работаете с перечислениями, которые обнаруживаются во время компиляции.
Питер Б
@PieterB - я думал, что вопрос OP был достаточно широким, а пример для «левого» - просто произвольный пример - где все случаи не соответствуют перечислению. Я согласен, что для набора направлений, в частности, enum является лучшим выбором, но мой ответ должен был быть более общим в том смысле, что «если поместить его в литерал, по крайней мере, сделать его константой» (перечисления, конечно, будучи тип константы)
jleach
3
Я имел удовольствие работать с приложением, в котором дни выходных назначаются днями, названия которых начинаются с "s". Очень удивлены они тем, что когда они интернационализировали приложение, у Франции был только один выходной день.
Питер Б
4
Назовите это микрооптимизацией, но некоторые языки могут интерпретировать это как сравнение значений и тратить много времени на проверку каждого символа в строке. Еще более вероятно, если вы сделаете, как сделал аскер, и сравните его с жестко закодированной строкой «слева». Если постоянное значение не обязательно должно быть строкой (то есть вы не печатаете его), лучше использовать вместо этого const int.
Devsman
4

Короткий ответ: Да, строки не идеальны для какой-либо задачи, кроме хранения и доступа к последовательности текстовых символов, и даже если лежащие в основе биты абстракции являются строками, есть преимущество ссылаться на них как на переменные или константы .

Длинный ответ: большинство языков предлагают типы, которые ближе к области вашей проблемы, и даже если они этого не делают, у них, вероятно, есть какой-то метод, с помощью которого вы можете определить leftкак объект, отличный от right(и т. Д. И т. Д.). Превращение в строку с помощью"left" просто лишает языка и полезной функциональности IDE, такой как проверка ошибок. Даже если вам придется использовать

left = "left";

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

if (player.direction == Left)

будет обнаружен как ошибка во время компиляции (в лучшем случае, java и т. д.) или будет помечен любой IDE, которая стоит своих битов как неправильная (в худшем случае, базовая и т. д.).

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

Тип, который вы действительно хотите, относится к вашему домену. Что ваш код планирует делать с вашим left? Возможно, вы захотите отразить ось x текстуры или спрайта, которые направлены влево или движутся вперед; если это так, вы можете создать leftобъект со свойством или методом, отражающим это поведение, при этом ваш rightобъект будет иметь противоположный результат без зеркального отображения. Вы можете сделать эту логику в объекте, который представляет спрайты или текстуры, и в этом случае вы снова будете страдать за использование "left"вместо leftпричин, указанных выше.

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

sqykly
источник
2

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

Каждый язык имеет различные базовые типы данных, и вы должны использовать тот, который наиболее подходит для задачи. Чтобы использовать ваш пример, вы можете использовать числовые константы для представления различных состояний. Таким образом, left может иметь значение ноль, а right будет единица. Помимо упомянутой причины, числовые значения занимают меньше места (памяти), и всегда быстрее сравнивать числа, чем сравнивать несколько строк символов.

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

Nagev
источник
Скорость и память вряд ли имеют значение. Тем не менее, предпочитайте перечисления или константы. Строки имеют больше смысла, если данные поступают из текстового формата, например, XML, но даже в этом случае должны быть согласованы со всеми заглавными буквами или со всеми заглавными буквами. Или всегда конвертировать , например, if (uppercase(foo) == "LEFT")
user949300
2
@ user949300 Преобразование строк в верхний или нижний регистр также ненадежно. Существует вероятность того, что кто-то может решить посетить другой регион (или расширить там свой бизнес), например, Турцию , и нарушить сравнение строк в верхнем или нижнем регистре.
8bittree
Забота о скорости и памяти всегда хорошая практика, они не бесконечные ресурсы. Хотя вполне нормально сознательно жертвовать им ради читабельности или какого-то другого компромисса, что здесь не так. Но невнимание к ним может легко привести к вялым или расточительным приложениям.
Нагев
Сравнение строк, вероятно, будет в 50 раз медленнее, чем сравнение перечислений. Большую часть времени это не имеет значения. Но если ваш код уже немного медленный, эта разница может сделать его неприемлемым. Конечно, более важным является тот факт, что компилятор не может помочь вам с орфографическими ошибками, если вы используете строки.
gnasher729
1

Просто используйте тип данных, который имеет смысл в вашей ситуации.

Использование stringтипа в качестве базовой связи между приложениями не является проблемой. На самом деле, иногда предпочтительнее использовать строки: если у вас есть открытый API, может быть желательно, чтобы значения, входящие и выходящие из API, были удобочитаемыми. Это облегчает изучение и отладку людьми, использующими ваш API.

Настоящая проблема с вашим кодом заключается в повторении значения.

Не делай этого:

if (direction == 'left') {
  goLeft();
}

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

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

Это означает, что LEFTдолжно быть назначено один раз и только один раз в вашем приложении. И остальная часть вашего кода должна использовать эти перечисления или константы:

const string LEFT = "left";

if (direction == LEFT) {
  goLeft();
}

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

svidgen
источник
1

Это плохая практика?

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

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

  • enumТипа было бы предпочтительнее, если ваш язык программирования поддерживает это.
  • Иначе, именованные целочисленные константы - это путь, с единственным определением констант.

Но ответ может быть другим, если набор значений является динамически изменяемым или должен развиваться с течением времени. В большинстве языков изменениеenum типа (например, для добавления или удаления значения) требует полной перекомпиляции. (Например, удаление значения перечисления в Java нарушает двоичную совместимость.) И то же самое относится к именованным целочисленным константам.

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


Если вы реализуете на Java, character.direction == "left"это плохая практика по другой причине. Вы не должны использовать ==для сравнения строк, потому что это проверка идентичности объекта, а не равенство строк. (Это сработало бы, если бы вы могли гарантировать, что все экземпляры "left"строки были интернированы, но это требует анализа всего приложения.)

Стивен С
источник
1
То, что кажется фиксированным навсегда, часто оказывается совсем не так. Четыре направления могут, например, стать восемь.
Джимми Т.
@JimmyT. - Это зависит от контекста. Например, в игре изменение количества направлений, которые персонаж может передвигать с 4 до 8, повлечет за собой полный пересмотр правил игры и кода, который реализует правила. Использование Stringдля представления направлений приведет к практически нулевой разнице в объеме работ, необходимых для внесения изменений. Более разумный подход состоит в том, чтобы предположить, что количество направлений не изменится, и признать, что вам придется проделать большую работу в любом случае, если правила игры так кардинально изменились.
Стивен С
@JimmyT - Полностью согласен, что я видел, как это происходило много раз, вы также получаете от разработчиков короткий путь переопределения строки, так, например, product = "Option" становится product = "Option_or_Future", полностью унижая смысл кода.
Крис Милберн
-3

Как и предполагалось, вы должны использовать Enums. Тем не менее, просто использование Enum в качестве замены для Strigns не является достаточным ИМХО. В вашем конкретном примере скорость игрока на самом деле должна быть вектором с помощью физики:

class Vector {
  final double x;
  final double y;
}

Этот вектор должен затем использоваться для обновления позиции игрока каждый тик.

Затем вы можете объединить это с перечислением для большей читабельности:

enum Direction {
  UP(new Vector(0, 1)),
  RIGHT(new Vector(1, 0)),
  DOWN(new Vector(0, -1)),
  LEFT(new Vector(-1, 0));

  private Vector direction;

  Direction(Vector v) {
    this.direction = v;
  }

  public Vector getVector() { return this.direction; }
}
marstato
источник
4
-1 Ты слишком далеко заходишь в пример, который он дает.
Питер Б