Магические числа против названных констант

При написании кода, особенно при работе с датами и временем, вам нужно работать со множеством конкретных чисел, например: 60 секунд в минуту, 3600 = секунды в час.

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

например:

$x = time() + 3600;
$y = time() + 86400;
$z = time() + 604800;

// vs

define('MINUTE', 60);
define('HOUR',   60 * MINUTE);   // 3600
define('DAY',    24 * HOUR);     // 86400
define('WEEK',    7 * DAY);      // 604800

$x = time() + HOUR;
$y = time() + DAY;
$z = time() + WEEK;

Конечно, второй легче читать, но немного OTT для некоторых более низких значений, поэтому , где именно вы рисуете линию? Лично я не вижу проблем с читабельностью 86400 (в моей голове я автоматически читаю это как "24 часа" ), но нарисую линию на постоянной WEEK.

Ответы

Ответ 1

86400 - это не нормально, так как вы можете легко обмануть его как 84600, 88400 и т.д.

Ошибочная константа будет ошибкой компиляции

Ответ 2

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

В вашем примере я бы предпочел символические имена во всех случаях.

Ответ 3

Я бы поместил константы (или какой-нибудь cutey производный, как Rails '15.minutes) почти везде. Для меня это упрощает "типизацию" всего этого; если я увижу "10 * MINUTES" где-то в очереди, я знаю, что имею дело со временем (или кто-то, кто за задницу). Если я увижу 10 * 60 или 600, вполне возможно, что я не могу понять, что мы имеем дело со временем довольно легко.

Ответ 4

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

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

const int secondsInDay = 86400;

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

Ответ 5

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

long twelvePM = 12 * 60 * 60 * 1000L;
long timeout = 60 * 1000L;

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

Ответ 6

Я говорю своим ученикам следующее:

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

Я также говорю им:

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

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

Ответ 7

Лично я бы использовал SECS_PER_MIN, SECS_PER_HOUR и т.д. Я даже иногда использовал NANOS_PER_SEC. Я всегда это делал бы, если бы язык не имел научной нотации для целых литералов.

Это не о читаемости, точно. Причина использования SECS_PER_DAY, а не 86400 заключается не только в том, что общего знания мы ожидаем от читателя. Это о самодокументируемом коде, что означает недвусмысленно показ вашей работы.

Конечно, все знают, что есть 60 секунд в минуту. Но они также знают, что есть 60 минут в час. Если вы используете литерал 60, это, вероятно, очевидно, что ваш код должен делать. Но зачем рисковать?

Если вы используете SECS_PER_MIN, то, безусловно, очевидно, что вы конвертируете время в минутах (всего 1 минуту, в вашем примере) в секундомер. Например, вы не добавляете один час к времени, измеренному в минутах, или один градус в угол за считанные минуты. Вы не добавляете 5 футов в длину, измеренную в дюймах.

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

Именованные константы также помогают избежать опечаток. Конечно, все знают, что есть 86400 секунд в день. Не обязательно следует, что они не будут опечатывать это как 84600, или что они немедленно обнаружат ошибку, если кто-то другой. Конечно, у вас есть полное тестирование, поэтому такая ошибка никогда не попадет в поле, но наиболее эффективным способом исправить это является предотвращение ошибочного кода, позволяющего ему проверить в первую очередь. Поэтому я бы определил такие константы (или любой другой синтаксис):

SECS_PER_MIN := 60; 
SECS_PER_HOUR := 60 * SECS_PER_MIN;
SECS_PER_DAY := 24 * SECS_PER_HOUR;
SECS_PER_WEEK := 7 * SECS_PER_DAY;

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

SECS_PER_MIN := 60;
MINS_PER_HOUR := 60;
HOURS_PER_DAY := 24;
DAYS_PER_WEEK := 7;

SECS_PER_HOUR := SECS_PER_MIN * MINS_PER_HOUR;
etc.

Обратите внимание на порядок в RHS: минуты заметно "отменяют", делая работу еще более понятной. Не так уж важно со временем, но хорошо установить согласованную схему на ранней стадии, так что, когда что-то станет неприятно позже (CLOCKS_PER_SEC, PLANCK_LENGTHS_PER_PARSEC), вы можете получить это правильно, используя знакомые методы.

Ответ 8

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

// The base unit of time is the second
const double second = 1.0;
const double ns = 1e-9 * second;
const double micros = 1e-6 * second;
const double ms = 1e-3 * second;
const double minute = 60.0 * second;
const double hour = 60 * minute;
const double day = 24 * hour;
const double week = 7 * day;
const double year = 365.24 * day; 

тогда всегда используйте соответствующий блок в своем коде

// Set up a 90 second timeout
timeout(90*second);

или

elapsedDays = floor(elapsedtime / day);

Вы видите эту формулировку в различных научных пакетах (например Geant4) время от времени.

Ответ 9

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

Ответ 10

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

int someDelay = 1232323; // in milliseconds.

Ответ 11

Мое эмпирическое правило при обучении (так, более жесткие основные правила, чем в реальной жизни) было любым числом, отличным от -1, 0, 1 или 2, которое использовалось более одного раза. НЕОБХОДИМО быть константой. Если вы используете его только один раз, вы можете просто прокомментировать его, если хотите...

Ответ 12

Я бы нарисовал линию на основе размера проекта. Чем больше проект, тем больше абстракций и констант... Проще всего.

Ответ 13

У дня всегда будет 24 часа, а час будет всегда 60 минут.

Я думаю, что смысл называть их как описательные константы, где a) числовое значение нечеткое (604800?) б) значение может когда-нибудь измениться

