Почему статические переменные считаются злыми? - PullRequest
585 голосов
/ 11 августа 2011

Я программист на Java, новичок в корпоративном мире.Недавно я разработал приложение с использованием Groovy и Java.Весь код, который я написал, использовал довольно много статики.Старшая техническая партия попросила меня сократить количество используемой статики.Я примерно так же гуглил и обнаружил, что многие программисты против использования статических переменных.

Я считаю, что статические переменные более удобны в использовании.И я предполагаю, что они тоже эффективны (пожалуйста, исправьте меня, если я ошибаюсь), потому что, если бы мне пришлось сделать 10000 вызовов функции внутри класса, я был бы рад сделать метод статичным и использовать прямое Class.methodCall() наэто вместо того, чтобы загромождать память 10 000 экземплярами класса, верно?

Более того, статика уменьшает взаимозависимости в других частях кода.Они могут выступать в качестве идеальных государственных держателей.В дополнение к этому я обнаружил, что статика широко применяется в некоторых языках, таких как Smalltalk и Scala .Так почему же это угнетение статики распространено среди программистов (особенно в мире Java)?

PS: пожалуйста, исправьте меня, если мои предположения о статике неверны.

Ответы [ 29 ]

648 голосов
/ 11 августа 2011

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

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

258 голосов
/ 17 августа 2011

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

В Java много уловок, когда вы начинаете использовать статику, которые не всегда очевидны. Например, если у вас есть две копии вашей программы, работающие на одной и той же виртуальной машине, будут ли они хранить значение статической переменной и связываться с состоянием друг друга? Или что происходит, когда вы расширяете класс, можете ли вы переопределить статический член? Ваша виртуальная машина исчерпала память, потому что у вас есть безумные числа статики, и эта память не может быть восстановлена ​​для других необходимых объектов экземпляра?

Срок службы объекта: Кроме того, статика имеет время жизни, которое соответствует всему времени выполнения программы. Это означает, что даже когда вы закончили использовать свой класс, память от всех этих статических переменных не может быть собрана сборщиком мусора. Если, например, вместо этого вы сделали свои переменные нестатическими, а в своей функции main () вы сделали один экземпляр вашего класса, а затем попросили ваш класс выполнить определенную функцию 10000 раз после выполнения этих 10000 вызовов и вы удаляете ссылки на один экземпляр, все ваши статические переменные могут быть собраны и использованы повторно.

Предотвращает повторное использование: Кроме того, статические методы нельзя использовать для реализации интерфейса, поэтому статические методы могут препятствовать использованию определенных объектно-ориентированных функций.

Другие опции: Если ваша главная задача - эффективность, то могут быть и другие более эффективные способы решения проблемы скорости, чем рассмотрение только преимущества вызова, заключающегося в большей скорости, чем создание. Подумайте, нужны ли где-нибудь временные или переменные модификаторы Чтобы сохранить возможность встраивания, метод может быть помечен как final, а не static. Параметры метода и другие переменные могут быть помечены как окончательные, чтобы разрешить определенные оптимизации компилятора на основе предположений о том, что может изменить эти переменные. Объект экземпляра можно использовать повторно несколько раз, вместо того, чтобы каждый раз создавать новый экземпляр. Там могут быть переключатели оптимизации компилятора, которые должны быть включены для приложения в целом. Возможно, дизайн должен быть настроен так, чтобы 10000 прогонов могли быть многопоточными и использовать преимущества многопроцессорных ядер. Если переносимость не имеет значения, возможно, нативный метод даст вам лучшую скорость, чем ваша статика.

Если по какой-то причине вы не хотите иметь несколько копий объекта, шаблон одноэлементного дизайна обладает преимуществами перед статическими объектами, такими как потокобезопасность (при условии, что ваш синглтон хорошо закодирован), что делает его ленивым-инициализация, гарантия того, что объект был должным образом инициализирован при его использовании, подклассификация, преимущества в тестировании и рефакторинге вашего кода, не говоря уже о том, если в какой-то момент вы передумали о том, что хотите получить только один экземпляр объекта, это НАМНОГОпроще удалить код, чтобы предотвратить дублирование экземпляров, чем реорганизовать весь код статической переменной для использования переменных экземпляра.Я должен был сделать это раньше, это не весело, и вам в конечном итоге приходится редактировать намного больше классов, что увеличивает ваш риск появления новых ошибок ... намного лучше, чтобы все было «правильно» в первый раз,даже если кажется, что у него есть свои недостатки.Для меня необходимая доработка, если вы решите, что вам понадобится несколько копий чего-либо, является, вероятно, одной из наиболее веских причин для использования статики как можно реже.И поэтому я бы также не согласился с вашим утверждением о том, что статика уменьшает взаимозависимости, я думаю, что в итоге вы получите код, который будет более связанным, если у вас есть много статик, к которым можно получить прямой доступ, а не объект, который «знает, как делать».что-то "на себя".

