Чи коли-небудь гарна ідея твердого коду значень у наших додатках?


45

Чи коли-небудь гарна ідея твердого коду значень у наших додатках? Або завжди правильно називати ці типи значень динамічно, якщо вони потребують змін?


2
Параметр config допоможе вам
Gopi,

53
Ніколи не знаєш, коли значення piможе змінитися ...
Гейб,

12
Людина, я думаю, такі люди, як @gabe, є причиною цього "Правила". Якщо ви повторите 3.14 в 20 місцях свого коду і виявите, що вам насправді потрібна більша точність, ви накрутитеся. Я не усвідомлював, що це не очевидно.
Білл К

17
Це було трохи грубо, @Bill. @Gabe явно жартував, але окрім цього, питання полягало у жорсткому кодуванні та конфігураційних параметрах, а не у використанні постійних проти повторних магічних чисел у кількох місцях.
Девід Конрад

1
Так, жорстке кодування іноді може бути хорошою ідеєю. Дивіться статтю у Вікіпедії про антидіаграму "Softcoding".
user16764

Відповіді:


64

Так, але зробіть це очевидним .

Зробіть:

  • використовувати константи
  • використовувати описову назву змінної

Не:


44
Що чистіше, diameter = 2 * radiusчи diameter = RADIUS_TO_DIAMETER_FACTOR * radius? Дійсно є кутові випадки, коли магічне число може бути кращим рішенням.
Joonas Pulakaka

5
Я не можу погодитися з цією відповіддю достатньо. Я схильний думати про програмування як про романіста. Ви розповідаєте свою історію за допомогою коду, і якщо люди не можуть зрозуміти логіку, це робить ваш код непотрібним, на мою думку. Ось чому добре продумані конвенції про іменування по суті є для читабельності. Також немає вагомих причин використовувати магічні числа. За допомогою магічних чисел ви виймаєте "чому" з рівняння і ускладнюєте його розуміння. Наприклад: "діаметр = радіус 2 *" Для чого це два? Цей "діаметр = RADIUS_TO_DIAMETER_FACTOR * радіус" має набагато більше сенсу.
chrisw

9
діаметр = 2 * радіус прямо від математики середньої школи. Причина того, що не називати «2», полягає в тому, що для того, щоб мати значення будь-чого іншого, знадобиться зміна законів фізики або математики, або обох. (З іншого боку, називання Pi або константою Планка є хорошим кроком для простого читання).
quick_now

8
@Joonas: Pfft. Звичайно, ти маєш на увазі diameter = radius << 1? Я гадаю, що це теж могло бути diameter = radius << RADIUS_TO_DIAMETER_BITS_TO_SHIFT.
Мураш

4
як битися з деякимиdiameter = radius.toDiameter()
Карсон Майєрс

26

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

Т.Л., д - р: Так, це іноді хороша ідея значень важко коду, але немає простого правила, щоб , коли ; це повністю залежить від контексту.

Питання звужує його до значень , які я вважаю магічними числами , але відповідь на те, чи є вони гарною ідеєю, стосується того, для чого вони насправді використовуються!

