Что такое магическое число?
Почему этого следует избегать?
Есть ли случаи, когда это уместно?
Что такое магическое число?
Почему этого следует избегать?
Есть ли случаи, когда это уместно?
Магическое число - это прямое использование числа в коде.
Например, если у вас есть (на 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, обнаруживают использование магических чисел в вашем коде и предлагают провести рефакторинг.
public static final HUNDRED_PERCENTS = 100;
?
- person Kirill V. Lyadvinsky; 22.07.2009
TRUE
/ FALSE
как исключение. Пример: if(array.length == 0)
(или < 1
)
- person das Keks; 08.04.2014
connection.setTimeout(50);
Должен ли 50
быть здесь константой? Кажется, довольно ясно, что 50
- это тайм-аут соединения.
- person Sergey Kolodyazhnyy; 12.12.2014
String.Format()
(C #) или чем-то еще, что вы могли бы сделать public const string CELLPosition= "ROW{0}Col{1}"
, а затем просто String.Format(CELLPosition,row,col)
, и вы получите то, что хотите.
- person WiiMaxx; 11.02.2016
const passwordExpiresAt = moment().add(2, 'days').toDate();
Мне действительно нужно определить PASSWORD_EXPIRE_TIME_IN_DAYS
константу? Это используется только в одном месте в коде, и значение 2 явно взято из остальной части строки. Если позже я изменю его на 1 или 3, значение все равно будет очевидным.
- person Merlin -they-them-; 07.12.2016
1
и 0
могут быть магическими числами, это просто зависит от контекста. Проверяя if someArray.length === 0
, цель очевидна, вы проверяете, пусто ли оно, не считается ли оно магическим числом. Но если вы сделаете createSomething(1)
, это магическое число. Невозможно получить из контекста, что это за 1, особенно когда это 1
действует как boolean
. Если у вас был true
или false
, ясно, что этот параметр включен или выключен.
- person tdtm; 22.01.2019
defined as a constant
1) чтобы быть уверенным, что ваш код синхронизирован 2) в случае, если вы хотите легко изменить его в future
, о чем всегда должен думать хороший разработчик. YAGNI, будучи сторонником недисциплинированного давайте-просто-отправим-за дверь, идет вразрез с долгосрочным мышлением. Так что я не удивлен, что YAGNI это делают.
- person MickyD; 21.02.2019
password.length() > 7
, что вовсе не говорит само за себя, и connection.setTimeout(50)
, где намерение совершенно ясно.
- person AgentCorleone; 22.02.2019
if (radix == 10) { return toString(i); }
- person Alex78191; 18.07.2019
MAX_PASSWORD_SIZE
не удаляет никакой магии из вашего примера кода. Это просто затрудняет чтение. Из кода было очевидно, что роль 7 - максимальная длина пароля. Это не нужно объяснять снова. Если таковые имеются, 7 должны получить комментарий, как будто это глупо иметь максимальную длину пароля, но это было решено на собрании 2011-04-01 руководством.
- person Roland Illig; 20.01.2020
connection.setTimeout(50)
не совсем ясно на месте звонка. Связанные со временем аргументы стандартной библиотеки в таких языках, как C и PHP обычно указываются в миллисекундах, в C ++ и Assembly обычно в наносекундах или тактовых циклах, а в Python и Ruby < i> обычно указываются в секундах, не говоря уже о нестандартных библиотечных функциях, которые имеют свои собственные соглашения (или Unix, у которых есть эпохи). connection.setTimeout(MILLISECONDS_50)
, connection.setTimeout(time::duration::ms(50))
и connection.setTimeout(time::time_point("03:50PM"))
более ясны / ясны.
- person Gabriel Francischini; 15.11.2020
Магическое число - это жестко запрограммированное значение, которое может измениться на более позднем этапе, но поэтому его сложно обновить.
Например, предположим, что у вас есть страница, на которой отображаются последние 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 is 50 ???"
Вот почему лучше всего иметь такие неоднозначные и произвольные числа ровно в одном месте - «const int NumOrdersToDisplay = 50
», потому что это делает код более читабельным («if a < NumOrdersToDisplay
», это также означает, что вам нужно изменить его только в 1 четко определенном месте.
Места, где подходят магические числа, - это все, что определено в стандарте, то есть SmtpClient.DefaultPort = 25
или TCPPacketSize = whatever
(не уверен, что это стандартизовано). Кроме того, все, что определено только в одной функции, может быть приемлемым, но это зависит от контекста.
SmtpClient.DefaultPort = 25
возможно ясно er, чем SmtpClient.DefaultPort = DEFAULT_SMTP_PORT
.
- person user253751; 09.11.2014
25
по всему приложению и следить за тем, чтобы вы изменяли только вхождения 25
, которые относятся к порту SMTP, а не 25, которые, например, ширина столбца таблицы или количество записей, отображаемых на странице.
- person Michael Stum; 01.04.2015
IANA
.
- person njsg; 08.06.2015
Вы видели запись в Википедии о магическом числе?
Здесь подробно рассказывается обо всех способах создания ссылки на магическое число. Вот цитата о магическом числе как о плохой практике программирования
Термин «магическое число» также относится к плохой практике программирования - использовать числа непосредственно в исходном коде без объяснения причин. В большинстве случаев это затрудняет чтение, понимание и сопровождение программ. Хотя большинство руководств делают исключение для чисел ноль и единица, рекомендуется определить все остальные числа в коде как именованные константы.
Магия: неизвестная семантика
Символьная константа -> Обеспечивает правильный семантический и правильный контекст для использования
Семантический: значение или цель вещи.
«Создайте константу, назовите ее по значению и замените им число». - Мартин Фаулер
Во-первых, магические числа - это не просто числа. Любое базовое значение может быть «волшебным». Базовые значения - это явные сущности, такие как целые числа, действительные числа, числа с плавающей запятой, числа с плавающей запятой, даты, строки, логические значения, символы и т. Д. Проблема не в типе данных, а в «магическом» аспекте значения, которое отображается в тексте нашего кода.
Что мы подразумеваем под «магией»? Чтобы быть точным: под «магией» мы намереваемся указать на семантику (значение или цель) значения в контексте нашего кода; что он неизвестен, непознаваем, неясен или сбивает с толку. Это понятие «магия». Базовое значение не является магическим, если его семантическое значение или цель существования - быстро и легко узнать, ясно и понять (не сбивая с толку) из окружающего контекста без специальных вспомогательных слов (например, символической константы).
Таким образом, мы идентифицируем магические числа, измеряя способность читателя кода знать, ясно и понимать значение и цель базового значения из окружающего его контекста. Чем менее известен, менее ясен и запутан читатель, тем более «волшебным» является его базовое значение.
У нас есть два сценария для наших основных волшебных ценностей. Только второе имеет первостепенное значение для программистов и кода:
Всеобъемлющая зависимость «магии» заключается в том, что единственное базовое значение (например, число) не имеет общеизвестной семантики (например, Пи), но имеет локально известную семантику (например, ваша программа), которая не совсем ясна из контекста или может быть использована. в хорошем или плохом контексте.
Семантика большинства языков программирования не позволяет нам использовать отдельные базовые значения, за исключением (возможно) данных (то есть таблиц данных). Когда мы встречаем «магические числа», мы обычно делаем это в контексте. Следовательно, ответ на
«Могу ли я заменить это магическое число на символическую константу?»
is:
«Как быстро вы сможете оценить и понять семантическое значение числа (его цель присутствия) в его контексте?»
Имея в виду эту мысль, мы можем быстро увидеть, что такое число, как Пи (3,14159), не является «магическим числом», если поместить его в надлежащий контекст (например, 2 x 3,14159 x радиус или 2 * Pi * r). Здесь число 3,14159 - это мысленное распознавание числа Пи без символьного идентификатора константы.
Тем не менее, мы обычно заменяем 3.14159 символическим идентификатором константы, таким как 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 UX «моей программы», чтобы использовать ее как default_padding
, быстро понимаемую в более широком контексте включающего кода. .
Таким образом, любое базовое значение, значение которого (семантика и цель) не может быть достаточно и быстро понято, является хорошим кандидатом на символическую константу вместо базового значения (например, магическое число).
Числа на шкале также могут иметь семантику. Например, представьте, что мы делаем игру D&D, в которой у нас есть понятие монстра. У нашего объекта-монстра есть функция под названием life_force
, которая является целым числом. Цифры имеют значение, которое невозможно узнать или понять без слов, передающих значение. Таким образом, мы начнем с того, что произвольно скажем:
Из символических констант, приведенных выше, мы начинаем получать мысленную картину живости, мертвости и «нежити» (а также возможных ответвлений или последствий) для наших монстров в нашей игре D&D. Без этих слов (символьных констант) останутся только числа от -10 .. 10
. Просто диапазон без слов оставляет нас в месте, возможно, большой путанице и потенциально с ошибками в нашей игре, если разные части игры имеют зависимости от того, что этот диапазон чисел означает для различных операций, таких как attack_elves
или seek_magic_healing_potion
.
Поэтому при поиске и рассмотрении вопроса о замене «магических чисел» мы хотим задавать очень целенаправленные вопросы о числах в контексте нашего программного обеспечения и даже о том, как числа семантически взаимодействуют друг с другом.
Давайте рассмотрим, какие вопросы нам следует задать:
У вас может быть магическое число, если ...
Изучите основные значения констант автономного манифеста в тексте кода. Задавайте каждый вопрос медленно и вдумчиво о каждом случае такого значения. Обдумайте силу своего ответа. Часто ответ не черно-белый, а оттенки неверно понятого значения и цели, скорости обучения и скорости понимания. Также необходимо увидеть, как он подключается к находящейся вокруг него программной машине.
В конце концов, ответ на замену - это мера (в вашем уме) силы или слабости читателя, чтобы установить связь (например, «получить»). Чем быстрее они поймут значение и цель, тем меньше у вас «магии».
ЗАКЛЮЧЕНИЕ: Заменяйте базовые значения символическими константами только тогда, когда магия достаточно велика, чтобы затруднить обнаружение ошибок, возникающих из-за путаницы.
Магическое число - это последовательность символов в начале формата файла или протокола обмена. Этот номер служит для проверки работоспособности.
Пример: откройте любой файл GIF, в самом начале вы увидите: GIF89. «GIF89» - это магическое число.
Другие программы могут читать первые несколько символов файла и правильно определять GIF.
Опасность состоит в том, что случайные двоичные данные могут содержать одни и те же символы. Но это очень маловероятно.
Что касается обмена протоколами, вы можете использовать его, чтобы быстро определить, что текущее «сообщение», которое передается вам, повреждено или недействительно.
Магические числа по-прежнему полезны.
В программировании «магическое число» - это значение, которому следует дать символическое имя, но вместо этого оно было вставлено в код как литерал, обычно в более чем одном месте.
Это плохо по той же причине, по которой SPOT (Single Point of Truth) хорош: если вы захотите изменить эту константу позже, вам придется рыться в коде, чтобы найти все экземпляры. Это также плохо, потому что другим программистам может быть непонятно, что представляет собой это число, отсюда и «магия».
Иногда люди идут дальше исключения магических чисел, перемещая эти константы в отдельные файлы, чтобы они действовали как конфигурация. Иногда это полезно, но также может создать большую сложность, чем она того стоит.
(foo[i]+foo[i+1]+foo[i+2]+1)/3
может вычисляться намного быстрее, чем цикл. Если бы кто-то заменил 3
, не переписывая код как цикл, кто-то, кто видел ITEMS_TO_AVERAGE
, определенный как 3
, мог бы подумать, что он может изменить его на 5
и получить в среднем больше элементов кода. Напротив, тот, кто посмотрел на выражение с буквальным знаком 3
, понял бы, что 3
представляет количество суммируемых элементов.
- person supercat; 31.03.2015
quadratic_root = (-b + sqrt(b*b - 4*a*c)) / (2*a)
? На самом деле любая математическая формула, в которой магические числа не имеют значения, кроме их включения в формулу.
- person jodag; 01.05.2018
Не упоминавшаяся проблема с использованием магических чисел ...
Если у вас их очень много, велика вероятность того, что у вас есть две разные цели, для которых вы используете магические числа, где значения совпадают. .
И тогда, конечно же, вам нужно изменить значение ... только для одной цели.
Магическое число также может быть числом со специальной жестко запрограммированной семантикой. Например, однажды я видел систему, в которой идентификаторы записей> 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
, объявленная вверху метода, ИМХО более читабельна.
- person Eva; 06.03.2013
@ 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
- person Thomas Eding; 02.06.2010
А как насчет возвращаемых переменных?
Мне особенно сложно реализовать хранимые процедуры.
Представьте себе следующую хранимую процедуру (я знаю, неправильный синтаксис, просто чтобы показать пример):
int procGetIdCompanyByName(string companyName);
Он возвращает идентификатор компании, если он существует в определенной таблице. В противном случае возвращается -1. Как-то это магическое число. В некоторых рекомендациях, которые я прочитал до сих пор, говорится, что мне действительно придется делать что-то вроде этого:
int procGetIdCompanyByName(string companyName, bool existsCompany);
Кстати, что он должен вернуть, если компании не существует? Хорошо: он установит для existesCompany значение false, но также вернет -1.
Другой вариант - сделать две отдельные функции:
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 на константу, такую как жители. - person   schedule 17.09.2019