Усунення магічних чисел: коли настав час сказати "ні"?


36

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

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

seconds = num_days * 24 * 60 * 60

з

seconds = num_days * HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE

У який момент ви вирішите, що цілком очевидно, що означає жорстко закодоване значення, і залишити його в спокої?


2
Чому б не замінити цей обчислення функцією або макросом, щоб ваш код виглядав такseconds = CALC_SECONDS(num_days);
FrustratedWithFormsDesigner

15
TimeSpan.FromDays(numDays).Seconds;
Ніхто

18
@oosterwal: За такої позиції ( HOURS_PER_DAY will never need to be altered) ви ніколи не будете кодувати програмне забезпечення, розгорнуте на Марсі. : P
FrustratedWithFormsDesigner

23
Я б скоротив кількість констант до SECONDS_PER_DAY = 86400. Навіщо обчислювати щось, що не зміниться?
JohnFx

17
А як із стрибними секундами?
Іван

Відповіді:


40

Існує дві причини використовувати символічні константи замість числових літералів:

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

  2. Для поліпшення читальності. Вираз «24 * 60 * 60» досить очевидний майже для всіх. "SECONDS_PER_DAY" теж є, але якщо ви полюєте на помилку, вам, можливо, доведеться перевірити, чи SECONDS_PER_DAY було визначено правильно. Є цінність у стислості.

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

Не роби цього:

public static final int THREE = 3;

3
+1 @kevin cline: Я згоден з вашою думкою про стислість полювання на помилок. Додатковою перевагою, яку я бачу при використанні названої константи, особливо при налагодженні, є те, що якщо буде виявлено, що константа була визначена неправильно, потрібно лише змінити один фрагмент коду, а не шукати через весь проект для всіх випадків неправильно реалізованого значення.
oosterwal

40
Або ще гірше:publid final int FOUR = 3;
габлін

3
О, дорогий, ти, мабуть, працював з тим самим хлопцем, з яким колись працював.
quick_now

2
@gablin: Для справедливості, пабам дуже корисно мати кришки.
Алан Пірс

10
Я бачив це: public static int THREE = 3;... зверніть увагу - ні final!
Стівен C

29

Я б дотримувався правила ніколи не мати магічних чисел.

Поки

seconds = num_days * 24 * 60 * 60

Ідеально читає більшу частину часу, після того, як кодував 10 годин на день протягом трьох або чотирьох тижнів у режимі хрускіт

seconds = num_days * HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE

набагато простіше читати.

Пропозиція FrustratedWithFormsDesigner краще:

seconds = num_days * DAYS_TO_SECOND_FACTOR

а ще краще

seconds = CONVERT_DAYS_TO_SECONDS(num_days)

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


13
Потрапити в режим хрускоту, як ви описуєте, - це контрпродуктивний антипатерн, якого слід уникати. Програмісти досягають максимальної продуктивності приблизно 35-40 годин на тиждень.
btilly

4
@btilly Я від усієї душі згоден з вами. Але це буває, часто через зовнішні фактори.
Vitor Py

3
Я загалом визначаю константи на секунду, хвилину, день і годину. Якщо нічого іншого «30 * MINUTE» насправді легко читати, і я знаю його час, не замислюючись про це.
Захарій К

9
@btilly: Пік становить 35-40 годин або рівень алкоголю в крові між 0,129% і 0,138%. Я читав це на XKCD , тому це повинно бути правдою!
oosterwal

1
Якби я побачив константу на зразок HOURS_PER_DAY, я б її видалив, а потім публічно принизив би перед вашими однолітками. Гаразд, можливо, я б відмовився від публічного приниження, але я, мабуть, видалив би його.
Ед С.

8

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

Але у вашому прикладі я думаю, що замінити 24 * 60 * 60на DAYS_TO_SECONDS_FACTORкраще.


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

Приклад:

Як зазначав @rmx, використовуючи 0 або 1, щоб перевірити, чи список порожній, чи, можливо, в межах циклу є прикладом випадку, коли призначення константи дуже чітке.


2
Зазвичай це нормально використовувати 0або 1я вважаю. if(someList.Count != 0) ...краще, ніж if(someList.Count != MinListCount) .... Не завжди, але загалом.
Ніхто

2
@Dima: дизайнер VS формує все це. Якщо він хоче створити константи, зі мною це добре. Але я не збираюся вносити згенерований код і замінювати всі твердо кодовані значення константами.
FrustratedWithFormsDesigner

4
Давайте не будемо плутати код, який породжувався та оброблявся інструментом із кодом, написаним для споживання людиною.
biziclop

1
@FrustratedWithFormsDesigner, як вказував @biziclop, генерований код - це зовсім інша тварина. Названі константи абсолютно повинні використовуватися в коді, який читають і змінюють люди. Створений код, принаймні в ідеальному випадку, не повинен взагалі змінюватися.
Діма

2
@FrustratedWithFormsDesigner: Що станеться, коли у вашій програмі є добре відоме значення, жорстко закодоване в десятках файлів, яке раптом потрібно змінити? Наприклад, ви жорстко кодуєте значення, що представляє кількість тактових частот на мікросекунду для вбудованого процесора, після чого вам пропонується перенести програмне забезпечення на конструкцію, де є різна кількість тактових годин на мікросекунду. Якщо ваше значення було чимось звичайним, наприклад 8, проведення пошуку / заміни на десятках файлів може призвести до виникнення додаткових проблем.
oosterwal

