Все магические числа созданы одинаково?

77

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

var kBval = byteVal / 1024;

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

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

Так действительно ли числа, которые являются известными константами, действительно магические или нет?


Смежные вопросы:

Когда число магическое число? и считается ли каждое число в коде "магическим числом"? - похожи, но гораздо более широкие вопросы, чем то, что я задаю. Мой вопрос сосредоточен на общеизвестных постоянных числах, которые не рассматриваются в этих вопросах.

Устранение магических чисел: когда пора сказать «нет»? также связан, но сосредоточен на рефакторинге в противоположность тому, является ли постоянное число магическим числом.

Сообщество
источник
17
Я на самом деле работал над проектом, где они создали такие константы, как FOUR_HUNDRED_FOUR = 404. Я работал над другим проектом, в котором они боролись за использование константных строк вместо литералов, поэтому они имели десятки строк в коде, который выглядел примерно такDATABASE = "database"
Роб
82
Обязательно используйте 1024, потому что в противном случае ваша команда разработчиков будет тратить все свое время на споры о том, являются ли они «килобайтами» или «кибибайтами».
Gort the Robot
6
Вы можете считать, что 1024 - это киби, #define KIBIа 1024 - MEBIкак 1024 * 1024…
ysdx
6
@Rob Y: звучит как старые добрые программисты на Фортране. Потому что этот язык программирования заставил программистов сделать это. Да, там вы увидите такие же константы, как ZERO=0, ONE=1, TWO=2и когда программы перенесены на другие языки (или программисты не меняют поведение при переключении своего языка), вы также увидите это там, и вы должны молиться, чтобы никто не изменил их на ONE=2...
Хольгер
4
@NoctisSkytower Моя команда предпочитает использовать явные операторы деления вместо операторов сдвига битов из-за множества используемых языков и потенциально несовместимой реализации в этих языках. Аналогично, отрицательные значения обрабатываются непоследовательно с побитовым сдвигом. Хотя у нас не обязательно могут быть отрицательные байтовые значения, у нас наверняка есть отрицательные значения с другими единицами измерения, которые мы конвертируем.

Ответы:

103

Не все магические числа одинаковы.

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

Скрытие 1024 за BYTES_PER_KBYTE также означает, что вы не сразу видите, правильно это или нет.

Я ожидаю, что кто-нибудь сразу поймет, почему значение равно 1024. С другой стороны, если бы вы конвертировали байты в мегабайты, я бы определил константу BYTES_PER_MBYTE или аналогичную, потому что константа 1 048 576 не настолько очевидна, что ее 1024 ^ 2, или что это даже правильно.

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

// Value must be less than 3.5 volts according to spec blah.
SomeTest = DataSample < 3.50

Я нахожу лучше чем

SomeTest = DataSample < SOME_THRESHOLD_VALUE

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

как зовут
источник
67
«Проблема с магическими числами в том, что они магические» - это такое блестящее объяснение этой концепции! Я серьезно! +1 только за это предложение.
Йорг Миттаг
20
Вот что я только что придумал: «Проблема не в числе, а в волшебстве».
Йорг Миттаг
10
1024 кому очевидно? Разве это не оправдание для каждого магического числа? Все магические числа используются потому, что они очевидны для тех, кто их написал. Разве 9.8 тоже не очевидно? Для меня совершенно очевидно, что это ускорение силы тяжести на земле, но я бы создал постоянную, потому что то, что очевидно для меня, может быть не очевидно для кого-то другого.
Тулаинс Кордова
15
Нет. Такой комментарий, как в вашем "лучшем" примере, это массивный красный флаг. Это код, который даже не проходит тест на читаемость того, кто его написал. Я приведу пример. e^i*pi = -1гораздо более явно (лучше), чем 2.718^i*3.142 = -1. Переменные имеют значение, и они не только для общего кода. Код написан для чтения первым, компиляция вторым. Кроме того, характеристики меняются (много). Хотя 1024, вероятно, не должно быть в конфигурации, 3.5 звучит так, как должно быть.
Натан Купер
51
Я бы тоже не использовал константу для 1024 ^ 2; 1024*1024плз!
Легкость гонок с Моникой
44

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

У номера есть имя?

