Обґрунтування переваги локальних змінних перед змінними екземпляра?


109

Кодова база, над якою я працюю, часто використовує змінні екземпляри для обміну даними між різними тривіальними методами. Оригінальний розробник переконаний, що він дотримується кращих практик, зазначених у книзі « Чистий код» дядька Боб / Роберта Мартіна: «Перше правило функцій - це те, що вони повинні бути маленькими». і "Ідеальна кількість аргументів для функції дорівнює нулю (niladic). (...) Аргументи важкі. Вони забирають багато концептуальної сили".

Приклад:

public class SomeBusinessProcess {
  @Inject private Router router;
  @Inject private ServiceClient serviceClient;
  @Inject private CryptoService cryptoService;

  private byte[] encodedData;
  private EncryptionInfo encryptionInfo;
  private EncryptedObject payloadOfResponse;
  private URI destinationURI;

  public EncryptedResponse process(EncryptedRequest encryptedRequest) {
    checkNotNull(encryptedRequest);

    getEncodedData(encryptedRequest);
    getEncryptionInfo();
    getDestinationURI();
    passRequestToServiceClient();

    return cryptoService.encryptResponse(payloadOfResponse);
  }

  private void getEncodedData(EncryptedRequest encryptedRequest) {
    encodedData = cryptoService.decryptRequest(encryptedRequest, byte[].class);
  }

  private void getEncryptionInfo() {
    encryptionInfo = cryptoService.getEncryptionInfoForDefaultClient();
  }

  private void getDestinationURI() {
    destinationURI = router.getDestination().getUri();
  }

  private void passRequestToServiceClient() {
    payloadOfResponse = serviceClient.handle(destinationURI, encodedData, encryptionInfo);
  }
}

Я б перетворив це на наступне, використовуючи локальні змінні:

public class SomeBusinessProcess {
  @Inject private Router router;
  @Inject private ServiceClient serviceClient;
  @Inject private CryptoService cryptoService;

  public EncryptedResponse process(EncryptedRequest encryptedRequest) {
    checkNotNull(encryptedRequest);

    byte[] encodedData = cryptoService.decryptRequest(encryptedRequest, byte[].class);
    EncryptionInfo encryptionInfo = cryptoService.getEncryptionInfoForDefaultClient();
    URI destinationURI = router.getDestination().getUri();
    EncryptedObject payloadOfResponse = serviceClient.handle(destinationURI, encodedData,
      encryptionInfo);

    return cryptoService.encryptResponse(payloadOfResponse);
  }
}

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

Звідси мої запитання: Яке об'єктивне наукове обгрунтування на користь локальних змінних перед змінними екземплярів? Я не можу, здається, покласти свій палець на це. Моя інтуїція підказує мені, що приховані муфти погані і що вузька сфера краще, ніж широка. Але що це за наука, що підтверджує це?

І навпаки, чи є якісь недоліки цього рефакторингу, які я, можливо, не помітив?


Коментарі не для розширеного обговорення; ця розмова переміщена до чату .
maple_shaft

Відповіді:


170

Яке об’єктивне наукове обґрунтування надати перевагу локальним змінним перед змінними екземплярів?

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

Global > Class > Local (method) > Local (code block, e.g. if, for, ...)

Редагувати: те, що я називаю "областю класу" - це те, що ви маєте на увазі під "змінною екземпляра". Наскільки мені відомо, це синоніми, але я C # dev, а не розробник Java. Заради стислості я перетворив всю статику на глобальну категорію, оскільки статистика не є темою питання.

Чим менший обсяг, тим краще. Обґрунтування полягає в тому, що змінні повинні жити в найменшому обсязі . У цьому є багато переваг:

  • Це змушує задуматися про відповідальність поточного класу і допомагає вам дотримуватися SRP.
  • Це дозволить вам не уникнути глобальних конфліктів імен, наприклад , якщо два або більше класів мають Nameвластивість, ви не примушували префікс їх , як FooName, BarName... Таким чином , зберігаючи ваші імена змінних , як чистий і лаконічний , наскільки це можливо.
  • Він декламує код обмеженням доступних змінних (наприклад, для Intellisense) лише тими, що контекстуально актуальні.
  • Це дає можливість певної форми контролю доступу, тому ваші дані не можуть маніпулювати деяким актором, про якого ви не знаєте (наприклад, інший клас, розроблений колегою).
  • Це робить код більш читабельним, оскільки ви гарантуєте, що декларація цих змінних намагається максимально наблизитись до фактичного використання цих змінних.
  • Обґрунтування оголошень змінних у занадто широкій області часто свідчить про розробника, який не зовсім розуміє OOP або як його реалізувати. Якщо бачити занадто широкомасштабні змінні, виступає червоним прапором, що, мабуть, щось не так з підходом OOP (або з розробником взагалі, або з базовою базою кодів).
  • (Коментар Кевіна) Використання місцевих жителів змушує вас робити справи в правильному порядку. У вихідному коді (змінної класу) ви могли неправильно перейти passRequestToServiceClient()до вершини методу, і він все одно буде компілюватися. З місцевими жителями ви могли зробити цю помилку, лише якщо перейшли неініціалізовану змінну, що, сподіваємось, досить очевидно, що ви насправді цього не робите.

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

