Узагальнення використання змінних всередині коду


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: будь ласка, не відповідайте на код wrt, наприклад, це було просто для того, щоб передати те, що я насправді маю на увазі.


Звичайно, ви повторно використовуєте ту саму змінну ... тому що ви її визначили у функції. Ось для чого призначені функції.
zzzzBov

Відповіді:


26

Необхідність задати собі це питання - це досить сильний запах, який ви не стежите за DRY (не повторюйте себе). Припустимо, у вас це є гіпотетичною фігурною дужкою:

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");
}

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


2
Я просто люблю принцип DRY :)
artjom

1
@tdammers чи добре мати лише 2 рядки коду у функції? Подумайте, чи є у мене функція doFoo () {print (runQuery ("Selct a, b, c від XYZ"));}
Shirish11,

1
Ні, стек виклику не збільшується - кожен виклик runAndPrintнатискає на один кадр стека, коли ви його викликаєте, а потім вискакує назад, коли функція закінчується. Якщо ви будете називати це три рази, він буде робити три push / pop пари, але стек ніколи не зростає більш ніж на один кадр одночасно. Вам слід потурбуватися лише про глибину стеку викликів за допомогою рекурсивних функцій.
tdammers

3
І функції, що мають лише два рядки коду, прекрасно: якщо два рядки складають логічну одиницю, то це два рядки. Я написав безліч функцій для одного вкладиша, просто щоб тримати трохи інформації в одному місці.
tdammers

1
@JamesAnderson: Це дещо надуманий приклад, але він служить для ілюстрації точки. Справа не в тому, скільки у вас є рядків коду. Це скільки разів ви констатуєте той самий факт. Ось що стосується DRY, а також принцип єдиного джерела істини, правило
Thou Shalt

14

Зазвичай це погана практика.

Повторне використання змінної таким чином може зробити код, який заплутається для розуміння читання.

Ті, хто читає код, не очікують, що змінна буде повторно використана таким чином і не знатиме, чому значення, встановлене на початку, має інше значення в кінці функції.

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

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


як щодо продуктивності системи вона впливає на неї?
Shirish11

@ Shirish11 - Це може. Залежить від компілятора, мови, середовища та інших змінних.
Одід

Зазвичай компілятор добре оптимізує це. Однак це завжди залежить від компілятора / форми планшета / конкретного випадку / конфігурації.
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, що працював з обмеженою екранністю нерухомості, тому повторне використання змінних мало сенс як для компільованого коду (тоді), так і для того, щоб дозволити читати більше коду відразу.

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

Що це для мене?

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

За розширенням це призводить до:

  • ви пишете короткі функції,
  • з чітко визначеними цілями,
  • які легше зрозуміти,
  • повторно використовувати,
  • продовжити (за спадщиною OO чи функціональним ланцюгом),
  • і документ (як уже самодокументування).

Я не кажу, що тут немає користі для змінного стану, я просто вказую, як звичка може зростати на вас і як це впливає на читабельність коду.


2

З точки зору дизайну коду

Загалом, повторно використовувати змінні для зберігання різних значень - зрештою, саме тому їх називають змінними, оскільки значення, що зберігаються в них, змінюються - до тих пір, поки значення є не тільки одного типу, але й означає те саме . Наприклад, звичайно, тут повторно використовувати currentQueryзмінну:

for currentQuery in queries:
    execute query;

Природно, що існує цикл, тому вам доведеться повторно використовувати змінну, але навіть якби не було циклу, було б добре. Якщо значення не означає те саме, використовуйте окрему змінну.

Зокрема, код, який ви описуєте, виглядає не дуже добре - він повторюється . Набагато краще використовувати цикл або допоміжний метод викликів (або обох). Особисто я дуже рідко бачив виробничий код, схожий або на вашу 1-ю чи 2-ю версії, але у тих випадках, які я маю, я думаю, що друга версія (повторне повторне використання) була більш поширеною.

В умовах виконання

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

Наприклад, gcc -O2генерує саме таку саму двійкову, і єдина, про яку я знаю різниця в продуктивності, - це розмір таблиці символів під час компіляції - абсолютно мізерно, якщо ви не повернетесь до 60-х.

Компілятор Java генерує байт-код, який потребує більшої кількості пам’яті для 1-ї версії, але тремтіння JVM все одно видалить його, тому, знову ж таки, я підозрюю, що практично не буде помітного впливу на продуктивність, навіть якщо вам потрібен високооптимізований код.


0

Я думаю, що повторне використання змінної є прекрасним більшість часу.

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


-1

Це може збільшити використання стека, якщо ваш компілятор особливо німий. Особисто я не думаю, що наявність окремої змінної для кожного запиту не додає будь-якої читабельності, вам все-таки потрібно насправді подивитися рядок запитів, щоб побачити, що це робить.


Я щойно подав простий приклад, щоб читачам було легше зрозуміти, чого я прагну. Мій код набагато складніший за цей.
Shirish11

-2

У прикладі я б пішов з другим прикладом. І читачеві, і оптимізаторам цілком зрозуміло, що ви робите. Перший приклад - трохи правильніший, і я дещо складнішим кодом користуюся ним, але роби це так:

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

(На даний момент я можу розглянути рішення тадмерів .)

Проблема першого прикладу полягає в тому, що querycreвін охоплює весь блок, який може бути обширним. Це може заплутати когось, хто читає код. Це також може заплутати оптимізатори, які можуть залишити зайву пам'ять, тому запит querycreбуде доступний пізніше (якщо це не так). З усіма дужками, queryзберігається лише в регістрі, якщо це.

З такими фразами, як "Створити таблицю" та "Виконати", на мене це не схоже на те, що тут буде помічено додаткове записування пам'яті, тому я б тільки помилився в коді, що заплутав читача. Але зручно бути в курсі цього, якщо ви пишете код, де швидкість має значення.


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

@haylem: У реальному простому випадку, як у цьому, ви додаєте допоміжний метод, який хтось, читаючи код, повинен знайти та знайти. (А у когось можуть виникнути проблеми з допоміжним методом і доведеться з’ясувати всі місця, з яких він викликаний.) Менша чіткість, приблизно стільки ж коду. У більш складному випадку я б пішов зі своїм рішенням, а потім із tdammer 's. Я відповів на це питання здебільшого, щоб вказати на (правда, незрозумілі, але цікаві) проблеми недостатньо використовуваних змінних, які ставлять перед людиною та оптимізаторами.
RalphChapin

@haylem: ви і тдаммер обидва даєте правильне рішення. Я просто думаю, що це може бути надмірно в деяких випадках.
RalphChapin
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.