Обобщение использования переменных внутри кода

11

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

 Strings querycre,queryins,queryup,querydel; 
    querycre = 'Create table XYZ ...';
    execute querycre ;
    queryins = 'Insert into XYZ ...';
    execute queryins ;
    queryup  = 'Update  XYZ set ...';
    execute queryup;
    querydel = 'Delete from XYZ ...';
    execute querydel ;

и

 Strings query; 
    query= 'Create table XYZ ... ';
    execute query ;
    query= 'Insert into XYZ ...';
    execute query ;
    query= 'Update  XYZ set ...';
    execute query ;
    query= 'Delete from XYZ ...';
    execute query ;

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

Кроме того, слишком много переменных влияют на мою производительность?

PS: пожалуйста, не отвечайте на код в примере, просто чтобы передать то, что я имею в виду.

Shirish11
источник
Конечно, вы используете одну и ту же переменную ... потому что вы определили ее в функции. Вот для чего нужны функции.
zzzzBov

Ответы:

26

Необходимость задать себе этот вопрос - довольно сильный запах, который вы не соблюдаете за СУХОЙ (не повторяйте себя). Предположим, у вас есть это на гипотетическом языке фигурных скобок:

function doFoo() {
    query = "SELECT a, b, c FROM foobar WHERE baz = 23";
    result = runQuery(query);
    print(result);

    query = "SELECT foo, bar FROM quux WHERE x IS NULL";
    result = runQuery(query);
    print(result);

    query = "SELECT a.foo, b.bar FROM quux a INNER JOIN quuux b ON b.quux_id = a.id ORDER BY date_added LIMIT 10";
    result = runQuery(query);
    print(result);
}

Преобразуйте это в:

function runAndPrint(query) {
    result = runQuery(query);
    print(result);
}

function doFoo() {
    runAndPrint("SELECT a, b, c FROM foobar WHERE baz = 23");
    runAndPrint("SELECT foo, bar FROM quux WHERE x IS NULL");
    runAndPrint("SELECT a.foo, b.bar FROM quux a INNER JOIN quuux b ON b.quux_id = a.id ORDER BY date_added LIMIT 10");
}

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

tdammers
источник
2
Я просто люблю принцип СУХОЙ :)
artjom
1
@tdammers, хорошо ли, чтобы внутри функции было всего 2 строки кода? подумайте, есть ли у меня эта функция doFoo () {print (runQuery ("Selct a, b, c from XYZ"));}
Shirish11
1
Нет, стек вызовов не увеличивается - каждый вызов runAndPrintвыталкивает один кадр стека при его вызове, а затем возвращает его обратно при выходе из функции. Если вы вызываете его три раза, он выполняет три пары push / pop, но стек никогда не увеличивается более чем на один кадр за раз. Вы должны действительно беспокоиться о глубине стека вызовов с рекурсивными функциями.
tdammers
3
И функции с двумя строками кода прекрасно работают: если две строки составляют логическую единицу, то это две строки. Я написал множество однострочных функций, просто чтобы сохранить некоторую информацию изолированно и в одном месте.
tdammers
1
@JamesAnderson: Это несколько надуманный пример, но он служит иллюстрацией. Дело не в том, сколько у вас строк кода. Сколько раз вы констатируете один и тот же факт. В этом суть DRY, а также принцип «Единого источника истины», правило «Не
копируй
14

Обычно это плохая практика.

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

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

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

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

Одед
источник
А как насчет производительности системы?
Shirish11
@ Shirish11 - Возможно. Зависит от компилятора, языка, окружения и других переменных.
Одед
Обычно компилятор умеет оптимизировать это. Тем не менее, это всегда зависит от компилятора / plateform / конкретного случая / конфигурации.
Deadalnix
7

Самодокументированный код легче читать и поддерживать

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

Правильно структурированный код проще (а значит и дешевле) использовать (пере)

Кроме того , здесь , казалось бы , что queryвсегда используется для подготовки заявления перед его выполнением. Это, вероятно, признак того, что вы хотите реорганизовать часть этого кода в один (или несколько) вспомогательных методов для подготовки и выполнения запроса (в соответствии с принципом DRY ).

Таким образом, вы эффективно:

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

Примеры:

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

Ваш пример 1:

Strings querycre,queryins,queryup,querydel; 
    querycre = 'Create table XYZ ...';
    execute querycre ;
    queryins = 'Insert into XYZ ...';
    execute queryins ;
    queryup  = 'Update  XYZ set ...';
    execute queryup;
    querydel = 'Delete from XYZ ...';
    execute querydel ;

Ваш пример 2:

 Strings query; 
    query= 'Create table XYZ ...';
    execute query ;
    query= 'Insert into XYZ ...';
    execute query ;
    query= 'Update  XYZ set ...';
    execute query ;
    query= 'Delete from XYZ ...';
    execute query ;

Пример 3 (Рефакторированный псевдокод):

def executeQuery(query, parameters...)
    statement = prepareStatement(query, parameters);
    execute statement;
end

// call point:
executeQuery('Create table XYZ ... ');
executeQuery('Insert into XYZ ...');
executeQuery('Update  XYZ set ...');
executeQuery('Delete from XYZ ...');

Преимущество показывает с регулярным повторным использованием.

Персональный анекдот

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

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

Что это значит для меня?

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

По сути, это приводит к:

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

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

haylem
источник
2

С точки зрения дизайна кода

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

for currentQuery in queries:
    execute query;

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

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

С точки зрения производительности

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

Например, gcc -O2генерирует точно такой же двоичный файл, и единственное различие в производительности, о котором я знаю, это размер таблицы символов во время компиляции - совершенно незначительный, если вы не вернетесь во времени к 60-м годам.

Компилятор Java сгенерирует байт-код, который нуждается в большем объеме памяти для первой версии, но джиттер JVM все равно удалит его, поэтому, опять же, я подозреваю, что заметного влияния на производительность практически не будет, даже если вам потребуется высоко оптимизированный код.

дуб
источник
0

Я думаю, что повторное использование переменной в большинстве случаев нормально.

Для меня я просто использую переменную запроса большую часть времени. Я почти всегда выполняю запрос сразу после. Когда я не выполняю запрос сразу, я обычно использую другое имя переменной.

Эхо говорит Восстановить Монику
источник
-1

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

Джеймс
источник
Я только что привел простой пример, чтобы читателям было легче понять, что я ищу. Мой код намного сложнее, чем этот.
Shirish11
-2

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

{
    String query = 'Create table XYZ ...';
    execute query;
}
{
    String query = 'Insert table XYZ ...';
    execute query;
}
And so on...

(На данный момент, я мог бы рассмотреть решение tdammers .)

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

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

RalphChapin
источник
Я не согласен. Если вы предпочитаете второй пример для ясности, он должен быть реорганизован для последовательных вызовов вспомогательного метода. это должно передать больше смысла и потребовать меньше кода.
Хайлем
@haylem: В таком простом случае, как этот, вы добавляете вспомогательный метод, который кто-то, читающий код, должен найти и найти. (И у кого-то могут возникнуть проблемы с вспомогательным методом, и ему придется выяснить все места, из которых он вызывается.) Меньшая ясность, примерно столько же кода. В более сложном случае я бы пошел с моим решением, а затем с tdammer . Я отвечал на этот вопрос главным образом, чтобы указать на (по общему мнению, неясные, но интересные) проблемы, которые недостаточно используемые переменные представляют для людей и оптимизаторов.
RalphChapin
@haylem: вы и tdammer оба даете правильное решение. Я просто думаю, что это может быть излишним в некоторых случаях.
RalphChapin