8

Зупиніться, коли ви не можете присвоїти значення чи мету до числа.

seconds = num_days * HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE

набагато простіше читати, ніж просто використовувати цифри. (Хоча це може бути зрозумілішим, маючи єдину SECONDS_PER_DAYконстанту, але це зовсім окрема проблема.)

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

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


7

Я, мабуть, сказав "ні" таким речам, як:

#define HTML_END_TAG "</html>"

І, безумовно , сказав би "ні":

#define QUADRATIC_DISCRIMINANT_COEF 4
#define QUADRATIC_DENOMINATOR_COEF  2

7

Один з найкращих прикладів, які я знайшов для просування використання констант для очевидних речей, як HOURS_PER_DAYце:

Ми підраховували, як довго речі сиділи в черзі на роботу людини. Вимоги були чітко визначені, і програміст жорстко закодований 24у ряді місць. Врешті-решт ми зрозуміли, що не справедливо карати користувачів за те, що вони сиділи на проблемі протягом 24 годин, коли вони працюють лише 8 годин на день. Коли виникла задача виправити це І побачити, які інші звіти можуть мати ту саму проблему, було досить важко виконувати / шукати код за 24, було б набагато простіше виконувати / шукатиHOURS_PER_DAY


о, ні, тому години на день змінюються залежно від того, ви посилаєтесь на робочий час на день (я працюю 7,5 BTW) або години на день. Щоб змінити таке значення константи, вам слід замінити її ім'я на щось інше. Ваша думка про легкий пошук є дійсною, хоча.
gbjbaanb

2
Схоже, і в цьому випадку HOURS_PER_DAY насправді не був бажаною константою. Але можливість шукати його по імені - це величезна користь, навіть якщо (або особливо, якщо) вам потрібно змінити це на щось інше в багатьох місцях.
Девід К

4

Я думаю, що поки число повністю постійне і не має можливості змінюватися, це цілком прийнятно. Тож у вашому випадку seconds = num_days * 24 * 60 * 60це нормально (якщо, звичайно, ви не робите чогось нерозумного, як робите такий розрахунок всередині циклу) і, можливо, краще для читабельності, ніж seconds = num_days * HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE.

Це коли ви робите такі речі, як це погано:

lineOffset += 24; // 24 lines to a page

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


2
Чи вважаєте ви прийнятним ввести жорсткий код незмінного значення 86400 замість використання названої константи SECONDS_PER_DAY? Як би ви переконалися, що всі випадки значення були правильними і, наприклад, не пропускали 0 або замінювали, наприклад, 6 оголошення 4?
oosterwal

Тоді чому б ні: секунди = число_днів * 86400? Це теж не зміниться.
JeffO

2
Я робив справу SECONDS_PER_DAY обома способами - використовуючи ім'я та використовуючи номер. Коли ви повернетесь до коду через 2 роки, іменований номер ВЖЕ завжди має більше сенсу.
швидко_вінь

2
секунди = num_days * 86400 мені незрозуміло. Ось що в кінцевому рахунку рахується. Якщо я бачу "секунди = num_days * 24 * 60 * 60", окрім того, що імена змінних досить добре надають значення в цьому випадку, я одразу запитаю себе, чому я розділив їх, і значення стає очевидним, тому що я пішов їх як числа (отже, вони постійні), а не змінні, для яких подальше дослідження вимагатиме від мене розуміння їх значень і якщо вони постійні.
Ніл

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

3
seconds = num_days * 24 * 60 * 60

Чудово чудово. Це насправді не магічні числа, оскільки вони ніколи не зміняться.

Будь-які числа, які можуть розумно змінюватися або не мають очевидного значення, слід вводити в змінні. Що означає майже всі вони.


2
Було б seconds = num_days * 86400все-таки прийнятним? Якщо подібне значення використовувалось кілька разів у багатьох різних файлах, як би ви переконалися, що хтось випадково не ввів seconds = num_days * 84600у одному чи двох місцях?
oosterwal

1
Написання 86400 дуже відрізняється від написання 24 * 60 * 60.
Carra

4
Звичайно, це зміниться. Не кожен день в ньому є 86 400 секунд. Розглянемо, наприклад, літній час. Раз на рік, у деяких місцях є тільки 23 годин на день, а в інший день вони матимуть 25. пуф ваші номери розбиті.
Дейв ДеЛонг

1
@Dave, відмінний момент. Існують високосні секунди - en.wikipedia.org/wiki/Leap_second

Справедлива точка. Додавання функції буде захисним засобом, якщо вам коли-небудь потрібно буде знайти ці винятки.
Карра

3

Я б уникав створювати константи (магічні значення) для перетворення значення з однієї одиниці в іншу. У випадку перетворення я віддаю перевагу назві методу говоріння. У цьому прикладі це було б, наприкладDayToSeconds(num_days) внутрішній метод не потребує магічних значень, оскільки значення "24" та "60" зрозуміло.

У цьому випадку я ніколи не використовував секунди / хвилини / години. Я б використовував лише TimeSpan / DateTime.


1

Використовуйте контекст як параметр для вирішення

Наприклад, у вас є функція під назвою "CalcuSecondsBet Between: aDay та: anotherDay", вам не потрібно буде робити великих пояснень щодо того, що роблять ці числа, оскільки назва функції є досить репрезентативним.

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

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