Каждое число в коде считается «магическим числом»?

21

То есть каждое число в коде, которое мы отправляем методу в качестве аргумента, считается магическим числом? Для меня это не должно. Я думаю, что если какое-то число, скажем, для минимальной длины имени пользователя, и мы начинаем использовать «6» в коде ... тогда да, у нас есть проблема с обслуживанием, и здесь «6» - это магическое число .... но если мы вызываем метод, один из аргументов которого принимает целое число, например, в качестве i-го члена коллекции, а затем мы передаем «0» вызову этого метода, в этом случае я не вижу, что «0» является магией число. Как вы думаете?

Блейк
источник
4
В вашем примере, что представляет собой 0?
Аарон Курцхалс
2
В случае, который вы иллюстрируете, этот «0» не имеет никаких магических свойств.
Тулаинс Кордова
4
Все, кроме 0,1 и 42, волшебно,
говорит Мауг, восстановите Монику

Ответы:

43

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

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

s := substring(big_string, 0, findFirstOccurence(SOME_TOKEN, big_string));

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

Другой пример может быть, если вы пытаетесь определить, является ли число четным или нечетным. Письмо:

isEven := x % 2;

не так странно, как:

TWO := 2;
isEven := x % TWO;

Тестирование отрицательных чисел как

MINUS_ONE := -1;
isNegativeInt := i <= MINUS_ONE;

также странно для меня, я бы предпочел увидеть

isNegativeInt := i <= -1;
FrustratedWithFormsDesigner
источник
6
Чтобы добавить другой пример, в коде, где вы явно работаете с градусами по кругу, было бы справедливо использовать число, например, 360для обозначения полного поворота с пониманием, что большинство людей будут знать, что это значит (хотя это это случай, когда не помешало бы предоставить константу)
KChaloux
11
KChaloux: Если бы я мог, я бы -1 ваш комментарий. 360 - это магическое число. Если 360 окажется значением для другой константы, то у вас есть 2 набора для 360, которые не связаны и неразличимы. Младший приходит, идет «Это волшебное число», глобальный поиск и заменяет 360 на «Degrees_in_Circle», запускает все модульные и регрессионные тесты, все проходят - доставляет исправление кода. Кодируйте сейчас завтрак с собаками, и мы все знаем, что происходит с этим через короткое время .......
mattnz
4
@mattnz: Надеюсь, что такого рода крупномасштабные изменения кода будут быстро обнаружены (надеюсь, во время проверки кода, если они младшие) задолго до того, как они когда-либо начнут производиться. Я думаю, что кто-то, кто сделал бы это в этом контексте, вероятно, также заменил бы 0в контексте моего примера подстроки. В этом случае это может быть наименьшее количество ущерба, которое они могут причинить. Прошло много времени с тех пор, как я делал любое кодирование, которое выполняло геометрические вычисления, но обычно значения 15, 30, 45, 60, 90, 180, 360 были константами, которые были приняты. Я никогда не видел, чтобы кто-нибудь определил FIFTEEN_DEGREES, ...
FrustratedWithFormsDesigner
5
@KChaloux Пример может развалиться, если произойдет переход от градусов к радианам. К 360 вы выражаете 1 полный оборот. Поскольку существует несколько представлений для одного и того же значения, его следует извлечь. Особенно с учетом того, что 360PI может выглядеть так же, как 2PI (180 оборотов, но в конце все же указывает одно и то же направление), или 360 оборотов, что и 1 вращение, но побочные эффекты могут отличаться.
Крис
14
Немного соломенного человека, TWO и MINUS_ONE совершенно плохи, потому что замена магического числа с его рендерингом в тексте, конечно, идиотская. Имя константы должно передавать ее значение. За исключением ваших примеров о фундаментальных фактах о числах, тесно связанных только с этими конкретными числами, так что на самом деле никакого смысла в этом нет.
Майкл Боргвардт
17
bool hasApples = apples > 0;

Очевидно, что ноль означает отсутствие. Я считаю, 0 легче понять, чем переменная с именем "senceValue ".


for(int i=0; i < arr.length; i++)

Очевидно, 0 - начальная позиция. Я был бы смущен переменной с именем "firstPosition". Такая переменная заставила бы меня задуматься, может ли измениться начальная позиция.

mike30
источник
14

Я бы предложил три ключевых фактора при принятии решения, должно ли что-то быть постоянным объявлением:

  1. Является ли число чем-то точно и кратко представимым
  2. Существуют ли вероятные сценарии, при которых значение должно было бы измениться, но код не должен был бы быть переписан
  3. Будет ли тот, кто видит число, склонен распознавать его быстрее или менее быстро, чем тот, кто видит названную константу?

Нечто подобное пи, вероятно, следует записывать как именованную константу, а не как числовой литерал, поскольку числовой литерал может быть излишне многословным, излишне неточным или и тем, и другим. Что-то вроде количества слотов в кеше должно быть именованной константой (хотя см. Примечание ниже), чтобы обеспечить возможность расширения кеша без необходимости изменять весь код, который его использует. Такие вещи, как числа «4», «28» и «29» в утверждении, if ((year % 4)==0) FebruaryDays = 29; else FebruaryDays = 28;вероятно, не должны называться константами, поскольку выражение почти наверняка более читабельно, чем if ((year % YearsBetweenLeapYears)==0) FebruaryDays = FebruaryDaysInLeapYear; else FebruaryDays = FebruaryDaysInNonLeapYear;. Обратите внимание, что разработчики стандартов указали, что продолжительность февраля 2100 в этом году не будет соответствовать вышеуказанной формуле, препятствие для правильной обработки таких дат (т. е. код не будет отключен из-за целочисленного переполнения или других подобных проблем).