Имена полезны, потому что мы можем прочитать имя и понять назначение числа, стоящего за ним. Константы именования могут улучшить читаемость, если имя легче понять, чем число, которое оно заменяет, а имя константы сжато.

Очевидно, что константы, такие как pi, e, et al. иметь значимые имена. Значение, такое как 1024, может быть, BYTES_PER_KBно я также ожидаю, что любой разработчик будет знать, что означает 1024. Целевая аудитория для исходного кода - профессиональные программисты, которые должны иметь опыт, чтобы знать различные степени двух и почему они используются.

Используется ли он в нескольких местах?

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

Ваш вопрос

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

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

Местоположение: даже если оно используется в нескольких местах, оно никогда не изменится, сводя на нет это преимущество.


источник
1
Причина использования констант вместо магических чисел заключается не только в том, что указанные числа будут меняться, но и для удобства чтения и самостоятельного документирования.
Тулаинс Кордова
4
@ user61852: именованные константы не всегда более читабельны. Они часто бывают, но не всегда.
whatsisname
2
Лично я вместо этого использую эти два вопроса: «Изменится ли когда-нибудь эта ценность в течение жизни программы?» и "Понимают ли разработчики, над которыми я работаю над этим программным обеспечением, этот номер?"
Gort the Robot
4
Вы имеете в виду проблему 2000 года? Я не уверен, что это актуально здесь. Да, там было много кода типа «дата - 1900», но в этом коде проблема была не в волшебном числе «1900».
Gort the Robot
1
Этот ответ мог бы извлечь пользу из упоминания о том, что некоторые «очевидные» числа, 1024 определенно равны единице, таковы, что другие разработчики могут спонтанно записать их в виде чисел, даже если кто-то определит для них именованную константу. Скорее всего, я бы даже не подумал о поиске исходного кода для существующей константы для 1024, если бы я еще не знал, что она есть, если мне нужно было использовать 1024 в преобразовании количества байтов.
Гайд
27

Эта цитата

Проблема не в количестве, а в магии.

как сказал по Йорг W Миттаг отвечает на этот вопрос достаточно хорошо.

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

Так что 1024это не волшебство, потому что из контекста становится ясно, что это подходящее постоянное значение для конверсий.

Аналогично, пример:

var numDays = numHours / 24; 

одинаково ясен и не волшебен, потому что хорошо известно, что есть 24 часа в день.

Сообщество
источник
21
Но ... но ... 24 могут измениться! Земля замедляет свое вращение и в конечном итоге будет 25 часов! (Конечно, к тому времени мы все
14
Что происходит после развертывания вашего программного обеспечения на Марсе? Вы должны вводить эту константу ...
durron597
8
@ durron597: что делать, если ваша программа работает достаточно долго для замедления Земли в течение этого времени . Вы не должны вводить константу, а функцию, которая принимает метку времени (по умолчанию сейчас) и возвращает количество часов в день, когда метка времени падает ;-)
Стив Джессоп,
13
Я должен буду выучить ЯГНИ.
whatsisname
3
@ durron597 Ничего особенного не происходит, когда ваше программное обеспечение для хронометража развернуто на Марсе, потому что по соглашению дни на Марсе равны 24 часам, но каждый час на 2,7% длиннее, чем на Земле . Конечно, ни звездный день Земли, ни солнечный день Земли не составляют ровно 24 часа (точные цифры указаны на той же странице), так что вы все 24 равно не сможете их использовать ! Как упомянул Изката, високосные секунды больно Может быть, вам больше повезло бы использовать константу 24на Марсе, чем на Земле!
CVN
16

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

килобайт кибибайт

Так что уже я знаю, что автор или был смущен. Страница Википедии добавляет путаницы:

1000 = KB kilobyte (metric)
1024 = kB kilobyte (JEDEC)
1024 = KiB kibibyte (IEC)

Таким образом, «Килобайт» может использоваться как коэффициент 1000 и 1024, с единственной разницей в сокращении, являющейся капитализацией «k». Кроме того, 1024 может означать килобайт (JEDEC) или кибибайт (IEC). Почему бы не разрушить всю эту путаницу с помощью константы со значимым именем? Кстати, этот поток часто использовал «BYTES_PER_KBYTE», и это не менее неоднозначно. KBYTE: это KIBIBYTE или KILOBYTE? Я предпочел бы игнорировать JEDEC и иметь BYTES_PER_KILOBYTE = 1000и BYTES_PER_KIBIBYTE = 1024. Нет больше путаницы.

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

