Чи кожне число в коді вважається "магічним числом"?


21

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


4
У вашому прикладі, що означає 0?
Аарон Куртжальс

2
У випадку, який ви ілюструєте, що "0" не має жодних магічних властивостей.
Тулен Кордова

4
Все, окрім 0,1 та 42, є магічним
Mawg

Відповіді:


43

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

Приклад: Скажімо, ви намагаєтеся отримати підрядку рядка від початку до деякого маркера, і код виглядає приблизно так (уявна мова та бібліотека):

s := substring(big_string, 0, findFirstOccurence(SOME_TOKEN, big_string));

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

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

isEven := x % 2;

не так дивно, як:

TWO := 2;
isEven := x % TWO;

Тестування від'ємних чисел як

MINUS_ONE := -1;
isNegativeInt := i <= MINUS_ONE;

мені також дивно, я б швидше побачив

isNegativeInt := i <= -1;

6
Щоб навести ще один приклад там, у коді, де ви явно працюєте зі ступенями по колу, було б справедливо використовувати таке число, як 360позначити повне обертання, розуміючи, що більшість людей знатиме, що це означає (хоча це це випадок, коли не завадило б забезпечити постійну роботу)
KChaloux

11
KChaloux: Якби я міг, я б -1 ваш коментар. 360 - це магічне число. Якщо 360 має значення для іншої постійної, то у вас є 2 набори для 360, які не пов'язані між собою і не відрізняються. Junior приходить разом, перейдіть "Це чарівне число", глобальний пошук і замініть 360 на "Degrees_in_Circle", проведіть всі тести одиниці та регресії, все проходьте - доставляє код виправлення. Запишіть тепер сніданок з собаками, і всі ми знаємо, що станеться з цим через короткий час .......
mattnz

4
@mattnz: Сподіваємось, що така масштабна зміна коду буде швидко спіймана (сподіваємось, під час перегляду коду, якщо вони такі молодші) задовго до того, як вона коли-небудь потрапить у виробництво. Я думаю, хтось, хто зробив би це в цьому контексті, ймовірно, також замінить 0у контексті мого прикладу підрядків. У цьому випадку це може бути найменший збиток, який вони можуть завдати. З давніх пір я робив будь-яке кодування, яке робило геометричні обчислення, але загалом значення 15, 30, 45, 60, 90, 180, 360 були константами, які були прийняті. Я ніколи не бачив, щоб хтось визначав FIFTEEN_DEGREES, ...
FrustratedWithFormsDesigner

5
@KChaloux Приклад може насправді розпастись, якщо відбудеться перехід від градусів до радіян. На 360 рік ви виражаєте 1 повне обертання. Оскільки є кілька представлень на одне значення, його слід витягнути. Особливо враховуючи, що 360PI може виглядати так само, як 2PI (180 обертів, але все ж вказує на той самий напрямок в кінці), або 360 обертів такі ж, як 1 обертання, але побічні ефекти можуть бути різними.
Кріс

14
Трохи солом’яної людини, TWO та MINUS_ONE - це зовсім погано, оскільки заміна магічного числа його відображенням у тексті є ідіотичним. Назва постійної має передати її значення. За винятком ваших прикладів, які стосуються фундаментальних фактів щодо цифр, тісно пов'язаних лише з цими конкретними числами, тому насправді немає сенсу.
Майкл Боргвардт

17
bool hasApples = apples > 0;

Очевидно, нуль означає відсутність. Я вважаю 0 простішим для розуміння, ніж змінною під назвою "відсутність валу".


for(int i=0; i < arr.length; i++)

Очевидно, що 0 - це вихідна позиція. Мене бентежить змінна назва "firstPosition". Така змінна змусила б мене замислитись, чи може початкова позиція змінитися.


14

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

  1. Чи є число чимось точно і стисло представним
  2. Чи є якісь правдоподібні сценарії, за яких значення повинно було б змінюватися, але код не потрібно було б переписувати
  3. Чи може хтось, хто бачить число, сприйнятий розпізнавати його швидше або менш швидко, ніж той, хто бачить названу константу

Щось на кшталт pi слід, мабуть, писати як названу константу, а не як числовий літерал, оскільки числовий літерал є сприйнятливим багатослівним, непотрібним, або обом. Щось подібне до кількості слотів у кеші, ймовірно, має бути названою константою (хоча див. Примітку нижче), щоб передбачити можливість розширення кеша без зміни коду, який його використовує. Такі речі, як числа "4", "28" та "29" у виписці, if ((year % 4)==0) FebruaryDays = 29; else FebruaryDays = 28;ймовірно, не повинні називатися константами, оскільки вираз майже напевно читабельніший, ніж if ((year % YearsBetweenLeapYears)==0) FebruaryDays = FebruaryDaysInLeapYear; else FebruaryDays = FebruaryDaysInNonLeapYear;. Зауважте, що керівники стандартів вказали, що тривалість лютого 2100 року в цьому році не буде відповідати вищевказаній формулі, перешкоджання правильному поводженню з такими датами (тобто код не зіткнеться з цілим числом переповнення або іншими подібними проблемами)