Важное предостережение с правилом № 2 заключается в том, что в некоторых случаях код может опираться на жестко запрограммированные числа таким образом, который не может быть легко представлен именованной константой. Например, метод, который вычисляет перекрестное произведение двух векторов, переданных в качестве дискретных параметров, будет иметь смысл только при использовании на трехмерных векторах. Требуемое количество измерений не является значением, которое можно было бы существенно изменить без полного переписывания процедуры. Даже если предвидеть возможную потребность в вычислении перекрестного произведения трех четырехмерных векторов, использование именованной константы для значения «3» мало что сделает для удовлетворения этой потребности.

Supercat
источник
4

Это, как и все принципы, является вопросом степени. Вообще говоря, числовые литералы в исходном коде более подозрительны, чем они больше. Максимальная длина, например 10, или адрес памяти, такой как 0x587FB0, явно плохая практика - почти наверняка вам рано или поздно придется повторять эти значения более одного раза, что создает риск несовместимости и незначительных ошибок в местах, которые не были изменилось.

0 находится на другом конце шкалы; это все еще подозрительно, но не так сильно. Используете ли вы 0 в качестве значения дозорного? Тогда вам, вероятно, следует использовать символическую константу вместо этого, просто потому, что константа может объяснить, что это значит. Это чрезвычайно укоренившееся культурное соглашение типа «0 означает успешное завершение»? Это, наверное, хорошо. Означает ли это «первый элемент в коллекции»? Это может быть безвредно, но если есть альтернативный метод, такой как first()я, вероятно, предпочел бы это.

Килиан Фот
источник
1
"Используете ли вы 0 в качестве значения дозорного?" <- Можете ли вы объяснить, что вы подразумеваете под "страж" здесь? Я не могу найти определение, которое кажется подходящим.
rory.ap
3

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

В django (веб-фреймворк Python) я могу определить поле базы данных с необработанным числом, например:

firstname = models.CharField(max_length=40)
middlename = models.CharField(max_length=40)
lastname =  models.CharField(max_length=40) 

что яснее (и рекомендуемая практика ) чем сказать

MAX_LENGTH_NAME = 40
...
firstname = models.CharField(max_length=MAX_LENGTH_NAME)
middlename = models.CharField(max_length=MAX_LENGTH_NAME)
lastname =  models.CharField(max_length=MAX_LENGTH_NAME) 

так как мне вряд ли когда-нибудь понадобится менять длину (и всегда можно сравнить с max_lengthполем). Если мне нужно изменить длину поля после первоначального развертывания приложения, мне нужно изменить его точно в одном месте на поле в моем коде django, а затем дополнительно написать миграцию для изменения схемы БД. Если мне когда-либо понадобится ссылка max_lengthна определенное поле типа объекта, я могу сделать это напрямую - если эти поля определяли Personкласс, я могу использовать Person._meta.get_field('firstname').max_lengthдля полученияmax_lengthиспользуется (который определяется в одном месте). Тот факт, что те же 40 использовались для нескольких полей, не имеет значения, так как я могу захотеть изменить их независимо. Длина имени не должна зависеть от длины среднего имени или фамилии; они являются отдельными значениями и могут меняться независимо.

Часто индексы массива могут использовать безымянные числа; например, если у меня есть файл данных CSV, который я хочу поместить в словарь Python, с первым элементом в строке в качестве словаря, который keyя бы написал:

mydict = {}
for row in csv.reader(f):
    mydict[row[0]] = row[1:]

Конечно, я мог бы назвать index_column = 0и сделать что-то вроде:

index_col = 0
mydict = {}
for row in csv.reader(f):
    mydict[row[index_col]] = row[:index_col] + row[index_col+1:]

или, что еще хуже, определение, after_index_col = index_col + 1чтобы избавиться от index_col+1, но это не делает код более понятным, на мой взгляд. Кроме того, если я даю index_colимя, я лучше заставлю код работать, даже если столбец не равен 0 (отсюда и row[:index_col] +часть).

доктор джимбоб
источник
7
На самом деле, max_lngth=40vs. max_length=MAX_LENGTH_NAME- это классический пример магического числа, которое кричит как символ. Придет день, когда вы захотите поддержать 45 имен персонажей, и теперь каждое использование «40» является подозрительным и должно быть тщательно изучено.
Росс Паттерсон
1
@RossPatterson - это не C, где мы постоянно сравниваем глобальную переменную MAX_ARRAY_SIZE, а достойный веб-фреймворк. Единственное место, где появляется магическое число, это где вы объявляете модель базы данных; все остальное сравнивается с этим значением (например, 40 больше нигде не встречается в коде). Также обратите внимание, что вы не можете легко изменить эту переменную без выполнения миграции схемы, так как она привязана к БД. Если бы я захотел изменить , чтобы сказать 1 персонаж отчества ее сразу видно одно место для изменения в коде 40к 1. Вы должны думать о контексте.
Доктор Джимбоб
2
Извините, вы ошибаетесь по двум пунктам. Во-первых, ФП задал вопрос «практики программирования», в котором не указан язык. Они сказали «метод», а не «функция», поэтому давайте предположим что-то объектно-ориентированное, но это не выводит нас из области магических данных. Во-вторых, если магическое число запекается в базе данных ( например , в схеме), то еще хуже иметь его в коде. Правильнее всего сделать так, чтобы получить почти постоянную магию из ее источника - либо самой базы данных, либо модуля схемы, который централизует все эти константы, которые будут меняться в течение всего времени существования кода.
Росс Паттерсон