Тимчасові змінні та вимоги до довжини рядка


10

Я читав Рефакторинг Мартіна Фаулера . Це, як правило, чудово, але, схоже, одна з рекомендацій Фоулера викликає невеликі проблеми.

Фоулер рекомендує замінити тимчасові змінні на запит, тож замість цього:

double getPrice() {
    final int basePrice = _quantity * _itemPrice;
    final double discountFactor;
    if (basePrice > 1000) discountFactor = 0.95;
    else discountFactor = 0.98;
    return basePrice * discountFactor;
}

ви використовуєте хелперний метод:

double basePrice() {
    return _quantity * _itemPrice;
}

double getPrice() {
    final double discountFactor;
    if (basePrice() > 1000) discountFactor = 0.95;
    else discountFactor = 0.98;
    return basePrice() * discountFactor;
}

Загалом я згоден, за винятком того, що я використовую тимчасові змінні, коли рядок занадто довгий. Наприклад:

$host = 'https://api.twilio.com';
$uri = "$host/2010-04-01/Accounts/$accountSid/Usage/Records/AllTime";
$response = Api::makeRequest($uri);

Якби я спробував це вкласти, рядок тривав би більше 80 символів.

По черзі я закінчую ланцюжками коду, які самі по собі не так просто читати:

$params = MustacheOptions::build(self::flattenParams($bagcheck->getParams()));

Які стратегії поєднання двох?


10
80 символів - це приблизно 1/3 з моїх моніторів. ви впевнені, що дотримуватися 80 лінійок знаків все ж варто для вас?
jk.

10
так, дивіться, наприклад, програмісти.stackexchange.com
questions/604/…

Хоча ваш $hostі $uriприклад надуманий - якщо хост не читався з налаштувань чи іншого вводу, я вважаю за краще, щоб вони знаходилися на одній лінії, навіть якщо він завертається або йде з краю.
Ізката

5
Не потрібно бути таким догматичним. Книга - це перелік прийомів, які можна використовувати, коли вони допомагають, а не набір правил, які ви повинні застосовувати всюди, щоразу. Сенс у тому, щоб зробити свій код більш ретельним та легшим для читання. Якщо рефактор цього не робить, ви не використовуєте його.
Шон Мак-Що-небудь

Хоча я думаю, що обмеження 80 символів трохи завищене, аналогічна межа (100?) Є розумною. Наприклад, мені часто подобається програмування на портретно-орієнтованих моніторах, тому надзвичайно довгі рядки можуть дратувати (принаймні, якщо вони звичайні).
Томас Едінг

Відповіді:


16

Як
1. Обмеження довжини рядків існують, щоб ви могли бачити + зрозуміти більше коду. Вони все ще діють.
2. Підкресліть судження щодо сліпої конвенції .
3. Уникайте тимчасових змінних, якщо не оптимізувати ефективність .
4. Уникайте використання глибоких відступів для вирівнювання у багаторядкових операторах.
5. Розбийте довгі висловлювання на кілька рядків уздовж меж ідеї :

// prefer this
var distance = Math.Sqrt(
    Math.Pow(point2.GetX() - point1.GetX(), 2) + // x's
    Math.Pow(point2.GetY() - point1.GetY(), 2)   // y's
);

// over this
var distance = Math.Sqrt(Math.Pow(point2.GetX() -
    point1.GetX(), 2) + Math.Pow(point2.GetY() -
    point1.GetY(), 2)); // not even sure if I typed that correctly.

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

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

Наведені вами приклади (Java та ... PHP?) Дозволяють створювати багаторядкові оператори. Якщо лінії виходять довгими, розбийте їх. Джерело jquery доводить це до крайності. (Перше твердження працює в рядку 69!) Не те, що я обов'язково погоджуюся, але є й інші способи зробити свій код читабельним, ніж використання тимчасових змін.

Деякі приклади
1. Посібник зі стилів PEP 8 для python (не найкрасивіший приклад)
2. Пол М Джонс у керівництві щодо стилю Груші (посеред дорожнього аргументу)
3. Довжина лінії Oracle + умовні умови (корисні стратегії для збереження до 80 символів)
4. MDN Java Practices (наголошує на судженнях програміста щодо конвенції)


1
Інша частина проблеми полягає в тому, що тимчасова змінна часто переживає своє значення. Не проблема в блоках з невеликим діапазоном, але у більших, так, велика проблема.
Росс Паттерсон

8
Якщо ви турбуєтесь про тимчасове внесення змін, поставте про це const.
Томас Едінг

3

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

(будь ласка, виправте мене, якщо я помиляюся)


3

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

Дозвольте мені переглянути приклади, які ви розмістили.

$host = 'https://api.twilio.com';
$uri = "$host/2010-04-01/Accounts/$accountSid/Usage/Records/AllTime";
$response = Api::makeRequest($uri);

