Що таке магічне число?
Чому цього слід уникати?
Чи є випадки, коли це доречно?
Що таке магічне число?
Чому цього слід уникати?
Чи є випадки, коли це доречно?
Відповіді:
Магічне число - це пряме використання числа в коді.
Наприклад, якщо у вас є (на Java):
public class Foo {
public void setPassword(String password) {
// don't do this
if (password.length() > 7) {
throw new InvalidArgumentException("password");
}
}
}
Це має бути відновлено до:
public class Foo {
public static final int MAX_PASSWORD_SIZE = 7;
public void setPassword(String password) {
if (password.length() > MAX_PASSWORD_SIZE) {
throw new InvalidArgumentException("password");
}
}
}
Це покращує читабельність коду і його легше підтримувати. Уявіть випадок, коли я встановив розмір поля пароля в графічному інтерфейсі. Якщо я використовую магічне число, кожного разу, коли змінюється максимальний розмір, я повинен змінюватись у двох місцях коду. Якщо я забуду одну, це призведе до невідповідностей.
JDK рясніє прикладами як у Integer
, так Character
і Math
класах.
PS: Інструменти статичного аналізу, такі як FindBugs та PMD, виявляють використання магічних чисел у вашому коді та пропонують рефакторинг.
TRUE
/ FALSE
)
Магічне число - це жорстко закодоване значення, яке може змінитися на більш пізньому етапі, але це може бути важко оновити.
Наприклад, скажімо, що у вас є Сторінка, яка відображає останні 50 замовлень на сторінці огляду "Ваші замовлення". 50 - Магічне число тут, оскільки воно не встановлюється за допомогою стандарту чи конвенції, це число, яке ви склали з причин, викладених у специфікації.
Тепер у вас є 50 у різних місцях - ваш SQL-скрипт ( SELECT TOP 50 * FROM orders
), ваш веб-сайт (ваші останні 50 замовлень), ваше вхід для замовлення (for (i = 0; i < 50; i++)
) та, можливо, багато інших місць.
Тепер, що відбувається, коли хтось вирішить змінити 50 на 25? або 75? або 153? Тепер вам доведеться замінити 50 у всіх місцях, і ви, швидше за все, його пропустите. Пошук / Заміна може не спрацювати, тому що 50 може використовуватися для інших речей, а сліпа заміна 50 на 25 може мати деякі інші погані побічні ефекти (тобто вашSession.Timeout = 50
дзвінок, який також встановлений на 25, і користувачі починають повідомляти про занадто часті очікування).
Також код може бути важко зрозумілим, тобто " if a < 50 then bla
" - якщо ви виявите, що посеред складної функції, інші розробники, які не знайомі з кодом, можуть запитати себе "WTF - це 50 ???"
Ось чому найкраще мати такі неоднозначні та довільні числа точно в 1 місці - " const int NumOrdersToDisplay = 50
", оскільки це робить код більш читабельним (" if a < NumOrdersToDisplay
", це також означає, що вам потрібно змінити його лише в 1 чітко визначеному місці.
Місця, в яких магічні числа підходять, - це все, що визначено за допомогою стандарту, тобто SmtpClient.DefaultPort = 25
або TCPPacketSize = whatever
(не впевнений, чи це стандартизовано). Крім того, все, що визначено лише в межах 1 функції, може бути прийнятним, але це залежить від контексту.
SmtpClient.DefaultPort = 25
можливо ясно ер ніж SmtpClient.DefaultPort = DEFAULT_SMTP_PORT
.
25
усьому додатку і переконайтеся, що ви змінюєте лише випадки, 25
що стосуються порту SMTP, а не 25-х, наприклад ширину стовпця таблиці або числа записів для показу на сторінці.
IANA
.
Ви подивилися на запис у Вікіпедії магічне число?
Це детально розглядає всі способи створення посилання на магічне число. Ось цитата про магічне число як погану практику програмування
Термін магічне число також відноситься до поганої практики програмування використання чисел безпосередньо у вихідному коді без пояснень. У більшості випадків це ускладнює програми для читання, розуміння та обслуговування. Хоча більшість довідників роблять виняток для чисел нульових і одиничних, хороша ідея визначити всі інші числа в коді як названі константи.
Магія: Невідомий семантичний
Symbolic Constant -> Забезпечує правильний смисловий і правильний контекст для використання
Семантичний: сенс чи мета речі.
"Створіть константу, назвіть її за значенням і замініть її числом." - Мартін Фаулер
По-перше, магічні числа - це не просто числа. Будь-яке основне значення може бути "магічним". Основні значення - це маніфестовані об'єкти, такі як цілі числа, числові знаки, парні, плавці, дати, рядки, булеві символи, символи тощо. Проблема полягає не в типі даних, а в "магічному" аспекті значення, яке відображається в нашому тексті коду.
Що ми маємо на увазі під магією? Якщо бути точним: "Магією" ми маємо намір вказати на семантику (значення чи призначення) значення в контексті нашого коду; що це невідомо, незрозуміло, незрозуміло чи заплутано. Це поняття «магія». Основна цінність не є магією, коли її смислове значення або мета буття є швидко і легко пізнаною, зрозумілою та зрозумілою (не збиває з пантелику) з оточуючого контексту без спеціальних допоміжних слів (наприклад, символічна константа).
Тому ми визначаємо магічні числа, вимірюючи здатність читача коду знати, бути зрозумілим та розуміти значення та призначення базового значення з його оточуючого контексту. Чим менш відомий, менш зрозумілий і більш заплутаний читач, тим більш "магічним" є основне значення.
У нас є два сценарії наших основних магічних цінностей. Тільки другий має головне значення для програмістів і код:
Загальна залежність "магії" полягає в тому, як одиночне основне значення (наприклад, число) не має загальновідомих семантичних (наприклад, Pi), але має локально відомий семантичний (наприклад, ваша програма), який не зовсім зрозумілий з контексту або його можна зловживати. у хорошому чи поганому контексті.
Семантика більшості мов програмування не дозволить нам використовувати одиночні основні значення, за винятком (можливо) в якості даних (тобто таблиць даних). Коли ми стикаємося з "магічними числами", ми зазвичай робимо це в контексті. Тому відповідь на
"Чи замінюю це магічне число символічною константою?"
є:
"Як швидко ви можете оцінити і зрозуміти смислове значення числа (його призначення для перебування там) у його контексті?"
Маючи на увазі цю думку, ми можемо швидко побачити, як число, подібне Pi (3.14159), не є "магічним числом", якщо розмістити його у відповідному контексті (наприклад, 2 x 3.14159 x радіус або 2 * Pi * r). Тут число 3.14159 розумово розпізнається Pi без символічного постійного ідентифікатора.
І все-таки ми зазвичай замінюємо 3.14159 таким символьним постійним ідентифікатором, як Pi через довжину та складність числа. Аспекти довжини та складності Pi (у поєднанні з потребою в точності) зазвичай означають, що символьний ідентифікатор або константа є менш схильними до помилок. Визнання "Пі" як імені є просто зручним бонусом, але не є основною причиною постійності.
Не відкладаючи звичайних констант, таких як Pi, давайте зосередимося головним чином на числах із спеціальними значеннями, але які ці значення обмежені всесвітом нашої програмної системи. Таке число може бути "2" (як основне ціле значення).
Якщо я використовую число 2 самостійно, моє перше запитання може бути: Що означає "2"? Значення "2" саме по собі є невідомим і не пізнаваним без контексту, залишаючи його використання незрозумілим і заплутаним. Незважаючи на те, що просто "2" в нашому програмному забезпеченні не відбудеться через мовну семантику, ми хочемо побачити, що "2" сам по собі не має жодної особливої семантики чи очевидних цілей бути самотнім.
Поставимо наш одинокий "2" в контекст: padding := 2
де контекст є "Контейнером графічного інтерфейсу". У цьому контексті значення 2 (як пікселів чи іншої графічної одиниці) пропонує нам швидко здогадатися про його семантику (значення та призначення). Ми можемо зупинитися тут і сказати, що в цьому контексті 2 добре, і нічого іншого нам не потрібно знати. Однак, можливо, у нашому програмному Всесвіті це не вся історія. Існує більше, але "padding = 2" як контекст не може його розкрити.
Давайте зробимо вигляд, що 2 як піксельні прокладки в нашій програмі мають різноманітність "default_padding" у всій нашій системі. Тому писати інструкцію padding = 2
недостатньо добре. Поняття «дефолт» не виявлено. Тільки коли я пишу: padding = default_padding
як контекст, а потім і в іншому місці: default_padding = 2
чи я цілком усвідомлюю краще та більш повне значення (семантичне та цільове) значення 2 у нашій системі.
Наведений вище приклад досить хороший, оскільки "2" сам по собі може бути будь-яким. Тільки коли ми обмежимо діапазон та область розуміння "моєю програмою", де 2 є default_padding
частинами GUI UX "моєї програми", ми нарешті маємо сенс "2" у відповідному контексті. Тут "2" - це "магічне" число, яке визначається символічною константою default_padding
в контексті графічного інтерфейсу GUI "моєї програми" для того, щоб використовувати його як можна default_padding
швидше зрозуміле в широкому контексті додаючого коду.
Таким чином, будь-яке основне значення, значення якого (семантичне та цільове) не може бути достатньо і швидко зрозуміле, є хорошим кандидатом на символічну константу замість базового значення (наприклад, магічне число).
Числа в масштабі також можуть мати семантику. Наприклад, зробіть вигляд, що ми робимо гру D&D, де у нас є поняття монстра. Наш об’єкт-монстр має функцію під назвою life_force
, яка є цілим числом. Цифри мають значення, які невідомі чи зрозумілі без слів, щоб надати значення. Таким чином, ми почнемо з довільного твердження:
З вищевказаних символічних констант ми починаємо отримувати душевну картину живості, смертності та "нежить" (і можливих наслідків або наслідків) для наших монстрів у нашій грі на тему та розробці. Без цих слів (символічні константи) нам залишаються лише числа, починаючи від -10 .. 10
. Просто діапазон без слів залишає нас у місці, можливо, великої плутанини та, можливо, з помилками в нашій грі, якщо різні частини гри залежать від того, що означає цей діапазон чисел для різних операцій, як attack_elves
або seek_magic_healing_potion
.
Тому, шукаючи та розглядаючи заміну «магічних чисел», ми хочемо задавати цілком цілеспрямовані питання щодо чисел у контексті нашого програмного забезпечення та навіть про те, як числа семантично взаємодіють між собою.
Давайте розглянемо, які питання нам слід задати:
У вас може виникнути чарівне число, якщо ...
Вивчіть окремі постійні базові значення в тексті коду. Задайте кожне запитання повільно і продумано про кожен екземпляр такої цінності. Розгляньте силу своєї відповіді. Багато разів відповідь не чорно-біла, але має відтінки неправильно зрозумілого значення та призначення, швидкість навчання та швидкість розуміння. Також потрібно побачити, як він підключається до програмної машини навколо нього.
Врешті-решт, відповідь на заміну - це відповідь мірою (на вашу думку) сили чи слабкості читача здійснити зв’язок (наприклад, "отримати це"). Чим швидше вони розуміють сенс і мету, тим менше "магії" у вас.
ВИСНОВОК: Замініть основні значення символічними константами лише тоді, коли магія є достатньо великою, щоб викликати важкі виявлення помилок, що виникають в результаті плутанини.
Магічне число - це послідовність символів на початку формату файлу або обміну протоколами. Цей номер служить перевіркою здоровості.
Приклад: відкрийте будь-який файл GIF, ви побачите на самому початку: GIF89. "GIF89" - це магічне число.
Інші програми можуть прочитати перші кілька символів файлу та правильно визначити GIF.
Небезпека полягає в тому, що випадкові бінарні дані можуть містити ці самі символи. Але це дуже малоймовірно.
Що стосується обміну протоколами, ви можете скористатися ним, щоб швидко визначити, що поточне повідомлення, яке передається вам, пошкоджене чи недійсне.
Магічні числа все ще корисні.
У програмуванні "магічне число" - це значення, якому слід надати символічну назву, але замість цього прослизнуло в код як буквальне, як правило, в більш ніж одному місці.
Це погано з тієї ж причини. SPOT (Єдина точка істини) хороша. Якщо ви хочете змінити цю константу пізніше, вам доведеться шукати свій код, щоб знайти кожен екземпляр. Це також погано, тому що іншим програмістам може бути незрозуміло, що це число представляє, звідси і "магія".
Люди іноді беруть додаткове усунення магічних чисел, переміщуючи ці константи в окремі файли, щоб діяти як конфігурація. Іноді це корисно, але також може створити більше складності, ніж це варто.
(foo[i]+foo[i+1]+foo[i+2]+1)/3
може оцінюватися набагато швидше, ніж цикл. Якби замінити 3
код без переписування коду як циклу, той, хто побачив, що ITEMS_TO_AVERAGE
визначений як такий, 3
міг би зрозуміти, він міг би змінити його 5
і мати в коді в середньому більше елементів. Навпаки, хтось, хто подивився на вираз з буквальним 3
, зрозумів, що 3
являє собою кількість елементів, що підсумовуються разом.
Магічне число також може бути числом зі спеціальною, твердо кодованою семантикою. Наприклад, я колись бачив систему, де ідентифікатори записів> 0 оброблялися нормально, 0 сам був "новим записом", -1 - "це корінь" і -99 - "це було створено в корені". 0 і -99 призведе до того, що WebService надасть новий ідентифікатор.
Що поганого в тому, що ви повторно використовуєте пробіл (прописаний цілі числа для ідентифікаторів запису) для спеціальних здібностей. Можливо, вам ніколи не захочеться створювати запис із ідентифікатором 0 або з негативним ідентифікатором, але навіть якщо ні, кожна людина, яка дивиться або на код, або в базу даних, може спотикатися на цьому і спочатку плутати. Само собою зрозуміло, що ці особливі цінності не були добре задокументовані.
Можливо, 22, 7, -12 та 620 теж враховуються як магічні числа. ;-)
Проблема, про яку не згадували використання магічних чисел ...
Якщо їх дуже багато, шанси досить добре, що у вас є дві різні цілі, для яких ви використовуєте магічні числа, де значення трапляються однаковими.
І тоді, досить впевнено, потрібно змінити значення ... лише для однієї мети.
Я припускаю, що це відповідь на мою відповідь на ваше попереднє запитання. У програмуванні магічне число - це вбудована числова константа, яка з’являється без пояснень. Якщо вона з’являється у двох різних місцях, це може призвести до обставин, коли змінюється один екземпляр, а не інший. З обох цих причин важливо виділити і визначити числові константи поза місцями їх використання.
Я завжди використовував термін "магічне число" по-різному, як неясне значення, що зберігається в структурі даних, яке можна перевірити як швидку перевірку дійсності. Наприклад, файли gzip містять 0x1f8b08 як перші три байти, файли класу Java починаються з 0xcafebabe тощо.
Ви часто бачите магічні числа, вбудовані у формати файлів, тому що файли можуть надсилатись досить розбірливо і втрачати будь-які метадані про те, як вони були створені. Однак магічні числа також іноді використовуються для структури даних в пам'яті, наприклад, виклики ioctl ().
Швидка перевірка магічного числа перед обробкою файлу або структури даних дозволяє подавати сигнали про помилки рано, а не шлепть весь шлях шляхом потенційно тривалої обробки, щоб оголосити, що введення було повним балдердашем.
Варто зазначити, що іноді ви хочете, щоб у вашому коді не настроювалися «жорсткі» номери. Існує ряд відомих, включаючи 0x5F3759DF, який використовується в оптимізованому алгоритмі зворотного квадратного кореня.
У тих рідкісних випадках, коли в мене виникає потреба використовувати такі Магічні Номери, я встановлюю їх у якості коду і документую, чому вони використовуються, як вони працюють і звідки вони з'явилися.
Що щодо ініціалізації змінної у верхній частині класу зі значенням за замовчуванням? Наприклад:
public class SomeClass {
private int maxRows = 15000;
...
// Inside another method
for (int i = 0; i < maxRows; i++) {
// Do something
}
public void setMaxRows(int maxRows) {
this.maxRows = maxRows;
}
public int getMaxRows() {
return this.maxRows;
}
У цьому випадку 15000 - це магічне число (згідно CheckStyles). Для мене встановлення значення за замовчуванням нормально. Я не хочу робити цього:
private static final int DEFAULT_MAX_ROWS = 15000;
private int maxRows = DEFAULT_MAX_ROWS;
Це ускладнює читання? Я ніколи про це не розглядав, поки не встановив CheckStyles.
static final
константи є надмірними, коли ви використовуєте їх одним методом. final
Мінлива , оголошена у верхній частині методу є більш зручним для читання ІМХО.
@ eed3si9n: Я навіть припускаю, що "1" - це магічне число. :-)
Принцип, пов'язаний з магічними числами, полягає в тому, що кожен факт, з яким має справу ваш код, повинен бути оголошений рівно один раз. Якщо ви використовуєте магічні цифри у своєму коді (наприклад, приклад довжини пароля, який подав @marcio, ви можете легко дублювати цей факт, і коли ваше розуміння цього факту зміниться, у вас є проблема обслуговування.
factorial n = if n == BASE_CASE then BASE_VALUE else n * factorial (n - RECURSION_INPUT_CHANGE); RECURSION_INPUT_CHANGE = 1; BASE_CASE = 0; BASE_VALUE = 1
Що щодо змінних, що повертаються?
Особливо мені це складно під час впровадження збережених процедур .
Уявіть наступну збережену процедуру (я знаю, неправильний синтаксис, лише щоб показати приклад):
int procGetIdCompanyByName(string companyName);
Він повертає ідентифікатор компанії, якщо він існує в певній таблиці. Інакше повертається -1. Якось це чарівне число. Деякі з рекомендацій, які я читав до цього часу, говорить про те, що мені дійсно доведеться робити щось подібне:
int procGetIdCompanyByName(string companyName, bool existsCompany);
До речі, що він повинен повернути, якщо компанії не існує? Гаразд: він встановить existesCompany як хибний , але також поверне -1.
Варіант Antoher - це зробити дві окремі функції:
bool procCompanyExists(string companyName);
int procGetIdCompanyByName(string companyName);
Отже, умовою другої збереженої процедури є те, що компанія існує.
Але я боюся одночасності, тому що в цій системі компанію може створити інший користувач.
Підсумок до речі: що ви думаєте про використання таких «магічних чисел», які є відносно відомими та безпечними, щоб сказати, що щось не вдається або що щось не існує?
Ще одна перевага вилучення магічного числа як константи дає можливість чітко документувати бізнес-інформацію.
public class Foo {
/**
* Max age in year to get child rate for airline tickets
*
* The value of the constant is {@value}
*/
public static final int MAX_AGE_FOR_CHILD_RATE = 2;
public void computeRate() {
if (person.getAge() < MAX_AGE_FOR_CHILD_RATE) {
applyChildRate();
}
}
}
const myNum = 22; const number = myNum / 11;
зараз моїми 11 можуть бути люди або пляшки пива чи щось таке, натомість я б змінив 11 на постійний наприклад, мешканці.