Название этого антипаттерна? Поля как локальные переменные [закрыто]


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
Трудно не назвать это свисающим антипаттерном.
PSR

Ответы:


86

Это явно плохо, правда?

Да.

  • Это делает методы не реентерабельными, что является проблемой, если они вызываются в одном и том же экземпляре рекурсивно или в многопоточном контексте.

  • Это означает, что состояние от одного вызова просачивается к другому вызову (если вы забыли повторно инициализировать).

  • Это делает код трудным для понимания, потому что вы должны проверить вышеизложенное, чтобы убедиться, что код на самом деле делает. (Контраст с использованием локальных переменных.)

  • Это делает каждый Fooэкземпляр больше, чем нужно. (И представьте, что вы делаете это для N переменных ...)

Есть ли название для этого антипаттерна?

ИМО, это не стоит называть антипаттерном. Это просто плохая привычка кодирования / неправильное использование Java-конструкций / дерьмового кода. Это то, что вы можете увидеть в коде студента, когда студент пропускает лекции и / или не имеет навыков программирования. Если вы видите это в рабочем коде, это признак того, что вам нужно сделать намного больше обзоров кода ...


Для записи, правильный способ написать это:

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 - Вы слишком много внимания уделяете конкретному примеру. Я говорю об общем случае, когда метод может быть вызван из нескольких потоков или рекурсивно.
Стивен С

39

Я бы назвал это ненужной большой областью действия для переменной. Этот параметр также учитывает условия гонки, если несколько потоков обращаются к MethodA и MethodB.


12
В частности, я думаю, что «переменная область» является названием проблемы здесь. Этот человек должен узнать, что такое локальные переменные и как / когда их использовать. Положительный шаблон или общее правило состоит в том, чтобы использовать наименьшую доступную вам область для каждой переменной и расширять ее только по мере необходимости. Чтобы быть понятным, эта ошибка не просто плохой стиль. Кодирование состояния гонки как это является ошибкой.
ГленПетерсон

30

Это частный случай общего паттерна, известного как global doorknobbing. При строительстве дома заманчиво купить только одну дверную ручку и оставить ее лежащей без дела. Когда кто-то хочет использовать дверь или шкаф, он просто берет эту глобальную ручку двери. Если дверные ручки стоят дорого, это может быть хорошим дизайном.

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

В этом случае дверная ручка - это просто ссылка, поэтому она дешевая. Если дверная ручка была реальным объектом (и они повторно использовали объект, а не просто ссылку), то это может быть достаточно дорого, чтобы быть prudent global doorknob. Однако, когда это дешево, это известно как cheapskate global doorknob.

Ваш пример cheapskateразнообразен.


19

Основной проблемой здесь будет параллелизм - если Foo используется несколькими потоками, у вас есть проблема.

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


15

Это особый случай «неправильного определения области действия» со стороной «повторного использования переменной».


7

Это не анти-паттерн. Анти-паттерны обладают некоторым свойством, которое делает их хорошей идеей, которая заставляет людей делать это нарочно; они запланированы как образцы, и тогда это идет ужасно неправильно.

Это также то, что вызывает споры относительно того, является ли что-то шаблоном, анти-шаблоном или часто неправильно применяемым шаблоном, который все еще имеет применение в некоторых местах.

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

Чтобы добавить немного больше.

Этот кодекс является суеверным или, в лучшем случае, практикой культа грузов.

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

Практика культа грузов - это та, в которой вы пытаетесь скопировать то, что вы узнали из более осведомленного источника, но вы на самом деле копируете поверхностные артефакты, а не процесс (так называемый культ в Папуа-Новой Гвинее, который радиоуправляемые самолеты из бамбука в надежде вернуть самолеты Второй мировой войны в Японию и США).

В обоих этих случаях нет никакого реального случая.

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

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

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


1
Но люди действительно думают , что это хорошая идея, вероятно , потому что они думают , что это форма повторного использования кода.
JSB ձոգչ

Начинающим часто кажется, что для объявления двух переменных каким-то образом требуется вдвое больше места, и поэтому они предпочитают повторно использовать переменные. Это показывает неправильное понимание модели памяти (свободное хранилище в сравнении со стеком, возможность повторного использования мест в стеке) и оптимизаций, которые могут выполнять все компиляторы. Я бы сказал, что новичок может посчитать, что повторное использование переменной - хорошая идея, поэтому ее можно классифицировать как антипаттерн.
Филипп

Это делает это суеверием, а не антипаттерном.
Джон Ханна

3

(1) Я бы сказал, что это не хорошо.

Он выполняет ручное хранение, что стек может делать автоматически с локальными переменными.

Выигрыш в производительности отсутствует, поскольку «new» вызывается при каждом вызове метода.

РЕДАКТИРОВАТЬ: объем памяти класса больше, потому что бар указатель всегда занимает память в течение жизни класса. Если бы указатель панели был локальным, то он использовал бы только память на время существования вызова метода. Память для указателя - это просто капля в море, но это все еще ненужная капля.

Панель видна другим методам в классе. Методы, которые не используют бар, не должны видеть его.

Ошибки на основе состояния. В этом конкретном случае ошибки базы состояний должны возникать только при многопоточности, поскольку каждый раз вызывается «new».

