Ім'я цього антипатрійця? Поля як локальні змінні [закрито]


68

У деякому коді, який я рецензую, я бачу речі, які є моральним еквівалентом наступного:

public class Foo
{
    private Bar bar;

    public MethodA()
    {
        bar = new Bar();
        bar.A();
        bar = null;
    }

    public MethodB()
    {
        bar = new Bar();
        bar.B();
        bar = null;
    }
}

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

  1. Це, очевидно, погано, правда?
  2. Чи є назва цього антипаттера?

60
Ну, це новий. Сподіваємось, цей клас не використовується в багатопотоковому контексті, або у вас попереду цікаві часи!
TMN

8
Це справді нечасто? Я бачив це багато разів у своїй кар’єрі.
JSB ձոգչ

32
@TMN Це не безпечно для потоків, але що ще гірше, це навіть не ретенція. Якщо будь-який код у MethodAабо MethodBвикликає MethodAабо MethodBмає бути викликаний будь-яким способом (і barвикористовується повторно, у випадку bar.{A,B}()злочинця), у вас є подібні проблеми навіть без жодної сукупності.

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

74
Важко не назвати це звисаючими приватними антидіаграми.
пн

Відповіді:


86

Це, очевидно, погано, правда?

Так.

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

  • Це означає, що стан з одного дзвінка просочується до іншого виклику (якщо ви забудете повторно реалізувати).

  • Це робить код важко зрозумілим, тому що ви повинні перевірити вище, щоб переконатися, що насправді робить код. (На відміну від використання локальних змінних.)

  • Це робить кожен Fooекземпляр більшим, ніж потрібно. (І уявіть, що ви робите це для N змінних ...)

Чи є назва цього антипаттера?

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


Для запису правильний спосіб записати це:

public class Foo
{
    public methodA()
    {
        Bar bar = new Bar();  // Use a >>local<< variable!!
        bar.a();
    }

    // Or more concisely (in this case) ...
    public methodB()
    {
        new Bar().b();
    }
}

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


11
Я б сказав: "Якщо ви бачите це у виробничому коді, це знак того, що вам слід переглянути свій процес найму "
бета-версія

@ Beta - це теж, хоча ви повинні мати змогу виправити такі погані звички кодування, як і за допомогою енергійного перегляду коду.
Стівен С

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

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

@Geek - Ви надто зосереджуєтесь на конкретному прикладі. Я говорю про загальний випадок, коли метод може бути викликаний з декількох потоків або рекурсивно.
Стівен C

39

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


12
Зокрема, я думаю, що "змінна область" - це назва проблеми. Цій людині потрібно дізнатися, що таке локальні змінні, і як / коли їх використовувати. Позитивний зразок або загальне правило полягає в тому, щоб використовувати найменший доступний вам діапазон для кожної змінної і лише розширити її за потребою. Щоб було зрозуміло, ця помилка - це не просто поганий стиль. Кодування такого стану гонки є помилкою.
GlenPeterson

30

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

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

У цьому випадку дверна ручка - це лише еталон, тому вона дешева. Якщо дверна ручка була фактичним об'єктом (і вони повторно використовували об'єкт замість просто посилання), це може бути досить дорого, щоб бути a prudent global doorknob. Однак, коли це дешево, він відомий як cheapskate global doorknob.

Ваш приклад має cheapskateрізноманітність.


19

Основною проблемою тут буде паралельність - якщо Foo використовується декількома потоками, у вас є проблема.

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


15

Це конкретний випадок "неналежного обстеження" зі стороною "змінного повторного використання".


7

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

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

Це просто неправильно.

Щоб додати ще трохи.

Цей код забобонний, або в кращому випадку - культовий культ.

Забобони - це щось, зроблене без чіткого обґрунтування. Це може бути пов’язане з чимось реальним, але зв’язок не логічний.

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

В обох цих випадках реальної справи не може бути зроблено.

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

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

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


