Чи всі магічні числа створені однаковими?


77

У недавньому проекті мені потрібно було перетворити з байтів на кілобайт кібібайт . Код був досить простим:

var kBval = byteVal / 1024;

Після написання цього запитання я отримав решту функції, яка працювала і продовжувала роботу.

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

Тож чи справді загальновідомі константи є чимось магічним чи ні?


Пов’язані запитання:

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

Усунення магічних чисел: коли настав час сказати "ні"? також пов'язаний, але орієнтований на рефакторинг на відміну від того, чи є постійне число магічним числом чи ні.


17
Я на насправді працював над проектом , де вони створили константи , як, FOUR_HUNDRED_FOUR = 404. Я працював над іншим проектом, де вони войовничо використовували постійні рядки замість літералів, тому у них було десятки рядків у коді, який виглядав так,DATABASE = "database"
Роб

82
Однозначно використовуйте 1024, тому що в іншому випадку ваша команда розробників витратить весь свій час, сперечаючись про те, що це "кілобайт" або "кібібайт".
Стівен Бернап

6
Ви можете вважати, що 1024 - це кібі, а #define KIBI1024 - MEBIяк 1024 * 1024…
ysdx

6
@Rob Y: схоже на старих добрих програмістів Fortran. Тому що ця мова програмування змусила програмістів до цього. Так, там ви побачите константи, як, ZERO=0, ONE=1, TWO=2і коли програми переносяться на інші мови (або програмісти не змінюють поведінку при переключенні своєї мови), ви також побачите це, і вам доведеться молитися, щоб ніколи хтось не змінив його на ONE=2
Холгер

4
@NoctisSkytower Моя команда вважає за краще використовувати оператори явного поділу замість операторів, що змінюють біт, через кілька мов, якими ми користуємось, та потенційно непослідовна реалізація цих мов. Аналогічно, негативні значення непослідовно обробляються з побітним зміщенням. Незважаючи на те, що ми не можемо мати негативні значення байтів, ми, безумовно, маємо негативні значення з іншими одиницями вимірювання, які ми перетворюємо.

Відповіді:


103

Не всі магічні числа однакові.

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

Сховатись 1024 за BYTES_PER_KBYTE також означає, що ви не бачите миттєво, правильно чи ні.

Я би сподівався, що хтось одразу дізнається, чому це значення 1024. З іншого боку, якби ви перетворювали байти в мегабайти, я визначив би константу BYTES_PER_MBYTE або подібну, оскільки константа 1,048,576 не така очевидна, що її 1024 ^ 2, або що це навіть правильно.

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

// Value must be less than 3.5 volts according to spec blah.
SomeTest = DataSample < 3.50

Я вважаю, що краще, ніж

SomeTest = DataSample < SOME_THRESHOLD_VALUE

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


67
"Проблема з магічними числами полягає в тому, коли вони магічні" - Це таке блискуче пояснення цієї концепції! Я серйозно! +1 лише для цього речення.
Йорг W Міттаг

20
Ось я тільки що придумав: "проблема не в кількості, це в магії".
Йорг W Міттаг

10
1024 очевидно, кому? Хіба це не виправдання кожного магічного числа? Всі магічні числа використовуються, оскільки вони очевидні для того, хто їх написав. Чи 9.8 також очевидно? Для мене досить очевидно, що це прискорення сили тяжіння на землі, але я все ж створив би константу, тому що те, що для мене очевидно, може бути очевидним для когось іншого.
Tulains Córdova

15
Ні. Коментар, як той у вашому "кращому" прикладі - це масивний червоний прапор. Це код, який навіть не проходить перевірку читабельності того, хто його пише. Наведу приклад. e^i*pi = -1набагато чіткіше (краще), ніж 2.718^i*3.142 = -1. Змінні мають значення і вони не просто для загального коду. Код пишеться для читання першого, складання другого. Також характеристики змінюються (сильно). Хоча 1024, мабуть, не повинен бути налаштований на 3,5 звуків, як це має бути.
Натан Купер

51
Я також не використовував би константу для 1024 ^ 2; 1024*1024плз!
Гонки легкості на орбіті

44

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

Чи має номер ім’я?

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

Зрозуміло, що константи, такі як pi, e, та ін. мають значущі імена. Таке значення, як 1024, може бути, BYTES_PER_KBале я б також очікував, що будь-який розробник буде знати, що означає 1024. Цільова аудиторія для вихідного коду - це професійні програмісти, які повинні мати знання про різні повноваження двох та чому вони використовуються.

Чи використовується в декількох місцях?

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

Твоє запитання

У випадку вашого запитання я б використовував число як є.

Ім'я: є ім'я для цього номера, але це нічого насправді корисного. Він не представляє математичну константу або значення, вказане в будь-якому документі про вимоги.

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


1
Причина використання констант замість магічних чисел не лише в тому, що зазначені числа будуть змінюватися, це також у читанні та самодокументації.
Tulains Córdova

4
@ user61852: названі константи не завжди є легшими для читання. Вони часто є, але не завжди.
whatsisname

2
Особисто я замість цього використовую ці два запитання: "Чи зміниться це значення протягом життя програми?" і "Чи зрозуміють розробники, які я очікую, що працюють над цим програмним забезпеченням, для чого це число?"
Стівен Бернап