Важливим застереженням із правила №2 є те, що в деяких випадках код може покладатися на жорстко закодовані числа таким чином, який не може бути легко представлений названою константою. Наприклад, метод, який обчислює поперечний добуток двох векторів, переданих як дискретні параметри, буде мати значення лише при використанні на тривимірних векторах. Необхідна кількість вимірів не є значенням, яке змістовно може бути змінено без повного перезапису рутини. Навіть якби передбачити можливу потребу в обчисленні поперечного добутку трьох 4-мірних векторів, використання названої константи для значення "3" мало би мало для того, щоб було легше задовольнити цю потребу.


4

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

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


1
Msgstr "Чи використовуєте ви 0 в якості вартового значення?" <- Чи можете ви пояснити, що ви маєте на увазі під «дозорним» тут? Я не можу знайти визначення, яке, здається, відповідає.
rory.ap

3

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

У django (python web Framework) я можу визначити деяке поле бази даних із необробленим числом, наприклад:

firstname = models.CharField(max_length=40)
middlename = models.CharField(max_length=40)
lastname =  models.CharField(max_length=40) 

що зрозуміліше (і рекомендована практика ), ніж сказати

MAX_LENGTH_NAME = 40
...
firstname = models.CharField(max_length=MAX_LENGTH_NAME)
middlename = models.CharField(max_length=MAX_LENGTH_NAME)
lastname =  models.CharField(max_length=MAX_LENGTH_NAME) 

тому що я навряд чи коли-небудь знадобиться змінювати довжину (і завжди можна порівняти max_lengthз польовою). Якщо мені потрібно змінити довжину поля після первинного розгортання програми, мені потрібно змінити його в точно одному місці на поле в моєму коді django, а потім додатково написати міграцію, щоб змінити схему БД. Якщо мені колись знадобиться посилання max_lengthна певне поле типу об’єкта, я можу це зробити безпосередньо - якщо ці поля визначали Personклас, я можу використовувати Person._meta.get_field('firstname').max_lengthдля отриманняmax_lengthвикористовується (що визначено в одному місці). Те, що ті самі 40 були використані для кількох полів, не має значення, оскільки я можу захотіти їх змінити самостійно. Довжина імені ніколи не повинна залежати від довжини прізвища чи прізвища; вони є окремими значеннями і можуть змінюватися незалежно.

Часто в індексах масиву можна використовувати неназвані числа; наприклад, якщо у мене є файл CSV даних, який я хочу вкласти в словник python, з першим елементом у рядку як словником, який keyя б написав:

mydict = {}
for row in csv.reader(f):
    mydict[row[0]] = row[1:]

Впевнений, що я міг назвати index_column = 0і зробити щось на кшталт:

index_col = 0
mydict = {}
for row in csv.reader(f):
    mydict[row[index_col]] = row[:index_col] + row[index_col+1:]

або гірше визначити, after_index_col = index_col + 1щоб позбутися від цього index_col+1, але це не робить код яснішим на мій погляд. Крім того, якщо я даю index_colім’я, я краще змушу код працювати, навіть якщо стовпець не 0 (звідси row[:index_col] +частина).


7
На насправді, по max_lngth=40порівнянні max_length=MAX_LENGTH_NAMEє класичним examlple ряду чарівного , що крики бути символом. Прийде день, коли ви хочете підтримати 45 імен символів, і тепер кожне використання "40" є підозрілим і його потрібно уважно вивчити.
Росс Паттерсон

1
@RossPatterson - Це не C, де ми постійно порівнюємось із глобальним варом MAX_ARRAY_SIZE, а гідна веб-рамка. Єдине місце, на яке з'являється магічне число, - це де оголосити модель бази даних; все інше порівнюється з цим значенням (наприклад, 40 ніде в коді не з’являється). Також зауважте, ви не можете легко змінити цю змінну, не роблячи міграцій схеми як її прив’язаних до БД. Якщо я хотів змінити, щоб сказати середні імена 1 символу, це відразу очевидно, в одному коді 40потрібно змінити код 1. Ви повинні думати про контекст.
dr jimbob

2
Вибачте, ви помиляєтесь у двох питаннях. По-перше, ОП задало питання "практики програмування", яке не визначає жодної мови. Вони сказали "метод", а не "функція", тому припустимо щось об'єктно-орієнтоване, але це не виводить нас із царини магічних даних. По-друге, якщо магічне число записується в базу даних ( наприклад , схема), то ще гірше мати його в коді. Правильно зробити - це отримати магію майже постійної константи від свого джерела - або самої бази даних, або модуля схеми, який централізує всі ці константи, які змінюватимуться протягом життя коду.
Росс Паттерсон
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.