Является ли это использование символической константы излишним?

34

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

for (int i = 0; i < 8; i++){
    for (int j = 0; j < 8; j++){

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

for (int i = 0; i < CHESS_CONST; i++){
    for (int j = 0; j < CHESS_CONST; j++){

с каким-то лучшим именем символа, о котором я не могу думать прямо сейчас.

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

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

действительно нет необходимости в символической константе.

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

chocolatedevil
источник
46
CHESS_CONST намного хуже, чем просто использование числа 8, но константа с описательным именем была бы улучшением. Вы говорите, что любой должен знать, что означает 8 в коде, но это не так. Целочисленный литерал без контекста может означать любое количество вещей, например, количество ходов, количество фигур на доске и так далее. Описательное имя константы прояснит намерение и, следовательно, код будет легче понять.
JacquesB
43
Лично я нахожу гораздо более резким, чем магическое число, это имена iи jпеременные цикла. Я не могу на всю жизнь выяснить, какой из них должен представлять ранг, а какой - файл. Ранги варьируются от 1..8, а файлы - от a..h, но в вашем случае, оба iи jот 0..7, так что это не помогает мне понять, что есть что. Есть ли какой-то международный кризис нехватки писем, о котором я не знаю, или что не так с переименованием их в rankи file?
Йорг Миттаг
4
Сыграйте адвоката дьявола на секунду; Представьте, что вы читаете чей-то шахматный код, где он использовал магическое число 8. Можете ли вы предположить, что знаете, для чего он используется? Как ты можешь быть уверен на 100%? Есть ли вероятность, что это может означать что-то еще? Разве это не было бы немного лучше, если бы вам даже не приходилось делать предположения? Сколько времени вы могли бы потратить на прохождение кода, чтобы выяснить, правильно ли ваше предположение? Вы бы потратили меньше времени, если бы код был более самодокументированным, используя вместо этого содержательное, проницательное имя?
Бен Коттрелл
16
@JacquesB: Действительно. Там 8 рангов, 8 файлов, 8 пешек. Некоторые шахматные движки используют систему очков, в которой к определенным фигурам, определенным полям и определенным ходам привязаны очки, и двигатель выбирает ход с наибольшим количеством очков. 8 может быть такой счет. 8 может быть глубиной поиска по умолчанию. Это может быть что угодно.
Йорг Миттаг
9
Я хотел бы рассмотреть использование цикла foreach, например foreach(var rank in Ranks). Я также рассмотрел бы объединение обоих циклов в один, где каждый элемент представляет собой кортеж (rank, file).
CodesInChaos

Ответы:

101

ИМХО, твой друг прав в использовании символического имени, хотя я думаю, что имя определенно должно быть более наглядным (например, BOARD_WIDTHвместо CHESS_CONST).

Даже если число никогда не изменится в течение жизни программы, в вашей программе могут быть другие места, где число 8 будет иметь другое значение. Замена "8" на то, BOARD_WIDTHгде подразумевается ширина доски, и использование другого символического имени, когда подразумевается другое, делает эти различные значения явными, очевидными, и ваша программа в целом становится более читаемой и поддерживаемой. Это также позволяет вам выполнять глобальный поиск по вашей программе (или поиск по обратному символу, если ваша среда это обеспечивает) в случае, если вам нужно быстро определить все места в коде, которые зависят от ширины платы.

Смотрите также этот предыдущий пост SE.SE для обсуждения, как (или как не) выбирать имена для чисел.

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

Док Браун
источник
22
Я хотел бы добавить, что если ОП написал действительно хороший шахматный движок, то хотел бы расширить его, включив в него 3D-шахматы или другие варианты, то какие восьмерки нужно изменить, будет непростой задачей.
Стив Барнс
32
@SteveBarnes: я стараюсь избегать таких аргументов, так как это слишком легко приводит к обратному аргументу вроде: «Я думаю, что совершенно невозможно сделать эту программу игрой другого типа, поэтому я могу оставить магическое число 8 там, где оно есть. "
Док Браун
4
@IllusiveBrian Это классический аргумент «соломенного чучела» по двум причинам: (1) простое определение константы не означает, что программист все еще не может набрать «8» (случайно, дизайн или злой умысел!) И (2) просто потому, что есть константа BOARD_WIDTH = 8, не делает BOARD_WIDTH правильной константой для использования в определенном месте в программе.
alephzero
3
@Blrfl ... или приведет к чрезмерному совершенствованию кода. В обязанности разработчика входит осознание того, что может измениться в будущем, и соответствующий код. Кодирование, подобное требованиям, не изменится, порождает проблемы, но другая крайность не лучше.
Малкольм
4
@corsiKa Когда переменные цикла соответствуют физическим осям, они должны называться x, y или row, col или, для шахмат, file, rank.
user949300
11

Хорошо, вот несколько комментариев, которые у меня есть:

Избавиться от магических чисел - отличная идея. Существует концепция, известная как СУХОЙ, которая часто искажается, но идея заключается в том, что вы не дублируете знания концепций в своем проекте. Поэтому, если у вас есть класс с именем ChessBoard, вы можете сохранить константу с именем BOARD_SIZE или ChessBoard.SIZE. Таким образом, существует один единственный источник этой информации. Кроме того, это помогает удобочитаемости позже:

for (int i = 0; i < ChessBoard.SIZE; i++){
  for (int j = 0; j < ChessBoard.SIZE; j++){

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

Плохое имя хуже, чем отсутствие имени, но это не значит, что что-то не должно быть названо. Просто измените имя. Не выбрасывайте ребенка с водой из ванны. : p Имя может быть описательным, если вы хорошо понимаете, что оно описывает. Затем эта концепция может быть использована для нескольких разных вещей.

unflores
источник
8
это , кажется, просто повтор точек уже сделанные и объяснены в верхнем ответе
комар
-7

Что вам действительно нужно, так это исключить ссылки на тошноту до констант, независимо от того, названы они или нет:

for_each_chess_square (row, col) {
  /*...*/
}

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

8самоописывает; это не макрос, который обозначает что-то еще.

Ты никогда не собираешься превратить это в шахматную программу 9x9, и если ты когда-либо сделаешь, распространение 8 не будет главной трудностью.

Мы можем выполнить поиск по токену 8 в базе кода на 150 000 строк и определить, какие случаи означают то, что в секундах.

Гораздо важнее модульный код, чтобы знание шахмат концентрировалось как можно меньше. Лучше иметь один, два, может быть, три шахматно-специфических модуля, в которых встречается буквальное число 8, чем тридцать семь модулей, на которые возложена ответственность за шахматы, ссылаясь на 8 через символическое имя.

Когда или если эта 8 константа становится источником напряжения в вашей программе, вы можете легко это исправить в это время. Исправьте реальные проблемы, которые происходят сейчас. Если вы не чувствуете, что вам мешает именно эта восьмерка, следуйте этому инстинкту.

Предположим, что в будущем вы хотите поддерживать альтернативные размеры платы. В этом случае эти циклы должны измениться, независимо от того, используют ли они именованную константу или 8, потому что измерения будут получены с помощью некоторого выражения, подобного board.widthи board.height. Если у вас есть BOARD_SIZEвместо того 8, эти места будет легче найти. Так что это меньше усилий. Тем не менее, вы не должны забывать о усилии замены 8с BOARD_SIZEв первую очередь. Общее усилие не ниже. Создание одного прохода по коду для изменения 8к BOARD_SIZE, а затем другой , чтобы поддерживать альтернативные размеры, не дешевле , чем просто переход от 8альтернативной поддержки измерения.

Мы также можем взглянуть на это из чисто холодного, объективного анализа рисков / выгод. В программе есть голые константы. Если они заменены константой, то никакой пользы нет; Программа идентична. С любым изменением существует ненулевой риск. В этом случае он маленький. Тем не менее, ни один риск не должен быть взят без выгоды. Чтобы «продать» изменения в свете этих рассуждений, мы должны предположить выгоду: будущую выгоду, которая поможет с другой программой: будущую версию программы, которой сейчас не существует. Если такая программа планируется, эта гипотеза и связанные с ней рассуждения являются добросовестными и должны приниматься всерьез.

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

Если вы когда-либо работаете с коммерческим программным обеспечением, аргументы ROI также будут применяться. Если программа не продается, а изменение некоторых жестко закодированных чисел на постоянные не улучшит продажи, вы не получите компенсацию за эти усилия. Изменение имеет нулевую отдачу от затрат времени. Аргументы ROI обобщают за деньги. Вы написали программу, вложив время и усилия, и получили из этого что-то: это ваше возвращение, ваше «R». Если, сделав это изменение в одиночку, вы получите больше этого «R», что бы это ни было, тогда непременно. Если у вас есть какой-то план дальнейшего развития, и это изменение улучшает ваш «R», то же самое. Если изменение не имеет немедленного или прогнозируемого «R» для вас, забудьте об этом.

Kaz
источник
13
8это не самоописание, это число, которое может означать что угодно. А может, они хотят сыграть в шахматы 9х9, почему бы и нет? Если у вас есть 150 000 строк кода, вы никак не сможете запомнить, что каждый из них 8означает в коде, и даже если бы вы могли, с чего бы это? Это просто много лишних хлопот. Что касается модульности, замена 8на что-то подобное NUM_RANKSне вредит модульности, на самом деле, это помогает, потому что облегчает работу с кодом.
Jerfov2
4
@ Каз я тоже так делаю. И затем я привожу значительные странные ошибки, потому что несколько случаев использования не были такими, как я думал, потому что трудно уделять столько внимания большому количеству замен, а также быть правильными каждый раз. По этой причине я избегаю попадания в этот сценарий в первую очередь, поэтому я не могу поддержать совет, который оправдывает попадание в него.
doppelgreener
1
«Тем не менее, вы не должны забывать о усилии замены 8с BOARD_SIZEв первую очередь» - время , которое вы потратили разглядывая над кодом , пытаясь понять , что каждый 8делает в вашей программе месяцев теперь, скорее всего , больше времени , затрачиваемого на смену 8с значимая константа уровня модуля.
Кристиан Дин,
1
@doppelganger Этот сценарий уже здесь. Я не говорю, взять шахматную программу , которая использует константы, и заменить их 8. И я не говорю, «план не использовать константы в пока еще не написанной шахматной программы»
Kaz
1
@Kaz Почему вы хотите заставить себя читать весь код вокруг восьмерки, и, может быть, иногда неправильно понимаете контекст, когда вы можете просто потратить дополнительные 3 секунды, давая содержательное имя? Что касается знания действительного значения константы, то гораздо проще найти точное значение для переменной, чем пытаться задом наперед
понять, что