І навпаки, чи є якісь недоліки цього рефакторингу, які я, можливо, не помітив?

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

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

private byte[] getEncodedData() {
    return cryptoService.decryptRequest(encryptedRequest, byte[].class);
}

private EncryptionInfo getEncryptionInfo() {
    return cryptoService.getEncryptionInfoForDefaultClient();
}

// and so on...

Я згоден з тим, що ви зробили в processметоді, але вам слід було викликати приватні підметоди, а не виконувати їх тіла безпосередньо.

public EncryptedResponse process(EncryptedRequest encryptedRequest) {
    checkNotNull(encryptedRequest);

    byte[] encodedData = getEncodedData();
    EncryptionInfo encryptionInfo = getEncryptionInfo();

    //and so on...

    return cryptoService.encryptResponse(payloadOfResponse);
}

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

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


Коментарі не для розширеного обговорення; ця розмова переміщена до чату .
maple_shaft

79

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


20
Повністю згоден! Ці змінні члена є просто неявними аргументами функції. Насправді це гірше, оскільки зараз немає явного зв’язку між значенням цієї змінної та використанням функції (від зовнішнього POV)
Rémi

1
Я б сказав, що це не те, що мала на увазі книга. Скільки функцій вимагає запуску рівно нульових вхідних даних? Цей один з бітів, я думаю, що книга помилилася.
Qwertie

1
@Qwertie Якщо у вас є об'єкт, дані, над якими він працює, можуть бути повністю капсульовані всередині нього. Функції на кшталт process.Start();або myString.ToLowerCase()не повинні здаватися занадто дивними (і насправді найпростішими для розуміння).
Р. Шмітц

5
У обох є один аргумент: неявний this. Можна навіть стверджувати, що цей аргумент наводиться явно - перед крапкою.
BlackJack

47

Інші відповіді вже чудово пояснили переваги локальних змінних, тому все, що залишається, - це частина вашого запитання:

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

Це повинно бути легко. Просто вкажіть його на таку цитату в Чистому коді дядька Боба:

Не мають побічних ефектів

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

(приклад пропущено)

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

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


3
У "світі префектів" це була б друга відповідь, перелічена. Перша відповідь за ідеальну ситуацію, яку слухає слухач міркування, - але якщо колега є завзятим, ця відповідь тут би розглядала ситуацію без надмірного пуху.
Р. Шмітц

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

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

26

"Це суперечить тому, що чийсь дядько думає" - НІКОЛИ хороший аргумент. НІКОЛИ. Не бери мудрості від дядьків, думай сам.

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

Тест: Напишіть коментар до документації для кожної змінної примірника. Чи можете ви написати що-небудь, що не зовсім безглуздо? І написати коментар до документації чотирма постачальникам. Вони однаково безглузді.

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

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

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


16
Думати про себе, звичайно, добре. Але ваш початковий абзац фактично включає учнів чи юніорів, які відмовилися від участі вчителів чи літніх людей; що надто далеко.
Flater

9
@Flater Після роздумів про вклад викладачів чи старших, і побачивши, що вони помиляються, відхилити їх внесок - єдине правильне. Зрештою, мова йде не про звільнення, а про допит і звільнення лише, якщо це безумовно виявиться неправильним.
glglgl

10
@ChristianHackl: Я повністю на борту з тим, щоб не сліпо слідувати традиціоналізму, але я також не став би його сліпо відхиляти. Відповідь, здавалося б, пропонує уникнути набутих знань на користь власного погляду, і це не здоровий підхід. Сліпо слідуючи чужим думкам, ні. Запитувати щось, коли ви не згодні, очевидно, так. Відверте відхилення, тому що ви не згодні, ні. Коли я читаю, перший абзац відповіді, принаймні, здається, підказує останній. Це дуже залежить від того, що означає гнашер з «подумайте самі», що потребує доробки.
Flater