Кілька прикладів "жорстко закодованих" значень:

  • Значення конфігурації

    Я плачу, коли бачу подібні твердження command.Timeout = 600. Чому 600? Хто це вирішив? Це було раніше часу, і хтось підняв тайм-аут як хак, замість того, щоб виправити основні проблеми ефективності? Або це насправді якесь відоме і задокументоване очікування часу на обробку?

    Це не повинні бути магічні числа чи константи, їх слід екстерналізувати у файлі конфігурації чи базі даних десь із значущим ім'ям, оскільки їх оптимальне значення значною мірою або повністю визначається середовищем, у якому працює програма.

  • Математичні формули

    Формули, як правило, досить статичні, так що природа постійних значень всередині насправді не особливо важлива. Об'єм піраміди становить (1/3) b * h. Чи нас хвилює, звідки взявся 1 чи 3? Не зовсім. Попередній коментатор справедливо зазначив, що diameter = radius * 2це, мабуть, краще, ніж diameter = radius * RADIUS_TO_DIAMETER_CONVERSION_FACTOR- але це хибна дихотомія.

    Що ви повинні робити для цього типу сценаріїв - це створити функцію . Мені не потрібно знати, як ви придумали формулу, але мені все одно потрібно знати, що це . Якщо замість будь-якої дурниці, написаної вище, я пишу, volume = GetVolumeOfPyramid(base, height)то раптом усе стає набагато зрозуміліше, і цілком нормально мати магічні числа всередині функції ( return base * height / 3), оскільки очевидно, що вони просто частина формули.

    Ключовим тут є, звичайно, короткі та прості функції. Це не працює для функцій з 10 аргументами та 30 рядками обчислень. У цьому випадку використовуйте функціональний склад або константи.

  • Правила домену / бізнесу

    Ця завжди є сірою зоною, оскільки це залежить від того, яке саме значення має. Здебільшого саме ці магічні числа є кандидатами на перетворення на константи, оскільки це полегшує розуміння програми, не ускладнюючи логіку програми. Розглянемо тест if Age < 19порівняно if Age < LegalDrinkingAge; ви, напевно, можете зрозуміти, що відбувається без постійного, але це описується легше з описовою назвою.

    Вони також можуть стати кандидатами на абстракцію функцій, наприклад function isLegalDrinkingAge(age) { return age >= 19 }. Єдине, що часто ваша логіка бізнесу набагато складніша за це, і, можливо, не має сенсу починати виписувати десятки функцій з 20-30 параметрами кожна. Якщо немає чіткої абстракції на основі об'єктів та / або функцій, тоді вдаються до констант.

    Застереження - якщо ви працюєте в податковому департаменті, це стає насправді, справді обтяжливим і чесно безглуздим AttachForm(FORM_CODE_FOR_SINGLE_TAXPAYER_FILING_JOINTLY_FOR_DEPRECIATION_ON_ARMPIT_HAIR). Ви не збираєтесь цього робити, ви збираєтесь, AttachForm("B-46")тому що кожен розробник, який коли-небудь працював або коли-небудь буде працювати там, дізнається, що "B-46" - це код форми для одного платника податків, який подає бла-бла-бла - коди форм є частиною самого домену, вони ніколи не змінюються, тому вони насправді не магічні числа.

    Отже, ви повинні використовувати константи в мірі ділової логіки; в основному ви повинні зрозуміти, чи є це "магічне число" насправді магічним числом чи це добре відомий аспект домену. Якщо це домен, тоді ви не вводите програмний код, якщо тільки не існує справжнього шансу, що він зміниться.

  • Коди помилок та прапори статусу

    Це ніколи не підходить для жорсткого коду, як Previous action failed due to error code 46може сказати вам будь-який бідний ублюдок, який коли-небудь потрапляв із кані. Якщо ваша мова підтримує це, вам слід використовувати тип перерахування. В іншому випадку, як правило, у вас є весь файл / модуль, повний констант із зазначенням дійсних значень для певного типу помилок.

    Ніколи не дозволяйте мені бачити return 42в обробці помилок, капіче? Не може бути ніяких виправдань.

Я, мабуть, залишив кілька сценаріїв, але думаю, що охоплює більшість із них.

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


Дякую за гарну розбивку! - Більшість людей не продумує всі параметри, які я додав би "Конфігурація середовища" - Я думаю, цього слід уникати (не жорстко закодовано), оскільки більшість даних мають бути розміщені у конфігураційному файлі чи базі даних. Це випливає з принципу "зберігання даних та логіки окремо", що є опорою для MVC або MVVM. рядок TestServerVar = "foo"; рядок ProdServerVal = "бар";
m1m1k

7

Існують різні причини присвоєння ідентифікатора номеру.

  • Якщо число може змінитися, воно повинно мати ідентифікатор. Набагато простіше знайти NUMBER_OF_PLANETS, ніж шукати кожен екземпляр з 9 і розглянути, чи слід його змінити на 8. (Зверніть увагу, що видимі користувачеві рядки, можливо, доведеться змінити, якщо програмне забезпечення колись потрібно використовувати іншою мовою, і це важко передбачити заздалегідь.)
  • Якщо число важко будь-яким способом набрати. Для констант, таких як pi, краще дати одне визначення максимальної точності, ніж повторно вводити його в декількох місцях, можливо, неточно.
  • Якщо число трапляється в різних місцях. Вам не доведеться дивитись на два способи використання 45 у суміжних функціях і дивуватися, чи означають вони те саме.
  • Якщо значення не можна впізнати миттєво. Можна припустити, що всі знають, що таке 3.14159265 ... Небезпечно припустити, що всі розпізнають гравітаційну константу або навіть pi / 2. ("Тут усі" залежать від характеру програмного забезпечення. Системні програмісти можуть знати октальне представлення дозволів бітів Unix тощо). У програмному забезпеченні морської / морської архітектури перевіряючи число запропонованого корпусу та швидкість Froude на подивіться, чи 1,1 або більше, може бути абсолютно зрозумілим для всіх, хто повинен над цим працювати.)
  • Якщо контекст не впізнаваний Всім відомо, що за годину йде 60 хвилин, але множення або ділення на 60 може бути незрозумілим, якщо немає негайних вказівок, що кількість - це часова величина або величина ставки.