1
Але люди дійсно думають , що це гарна ідея, ймовірно , тому що вони думають , що це форма повторного використання коду.
JSB ձոգչ

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

Це робить це забобоном, а не антипаттером.
Джон Ханна

3

(1) Я б сказав, що це не добре.

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

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

EDIT: Площа пам’яті класу більша, оскільки вказівник смуги завжди займає пам’ять протягом життя класу. Якщо вказівник на панелі був локальним, він використовував би пам'ять лише протягом виклику методу. Пам'ять для вказівника - це лише крапля в океані, але це все-таки зайва крапля.

Рядок видно для інших методів у класі. Методи, які не використовують панель, не повинні бачити її.

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

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


2

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


2

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

Це досить стандартний спосіб тестування одиниць

public class BarTester
{
    private Bar bar;

    public Setup() { bar = new Bar(); }
    public Teardown() { bar = null; }

    public TestMethodA()
    {
        bar.A();        
    }

    public TestMethodB()
    {
        bar.B();
    }
}

це лише рефакторинг цього еквівалента коду ОП

public class BarTester
{
    private Bar bar;

    public TestMethodA()
    {
        bar = new Bar();
        bar.A();
        bar = null;
    }

    public TestMethodB()
    {
        bar = new Bar();
        bar.B();
        bar = null;
    }
}

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


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

1
@Philipp - Я не заперечую проблему повторного використання або одночасності, але питання ОП стосувалося тієї моделі, яка зазвичай використовується при тестуванні одиниць. Те, що кріплення є примірником для кожного тесту, є детальною ознакою тестової основи. Навіть при тестуванні одиниць, модель є безпечною лише тоді, коли використовується тестова основа, як передбачається. Це ж міркування застосовується і при використанні цієї моделі у виробничому коді.
Lieven Keersmaekers

Там немає необхідності встановлювати barв null, JUnit в будь-якому випадку створює новий екземпляр тесту для кожного методу випробувань.
oberlies

2

Цей запах коду Бек / Фоулер називає тимчасовим полем у рефакторингу.

http://sourcemaking.com/refactoring/temporary-field

Відредаговано, щоб додати більше пояснень на запит модераторів: Ви можете прочитати більше про запах коду у вказаній вище URL-адресі або на паперовій копії книги. Оскільки ОП шукала назву саме цього антипатерн / кодового запаху, я подумав, що цитування раніше згадуваної посилання на це зі встановленого джерела, якому старше 10 років, буде корисним, хоча я повністю усвідомлюю, що я запізнююсь на гра на це питання, тому навряд чи за відповідь буде проголосовано чи прийнято.

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

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


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

1

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

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


1

Окрім вже згаданих проблем, використання цього підходу в середовищі без автоматичного збирання сміття (наприклад, C ++) призведе до витоку пам'яті, оскільки тимчасові об'єкти не звільняються після використання. (Хоча це може здатися трохи далеко знайденим, там є люди, які просто змінять "null" на "NULL" і будуть раді тому, що код складається.)


1

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


-1

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

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

Насправді він не заслуговує на тему "антипатерн" - але добре було побачити пропозиції, як можна перекодувати ОП.


1
Ласкаво просимо до програмістів Stack Exchange, дякуємо, що ввели свою першу відповідь. Схоже, ви проголосували проти, можливо, з причин, перелічених у FAQ FAQ programmers.stackexchange.com/faq . FAQ дає чудові пропозиції щодо отримання ефективних відповідей (і під голосування). Іноді люди досить люб'язні, щоб додати записку про відмову від голосу, щоб вказати, чи це неправильно якимось чином, дублікат, думка без доказів, чи я теж повторюю попередню відповідь. Більшість із нас вже проголосували, закрили чи видалили відповіді, тому не турбуйтеся. Це лише частина початку роботи. Ласкаво просимо.
DeveloperDon
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.