Должен ли я повторно использовать переменные?

59

Должен ли я повторно использовать переменные?

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

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

Лучшие практики даже против обнуления предыдущих переменных (в Java есть Sonar, который выдает предупреждение при назначении nullпеременной, что вам не нужно делать это для вызова сборщика мусора с Java 6. Вы не всегда можете контролировать какие предупреждения отключены; большую часть времени по умолчанию включено.)

IAdapter
источник
11
С Java 6? Вам никогда не нужно было присваивать переменным значение null в любой версии Java. Когда переменная выходит из области видимости, она освобождает ссылку на свой объект.
Кевин Панко
35
повторное использование переменных - это одна из первых вещей, которые используют обфускаторы кода
Lelouch Lamperouge
7
Повторное использование переменной конфликтует с выбором подходящих идентификаторов для ваших переменных. В современных языках нет действительно никаких преимуществ для повторного использования переменных, которые перевешивают преимущества наличия хороших идентификаторов.
Джорен
4
Также обратите внимание, что не все повторное использование является повторным использованием. Старые программисты на ФОРТРАНе, даже те, кто перешел на другие языки, обычно используют I, J, K, L, M, N (или i, j, k, l, m, n) для всех наших циклов DO (для петли) счетчики. Мы привыкли видеть такие вещи, как SUM = SUM + A (I, K) * B (K, J) или sum + = a [i] [k] * b [k] [j] ;.
Джон Р. Штром
1
Повторное использование переменных может быть оправдано при программировании микроконтроллеров с очень ограниченными ресурсами (несколько килобайт ОЗУ).
м.Алин

Ответы:

130

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

Лучшая практика IMO - использовать достаточно короткие методы, которые делают только одно, устраняя всю проблему.

Петер Тёрёк
источник
как насчет юнит-тестирования? например, мне нужно проверить, если после выполнения метода во второй раз он все еще работает, как ожидалось.
IAdapter
20
@IAdapter, код модульного тестирования - это другая проблема, где стандарты могут быть более мягкими, чем для производственного кода. Тем не менее, сделать его читабельным и простым в обслуживании является первоочередной задачей. Вы можете (и должны) извлекать методы из своих модульных тестов, чтобы они были короткими и легкими для чтения (и чтобы исключить необходимость повторного использования переменных).
Петер Тёрёк
3
@IAdapter, для сценария, который вы описали, я бы не использовал переменную повторно. Подходящим было бы что-то вроде того, result1 = foo(); result2 = foo(); Assert.AreEqual(result1, result2);что я не вижу много мест, где вам нужно будет повторно использовать переменную в этом случае.
JSB ձոգչ
1
@IAdapter for (int i = 0; i <2; i ++) {result = foo (); assertEquals (Ожидаемый результат, результат);}
Билл К
2
+1 - это хороший запах кода для того, когда делать меньшие методы.
49

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

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

Танос Папатанасиу
источник
19

По-разному.

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

void my_function() {
    HRESULT errorcode;
    errorcode = ::SomeWindowsApiFunction();
    if (FAILED(errorcode)) { /* handle error */ }
    errorcode = ::AnotherWindowsApiFunction();
    if (FAILED(errorcode)) { /* handle error */ }
}

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

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

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

Феликс Домбек
источник
15

Самая большая причина, по которой я не использую переменные (особенно в модульных тестах), заключается в том, что они вводят ненужный путь к коду, который сложно тестировать и отлаживать. Хороший модульный тест должен быть независим от других тестов, и когда вы повторно используете переменную уровня класса (экземпляра) в модульном тестовом модуле, вы должны убедиться в том, что они утверждают свое состояние перед каждым тестом. Хороший модульный тест также изолирует дефекты, поэтому теоретически каждый тестовый пример (метод) должен утверждать только 1 поведение для тестируемой системы. Если ваши методы тестирования написаны таким образом, редко возникает необходимость или преимущество в повторном использовании переменной уровня метода. Наконец, в языках, которые поддерживают замыкания и асинхронную обработку, действительно трудно рассуждать о том, что происходит, если вы повторно используете переменные в методе.

sbrenton
источник
поэтому я должен написать методы тестирования, такие как -testSomething и assert для одиночного вызова метода -testSomethingTwoInvocations и утверждать только для второго вызова?
IAdapter
2
Да. Это поможет вам точно определить, не прошла ли первая или вторая итерация. В этом может помочь mspec, его дерево наследования будет очень полезным.
Брайан Бетчер
Если вы используете «переменную», когда имеете в виду «переменная класса» или «переменная экземпляра», вам нужно выразить себя более четко.
gnasher729
13

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

РЕДАКТИРОВАТЬ: повторное использование переменных иногда является признаком нарушения принципа единой ответственности . Проверьте, используется ли повторно используемая переменная в той же роли. Если это так, он может вообще не использоваться повторно (хотя все же может быть предпочтительнее иметь две разные переменные, чтобы ограничить область действия указанных переменных). Если он используется в разных ролях, у вас есть нарушение SRP.

SL Barth - Восстановить Монику
источник
4

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

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

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