(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();
    }
}

это просто рефакторинг этого эквивалента кода OP

public class BarTester
{
    private Bar bar;

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

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

Я бы никогда не написал код, указанный в примере с OP, он кричит рефакторинг, но я считаю, что шаблон не является ни недействительным, ни антипаттерном .


В этом случае прибор создается независимо для каждого теста, и проблем, связанных с повторным использованием переменных или параллелизмом, не возникает. В общем случае (вне модульных тестов) неизвестно, вызывается ли для каждого объекта не более одного метода.
Филипп

1
@Philipp - я не оспариваю проблему повторного использования или параллелизма, но вопрос OP был о шаблоне, который обычно используется в модульном тестировании. Тот факт, что для каждого теста создается экземпляр, является деталью реализации инфраструктуры тестирования. Даже в модульном тестировании шаблон безопасен только тогда, когда тестовая среда используется так, как предполагается. То же самое относится к использованию этого шаблона в производственном коде.
Ливен Кирсмейкерс

Нет необходимости устанавливать barзначение null, JUnit в любом случае создает новый экземпляр теста для каждого метода тестирования.
Оберли

2

Этот запах кода упоминается как Временное поле при рефакторинге Беком / Фаулером.

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

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

Если это не слишком личная реклама, я также упоминаю и объясняю этот специфический запах кода в моем предстоящем курсе Pluralsight «Рефакторинг Основы», который, как я ожидаю, будет опубликован в августе 2013 года.

Чтобы добавить больше ценности, давайте поговорим о том, как исправить эту конкретную проблему. Есть несколько вариантов. Если поле действительно используется только одним методом, его можно преобразовать в локальную переменную. Однако, если несколько методов используют поле для связи (вместо параметров), то это классический случай, описанный в Рефакторинге, и его можно исправить, заменив поле параметрами, или если методы, работающие с этим кодом, тесно related, Extract Class может использоваться для извлечения методов и полей в их собственный класс, в котором поле всегда находится в допустимом состоянии (возможно, заданном во время конструирования) и представляет состояние этого нового класса. Если вы идете по маршруту параметра, но слишком много значений для передачи, другой вариант - ввести новый объект параметра,


Важно отметить, что временные поля часто требуются для рефакторинга класса извлечения. Но тот, кто знает об этом, вероятно, не будет проверять код в этом промежуточном состоянии.
Оберли

1

Я бы сказал, что когда вы к этому приступите, это действительно отсутствие сплоченности, и я могу дать ему название «Ложная сплоченность». Я бы назвал этот термин (или, если я получаю его откуда-то и забыл об этом, «заимствовал»), потому что класс кажется связным в том смысле, что его методы работают с полем-членом. Однако на самом деле это не так, что означает, что кажущаяся сплоченность фактически ложна.

Что касается того, плохо это или нет, я бы сказал, что это ясно, и я думаю, что Стивен С. отлично объясняет почему. Тем не менее, независимо от того, заслуживает ли оно того, чтобы его называли «анти-паттерном» или нет, я думаю, что это и любая другая парадигма кодирования могут быть связаны с меткой, поскольку это обычно облегчает общение.


1

Помимо уже упомянутых проблем, использование этого подхода в среде без автоматической сборки мусора (например, C ++) приведет к утечкам памяти, поскольку временные объекты не освобождаются после использования. (Хотя это может показаться немного надуманным, есть люди, которые просто поменяют 'null' на 'NULL' и будут рады, что код компилируется.)


1

Это выглядит для меня как ужасное неправильное применение шаблона Flyweight. К сожалению, я знал нескольких разработчиков, которые должны использовать одну и ту же «вещь» несколько раз в разных методах и просто вытаскивать ее в приватное поле, пытаясь сбить с толку память, улучшить производительность или какую-то другую странную псевдооптимизацию. По их мнению, они пытаются решить аналогичную проблему, поскольку Flyweight предназначен для решения только они делают это в ситуации, когда это на самом деле не проблема, которая требует решения.


-1

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

Сказать, что это действительно плохая практика программирования, правильно, но, возможно, это просто результат того, что интернет-ресурсы и ресурсы, прошедшие экспертную оценку, такие, которые нам всем нравятся сегодня, могли бы привести первоначального разработчика к более «безопасному» пути.

Хотя на самом деле он не заслуживает тега «antipattern», но было приятно увидеть предложения о том, как можно перекодировать OP.


1
Добро пожаловать в Stack Exchange Programmers, спасибо за ввод вашего первого ответа. Похоже, что у вас был отрицательный голос, возможно, из-за причин, перечисленных в часто задаваемых вопросах programmers.stackexchange.com/faq . Часто задаваемые вопросы дают отличные предложения по созданию эффективных (и с правом голоса) ответов. Иногда люди достаточно любезны, чтобы добавить примечание об отрицательном голосовании, чтобы указать, является ли оно каким-то образом неправильным, дублирующим, мнение без доказательств или я тоже, который повторяет более ранний ответ. У большинства из нас были отрицательные голоса, закрытые или удаленные ответы, так что не беспокойтесь. Это только часть начала. Добро пожаловать.
DeveloperDon
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.