Це дає нам критерії для жорсткого кодування літералів. Вони повинні бути незмінними, не важкими для набору, що виникають лише в одному місці чи контексті, і мають впізнаваний зміст. Немає сенсу визначати 0 як, наприклад, ARRAY_BEGINNING, або 1 як ARRAY_INCREMENT.


5

Як доповнення до інших відповідей. Використовуйте константи для рядків, коли це можливо. Звичайно, ви цього не хочете

const string server_var="server_var";

але ти повинен був

const string MySelectQuery="select * from mytable;";

(припустимо, що ви насправді маєте запит, де завжди потрібно отримати всі результати з певної таблиці)

Крім цього, використовуйте константи для будь-якого числа, крім 0 (зазвичай). Якщо вам потрібна бітова маска дозволу 255, не використовуйте

const int 8th_bit=255; //or some other obscure naming scheme that equates to 255.

замість цього використовувати

const int AllowGlobalRead=255;

Звичайно, поряд з константами, знайте, коли використовувати нумератори. Наведений вище випадок, ймовірно, добре впишеться в один.


typedef enum {state_0 = 0, state_1 = 1, state_2 = 2, ...} ... Не смійся, я це бачив. Пощипайте цю людину навколо голови мокрою рибою!
quick_now

@quickly добре, звичайно, ви хочете чогось більш схожогоtypedef enum {init_state=0, parse_state=1, evaluation_state=2, ... }
Earlz

6
THIS_NAMING_CONVENTION_IS_RECOMMENDED_FOR_CONSTANTS
StuperUser

4
Для рядків не потрібно просто константи. Ви хочете помістити будь-які видимі користувачу рядки в якийсь файл ресурсу (деталі залежать від вашої платформи), щоб ви могли легко змінити іншу мову.
Девід Торнлі

Можливо, ви також хочете вставити рядки, пов’язані з бізнес-логікою (наприклад, SQL-запити) у файл ресурсу з певним шифруванням чи обфускацією. Це запобіжить "допитливим" користувачам зворотну інженерію вашої логіки (або схеми бази даних).
TMN

4

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

У будь-яких розумних рамках дуже багато речей, і вони працюють. тобто немає жодної технічної причини, чому я не зміг би змінити точку входу додатка C # (статичний недійсний Main), але жорстке кодування, яке не створює жодних проблем жодному користувачеві (крім випадкових запитань SO )

Я використовую правило, що все, що може і зміниться, не впливаючи на стан всієї системи, повинно бути заплутаним.