Йорис Тиммерманс
источник
17
-1, если вы не можете указать на реальную ситуацию, когда это действительно происходит, и где повторное использование переменной имеет существенное значение. Это теоретическое решение теоретической проблемы, но не очень хорошее. Где компилятор решает поместить переменные, это проблема компилятора; если ваш компилятор генерирует плохой код и если он действительно имеет значение, то исправьте или замените компилятор.
Калеб
3
@Caleb - у вас не всегда есть доступ к исходному коду компилятора для платформы, на которой вы разрабатываете, особенно для проприетарных встроенных платформ. Я также ДЕЙСТВИТЕЛЬНО сказал в своем ответе, что я не знаю ни одного из текущих основных компиляторов (или фактически интерпретаторов / виртуальных машин), которые не правильно выполняют этот анализ жизнеспособности во всех случаях, на которые я смотрел. Тем не менее, я уже работал на собственных встраиваемых систем с настраиваемыми компиляторов , которые были очень голые кости в прошлом (10 лет назад или около того ). Система была очень ограничена в возможностях, как и компилятор, так что такая ситуация возникла там.
Йорис Тиммерманс
2
+1 Я сделал именно такое повторное использование в прошлом проекте (высокооптимизированный медиа-код, написанный на ANSI C). Насколько я помню, разница была легко заметна в сборке и измерима в тестах. Никогда не делал и надеюсь, что никогда не придется делать такое повторное использование в Java.
комнат
@Caleb исправить или заменить компилятор может не быть вариантом по ряду причин, и вы просто должны делать то, что у вас есть.
@ ThorbjørnRavnAndersen Возможно ли, что кто-то может быть вынужден использовать паршивый компилятор, и, кроме того, производительность может быть настолько критичной, что вам нужно сначала угадать компилятор и манипулировать вашим кодом, чтобы повторно использовать регистр? Да. Это вероятно ? Нет. MadKeithV соглашается, что он не может привести пример из реальной жизни. Это лучшая практика ? Это хороший стиль ? Это показатель качества кода ? Точно нет. Это полезный ответ на вопрос, как написано? Из-за уважения к MadKeithV, но я не думаю, что это так.
Калеб
2

Я бы сказал, нет.

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

Фортран
источник
Если ваш дамп ядра получен из оптимизированной сборки, переменные могут в любом случае иметь общую память. Так что это менее полезно, чем могло бы быть.
Уинстон Эверт
Конечно, оптимизированная сборка не очень полезна для отладки! но в любом случае у вас все еще есть возможность использовать отладочную сборку ... А когда у вас подключен отладчик, вам не нужно идти шаг за шагом от начала функции, чтобы увидеть, как меняются переменные, потому что они нет! :-D
Фортран
2

Нет, вы не улучшаете код, который работает на машине (... код сборки). Оставьте повторное использование любой памяти, которую компилятор использует для переменной, компилятору. Довольно часто это будет регистр, и повторное использование ничего не купит. Сосредоточьтесь на том, чтобы сделать код проще для чтения.

Rawbert
источник
0

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

scrote = int (scrote)

Но в конечном итоге я хотел бы изменить тип, отправляемый в функцию, и заметить изменение типа параметра.

Paddy3118
источник
1
Помещение строки типа «scrote = int (scrote)» в середине длинного пакета кода - отличный способ свести с ума программистов по техническому обслуживанию.
Джоккинг
Проблема, скорее всего, связана с упомянутой вами «длинной партией кода».
Paddy3118
Как отмечается в принятых ответах, повторное использование переменных действительно является проблемой только для длинных пакетов кода. В принципе, в любой ситуации, когда вы пишете что-то вроде «scrote = int (scrote)», я бы предпочел поместить int (scrote) в новую переменную или, что еще лучше, передать int (scrote) новой функции.
Джоккинг
0

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

Ваш отладчик также может затруднить отладку функции со многими переменными. Если отладить функцию с 20 переменными труднее, чем функцию с 16 переменными, то вы можете рассмотреть возможность замены 5 переменных одной. («Могу рассмотреть» - это не то же самое, что «должен всегда»).

Можно использовать одну переменную в нескольких местах, если переменная всегда имеет одно и то же назначение. Например, если десять вызовов функций возвращают код ошибки, который обрабатывается немедленно для каждого вызова, используйте одну переменную, а не 10. Но не используйте одну и ту же переменную для совершенно разных вещей. Подобно использованию «имени» для имени клиента и 10 строкам позже, использующим ту же переменную для названия компании, это плохо и может привести к неприятностям. Хуже того, используя «customerName» для имени клиента, и через 10 строк, используя ту же переменную «customerName» для названия компании.

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

gnasher729
источник
0

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

Ваш отладчик также может затруднить отладку функции со многими переменными. Если отладить функцию с 20 переменными труднее, чем функцию с 16 переменными, то вы можете рассмотреть возможность замены 5 переменных одной. («Могу рассмотреть» - это не то же самое, что «должен всегда»).

Можно использовать одну переменную в нескольких местах, если переменная всегда имеет одно и то же назначение. Например, если десять вызовов функций возвращают код ошибки, который обрабатывается немедленно для каждого вызова, используйте одну переменную, а не 10. Существует одна проблема с этим: обычно компилятор сообщит вам, когда вы используете неинициализированную переменную. Но здесь, если вы прочитаете код ошибки после вызова 6, но переменная фактически не изменилась после вызова 5, вы получите бесполезные данные без предупреждения компилятора. Поскольку переменная была инициализирована, с данными, которые теперь бесполезны.

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

gnasher729
источник
-2

Используйте глобальные переменные, когда вам нужно поддерживать состояние или хранить константы. Повторно используя переменные, вы только повторно используете имя, которое указывает на место в памяти. Описательные имена при инициализации могут только выиграть ясность (при условии Java и без примитивного OCD).

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

JunoA
источник