Зачем объявлять переменные рядом с тем местом, где они используются?

10

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

Например, эта политика предложит мне сделать это:

foreach (var item in veryLongList) {
  int whereShouldIBeDeclared = item.Id;
  //...
}

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

int whereShouldIBeDeclared;
foreach (var item in veryLongList) {
  whereShouldIBeDeclared = item.Id;
  //...
}

Пожалуйста, кто-нибудь может объяснить?

Джеймс
источник
3
Это был бы чертовски плохой язык, который бы по-разному относился к этим двум случаям
Пол Томблин
5
Вы исходите из ложной предпосылки. Пожалуйста, смотрите мой ответ на этот вопрос: stackoverflow.com/questions/6919655/…
CesarGon
8
Если вы так думаете, вы не годитесь для оптимизации или даже рассмотрения влияния на производительность в целом. Языковые реализации умны, и если вы думаете, что это не так, то докажите это с помощью достоверных данных, полученных с помощью объективных, реалистичных тестов.
4
Если два примера кода имеют значительную семантическую разницу, то они делают разные вещи. Вы должны использовать тот, который делает то, что вы хотите сделать. Правило о том, где вы объявляете переменные, применяется только в тех случаях, когда оно не имеет смысловой разницы.
Дэвид Шварц
4
Рассмотрим противоположный конец шкалы - все является глобальной переменной. Разумеется, «заявленное близкое использование» - лучший конец этого спектра?
JBRWilkinson

Ответы:

27

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

Настоящая причина этого правила - также причина, по которой вы не понимаете, почему это правило. Раньше люди писали функции длиной в сотни, даже тысячи строк и писали их в текстовых редакторах (например, в «Блокноте») без поддержки Visual Studio. В этой среде объявление переменной в сотнях строк от места ее использования означало, что человек, читающий

if (flag) limit += factor;

не было много подсказок о том, что флаг, лимит и фактор были. Соглашения об именах, такие как венгерская нотация, были приняты, чтобы помочь с этим, как и правила, такие как объявление вещей, близких к тому, где они используются. Конечно, в наши дни все дело в рефакторинге, и функции, как правило, занимают менее одной страницы, что затрудняет очень большое расстояние между тем, где вещи объявляются, и тем, где они используются. Вы работаете в диапазоне от 0 до 20, и вы говорите, что, возможно, 7 - это нормально, в данном конкретном случае, в то время как парень, который создал правило, хотел бы получить LOVED на расстоянии 7 строк и пытаться отговорить кого-то от 700. И на Вдобавок к этому, в Visual Studio вы можете навести курсор мыши на что угодно и увидеть его тип, является ли он переменной-членом и так далее. Это означает, что необходимость видеть линию, заявляющую, что она уменьшена.

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

Кейт Грегори
источник
Спасибо за Ваш ответ. Но, безусловно, независимо от типа данных, новый экземпляр создается на каждой итерации, в зависимости от того, как я это делаю? Просто во втором случае мы не просим каждый раз новую ссылку на память. Или я упустил суть? И вы говорите, что оптимизатор C # автоматически улучшит мой код, когда он все равно будет компилироваться? Я этого не знал!
Джеймс
2
Затраты на создание int крошечные. Если бы вы строили что-то сложное, накладные расходы были бы более значительными.
Кейт Грегори
17
Вопрос не только в том, чтобы увидеть его тип и тому подобное. Это также вопрос жизни. Если переменная "wibble" объявлена ​​за 30 строк до ее первого использования, есть 30 строк, в которых ошибочное использование "wibble" может привести к ошибке. Если он объявлен непосредственно перед его использованием, то использование "wibble" в этих 30 предыдущих строках не приведет к ошибке. Это приведет к ошибке компилятора.
Майк Шеррилл 'Cat Recall'
В этом случае новый экземпляр создается не каждый цикл. Одна переменная верхнего уровня создается и используется для каждой итерации (посмотрите на IL). Но это деталь реализации.
thecoop
«В Visual Studio вы можете навести курсор мыши на все что угодно и увидеть» и т. д. Также имеется переход к определению, в котором есть ярлык, F12который необходим.
StuperUser
15

Определение переменной внутри цикла делает ее видимой только для этого цикла. Это имеет как минимум 3 преимущества для читателя:

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

Что касается бита эффективности, то компилятор разумно генерирует определение вне цикла в сгенерированном оптимизированном коде. Переменная не будет создаваться при каждой итерации цикла.

Без шансов
источник
4

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

инвариантный
источник
4

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

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

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

Карл Билефельдт
источник
1

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

Вы просто должны выбрать свой путь и следовать по нему.

Аурелио Де Роса
источник
2
Это не просто мнение, не так ли? Разве исследования программной инженерии не документировали связь между временем жизни и количеством ошибок, по крайней мере, с 1980-х годов?
Майк Шеррилл 'Cat Recall'
1

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

kylben
источник