Если я вижу:

int BYTES_PER_KIBIBYTE = 1024;  
...  
var kibibytes = bytes / BYTES_PER_KIBIBYTE;  

Тогда сразу становится очевидно, что автор намеревался сделать, и нет никакой двусмысленности. Я могу проверить константу за считанные секунды (даже если она находится в другом файле), поэтому, даже если она не «мгновенная», она достаточно близка к мгновенной.

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

Shaz
источник
2
Хороший встречный ответ. Было бы сильнее, если бы вы приняли во внимание индивидуальную командную культуру. Если вы верите моему профилю SE , я достаточно взрослый, чтобы соответствовать этим конкретным стандартам. Таким образом, единственная путаница возникает из-за того, что является текущим (не) стандартным термином? И вы, вероятно, были бы в безопасности, предполагая, что я работаю с командой других динозавров, у которых у всех одинаковая терминологическая (не) сложность.
@ GlenH7: ИМХО, модули на основе степени двух должны были быть сохранены для хранения, так как они распределены по частям размером в две степени. Если минимальный размер выделения составляет 4096 байт, имеет ли смысл иметь единицу для объема хранилища, необходимого для хранения 256 файлов минимального размера, или для объема хранилища, необходимого для хранения 244,140625 таких файлов? Лично я считаю разницу между мегабайтами производителя жестких дисков и другими мегабайтами аналогичной разнице между диагональю телевизора и реальной диагональю дюйма.
суперкат
@Ryan: В этом конкретном случае я бы предпочел использовать стандартные единицы: KB - 1000 байт или код неправильный, а 1024 байта - KiB или код неправильный. Это единственный способ преодолеть проблему «единицы неоднозначны». Разные люди, определяющие «магические константы» (вроде KB) по-разному, не помогут.
Брендан
11

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

Тот факт, что в двух разных местах используется определенное буквальное значение (например, 1024), будет слабо свидетельствовать о том, что изменения, которые побудят программиста изменить одно, скорее всего, побудят программиста захотеть изменить другие, но это значение гораздо слабее, чем применимо если программист присвоил имя такой константе.

Основная опасность, связанная с чем-то подобным, #define BYTES_PER_KBYTE 1024заключается в том, что кому-то, кто сталкивается с этим, может быть предложено, printf("File size is %1.1fkB",size*(1.0/BYTES_PER_KBYTE));что безопасный способ заставить код использовать тысячи байтов - это изменить #defineвыражение. Однако такое изменение может привести к катастрофическим последствиям, если, например, какой-то другой не связанный код получит размер объекта в килобайтах и ​​использует эту константу при выделении для него буфера.

Возможно, было бы разумно использовать #define BYTES_PER_KBYTE_FOR_USAGE_REPORT 1024и #define BYTES_PER_KBYTE_REPORTED_BY_FNOBULATOR 1024, назначая разные имена для каждой другой цели, обслуживаемой константой 1024, но это привело бы к тому, что многие идентификаторы были бы определены и использованы ровно один раз. Кроме того, во многих случаях проще всего понять, что означает значение, если увидеть код, в котором он используется, и проще всего выяснить, где находится код, если он видит значения любых констант, используемых в нем. Если числовой литерал используется только один раз для определенной цели, написание литерала в том месте, где он используется, часто дает более понятный код, чем присвоение ему метки в одном месте и использование его значения в другом месте.

Supercat
источник
7

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

1024 также число КиБ на МиБ. Предположим, что мы используем 1024, чтобы также представить это вычисление где-то или в нескольких местах, и теперь нам нужно перейти на него, чтобы вместо этого вычислить GiB. Изменить константу проще, чем глобальный поиск / замена, где вы можете случайно изменить неправильный в некоторых местах или пропустить его в других.

Или это может быть даже небольшая маска, введенная ленивым программистом, которую нужно обновить однажды.

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

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

Ник П
источник