Что такое магическое число?
Почему этого следует избегать?
Есть ли случаи, когда это уместно?
Что такое магическое число?
Почему этого следует избегать?
Есть ли случаи, когда это уместно?
Ответы:
Магическое число - это прямое использование числа в коде.
Например, если у вас есть (на 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
.
Вы смотрели на запись в Википедии для магического числа?
В нем подробно рассказывается обо всех способах ссылки на магические числа. Вот цитата о магическом числе как плохой практике программирования
Термин магическое число также относится к плохой практике программирования использования чисел непосредственно в исходном коде без объяснения причин. В большинстве случаев это затрудняет чтение, понимание и поддержку программ. Хотя большинство руководств делают исключение для чисел, равных нулю и единице, было бы неплохо определить все остальные числа в коде как именованные константы.
Магия: неизвестная семантика
Символическая константа -> Обеспечивает как правильный семантический, так и правильный контекст для использования
Семантическая: смысл или цель вещи.
«Создайте константу, назовите ее после значения и замените ее числом». - Мартин Фаулер
Во-первых, магические числа - это не просто числа. Любое базовое значение может быть «волшебным». Базовые значения - это объекты манифеста, такие как целые числа, вещественные числа, числа с плавающей точкой, числа с плавающей запятой, даты, строки, логические значения, символы и т. Д. Проблема заключается не в типе данных, а в «магическом» аспекте значения, как это показано в нашем коде.
Что мы подразумеваем под "магией"? Чтобы быть точным: «магией» мы намереваемся указать на семантику (значение или цель) значения в контексте нашего кода; что это неизвестно, непостижимо, неясно или сбивает с толку. Это понятие "магия". Базовое значение не является магическим, когда его семантическое значение или цель существования быстро и легко узнаются, ясны и понятны (не путаются) из окружающего контекста без специальных вспомогательных слов (например, символической константы).
Поэтому мы идентифицируем магические числа, измеряя способность читателя кода знать, быть ясным и понимать значение и цель базовой ценности из окружающего контекста. Чем менее известный, менее понятный и более запутанный читатель, тем более «волшебным» является основное значение.
У нас есть два сценария для наших магических базовых ценностей. Только второе имеет первостепенное значение для программистов и кода:
Общая зависимость «волшебства» заключается в том, что одиночное базовое значение (например, число) не имеет общеизвестной семантики (например, Pi), но имеет локально известную семантику (например, вашу программу), которая не совсем понятна из контекста или может быть злоупотреблена в хорошем или плохом контексте (ах).
Семантика большинства языков программирования не позволит нам использовать отдельные базовые значения, кроме (возможно) в качестве данных (то есть таблиц данных). Когда мы сталкиваемся с «магическими числами», мы обычно делаем это в контексте. Следовательно, ответ на
"Заменить ли это магическое число на символическую константу?"
является:
«Как быстро вы сможете оценить и понять семантическое значение числа (его цель присутствия) в его контексте?»
Имея в виду эту мысль, мы можем быстро увидеть, что число, такое как Pi (3.14159), не является «магическим числом», если помещено в соответствующий контекст (например, 2 x 3,14159 x радиуса или 2 * Pi * r). Здесь число 3.14159 мысленно распознается как Пи без символического идентификатора константы.
Тем не менее, мы обычно заменяем 3.14159 символьным постоянным идентификатором, таким как Pi, из-за длины и сложности числа. Аспекты длины и сложности числа Pi (в сочетании с необходимостью в точности) обычно означают, что символический идентификатор или константа менее подвержены ошибкам. Признание «Пи» в качестве имени является просто удобным бонусом, но не является основной причиной наличия константы.
Оставляя в стороне общие константы, такие как Pi, давайте сосредоточимся в первую очередь на числах с особыми значениями, но эти значения ограничены вселенной нашей программной системы. Такое число может быть «2» (как базовое целочисленное значение).
Если я использую число 2 само по себе, мой первый вопрос может быть таким: что означает «2»? Значение «2» само по себе неизвестно и непостижимо без контекста, что делает его использование неясным и запутанным. Хотя наличие только 2 в нашем программном обеспечении не произойдет из-за языковой семантики, мы хотим видеть, что само по себе «2» не несет никакой особой семантики или очевидной цели в одиночестве.
Давайте поместим нашу одиночную «2» в контекст:, padding := 2
где контекст - «Контейнер GUI». В этом контексте значение 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
представляет количество элементов, суммируемых вместе.
Магическое число также может быть числом со специальной жестко закодированной семантикой. Например, однажды я видел систему, в которой идентификаторы записей> 0 обрабатывались нормально, сам 0 был «новой записью», -1 был «это корень», а -99 был «это было создано в корне». 0 и -99 приведут к тому, что WebService предоставит новый идентификатор.
Что плохого в этом, так это то, что вы повторно используете пробел (знак целых чисел со знаком для идентификаторов записей) для специальных способностей. Возможно, вам никогда не захочется создавать запись с идентификатором 0 или с отрицательным идентификатором, но даже если нет, то каждый, кто просматривает код или базу данных, может наткнуться на это и сначала запутаться. Само собой разумеется, что эти специальные значения не были хорошо документированы.
Возможно, 22, 7, -12 и 620 тоже считаются магическими числами. ;-)
Проблема, о которой не упоминалось при использовании магических чисел ...
Если у вас их очень много, шансы достаточно хороши, так как у вас есть две разные цели , для которых вы используете магические числа, где значения оказываются одинаковыми.
И затем, конечно же, вам нужно изменить значение ... только для одной цели.
Я предполагаю, что это ответ на мой ответ на ваш предыдущий вопрос. В программировании магическое число - это встроенная числовая константа, которая появляется без объяснения причин. Если он появляется в двух разных местах, это может привести к обстоятельствам, когда один экземпляр изменяется, а не другой. По обеим этим причинам важно изолировать и определить числовые константы вне мест, где они используются.
Я всегда использовал термин «магическое число» по-разному, как неясное значение, хранимое в структуре данных, которое можно проверить как быструю проверку достоверности. Например, файлы gzip содержат 0x1f8b08 в качестве первых трех байтов, файлы классов Java начинаются с 0xcafebabe и т. Д.
Вы часто видите магические числа, встроенные в форматы файлов, потому что файлы могут быть отправлены довольно беспорядочно и теряют любые метаданные о том, как они были созданы. Однако магические числа также иногда используются для структур данных в памяти, таких как вызовы ioctl ().
Быстрая проверка магического числа перед обработкой файла или структуры данных позволяет раньше сигнализировать об ошибках, а не проходить весь путь через потенциально длительную обработку, чтобы объявить, что ввод был завершен.
Стоит отметить, что иногда вы хотите, чтобы в вашем коде не настраивались «жестко запрограммированные» числа. Существует ряд известных, включая 0x5F3759DF, который используется в оптимизированном алгоритме обратного квадратного корня.
В тех редких случаях, когда мне нужно использовать такие магические числа, я определяю их как const в своем коде и документирую, почему они используются, как они работают и откуда они взялись.
Как насчет инициализации переменной в начале класса значением по умолчанию? Например:
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 как 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 на постоянную такие как жители.