Ответ 14

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

Ответ 15

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

Ответ 16

$x = time() + HOUR;
$y = time() + DAY;
$z = time() + WEEK;

Как насчет магических строк? Я обычно делаю так:

  $x = strtotime('now + 1 hour');
  $y = strtotime('now + 1 day');
  $z = strtotime('now + 1 week');

Да, он, вероятно, работает дробно медленнее. Но в большей схеме вещей, действительно ли это имеет значение?

Ответ 17

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

Мой подход представляет собой сочетание некоторых из предыдущих ответов:

// properly named variables
int daysBeforeFriday = 4;

// expanded math expressions
int minutesBeforeFriday = 4 * 24 * 60;

// 4 days in seconds (clarification comments)
int secondsBeforeFriday = 4 * 24 * 60 * 60;

// etc..

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

Извините меня за использование C в качестве примера этого последнего случая, но посмотрите здесь, чтобы понять, что я имею в виду.;)

Ответ 18

Я использую именованные константы

  • Если число будет отображаться в нескольких местах
  • Если число может меняться (новая конфигурация, новый проект)

Иногда я вставляю номер в качестве подсказки для чтения.

#define LPP57   57     // Lines per page

и др.

Ответ 19

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

$x = time() + HOUR;

едва ли лучше. Откуда мы знаем, что $x, функция time() и константа HOUR - это все числа в секундах? Ну, мы не можем переименовать функцию time(), но мы можем хотя бы написать

$x_seconds = время() + SECONDS_PER_HOUR;

и теперь это ясно, без необходимости комментировать.

В этом отношении, как мы узнаем, что вы не выполняете конкатентацию строк, то есть добавляете суффикс "am" или "pm". Вы могли бы сказать: "ну, никто в здравом уме не назвал бы это ЧАСОМ", но я не уверен.

Некоторые люди выступают за венгерскую нотацию для этой цели. Мне не нравится форма венгерского, который украшает имена с их базовым типом (int, handle и т.д.), Но мне нравится украшение с семантическим типом (в секундах).

Ответ 20

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

Константы скрыты внутри соответствующих названных методов и не повторяются (вы можете unit test тип/константы, если хотите).

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

например. для иллюстрации что-то вроде:

class Time
{
   private long time;

   Time(long hours, long minutes, long seconds, long milliseconds) { ... }
   Time(long milliseconds) { ... }

   Time addMilliseconds(long increment)
   {
      return new Time(time + increment));
   }

   Time addSeconds(long increment)
   {
      return addMilliseconds(increment * 1000);
   }

   Time addMinutes(long increment)
   {
      addSeconds(increment * 60);
   }       

   Time addHours(long increment)
   {
      addMinutes(increment * 60);
   }

   [...]

   long getTimeInMilliseconds() { ... };
   long getTimeInSeconds() { ... };
   long getTimeInMinutes() { ... };

   [.. maybe some static factory methods ...]
}

Что можно использовать следующим образом:

Time oneHourAhead = new Time(now()).addHours(1);
Time tenMinutesAgo = new Time(now()).addMinutes(-10);
Time oneHourTenMinutesAhead = new Time(now()).addHours(1).addMinutes(10);

Ответ 21

Я бы сказал, что ЧАСЫ, МИНУТЫ И ДЕНЬ хороши. Похоже, что более гранулированный не обеспечивает большей удобочитаемости.

Ответ 22

Работайте на самом высоком уровне абстракции.

Пример Ruby ActiveSupport

3.months.ago
1.year.from_now

Тот же принцип может быть применен и к другим менее гибким языкам, Java позволит вам сделать что-то вроде:

TimeHelper.months().ago(3);

Если у вас нет объектов на вашем языке, вы можете сделать это:

$x = strtotime('now + 1 hour');
$y = strtotime('now + 1 day');
$z = strtotime('now + 1 week');

Намного меньше места для неправильной интерпретации. Зачем возиться с вещами под проблемным доменом, если вам не нужно?