Включает ли «переменные в минимально возможную область видимости» случай «переменные не должны существовать, если возможно»?

32

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

public class Main {
    private A a;
    private B b;

    public ABResult getResult() {
        getA();
        getB();
        return ABFactory.mix(a, b);
    }

    private getA() {
      a = SomeFactory.getA();
    }

    private getB() {
      b = SomeFactory.getB();
    }
}

во что-то вроде этого:

public class Main {
    public ABResult getResult() {
        A a = getA();
        B b = getB();
        return ABFactory.mix(a, b);
    }

    private getA() {
      return SomeFactory.getA();
    }

    private getB() {
      return SomeFactory.getB();
    }
}

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

public class Main {
    public ABResult getResult() {
        return ABFactory.mix(getA(), getB());
    }

    private getA() {
      return SomeFactory.getA();
    }

    private getB() {
      return SomeFactory.getB();
    }
}

так что getResult()вообще не имеет локальных переменных. Это правда?

ocomfd
источник
72
Создание явных переменных имеет то преимущество, что им приходится называть их имена. Введение нескольких переменных может быстро превратить непрозрачный метод в читаемый.
Джаред Гогуэн
11
Честно говоря, пример с точки зрения имен переменных a и b слишком придуман, чтобы продемонстрировать значение локальных переменных. К счастью, вы все равно получили хорошие ответы.
Док Браун
3
Во втором фрагменте ваши переменные на самом деле не являются переменными . Они фактически являются локальными константами, потому что вы не изменяете их. Java-компилятор будет обрабатывать их одинаково, если вы определили их окончательно, потому что они находятся в локальной области видимости, и компилятор знает, что с ними произойдет. Это вопрос стиля и мнения, нужно ли вам использовать finalключевое слово или нет.
Хайд
2
@JaredGoguen Создание явных переменных сопряжено с бременем их именования и возможностью их именования.
Берги
2
Абсолютно ноль правил стиля кода, таких как «переменные должны находиться в наименьшей возможной области видимости», универсально применимы без размышлений. Таким образом, вы никогда не захотите основывать решение о стиле кода на аргументации формы в этом вопросе, где вы берете простое руководство и расширяете его в менее очевидную область («переменные должны иметь небольшие области действия, если это возможно» -> «переменные не должны существовать, если это возможно»). Либо есть веские причины, конкретно подтверждающие ваше заключение, либо ваше заключение является неуместным продолжением; в любом случае вам не нужно оригинальное руководство.
Бен

Ответы:

110

Нет. Есть несколько причин, почему:

  1. Переменные со значимыми именами могут облегчить понимание кода.
  2. Разбиение сложных формул на более мелкие этапы может облегчить чтение кода.
  3. Кэширование.
  4. Содержит ссылки на объекты, чтобы их можно было использовать более одного раза.

И так далее.

Роберт Харви
источник
22
Также стоит упомянуть: значение будет сохранено в памяти независимо, так что в любом случае оно фактически будет иметь одинаковую область видимости. Можно также назвать его (по причинам, которые Роберт упоминает выше)!
Может быть, Факт
5
@Maybe_Factor Руководства не существует по причинам производительности; речь идет о том, как легко поддерживать код. Но это все еще означает, что значимые локальные объекты лучше, чем одно огромное выражение, если вы просто не пишете хорошо названные и разработанные функции (например, нет причин делать это var taxIndex = getTaxIndex();).
Луаан
16
Все это важные факторы. То, как я на это смотрю: краткость - это цель; ясность - другое; надежность - это другое; производительность это другое; и так далее. Ни одна из них не является абсолютной целью, и они иногда конфликтуют. Таким образом, наша работа состоит в том, чтобы решить, как наилучшим образом сбалансировать эти цели.
Gidds
8
Компилятор позаботится о 3 и 4.
Стоп Harming Monica
9
Номер 4 может быть важен для правильности. Если значение вызова функции может измениться (например now(),) удаление переменной и вызов метода более одного раза может привести к ошибкам. Это может создать ситуацию, которая действительно тонкая и сложная для отладки. Это может показаться очевидным, но если вы выполняете слепую миссию по рефакторингу, чтобы удалить переменные, легко в конечном итоге ввести недостатки.
Джимми Джеймс
16

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

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

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

Например, если у вас есть

    a=getA();
    b=getB();
    m = ABFactory.mix(a,b);

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

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

