Чи зміна вхідного параметра є антипаттерном? [зачинено]


60

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

public OtherObject MyObject2OtherObject(MyObject mo){
    ... Do the conversion
    return otherObject;
}

На новому робочому місці схема:

public void MyObject2OtherObject(MyObject mo, OtherObject oo){
    ... Do the conversion
}

Для мене це трохи смердюче, оскільки я звик не змінювати вхідні параметри. Це зміна вхідних параметрів антипатернів чи це нормально? Чи є якісь серйозні недоліки?


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

1
Другий зразок іноді використовується як міра ефективності для повторного використання об'єктів. Якщо припустити, що ooце об'єкт, який передається методу, а не покажчик, встановлений на новий об'єкт. Це так? Якщо це Java, це, мабуть, якщо її C ++, можливо, не буде
Річард Тінгл,

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

2
Другий рисунок корисний, коли функція також повертає значення, наприклад, для успіху / відмови. Але в цьому немає нічого насправді «поганого». Це дійсно залежить від того, де ви хочете створити об’єкт.
GrandmasterB

12
Я б не назвав функції, що реалізують ці дві різні схеми однаково.
Кейсі

Відповіді:


70

Це не антипаттерн, це погана практика.

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

Новий стиль робочого місця, який ви показуєте, - це погана практика , вестигіальні або до початку OOP, згідно з Чистим кодексом дядька Боба.

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

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


7
Я не бачу, чому визначення, яке ви пов'язували з цим, перешкоджає вважати це антидією.
Семюель Едвін Уорд

7
Я гадаю, що це тому, що обґрунтування того, що "ми економимо CPU / mem, не створюючи новий об'єкт, натомість ми повинні переробити той, який ми маємо, і передавати його як аргумент" настільки очевидно неправильно для всіх, хто має серйозний фон OOP, що це може не становлять зразок ніколи - тому немає ніякого способу ми могли б вважати його
антидіаграмою

1
Що з випадком, коли вхідним параметром є велика колекція об'єктів, яку потрібно змінити?
Стів Чемберс

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

@SteveChambers Я думаю, що в цьому випадку дизайн класу чи методу поганий, і його слід переробити, щоб його уникнути.
piotr.wittchen

16

Цитуючи відому книгу Роберта К. Мартіна "Чистий код":

Вихідних аргументів слід уникати

Функції повинні мати невелику кількість аргументів

Друга закономірність порушує обидва правила, особливо "вихідний аргумент". У цьому сенсі це гірше, ніж перший візерунок.


1
Я б сказав, що це порушує перший, багато аргументів означають безладний код, але я думаю, два аргументи абсолютно нормально. Я думаю, що правило про чистий код означає, що якщо вам потрібно 5 або 6 вхідних даних, ви хочете зробити занадто багато в одному методі, тому вам слід зробити рефактор для збільшення читабельності коду та обсягу OOP.
CsBalazsHungary

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

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

3
Ну, пекло, якщо якийсь хлопець написав це в книзі, це повинен бути гарною порадою для будь-якої можливої ​​ситуації. Не потрібно думати.
Кейсі

2
@emodendroket Але чувак, це хлопець!
П’єр Арло

15

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

Я б його взагалі не використовував, але, наприклад. в ігровому програмуванні воно має своє місце. Наприклад, подивіться на багато операцій Vector3f JavaMonkey дозволяє передавати екземпляр, який повинен бути змінений і повернутий як результат.


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

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

JMonkey також там, де я бачив це багато, оскільки це часто критично. Я ніколи не чув, щоб це називалося "JavaMonkey"
Річард Тінгл,

3
@JAB: GC JVM налаштований спеціально для ефемерних об'єктів. Велика кількість дуже короткотривалих предметів є тривіальною для збору, і в багатьох випадках все ваше ефемерне покоління може бути зібране одним ходом вказівника.
Phoshi

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

10

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


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

@CsBalazsHungary Я б сказав так. Судячи з невеликого фрагменту коду, який ви надали.
Ейфорія

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

1
@CsBalazsHungary У випадку примітивного аргументу, функція навіть не працюватиме. Тож у вас гірша проблема, ніж зміна аргументів.
Ейфорія

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

7

Це дійсно залежить від мови.

У Java друга форма може бути антидіаграмою, але деякі мови трактують параметр по-різному. У Ada і VHDL, наприклад, замість того , щоб прохід по значенню або за посиланням, параметри можуть мати режими in, outабо in out.

Помилка зміни inпараметру чи зчитування outпараметра, але будь-які зміни in outпараметра передаються назад абоненту.

Тож дві форми в Ада (це теж юридичні VHDL) є

function MyObject2OtherObject(mo : in MyObject) return OtherObject is
begin
    ... Do the conversion
    return otherObject;
end MyObject2OtherObject;

і

procedure MyObject2OtherObject(mo : in MyObject; oo : out OtherObject) is
begin
    ... Do the conversion
    oo := ... the conversion result;
end MyObject2OtherObject;

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


3

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

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

public void ConvertFoo(Foo from, Foo to) {
    if (can't convert) {
        return;
    }
    ...
}

Foo a;
Foo b = DefaultFoo();
ConvertFoo(a, b);
// If conversion fails, b is unchanged

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

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

Java не чудово справляється з кількома виходами - він не може мати лише параметри виводу, як деякі мови, або мати кілька повернених значень, як інші - але навіть все-таки ви можете використовувати об'єкти повернення.

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

  • Можливо, поліцейські стилів на робочому місці (або у вашій кодовій базі) походять з не-Java-фону та неохоче змінюються.
  • Ваш код, можливо, був портом з мови, де цей стиль є більш ідіоматичним.
  • Можливо, вашій організації потрібно буде підтримувати послідовність API для різних мов, і це був стиль найменшого загального знаменника (це просто, але це трапляється навіть для Google ).
  • Або, можливо, стиль мав більше сенсу в далекому минулому і перетворився на його сучасний вигляд (наприклад, це міг бути шаблон TryParse, але якийсь навмисний попередник видалив повернене значення після того, як виявив, що його ніхто взагалі не перевіряв).

2

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

Недоліками цього способу є:

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

Відповідь ґрунтується на моєму досвіді роботи з c #, сподіваюся, логіка перекладається на Java.


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

Це я думав. Первісний вид ін'єкції залежності
Ліндон Уайт,

Ще одна перевага - якщо OtherObject абстрактний або інтерфейс. Функція не має уявлення, який тип створити, але може викликати абонент.
user949300

2

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

Однак якби назва методу була Convert(що це має бути, якщо ми подивимось на частину "... зроби конверсію"), я б сказав, що другий метод не мав би сенсу. Ви не перетворюєте значення в інше значення, яке вже існує, IMO.

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