Моє зауваження полягає в тому, що всі виклики Twilio api починатимуться з "https://api.twilio.com/2010-04-1/", і, таким чином, існує дуже очевидна функція багаторазового використання:

$uri = twilioURL("Accounts/$accountSid/Usage/Records/AllTime")

Насправді я вважаю, що єдиною причиною для створення URL-адреси є здійснення запиту, тому я б зробив:

$response = TwilioApi::makeRequest("Accounts/$accountSid/Usage/Records/AllTime")

Насправді, багато URL-адрес насправді починаються з "Accounts / $ accountSid", тому я, мабуть, витяг би це також:

$response = TwilioApi::makeAccountRequest($accountSid, "Usage/Records/AllTime")

І якщо ми зробимо twilio api об'єктом, який містить номер рахунку, ми могли б зробити щось на кшталт:

$response = $twilio->makeAccountRequest("Usage/Records/AllTime")

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

Давайте подивимось на інший

$params = MustacheOptions::build(self::flattenParams($bagcheck->getParams()));

Тут я б подумав про будь-яке:

$params = MustacheOptions::buildFromParams($bagcheck->getParams());

або

$params = MustacheOptions::build($bagcheck->getFlatParams());

або

$params = MustacheOptions::build(flatParams($backCheck));

Залежно від того, яка ідіома є більш багаторазовою.


1

Власне, я не погоджуюсь із відомим містером Фаулером з цього приводу в загальному випадку.

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

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

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

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

double? _basePrice; //not sure if Java has C#'s "nullable" concept
double basePrice(bool forceCalc)
{
   if(forceCalc || !_basePrice.HasValue)
      return _basePrice = _quantity * _itemPrice;
   return _basePrice.Value;
}

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


1

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

Наприклад, власний приклад наводить:

double getPrice() {
    final double discountFactor;
    if (basePrice() > 1000) discountFactor = 0.95;      <--- first call
    else discountFactor = 0.98;
    return basePrice() * discountFactor;                <--- second call
}

Зрозуміло, що обидві _quantityі _itemPriceє глобальними змінними (або принаймні рівнем класу), і тому існує потенціал для їх модифікації поза межамиgetPrice()

Тому для першого дзвінка є basePrice()можливість повернути інше значення від другого дзвінка!

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


Ви також повинні уникати скорочення ad absurdum - чи слід обчислення discountFactorвилучення з методу? Тож ваш приклад стає:

double getPrice()
{
    final double basePrice      = calculateBasePrice();
    final double discountFactor = calculateDiscount( basePrice );

    return basePrice * discountFactor;
}

Розмежування за певним рівнем фактично робить код менш читабельним.


+1 - зробити код менш читабельним. Над розділенням можна приховати ділову проблему, яку намагається вирішити вихідний код. Можуть бути особливі випадки, коли купон застосовується в getPrice (), але якщо це приховано глибоко у ланцюзі викликів функцій, то правило бізнесу також приховане.
Реакційний

0

Якщо ви працюєте мовою з названими параметрами (ObjectiveC, Python, Ruby тощо), тимчасові зміни є менш корисними.

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

Як і Ви, я використовую тимчасові змінні для чіткості та довжини рядків.

Я також бачив, як програмісти роблять наступне в PHP. Це цікаво і чудово для налагодження, але це трохи дивно.

$rs = DB::query( $query = "SELECT * FROM table" );
if (DEBUG) echo $query;
// do something with $rs

0

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

Новий метод може бути використаний в інших методах

    double basePrice = _quantity * _itemPrice;
    if (basePrice > 1000)
        return basePrice * 0.95;
    else
        return basePrice * 0.98;

           http://i.stack.imgur.com/mKbQM.gif

    if (basePrice() > 1000)
        return basePrice() * 0.95;
    else
        return basePrice() * 0.98;
...
double basePrice() {
    return _quantity * _itemPrice;
}

Таким чином, у вашому прикладі хоста та URI я застосував би цю рекомендацію лише у випадку, коли я планую повторно використовувати той самий URI або визначення хосту.

У такому випадку через простір імен я не визначатиму глобальний метод uri () або host (), а ім’я з додатковою інформацією, наприклад twilio_host () або archive_request_uri ().

Потім, для випуску довжини рядка, я бачу кілька варіантів:

  • Створіть локальну змінну, наприклад uri = archive_request_uri().

Обґрунтування: У поточному методі ви хочете, щоб URI був згаданим. Визначення URI все ще є факторизованим.

  • Визначте локальний метод, наприклад uri() { return archive_request_uri() }

Якщо ви часто використовуєте рекомендацію Фаулера, ви знаєте, що метод uri () - це той самий шаблон.

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

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