91 голосов
/ 11 августа 2011

Зло - это субъективный термин.

Вы не контролируете статику с точки зрения создания и разрушения.Они живут по указанию программы загрузки и выгрузки.

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

Это две основные проблемы, с которыми я столкнулся.

55 голосов
/ 11 августа 2011

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

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

Люди, которые пытаются избавиться от глобальных государств ради нее, неизбежно оказываются в гораздо более сложной системе - и глобальные государства все еще там, умело / идиотски замаскированные под многими слоями косвенных указаний;и мы все еще должны рассуждать о глобальных состояниях, развернув все косвенности.

Как люди Spring, которые щедро объявляют глобальные состояния в xml и думают, что это как-то лучше.

@ Jon Skeet if I create a new instance of an object Теперь у вас есть две причины: состояние внутри объекта и состояние среды, в которой находится объект.

30 голосов
/ 11 августа 2011

Существуют 2 основные проблемы со статическими переменными:

  • Потокобезопасность - статические ресурсы по определению не являются поточно-ориентированными
  • Простота кода - Вы не знаете, когда статические переменныеи будет ли он создан перед другой статической переменной
27 голосов
/ 25 апреля 2012

Если вы используете ключевое слово «static» без ключевого слова «final», это должно быть сигналом к ​​тщательному рассмотрению вашего дизайна.Даже наличие 'final' не является свободным проходом, так как изменяемый статический конечный объект может быть столь же опасным.

Я бы оценил где-то в 85% случаев, когда я вижу "static" без«окончательный», это НЕПРАВИЛЬНО.Часто я нахожу странные обходные пути, чтобы маскировать или скрывать эти проблемы.

Пожалуйста, не создавайте статические изменяемые файлы.Особенно Коллекции.В общем, Коллекции должны быть инициализированы, когда инициализируется содержащий их объект, и должны быть спроектированы таким образом, чтобы их сбрасывали или забывали о том, что их содержащий объект забыт.

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

Если вы хотите получить более подробную информацию, пожалуйста, прочитайте…

Почему бы не использовать статику?

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

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

Если вы используете статику, невозможно поменять реализацию класса, чтобы протестировать компоненты более высокого уровня.Например, представьте статический CustomerDAO, который возвращает объекты Customer, которые он загружает из базы данных.Теперь у меня есть класс CustomerFilter, которому нужен доступ к некоторым объектам Customer.Если CustomerDAO статический, я не могу написать тест для CustomerFilter без предварительной инициализации моей базы данных и заполнения полезной информации.

А заполнение базы данных и ее инициализация занимают много времени.По моему опыту, ваша структура инициализации БД со временем будет меняться, а это значит, что данные будут изменяться, и тесты могут прерваться.IE, представьте, что Customer 1 раньше был VIP, но структура инициализации БД изменилась, и теперь Customer 1 больше не VIP, но ваш тест был жестко запрограммирован для загрузки Customer 1…