Отже, ІМХО, нерозумно не твердо кодувати речі, які ніколи не змінюються (пі, гравітаційна константа, константа в математичній формулі - думай об'єм сфери).

Також нерозумно не твердо кодувати речі чи процеси, які матимуть вплив на вашу систему, що потребуватиме програмування в будь-якому випадку, тобто марно дозволяти користувачеві додавати динамічні поля до форми, якщо будь-яке додане поле вимагатиме від розробника технічного обслуговування увійдіть і напишіть якийсь сценарій, який змусить цю справу працювати. Крім того, нерозумно (і я це бачив кілька разів у корпоративному середовищі) створювати якийсь інструмент конфігурації, тому нічого не важко закодовано, але тільки ним можуть користуватися лише розробники в ІТ-відділі, і користуватися ним лише трохи легше, ніж зробити це у Visual Studio.

Отже, підсумок, чи має бути рівнокодована річ, є функцією двох змінних:

  • зміниться значення
  • як зміна значення вплине на систему

4

Чи коли-небудь гарна ідея твердого коду значень у наших додатках?

Значення жорсткого коду, тільки якщо значення вказані в Специфікації (під час остаточного випуску специфікації), наприклад, відповідь HTTP OK завжди буде 200(якщо вона не зміниться в RFC), так що ви побачите (у деяких моїх кодах ) константи типу:

public static final int HTTP_OK = 200;

В іншому випадку я зберігаю константи у файлі властивостей.

Причина, чому я вказав специфікації, полягає в тому, що зміна констант в специфікаціях вимагає управління змінами, в якому зацікавлені сторони переглянуть зміни та затвердять / не схвалюють. Це ніколи не відбувається за ніч, і на затвердження потрібні місяці / роки. Не забувайте, що багато розробників використовують технічні характеристики (наприклад, HTTP), тому змінити це означає зламати мільйони систем.


3
  • якщо значення може змінюватися і дійсно може змінюватися, тоді програмно кодуйте його, коли це можливо, доки зусилля, що вкладаються, не перевищують очікуваного повернення
  • деякі значення не можуть бути м'яко кодованими; дотримуйтесь вказівок Джонатана в тих (рідкісних) випадках

2

Я помітив, що щоразу, коли ви можете отримати дані зі свого коду, це покращує те, що залишилося. Ви починаєте помічати нові рефактори та вдосконалювати цілі розділи коду.

Це просто гарна ідея працювати над вилученням констант, не вважайте це дурним правилом, подумайте про це як про можливість кодування краще.

Найбільшою перевагою було б те, як ви можете знайти подібні константи, є єдиною різницею в групах коду - абстрагування їх у масивах допомогло мені зменшити деякі файли на 90% від їх розміру та виправити досить багато помилок копіювання та вставки. .

Я ще не бачив єдиної переваги в тому, щоб не витягувати дані.


2

Нещодавно я зашифрував функцію MySQL, щоб правильно обчислити відстань між двома парами lat / long. Ви не можете просто робити піфагор; Лінії довготи зближуються, коли широта збільшується до полюсів, отже, тут задіяний якийсь волохатий триггер. Справа в тому, що я був дуже розчулений, чи варто жорстко кодувати значення, що представляє радіус Землі в милях.

Я в кінцевому підсумку це робив, хоча факт: lat / lng лінії набагато ближче один до одного, скажімо, на Місяці. І моя функція різко занижує відстані між точками на Юпітері. Я подумав, що шанси на веб-сайт, який я будую, має місце позаземного розташування, щоб увійти, досить малий.


Так, напевно, але що робити з google.com/moon
Residuum

1

Ну, це залежить, якщо ваша мова складена. Якщо це не складено, це не велика справа, ви просто відредагуйте вихідний код, навіть якщо він буде делікатним для непрограміста.

Якщо ви програмуєте на компільованій мові, це, очевидно, не є гарною ідеєю, оскільки якщо змінні змінюються, вам доведеться перекомпілювати, що є великою витратою часу, якщо ви хочете скорегувати цю змінну.

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

Наприклад, з моїм проектом ogre, я завжди використовую клас ConfigFile для завантаження змінної, яку я записав у конфігураційний файл.


1

Два випадки, коли константи (на мій погляд, принаймні) ОК:

  1. Константи, що стосуються нічого іншого; ви можете змінювати ці константи коли завгодно, не змінюючи нічого іншого. Приклад: Ширина стовпця сітки за замовчуванням.

  2. Абсолютно незмінні, точні, очевидні константи, типу "кількість днів на тиждень". days = weeks * 7Заміна 7на постійну DAYS_PER_WEEKнавряд чи дає якесь значення.


0

Я повністю погоджуюся з Джонатаном, але, як всі правила, є винятки ...

"Магічне число в специфікації: Магічне число в коді"

В основному зазначається, що будь-які магічні числа, які залишаються в специфікації після розумних спроб отримати для них описовий контекст, повинні бути відображені як такі в коді. Якщо магічні числа залишаються в коді, слід докласти всіх зусиль, щоб виділити їх і чітко пов'язати їх з точкою походження.

Я виконав декілька договорів взаємодії, де потрібно заповнити повідомлення значеннями, відображеними з бази даних. У більшості випадків картографування є досить прямим уперед та підходитиме до загальних керівних ліній Джонатана, але я стикався з випадками, коли цільова структура повідомлення була просто жахливою. Понад 80% значень, які мали бути передані в структурі, були константами, що застосовуються специфікацією віддаленої системи. це поєднується з тим, що структура повідомлення була загальноприйнятою, що багато таких констант довелося заповнювати. У більшості випадків вони не надавали сенсу чи причини, просто сказали "покласти М сюди" або "покласти сюди 4.10.53.10100.889450.4452". Я не намагався поставити коментар поруч із усіма ними, це зробило б отриманий код нечитабельним.

Це було сказано, коли ви думаєте про це ... це майже все, щоб зробити це очевидним ...


0

Якщо ви жорстко кодуєте значення гравітаційної константи Землі, ніхто не піде на це. Якщо ви жорстко кодуєте IP-адресу вашого проксі-сервера, у вас виникнуть проблеми.


1
Можливо, вам буде потрібна більш точна гравітаційна константа Землі, тому жорстке кодування її кілька разів може призвести до проблем.
користувач281377

1
Пітер Ніхто? Від пустельників Германа?
Девід Конрад

Гравітаційне прискорення на Землі становить майже 9,81 м / с ^ 2 для більшості широт і висот (звичайно, якщо ви шукаєте нафту під землею або знімаєте МБР над Північним полюсом, знаючи, що зміна сили тяжіння дуже важливо набагато більше десяткових знаків), причому прискорення гравітації на інших планетах мають інше число, але, наскільки я знаю, гравітаційна константа є постійною навколо Всесвіту. Існує багато фізики, яка мала б змінитися, якби g були змінними.
Tangurena

0

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

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