Чи погана практика зберігати певні значення як рядки?


19

Це дуже розпливчаста назва, але я не міг придумати кращого способу сказати це. Але, лише як приклад, подумайте про напрямок, в якому рухається персонаж у грі. Просто відчуває себе неправильно: використовуючи рядок, а потім робити подібні речі if(character.direction == "left"). Мені здається , що вона залишає занадто багато місця для дурних помилок, на кшталт випадкового використання Leftабо lчи що - то замість left. Чи правильні мої підозри? Якщо так, то який бажаний спосіб досягти чогось подібного?


10
ти маєш на увазі, окрім переліків, якщо твоя мова підтримує це?
hoffmale

12
Ви маєте на увазі Строго набране ?
RubberDuck


Деякі язики не в змозі зберігати , крім як рядки, наприклад , значення, bash.
mouviciel

не знаю чому, але я запам'ятав "визначити" у C. Ви можете робити такі речі, як: #define false true
linuxunil

Відповіді:


28

Якщо мова, якою ви користуєтеся, підтримує використання переліків, я б їх використовував. Це дозволяє обмежити кількість варіантів, доступних для даного типу. наприклад на Java:

public enum Direction {
    LEFT,
    RIGHT,
    UP,
    DOWN
}

2
Ця відповідь не сприяє розумінню проблеми ОП; Я не бачу тут жодних міркувань, лише думка, обмежена за обсягом мовами з класними enums, як 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


1
Проблема, яку я маю при такому підході, полягає в тому, що ви отримуєте помилки помилки типу --const string LEFT = "letf" - тоді як, якщо ви працюєте з перерахунками, які потрапляють під час компіляції.
Пітер Б

@PieterB - Я вважав, що питання щодо ОП є досить широким, і приклад для "лівого" - лише умовний приклад - де всі випадки не збігаються з перерахунком. Я погоджуюся, що для набору конкретних напрямків перерахунок є найкращим вибором, але моя відповідь мала на увазі бути більш загальною в тому, що "якщо ти введеш це в буквальне значення, принаймні зроби це постійним" (перераховує, звичайно, будучи типом константи)
jleach

3
Мені приємно працювати з додатком, який призначав дні вихідних, як ті дні, назва яких починається з "s". Дуже здивовані, що вони були з приводу "факту" того, що, коли вони інтернаціоналізували заявку, Франція мала лише одноденні вихідні.
Пітер Б

4
Назвіть це мікрооптимізацією, але деякі мови можуть інтерпретувати це як порівняння значень і витрачати багато часу на перевірку кожного символу в рядку. Ще більше ймовірності, якщо ви зробите так, як це зробив запитувач, і порівняйте з твердо кодованим рядком "зліва". Якщо немає необхідності, щоб постійне значення було рядком (тобто ви не друкуєте його), краще буде замість цього використовувати const int.
Девсман

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перш ніж розглянути варіант.


2

Ви не вказали свою мову, але щоб дати загальну відповідь, вам слід використовувати рядки, коли текст насправді потрібен, а не для того, щоб щось представляти. Приклад, який ви навели, наприклад, просто не був би прийнятним у виробничому програмному забезпеченні.

Кожна мова має різні основні типи даних, і ви повинні використовувати один, найбільш відповідний для виконання завдання. Щоб використовувати свій приклад, ви можете використовувати числові константи для представлення різних станів. Тож лівий міг мати значення нуля, а правий - один. Окрім причини, яку ви вказали, числові значення займають менше місця (пам'яті), і порівняти числа завжди швидше, ніж порівняти кілька рядків символів.

Як було запропоновано, використовуйте перелічення, якщо дозволяє ваша мова. У вашому конкретному прикладі це, безумовно, кращий вибір.


Швидкість і пам'ять навряд чи мають значення. Однак, віддайте перевагу перелікам або константам. Рядки мають більше сенсу, якщо дані надходять із текстового формату, наприклад, 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();
}

Це також дозволяє згодом легше змінити тип даних, який ви використовуєте для своїх вказівок, якщо ви захочете.


1

Це погана практика?

Можливо, так, але це залежить від того , що саме ви робите, а також від мови програмування, якою ви користуєтесь.

У вашому прикладі ми можемо зробити висновок про те, що існує невеликий і фіксований назавжди набір значень, який може прийняти напрямок. У цьому випадку немає жодних переваг у використанні рядка та певних недоліків. Для цього прикладу:

  • Типом enumбуло б краще, якщо ваша мова програмування підтримує це.
  • В іншому випадку названі цілі константи - це шлях, з єдиним визначенням для констант.

Але відповідь може бути різною, якщо набір значень динамічно змінюється або потребує розвитку з часом. У більшості мов для зміни enumтипу (наприклад, для додавання або видалення значення) потрібна повна перекомпіляція. (Наприклад, видалення значення enum у Java порушує двійкову сумісність.) І те ж саме стосується іменованих цілих констант.

У подібних сценаріях може знадобитися інше рішення, а використання рядків є одним із варіантів. (І ви можете комбінувати рядкові літерали та названі константи ... якщо сценарій тягне за собою нерухоме ядро ​​з динамічними розширеннями.)


Якщо ви реалізуєте в Java, character.direction == "left"це погана практика з іншої причини. Не слід використовувати ==для порівняння рядків, оскільки це тестування ідентичності об'єкта, а не рівність рядків. (Це спрацювало б, якщо ви могли гарантувати, що всі екземпляри "left"рядка були інтерновані, але для цього потрібен аналіз цілої програми.)


1
Те, що здається виправленим назавжди, часто виявляється таким, що не є таким виправленим. Наприклад, чотири напрямки можуть стати вісьмома.
Джиммі Т.

@JimmyT. - Це залежить від контексту. Наприклад, у грі зміна кількості напрямків, за якими персонаж може переходити з 4 на 8, спричинить за собою повний перегляд правил гри та коду, який реалізує правила. Використання Stringдля подання вказівок мало б різнитися з нульовою різницею щодо кількості роботи, яка займається внесенням змін. Більш розумний підхід - припустити, що кількість напрямків не зміниться, і визнати, що вам доведеться зробити багато роботи в будь-якому випадку, якби правила гри змінилися настільки принципово.
Stephen C

@JimmyT - Цілком погоджуюся: Я бачив це трапляється багато разів, ви також отримуєте розробники, які беруть скорочення для переосмислення рядка, наприклад, product = "Option" стає product = "Option_or_Future", що повністю знижує значення коду.
Кріс Мілберн

-3

Як було запропоновано, ви повинні використовувати Enums. Однак просто використання Enum в якості заміни Strigns не є достатнім IMHO. У вашому прикладі particualr, швидкість гравця фактично повинна бути вектором за допомогою фізики:

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

4
-1 Ви занадто далеко заїжджаєте в приклад, який він дає.
Пітер Б
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.