JacquesB
источник
2
Конечно, к тому моменту, когда метод становится настолько длинным, что число локальных пользователей слишком велико, чтобы его было легко обслуживать, вы, вероятно, все равно захотите разбить его.
Луаан
4
Одним из преимуществ «посторонних» переменных, таких как var result = getResult(...); return result;то, что вы можете установить точку останова returnи узнать, что именно result.
Joker_vD
5
@Joker_vD: отладчик, достойный своего имени, должен в любом случае позволить вам делать все это (просматривать результаты вызова функции без сохранения в локальной переменной + установка точки останова при выходе из функции).
Кристиан Хакл
2
@ChristianHackl Очень немногие отладчики позволяют вам делать это, не устанавливая, возможно, сложные часы, для добавления которых требуется время (и если у функций есть побочные эффекты, это может сломать все). Я возьму версию с переменными для отладки в любой день недели.
Гейб Сечан
1
@ChristianHackl и на этом основании, даже если отладчик не позволяет вам сделать это по умолчанию, потому что отладчик ужасно спроектирован, вы можете просто изменить локальный код на своей машине, чтобы сохранить результат, а затем проверить его в отладчике. , Там до сих пор нет оснований для сохранения значения в переменной для всего этой цели.
Великая утка
11

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

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

Давайте посмотрим на ваш начальный getResultметод:

public ABResult getResult() {
    getA();
    getB();
    return ABFactory.mix(this.a, this.b);
}

Теперь имена getAи getBмогут предложить , что они будут назначать this.aи this.bмы не можем знать наверняка , от просто глядя getResult. Таким образом, возможно , что this.aи this.bзначение , передаваемое в mixметод , а не исходить от состояния thisобъекта, чем прежде getResultбыло вызвано, что невозможно предсказать , так как клиенты контролировать , как и когда методы называются.

В пересмотренном коде с локальными aи bпеременными ясно, что существует ровно один (без исключений) поток управления от присваивания каждой переменной ее использованию, потому что переменные объявляются непосредственно перед их использованием.

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

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

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

YawarRaza7349
источник
2

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

(reduce (fn [map string] (assoc map string (inc (map string 0))) 

Или какой-нибудь LINQ:

var query2 = mydb.MyEntity.Select(x => x.SomeProp).AsEnumerable().Where(x => x == "Prop");

Или Node.js:

 return visionFetchLabels(storageUri)
    .then(any(isCat))
    .then(notifySlack(secrets.slackWebhook, `A cat was posted: ${storageUri}`))
    .then(logSuccess, logError)
    .then(callback)

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

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

Трубный оператор F # |>полезен отчасти потому, что он позволяет писать вещи слева направо, которые в противном случае должны были бы быть справа налево.

pjc50
источник
2
Я использовал подчеркивание в качестве имени моей переменной в простых выражениях LINQ или лямбдах. myCollection.Select(_ => _.SomeProp).Where(_ => _.Size > 4);
Грэм
Не уверен, что это отвечает на вопрос OP в малейшей степени ...
AC
1
Почему отрицательные голоса? Похоже, хороший ответ для меня, даже если он не дает прямого ответа на вопрос, это все еще полезная информация
reggaeguitar
1

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

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

Рассмотрим функции ( методы ). Там нет ни разбиения кода на наименьшую возможную подзадачу, ни самого большого одиночного фрагмента кода.

Это смещающий предел с разделением логических задач на расходные части.

То же самое верно для переменных . Выделение логических структур данных на понятные части. Или также просто назвав (заявив) параметры:

boolean automatic = true;
importFile(file, automatic);

Но, конечно, с объявлением вверху и двумя сотнями строк, первое использование в настоящее время считается плохим стилем. Это ясно, что «переменные должны жить в наименьшей возможной области» намеревается сказать. Как очень близко "не используйте переменные повторно".

Joop Eggen
источник
1

Чего не хватает в качестве причины для НЕТ, так это отладки / читабельности. Код должен быть оптимизирован для этого, и четкие и краткие имена очень помогают, например, представьте 3 способа, если

if (frobnicate(x) && (get_age(x) > 2000 || calculate_duration(x) < 100 )

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

can_be_frobnicated = frobnicate(x)
is_short_lived_or_ancient = get_age(x) > 2000 || calculate_duration(x) < 100
if (can_be_frobnicated || is_short_lived_or_ancient )

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

Другим примером могут быть такие языки, как R, где последняя строка автоматически возвращает значение:

some_func <- function(x) {
    compute(x)
}

это опасно, возврат ожидается или нужен? это понятнее

some_func <- function(x) {
   rv <- compute(x)
   return(rv)
}

Как всегда, это суждение - исключить промежуточные переменные, если они не улучшают чтение, в противном случае сохраните или введите их.

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

Кристиан Зауэр
источник
0

Обращаясь только к вашему названию: абсолютно, если переменная не нужна, ее следует удалить.

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

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

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

Читаемость кода превосходит все, кроме правильной работы (правильно включает приемлемые критерии производительности, которые редко бывают «максимально быстрыми»).

jmoreno
источник