Что такое магическое число?
Почему этого следует избегать?
Есть ли случаи, когда это необходимо?
Что такое магическое число?
Почему этого следует избегать?
Есть ли случаи, когда это необходимо?
Магическое число - это прямое использование числа в коде.
Например, если у вас есть (в 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, обнаруживают использование магических чисел в вашем коде и предлагают рефакторинг.
Магический номер - это жестко закодированное значение, которое может измениться на более позднем этапе, но поэтому его трудно обновить.
Например, скажем, у вас есть Страница, на которой отображаются последние 50 заказов на странице "Ваши заказы". 50 - это номер Magic здесь, потому что он не задан стандартным или условным, это число, которое вы составили по причинам, изложенным в спецификации.
Теперь, что вы делаете, у вас есть 50 в разных местах - ваш SQL script (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??"
Для этого лучше всего иметь такие двусмысленные и произвольные числа в одном месте - "const int NumOrdersToDisplay = 50
", потому что это делает код более читаемым ( "if a < NumOrdersToDisplay
", это также означает, что вам нужно только его изменить в 1 хорошо определенное место.
Места, где подходят магические числа, - это все, что определено стандартом, т.е. SmtpClient.DefaultPort = 25
или TCPPacketSize = whatever
(не уверен, что это стандартизован). Кроме того, все, что определено только в 1 функции, может быть приемлемым, но это зависит от контекста.
Вы смотрели на запись в Википедии для магического числа?
В нем подробно рассказывается обо всех способах ссылки на магические числа. Вот цитата о магическом числе как плохой практике программирования
Термин магическое число также относится к плохой практике программирования использования чисел непосредственно в исходном коде без объяснения причин. В большинстве случаев это затрудняет чтение, понимание и поддержку программ. Хотя большинство руководств делают исключение для чисел, равных нулю и единице, было бы неплохо определить все остальные числа в коде как именованные константы.
Магия: Неизвестная семантика
Символическая константа → Предоставляет как правильный семантический, так и правильный контекст для использования
Семантика: смысл или цель вещи.
"Создайте константу, назовите ее по смыслу и замените ее номером". - Мартин Фаулер
Во-первых, магические числа - это не просто цифры. Любая базовая ценность может быть "волшебной". Основными значениями являются явные сущности, такие как целые числа, числа, удвоения, поплавки, даты, строки, логические значения, символы и т.д. Проблема заключается не в типе данных, а в "волшебном" аспекте значения, как он отображается в нашем тексте кода.
Что мы подразумеваем под "магией"? Если быть точным: "магией" мы намерены указать на семантику (значение или цель) значения в контексте нашего кода; что он неизвестен, непознаваем, неясен или запутан. Это понятие "волшебство". Базовое значение не является магическим, когда его смысловое значение или цель-бытие - быстро и легко известно, понятно и понятно (не путается) из контекста окружающего звука без специальных вспомогательных слов (например, символической константы).
Таким образом, мы идентифицируем магические числа, измеряя способность читателя кода знать, быть понятным и понимать смысл и цель базовой ценности из своего окружающего контекста. Менее известный, менее ясный и более запутанный читатель, тем более "магическим" является основное значение.
У нас есть два сценария для наших магических базовых значений. Только второе имеет первостепенное значение для программистов и кода:
Общая зависимость "магии" заключается в том, что одно базовое значение (например, число) не имеет общеизвестного семантического (например, Pi), но имеет локально известную семантику (например, вашу программу), которая не совсем понятна из контекста или могут быть злоупотреблены в хорошем или плохом контексте.
Семантика большинства языков программирования не позволит нам использовать одиночные базовые значения, за исключением (возможно) как данных (т.е. таблиц данных). Когда мы сталкиваемся с "магическими числами", мы обычно делаем это в контексте. Поэтому ответ на
"Я заменяю это магическое число символической константой?"
:
"Как быстро вы можете оценить и понять смысловой смысл номер (его цель быть там) в его контексте?"
С учетом этой мысли мы можем быстро увидеть, как число, подобное Pi (3.14159), не является "магическим числом" при размещении в надлежащем контексте (например, 2 x 3.14159 x радиус или 2 * Pi * r). Здесь число 3.14159 является умственно распознанным Pi без символьного идентификатора константы.
Тем не менее, мы обычно заменяем 3.14159 символьным константным идентификатором типа Pi из-за длины и сложности числа. Аспекты длины и сложности Pi (в сочетании с необходимостью точности) обычно означают, что символический идентификатор или константа менее подвержены ошибкам. Признание "Pi" как имени является просто удобным бонусом, но не является основной причиной наличия константы.
Отложив в сторону общие константы, такие как Pi, позвольте сосредоточиться в первую очередь на числах со специальными значениями, но эти значения ограничены вселенной нашей программной системы. Такое число может быть "2" (как основное целочисленное значение).
Если я сам использую номер 2, мой первый вопрос может быть: Что означает "2" ? Значение "2" само по себе неизвестно и непознаваемо без контекста, оставив его использование неясным и запутанным. Несмотря на то, что наличие всего лишь "2" в нашем программном обеспечении не произойдет из-за семантики языка, мы хотим видеть, что "2" само по себе не имеет специальной семантики или очевидной цели в одиночестве.
Положим наш одиночный "2" в контексте: padding := 2
, где контекст является "GUI Container". В этом контексте значение 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
в контексте UI GUI "моей программы", чтобы использовать его как 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) хорош: если вы хотите позже изменить эту константу, вам нужно будет искать ваш код, чтобы найти каждый экземпляр. Это также плохо, потому что другим программистам может быть непонятно, что представляет это число, отсюда и "волшебство".
Люди иногда дополнительно устраняют магическое число, перемещая эти константы в отдельные файлы, чтобы действовать как конфигурация. Это иногда полезно, но также может создавать большую сложность, чем это стоит.
Проблема, которая не упоминалась при использовании магических чисел...
Если у вас их очень много, шансы достаточно хороши, что у вас есть две разные цели, для которых вы используете магические числа, где значения совпадают.
И тогда, конечно же, вам нужно изменить значение... только для одной цели.
Магическое число также может быть числом со специальной, жестко закодированной семантикой. Например, я когда-то видел систему, в которой идентификаторы записей > 0 обрабатывались нормально, 0 сама была "новой записью", -1 была "это корень", а -99 была "это было создано в корне". 0 и -99 приведет к тому, что WebService предоставит новый идентификатор.
Что плохого в этом, так это то, что вы повторно используете пространство (целое число со знаком для идентификаторов записей) для специальных способностей. Возможно, вы никогда не захотите создать запись с идентификатором 0 или с отрицательным идентификатором, но даже если это не так, каждый человек, который смотрит либо на код, либо на базу данных, может наткнуться на это и сначала запутаться. Разумеется, эти специальные значения не были хорошо документированы.
Возможно, 22, 7, -12 и 620 также считаются магическими числами.; -)
Я предполагаю, что это ответ на мой ответ на ваш предыдущий вопрос. В программировании магическое число представляет собой встроенную численную константу, которая появляется без объяснения причин. Если он отображается в двух разных местах, это может привести к обстоятельствам, когда один экземпляр изменен, а не другой. По обоим этим причинам важно изолировать и определить числовые константы за пределами тех мест, где они используются.
Стоит отметить, что иногда вы предпочитаете неконфигурируемые "жестко закодированные" номера в вашем коде. Существует ряд известных, включая 0x5F3759DF, который используется в оптимизированном алгоритме обратного квадратного корня.
В редких случаях, когда я нахожу необходимость использовать такие магические числа, я устанавливаю их как const в своем коде и документирую, почему они используются, как они работают и откуда они пришли.
Я всегда использовал термин "магическое число" по-разному, как неявное значение, хранящееся в структуре данных, которое можно проверить как проверку быстрой проверки. Например, файлы gzip содержат 0x1f8b08 в качестве первых трех байтов, файлы классов Java начинаются с 0xcafebabe и т.д.
Вы часто видите магические числа, встроенные в форматы файлов, потому что файлы могут быть отправлены примерно беспорядочно и потерять любые метаданные о том, как они были созданы. Однако магические числа также иногда используются для структур данных в памяти, таких как вызовы ioctl().
Быстрая проверка магического номера перед обработкой файла или структуры данных позволяет некорректно сигнализировать об ошибках раньше, а не полностью прокладывать путь через потенциально длительную обработку, чтобы объявить, что вход был завершен.
Как инициализировать переменную в верхней части класса со значением по умолчанию? Например:
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.
@eed3si9n: Я даже предположил, что "1" - магическое число.: -)
Принцип, связанный с магическими числами, состоит в том, что каждый факт, с которым связан ваш код, должен быть объявлен ровно один раз. Если вы используете магические числа в своем коде (например, пример длины пароля, который дал @marcio, вы можете легко закончить дублирование этого факта, и когда вы поймете об этом факте, у вас возникла проблема обслуживания.
Как насчет возвращаемых переменных?
Мне особенно сложно выполнять хранимые процедуры.
Представьте следующую хранимую процедуру (неверный синтаксис, я знаю, просто чтобы показать пример):
int procGetIdCompanyByName(string companyName);
Он возвращает идентификатор компании, если он существует в определенной таблице. В противном случае он возвращает -1. Как-то это волшебное число. Некоторые из рекомендаций, которые я прочитал до сих пор, говорят, что мне действительно нужно будет сделать что-то вроде этого:
int procGetIdCompanyByName(string companyName, bool existsCompany);
Кстати, что он должен вернуть, если компания не существует? Ок: он установит existesCompany как false, но также вернет -1.
Функция Antoher состоит в том, чтобы сделать две отдельные функции:
bool procCompanyExists(string companyName);
int procGetIdCompanyByName(string companyName);
Таким образом, предварительным условием для второй хранимой процедуры является то, что компания существует.
Но я боюсь concurrency, потому что в этой системе компания может быть создана другим пользователем.
Суть в следующем: что вы думаете об использовании таких "волшебных чисел", которые относительно известны и безопасны, чтобы сказать, что что-то не увенчалось успехом или что-то не существует?
Еще одно преимущество извлечения магического числа в качестве константы дает возможность четко документировать деловую информацию.
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();
}
}
}