Чи “змінні повинні жити в найменшому обсязі”, включає випадок “змінні не повинні існувати, якщо це можливо”?


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()взагалі немає локальних змінних. Це правда?


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.
Зупиніть шкодити Моніці

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

16

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

Я не бачу переваги змінних aі bу вашому прикладі, тому я писав би версію без змінних. З іншого боку, функція, в першу чергу, така проста, я не думаю, що це має велике значення.

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

Наприклад, якщо у вас є

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

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

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


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

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

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

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


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 # |>корисний частково, оскільки дозволяє писати речі зліва направо, які в іншому випадку мали б бути справа наліво.


2
Я прийняв використовувати підкреслення як своє ім'я змінної у простих виразах LINQ або лямбдах. myCollection.Select(_ => _.SomeProp).Where(_ => _.Size > 4);
Грем

Не впевнений, що це відповідає на питання ОП у найменшій формі
AC

1
Чому голоси? Мені здається, що це хороша відповідь, навіть якщо це не відповідає безпосередньо на питання, це все-таки корисна інформація
reggaeguitar

1

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


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

1

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

Це межа зрушення, що розмежовує логічні завдання, на витратні частини.

Те ж саме стосується змінних . Вказуючи логічні структури даних на зрозумілі частини. Або також просто назвати (вказати) параметри:

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

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


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

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

Але "непотрібне" не означає, що еквівалентну програму можна записати без використання змінної, інакше нам сказали б, що ми повинні писати все у двійковому.

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

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

Читання коду перемагає все, крім правильної роботи (правильно включає прийнятні критерії продуктивності, які рідко "як можна швидше").

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