4
Ви маєте на увазі проблему Y2K? Я не впевнений, що це актуально тут. Так, було багато коду типу "дата - 1900", але в цьому коді проблема не була в магічному номері "1900".
Стівен Бернап

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

27

Ця цитата

Справа не в кількості, це в магії.

як сказав по Йорг W Миттаг відповідає на це питання досить добре.

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

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

Аналогічно, приклад:

var numDays = numHours / 24; 

однаково ясна і не магічна, тому що добре відомо, що в день є 24 години.


21
Але ... але ... 24 може змінитися! Земля сповільнює своє обертання і, врешті-решт, матиме 25 годин! (Звичайно, ми до цього часу всі будемо мертві, зробивши технічне обслуговування цього програмного забезпечення чужою проблемою)

14
Що відбувається після розгортання вашого програмного забезпечення на Марсі? Ви повинні вводити цю постійну ...
durron597

8
@ durron597: що робити, якщо ваша програма працює досить довго, щоб Земля сповільнилася протягом цього часу . Ви не повинні вводити константу, а функцію, яка приймає позначку часу (за замовчуванням зараз) і повертає кількість годин у день, коли часова мітка падає ;-)
Стів Джессоп

13
Я повинен буде вивчити ЯГНІ.
whatsisname

3
@ durron597 Нічого особливого не відбувається, коли на Марсі розгорнуто ваше програмне забезпечення хронометражу, оскільки, за умовами, Марсові дні тривають 24 години, але кожна година на 2,7% довша, ніж на Землі . Звичайно, ані земний день, ані сонячний день Землі - це точно 24 години (точні цифри є на тій самій сторінці), тож ви не можете використовувати 24 все одно! Як згадував Ізката, стрибні секунди боляче. Можливо, вам би пощастило насправді, використовуючи константу 24на Марсі, ніж на Землі!
CVn

16

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

кілобайт кібібайт

Тому я вже знаю, що автор є або був розгублений. Сторінка Вікіпедії додає плутанини:

1000 = KB kilobyte (metric)
1024 = kB kilobyte (JEDEC)
1024 = KiB kibibyte (IEC)

Отже, "Кілобайт" може означати як коефіцієнт 1000 і 1024, з єдиною різницею скорочень є капіталізація значення "k". Крім того, 1024 може означати кілобайт (JEDEC) або кібібайт (IEC). Чому б не зруйнувати всю цю плутанину прямо з постійною зі змістовною назвою? До речі, ця нитка часто використовує "BYTES_PER_KBYTE", і це не менш неоднозначно. KBYTE: це KIBIBYTE чи KILOBYTE? Я вважаю за краще ігнорувати JEDEC і мати BYTES_PER_KILOBYTE = 1000і BYTES_PER_KIBIBYTE = 1024. Більше плутанини більше немає.

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

Якщо я бачу:

int BYTES_PER_KIBIBYTE = 1024;  
...  
var kibibytes = bytes / BYTES_PER_KIBIBYTE;  

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

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


2
Хороша зустрічна відповідь. Було б сильніше, якщо врахувати індивідуальну культуру команди. Якщо ви вірили моєму профілю SE , я вже досить старший, щоб передувати ці конкретні стандарти. Тож єдина плутанина походить від "що є поточним (не) стандартним терміном?" І ви, ймовірно, будете в безпеці, якщо припустити, що я працюю з командою динозаврів, які мають однакові термінологічні (не) труднощі.

@ GlenH7: IMHO, одиниці потужності, що базуються на двох, повинні були зберігатися для зберігання, оскільки вони розподіляються на шматки потужності двох розмірів. Мінімальний розмір виділення становить 4096 байт, чи має сенс мати одиницю кількості пам’яті, необхідної для вміщення 256 файлів мінімального розміру, або об’єм пам’яті, необхідний для вміщення 244.140625 таких файлів? Особисто я вважаю, що різниця між мегабайтами виробника жорсткого диска та іншими мегабайтами є аналогічною різниці між діагоналями дюймів телевізора та реальними діагональними дюймами.
supercat

@ Райан: У цьому конкретному випадку я б скоріше був войовничим щодо прийняття стандартних одиниць - КБ - 1000 байт або код неправильний, а 1024 байт - KiB або код неправильний. Це єдиний спосіб, коли ми колись подолаємо проблему "одиниці неоднозначні". Різні люди, що визначають "магічні константи" (як KB) по-різному, не допоможуть.
Брендан

11

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

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

Основна небезпека, пов'язана з чимось подібним, #define BYTES_PER_KBYTE 1024полягає в тому, що це може запропонувати тому, хто стикається, printf("File size is %1.1fkB",size*(1.0/BYTES_PER_KBYTE));що безпечним способом змусити код використовувати тисячі байтів було б змінити #defineоператор. Однак така зміна може бути катастрофічною, якщо, наприклад, якийсь інший незв'язаний код отримує розмір об'єкта в Кбайт і використовує цю константу при виділенні для цього буфера.

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


7

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

1024 - це також кількість кіБ на МіБ. Припустимо, ми використовуємо 1024, щоб також представити цей розрахунок десь або в декількох місцях, і тепер нам потрібно змінити його для обчислення GiB. Змінювати константу простіше, ніж глобальна пошук / заміна, коли ви можете випадково змінити неправильну в деяких місцях або пропустити її в інших.

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

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

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

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