Что такое магическое число и почему оно плохо?

Что такое магическое число?

Почему этого следует избегать?

Есть ли случаи, когда это уместно?

15 ответов

Решение

Магическое число - это прямое использование числа в коде.

Например, если у вас есть (на 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 - это магическое число, потому что оно не установлено стандартом или соглашением, это число, которое вы создали по причинам, указанным в спецификации.

Теперь, что вы делаете, у вас есть 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???"

Вот почему лучше иметь такие неоднозначные и произвольные числа ровно в одном месте - "const int NumOrdersToDisplay = 50", потому что это делает код более читабельным ("if a < NumOrdersToDisplay", это также означает, что вам нужно только изменить его в 1 четко определенном месте.

Места, где подходят магические числа, - это все, что определяется стандартом, т.е. SmtpClient.DefaultPort = 25 или же TCPPacketSize = whatever (не уверен, что это стандартизировано). Кроме того, все, что определено только в пределах 1 функции, может быть приемлемым, но это зависит от контекста.

Вы смотрели на запись в Википедии для магического числа?

В нем подробно рассказывается обо всех способах ссылки на магические числа. Вот цитата о магическом числе как плохой практике программирования

Термин магическое число также относится к плохой практике программирования использования чисел непосредственно в исходном коде без объяснения причин. В большинстве случаев это затрудняет чтение, понимание и поддержку программ. Хотя большинство руководств делают исключение для чисел, равных нулю и единице, было бы неплохо определить все остальные числа в коде как именованные константы.

Волшебный номер против Символическая константа: когда заменить?

Магия: неизвестная семантика

Символическая константа -> Обеспечивает как правильный семантический, так и правильный контекст для использования

Семантическая: смысл или цель вещи.

"Создайте константу, назовите ее после значения и замените ее числом". - Мартин Фаулер

Во-первых, магические числа - это не просто числа. Любое базовое значение может быть "магическим". Базовые значения - это объекты манифеста, такие как целые числа, вещественные числа, числа с плавающей запятой, числа с плавающей запятой, строки, логические значения, символы и т. Д. Проблема заключается не в типе данных, а в "магическом" аспекте значения, как это показано в нашем коде.

Что мы подразумеваем под "магией"? Чтобы быть точным: "магией" мы намереваемся указать на семантику (значение или цель) значения в контексте нашего кода; что это неизвестно, непостижимо, неясно или сбивает с толку. Это понятие "магия". Основное значение не является магическим, когда его семантическое значение или цель бытия быстро и легко узнаются, ясны и понятны (не путаются) из окружающего контекста без специальных вспомогательных слов (например, символической константы).

Поэтому мы идентифицируем магические числа, измеряя способность читателя кода знать, быть ясным и понимать значение и цель базовой ценности из окружающего контекста. Чем менее известный, менее понятный и более запутанный читатель, тем более "волшебным" является основное значение.

Полезные определения

  • запутать: заставить (кого-то) сбиться с толку или растерянности.
  • сбит с толку: привести (кого-то) в замешательство и растерянность.
  • озадачен: полностью сбит с толку; очень озадачен
  • сбит с толку: совершенно сбит с толку или недоумение.
  • озадачен: не в состоянии понять; озадачен.
  • понимать: воспринимать предполагаемое значение (слова, язык или говорящий).
  • значение: что подразумевается под словом, текстом, понятием или действием.
  • означает: намереваться передать, указать или сослаться (на конкретную вещь или понятие); означают.
  • означать: быть признаком.
  • индикация: знак или часть информации, которая указывает что-то.
  • указать: указать; шоу.
  • знак: объект, качество или событие, наличие или происхождение которого указывает на вероятное присутствие или возникновение чего-либо другого.

основы

У нас есть два сценария для наших магических базовых ценностей. Только второе имеет первостепенное значение для программистов и кода:

  1. Одиночное базовое значение (например, число), значение которого неизвестно, непознаваемо, неясно или запутано.
  2. Базовое значение (например, число) в контексте, но его значение остается неизвестным, непознаваемым, неясным или запутанным.

Общая зависимость "волшебства" заключается в том, что одиночное базовое значение (например, число) не имеет общеизвестной семантики (например, 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, которое является целым числом. Числа имеют значения, которые невозможно понять или понять без слов, чтобы дать значение. Таким образом, мы начинаем с произвольного высказывания:

  • full_life_force: INTEGER = 10 - очень живой (и невредимый)
  • imum_life_force: INTEGER = 1 - едва жив (очень больно)
  • Dead: INTEGER = 0 - Dead
  • нежить: INTEGER = -1 - Мин нежити (почти мертвый)
  • зомби: INTEGER = -10 - Макс нежити (очень нежить)

Исходя из приведенных выше символических констант, мы начинаем получать мысленную картину живости, мертвости и "нежити" (и возможных последствий или последствий) для наших монстров в нашей игре D&D. Без этих слов (символических констант) у нас останутся только цифры в диапазоне от -10 .. 10, Просто диапазон без слов оставляет нас в некотором заблуждении и, возможно, с ошибками в нашей игре, если разные части игры зависят от значения этого диапазона чисел для различных операций, таких как attack_elves или же seek_magic_healing_potion,

Поэтому при поиске и рассмотрении вопроса о замене "магических чисел" мы хотим задать очень целенаправленные вопросы о числах в контексте нашего программного обеспечения и даже о том, как числа семантически взаимодействуют друг с другом.

Заключение

Давайте рассмотрим, какие вопросы мы должны задать:

У вас может быть магическое число, если...

  1. Может ли базовое значение иметь особое значение или цель в вашей вселенной программного обеспечения?
  2. Может ли специальное значение или цель быть неизвестными, непознаваемыми, неясными или запутанными, даже в соответствующем контексте?
  3. Может ли правильное базовое значение неправильно использоваться с плохими последствиями в неправильном контексте?
  4. Может ли неправильное базовое значение правильно использоваться с плохими последствиями в правильном контексте?
  5. Имеет ли базовое значение семантические или целевые отношения с другими базовыми ценностями в определенных контекстах?
  6. Может ли базовое значение существовать в более чем одном месте в нашем коде с различной семантикой в ​​каждом, что вызывает у нашего читателя путаницу?

Изучите автономные манифесты основных констант в вашем коде. Медленно и вдумчиво задавайте каждый вопрос о каждом случае такой ценности. Подумайте о силе вашего ответа. Часто ответ не черно-белый, но имеет оттенки неправильно понятого значения и цели, скорости обучения и скорости понимания. Также необходимо посмотреть, как он подключается к программному компьютеру вокруг него.

В конце ответом на замену является ответ на меру (по вашему мнению) силы или слабости читателя, чтобы установить связь (например, "получить его"). Чем быстрее они понимают смысл и цель, тем меньше у вас "волшебства".

ЗАКЛЮЧЕНИЕ: заменяйте базовые значения символическими константами только тогда, когда магия достаточно велика, чтобы затруднять обнаружение ошибок, возникающих в результате путаницы.

Магическое число - это последовательность символов в начале формата файла или протокола обмена. Этот номер служит проверкой работоспособности.

Пример: Откройте любой файл GIF, вы увидите в самом начале: GIF89. "GIF89" - это магическое число.

Другие программы могут читать первые несколько символов файла и правильно идентифицировать GIF-файлы.

Опасность заключается в том, что случайные двоичные данные могут содержать эти же символы. Но это очень маловероятно.

Что касается обмена протоколами, вы можете использовать его для быстрой идентификации того, что текущее "сообщение", которое передается вам, повреждено или недействительно.

Магические числа все еще полезны.

В программировании "магическое число" - это значение, которому следует дать символическое имя, но вместо этого оно вставляется в код в виде литерала, обычно в нескольких местах.

Это плохо по той же причине, что SPOT (Single Point of Truth) хорош: если вы захотите изменить эту константу позже, вам придется искать код, чтобы найти каждый экземпляр. Это также плохо, потому что другим программистам может быть непонятно, что представляет это число, отсюда и "волшебство".

Иногда люди решают проблему удаления магических чисел, перемещая эти константы в отдельные файлы в качестве конфигурации. Это иногда полезно, но также может создать больше сложности, чем оно того стоит.

Проблема, которая не была упомянута с использованием магических чисел...

Если у вас их очень много, шансы достаточно хороши, так как у вас есть две разные цели, для которых вы используете магические числа, где значения оказываются одинаковыми.

И затем, конечно же, вам нужно изменить значение... только для одной цели.

Магическое число также может быть числом со специальной жестко закодированной семантикой. Например, однажды я видел систему, в которой идентификаторы записей> 0 обрабатывались нормально, сам 0 был "новой записью", -1 был "это корень", а -99 был "это было создано в корне". 0 и -99 приведут к тому, что WebService предоставит новый идентификатор.

Что плохого в этом, так это то, что вы повторно используете пробел (знак целых чисел со знаком для идентификаторов записей) для специальных способностей. Возможно, вам никогда не захочется создавать запись с идентификатором 0 или с отрицательным идентификатором, но даже если нет, то каждый, кто просматривает код или базу данных, может наткнуться на это и сначала запутаться. Само собой разумеется, что эти специальные ценности не были хорошо документированы.

Возможно, 22, 7, -12 и 620 тоже считаются магическими числами.;-)

Я предполагаю, что это ответ на мой ответ на ваш предыдущий вопрос. В программировании магическое число - это встроенная числовая константа, которая появляется без объяснения причин. Если он появляется в двух разных местах, это может привести к обстоятельствам, когда один экземпляр изменяется, а не другой. По обеим этим причинам важно изолировать и определить числовые константы вне мест, где они используются.

Я всегда использовал термин "магическое число" по-разному, как неясное значение, хранимое в структуре данных, которое можно проверить как быструю проверку достоверности. Например, файлы 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.

Стоит отметить, что иногда вы хотите, чтобы в вашем коде не настраивались "жестко запрограммированные" числа. Существует ряд известных, включая 0x5F3759DF, который используется в оптимизированном алгоритме обратного квадратного корня.

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

@eed3si9n: Я бы даже предположил, что "1" - это магическое число.:-)

Принцип, связанный с магическими числами, заключается в том, что каждый факт, с которым работает ваш код, должен быть объявлен ровно один раз. Если вы используете магические числа в своем коде (например, пример длины пароля, который дал @marcio, вы легко можете в конечном итоге воспроизвести этот факт, и когда ваше понимание этого факта изменится, у вас возникнет проблема с обслуживанием.

Как насчет возвращаемых переменных?

Я особенно нахожу это сложным при реализации хранимых процедур.

Представьте себе следующую хранимую процедуру (неправильный синтаксис, я знаю, просто чтобы показать пример):

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();
         }
    }
}
Другие вопросы по тегам