9
На платформі спільної мудрості це здається трохи поза місцем
drjpizzle

4
НІКОЛИ також НІКОЛИ хороший аргумент ... Я повністю погоджуюся, що правила існують, щоб керуватися, а не прописувати. Однак правила щодо правил - це лише підклас правил, тому ви висловили свій власний вердикт ...
cmaster

14

Яке об’єктивне наукове обґрунтування надати перевагу локальним змінним перед змінними екземплярів? Я не можу, здається, покласти свій палець на це. Моя інтуїція підказує мені, що приховані муфти погані і що вузька сфера краще, ніж широка. Але що це за наука, що підтверджує це?

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

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

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


7
Поки що це єдина відповідь, яка згадує безпеку потоку та одночасність. Це дивовижно, враховуючи приклад конкретного коду у запитанні: екземпляр SomeBusinessProcess не може безпечно обробити відразу кілька зашифрованих запитів. Метод public EncryptedResponse process(EncryptedRequest encryptedRequest)не синхронізований, і одночасні виклики цілком можливо обмежують значення змінних примірника. Це хороший момент для виховання.
Джошуа Тейлор

9

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

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

public class SomeBusinessProcess {
  @Inject private Router router;
  @Inject private ServiceClient serviceClient;
  @Inject private CryptoService cryptoService;

  public EncryptedResponse process(EncryptedRequest request) {
    checkNotNull(encryptedRequest);

    return encryptResponse
      (routeTo
         ( destination()
         , requestData(request)
         , destinationEncryption()
         )
      );
  }

  private byte[] requestData(EncryptedRequest encryptedRequest) {
    return cryptoService.decryptRequest(encryptedRequest, byte[].class);
  }

  private EncryptionInfo destinationEncryption() {
    return cryptoService.getEncryptionInfoForDefaultClient();
  }

  private URI destination() {
    return router.getDestination().getUri();
  }

  private EncryptedObject routeTo(URI destinationURI, byte[] encodedData, EncryptionInfo encryptionInfo) {
    return serviceClient.handle(destinationURI, encodedData, encryptionInfo);
  }

  private void encryptResponse(EncryptedObject payloadOfResponse) {
    return cryptoService.encryptResponse(payloadOfResponse);
  }
}

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

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


4
Усунення всіх змінних не обов'язково полегшує читання коду.
Фарап

Виключіть усі змінні повністю за допомогою pointfree style en.wikipedia.org/wiki/Tacit_programming
Marcin

@Pharap Правда, відсутність змінних не забезпечує розбірливості. У деяких випадках це навіть ускладнює налагодження. Справа в тому, що добре підібрані імена, чітке використання виразів, можуть дуже чітко передавати ідею, залишаючись при цьому ефективними.
Kain0_0

7

Я би просто видалив ці змінні та приватні методи взагалі. Ось мій рефактор:

public class SomeBusinessProcess {
  @Inject private Router router;
  @Inject private ServiceClient serviceClient;
  @Inject private CryptoService cryptoService;

  public EncryptedResponse process(EncryptedRequest encryptedRequest) {
    return cryptoService.encryptResponse(
        serviceClient.handle(router.getDestination().getUri(),
        cryptoService.decryptRequest(encryptedRequest, byte[].class),
        cryptoService.getEncryptionInfoForDefaultClient()));
  }
}

Для приватного методу, наприклад router.getDestination().getUri(), більш зрозумілий і легкий для читання getDestinationURI(). Я навіть просто повторю, що якщо я використовую один і той же рядок двічі в одному класі. Якщо поглянути на це по-іншому, якщо є потреба в а getDestinationURI(), це, мабуть, належить до якогось іншого класу, а не до SomeBusinessProcessкласу.

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

У будь-якому випадку, класу потрібно лише зробити, process()і тоді об'єкт буде викинутий, не потрібно зберігати жодного стану в пам'яті. Подальшим потенціалом рефактора може бути виведення CryptoService з цього класу.

На підставі коментарів я хочу додати, що ця відповідь ґрунтується на реальній світовій практиці. Дійсно, в огляді коду перше, що я вибрав би, - це переробити клас і перемістити роботу шифрування / розшифрування. Після цього я б запитав, чи потрібні методи та змінні, чи правильно вони названі тощо. Кінцевий код, ймовірно, наблизиться до цього:

public class SomeBusinessProcess {
  @Inject private Router router;
  @Inject private ServiceClient serviceClient;

  public Response process(Request request) {
    return serviceClient.handle(router.getDestination().getUri());
  }
}

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

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


Моя заява, хоча багато хто буде вагатися тут. Звичайно, деякі абстракції мають сенс. (BTW метод, який називається "процес", жахливий.) Але тут логіка мінімальна. Питання ОП, однак, стосується всього стилю коду, і там випадок може бути складнішим.
Joop Eggen

1
Одним із чітких проблем, пов’язаних із введенням цього методу в один виклик методу, є читання. Він також не працює, якщо вам потрібно більше однієї операції над заданим об'єктом. Крім того, це неможливо налагодити, тому що ви не можете переглядати операції та перевіряти об'єкти. Хоча це працює на технічному рівні, я б не виступав за це, оскільки він масово нехтує аспектами розробки програмного забезпечення.
Флатер

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

@JoopEggen Так, абстракції мають сенс. У прикладі приватні методи так чи інакше не дають абстракцій, користувачі класу навіть не знають про них
imela96

1
@ ime96 це смішно, що ти, мабуть, один з небагатьох людей, котрі помітили, що зв'язок між ServiceClient та CryptoService робить орієнтованим на введення CS в SC замість SBP, вирішення основної проблеми на вищому архітектурному рівні ... ось ІМВХО головний пункт цієї історії; занадто легко відслідковувати велику картину, фокусуючись на деталях.
vaxquis

4

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

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

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

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

Зберігаючи тільки бізнес - логіку названих функцій, ми отримуємо краще з обох світів (де - то між відповіддю Flater в і відповідь imel96 в ):

public EncryptedResponse process(EncryptedRequest encryptedRequest) {

    byte[] requestData = decryptRequest(encryptedRequest);
    EncryptedObject responseData = handleRequest(router.getDestination().getUri(), requestData, cryptoService.getEncryptionInfoForDefaultClient());
    EncryptedResponse response = encryptResponse(responseData);

    return response;
}

// define: decryptRequest(), handleRequest(), encryptResponse()

3

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

Вся ідея Clean Code полягає в поліпшенні читабельності та уникненні помилок. Існує кілька правил, які порушують одне одного.

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

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

Змінні, які не використовуються у багатьох місцях класу, слід перемістити.

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


1

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

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

Сказати, що одне правильно, а друге - неправильно, може бути вагомим висновком у будь-якому конкретному випадку, але як загальна порада ...

TL; DR: Я думаю, що причина, коли ти пахнеш занадто великим завзяттям, є надто завзяття.


0

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

public class SomeBusinessProcess {
  @Inject private Router router;
  @Inject private ServiceClient serviceClient;
  @Inject private CryptoService cryptoService;

  public EncryptedResponse process(EncryptedRequest encryptedRequest) {
    checkNotNull(encryptedRequest);

    return getEncryptedResponse(
            passRequestToServiceClient(getDestinationURI(), getEncodedData(encryptedRequest) getEncryptionInfo())
        );
  }

  private EncryptedResponse getEncryptedResponse(EncryptedObject encryptedObject) {
    return cryptoService.encryptResponse(encryptedObject);
  }

  private byte[] getEncodedData(EncryptedRequest encryptedRequest) {
    return cryptoService.decryptRequest(encryptedRequest, byte[].class);
  }

  private EncryptionInfo getEncryptionInfo() {
    return cryptoService.getEncryptionInfoForDefaultClient();
  }

  private URI getDestinationURI() {
    return router.getDestination().getUri();
  }

  private EncryptedObject passRequestToServiceClient(URI destinationURI, byte[] encodedData, EncryptionInfo encryptionInfo) {
    return serviceClient.handle(destinationURI, encodedData, encryptionInfo);
  }
}

0

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

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

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

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

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


Це дійсно виявилося для мене головною перешкодою для розуміння потоку. Цей приклад досить малий, але інший використовував 35 (!) Змінних екземплярів для своїх проміжних результатів, не рахуючи десяток змінних екземплярів, що містять залежності. Слідкувати за цим було досить важко, оскільки ви повинні слідкувати за тим, що вже було встановлено раніше. Деякі були згодом навіть повторно використані, що ще більше ускладнило ситуацію. На щастя, наведені тут аргументи остаточно переконали мого колегу погодитися з рефакторингом.
Олександр
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.