. Лучший подход - этосоздать экземпляр CustomerDAO и передать его в CustomerFilter при его создании.(Еще лучшим подходом было бы использование Spring или другой платформы Inversion of Control.

После того, как вы это сделаете, вы можете быстро смоделировать или заглушить альтернативный DAO в вашем CustomerFilterTest, что позволит вам лучше контролироватьtest,

Без статического DAO тест будет более быстрым (без инициализации db) и более надежным (потому что он не потерпит неудачу при изменении кода инициализации db). Например, в этом случае обеспечение Customer 1является и всегда будет VIP, если речь идет о тесте.

Выполнение тестов

Статика вызывает реальную проблему при одновременном запуске комплектов модульных тестов (дляНапример, с вашим сервером Continuous Integration.) Представьте статическую карту сетевых объектов Socket, которая остается открытой от одного теста к другому. Первый тест может открыть Socket на порту 8080, но вы забыли очистить карту, когда тест разрываетсявниз. Теперь, когда запускается второй тест, он может произойти сбой при попытке создать новый сокетдля порта 8080, так как порт все еще занят.Представьте также, что ссылки на сокеты в вашей статической коллекции не удаляются и (за исключением WeakHashMap) никогда не могут быть подвергнуты сборке мусора, что приводит к утечке памяти.

Это слишком обобщенный пример, но вВ больших системах эта проблема возникает ВСЕ ВРЕМЯ.Люди не думают о модульных тестах, запускающих и останавливающих свое программное обеспечение повторно в одной и той же JVM, но это хороший тест вашего программного обеспечения, и если у вас есть стремление к высокой доступности, это то, о чем вы должны знать.

Эти проблемы часто возникают прикаркасные объекты, например, уровни доступа к БД, кэширования, обмена сообщениями и ведения журнала.Если вы используете Java EE или некоторые из лучших в своем роде фреймворков, они, вероятно, справятся с этим для вас, но если вы, как и я, имеете дело с устаревшей системой, у вас может быть множество пользовательских фреймворков для доступа к этим слоям.

Если конфигурация системы, которая применяется к этим компонентам инфраструктуры, изменяется между модульными тестами, а среда модульного тестирования не разрушает и не перестраивает компоненты, эти изменения не вступают в силу, и когда тест полагается на эти изменения, они потерпят неудачу.

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

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

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

незначительные ошибки

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

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

Когдапоток умирает, статический объект не сбрасывается или сборщик мусора.Представьте, что у вас есть поток с именем «EmailCustomers», и когда он запускается, он заполняет статическую коллекцию строк списком адресов электронной почты, а затем начинает отправлять электронные письма на каждый из адресов.Допустим, поток каким-то образом прерван или отменен, поэтому среда высокой доступности перезапускает поток.Затем, когда поток запускается, он перезагружает список клиентов.Но поскольку коллекция является статической, она может сохранить список адресов электронной почты из предыдущей коллекции.Теперь некоторые клиенты могут получать дубликаты электронных писем.

В стороне: статический финал

Использование «статического финала» фактически является Java-эквивалентом C #define, хотяСуществуют технические различия в реализации.AC / C ++ #define выгружается из кода препроцессором перед компиляцией.Java «статический финал» в конечном итоге останется в стеке.Таким образом, она больше похожа на переменную «static const» в C ++, чем на # define.

Резюме

Надеюсь, это поможет объяснитьнесколько основных причин, по которым статика проблематична.Если вы используете современную среду Java, такую ​​как Java EE или Spring и т. Д., Вы можете не сталкиваться со многими из этих ситуаций, но если вы работаете с большим объемом устаревшего кода, они могут стать гораздо более частыми.

15 голосов
/ 12 августа 2011

Поскольку никто * не упомянул это: параллелизм. Статические переменные могут вас удивить, если у вас есть несколько потоков, читающих и записывающих в статическую переменную. Это часто встречается в веб-приложениях (например, ASP.NET) и может вызывать довольно сумасшедшие ошибки. Например, если у вас есть статическая переменная, которая обновляется страницей, и страница запрашивается двумя людьми «почти в одно и то же время», один пользователь может получить результат, ожидаемый другим пользователем, или хуже.

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

Надеюсь, вы готовы использовать замки и справиться с раздорами.

* На самом деле, Прит Сангха упомянул это.

13 голосов
/ 11 августа 2011

если бы мне нужно было сделать 10000 вызовов функции внутри класса, я бы рад сделать метод статичным и использовать простой class.methodCall () на нем вместо того, чтобы загромождать память 10 000 экземпляры класса, верно?

Вы должны сбалансировать необходимость инкапсуляции данных в объект с состоянием, а не просто вычисление результата функции для некоторых данных.

Более того, статика уменьшает взаимозависимости между другими частями кода.

Так же, как и инкапсуляция. В больших приложениях статика имеет тенденцию создавать код спагетти и не позволяет легко проводить рефакторинг или тестирование.

Другие ответы также дают веские основания против чрезмерного использования статики.

12 голосов
/ 11 августа 2011

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

При этом важно различать обычные статические переменные (обычно считается плохими) и конечные статические переменные (константы АКА; не так уж плохо).

10 голосов
/ 22 сентября 2016

Обобщение нескольких основных преимуществ и недостатков использования статических методов в Java:

Преимущества:

  1. Доступен глобально, т. Е. Не связан с каким-либо конкретным экземпляром объекта.
  2. Один экземпляр для JVM.
  3. Доступ к нему можно получить с помощью имени класса (объект не требуется).
  4. Содержитодно значение применимо ко всем экземплярам.
  5. Загружается при запуске JVM и умирает при выключении JVM.
  6. Они не изменяют состояние объекта.

Недостатки:

  1. Статические члены всегда являются частью памяти независимо от того, используются они или нет.
  2. Вы не можете контролировать создание и уничтожение статической переменной.Полезно, что они были созданы при загрузке программы и уничтожены при выгрузке программы (или когда JVM завершает работу).
  3. Вы можете сделать статический поток безопасным, используя синхронизацию, но вам потребуются дополнительные усилия.
  4. Если один поток изменяет значение статической переменной, которая может нарушить функциональность других потоков.
  5. Вы должны знать «static» перед его использованием.
  6. Вы не можете переопределить статические методы.
  7. Сериализация не работает с ними.
  8. Они не участвуют вполиморфизм во время выполнения.
  9. Существует проблема с памятью (в некоторой степени, но не так много, я думаю), если используется большое количество статических переменных / методов.Потому что они не будут собираться мусором, пока не закончится программа.
  10. Статические методы тоже сложно проверить.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...