Если вы используете ключевое слово «static» без ключевого слова «final», это должно быть сигналом к тщательному рассмотрению вашего дизайна. Даже наличие 'final' не является свободным проходом, поскольку изменяемый статический конечный объект может быть столь же опасным.
Я бы оценил где-то в 85% случаев, когда вижу «статичный» без «финала», это НЕПРАВИЛЬНО. Часто я нахожу странные обходные пути, чтобы замаскировать или скрыть эти проблемы.
Пожалуйста, не создавайте статические изменяемые файлы. Особенно Коллекции. В общем, Коллекции должны быть инициализированы, когда инициализируется их содержащий объект, и должны быть спроектированы таким образом, чтобы они были сброшены или забыты, когда их содержащий объект забыт.
Использование статики может создать очень тонкие ошибки, которые причинят инженерам мучительные дни боли. Я знаю, потому что я и создал и охотился на этих ошибок.
Если вы хотите получить более подробную информацию, пожалуйста, читайте дальше ...
Почему бы не использовать статику?
Есть много проблем со статикой, включая написание и выполнение тестов, а также тонкие ошибки, которые не сразу очевидны.
Код, основанный на статических объектах, не может быть легко протестирован модулем, а статика не может быть легко смоделирована (обычно).
Если вы используете статику, невозможно поменять реализацию класса, чтобы протестировать компоненты более высокого уровня. Например, представьте статический CustomerDAO, который возвращает объекты Customer, которые он загружает из базы данных. Теперь у меня есть класс CustomerFilter, которому требуется доступ к некоторым объектам Customer. Если CustomerDAO статический, я не могу написать тест для CustomerFilter без предварительной инициализации моей базы данных и заполнения полезной информации.
А заполнение и инициализация базы данных занимает много времени. По моему опыту, ваша структура инициализации БД со временем будет меняться, а это значит, что данные будут изменяться, и тесты могут прерваться. IE, представьте, что Customer 1 раньше был VIP, но структура инициализации БД изменилась, и теперь Customer 1 больше не VIP, но ваш тест был жестко запрограммирован для загрузки Customer 1…
Лучшим подходом является создание экземпляра CustomerDAO и передача его в CustomerFilter при его создании. (Еще лучший подход - использовать Spring или другую платформу Inversion of Control.
Как только вы это сделаете, вы можете быстро смоделировать или заглушить альтернативный DAO в вашем CustomerFilterTest, что позволит вам иметь больше контроля над тестом,
Без статического DAO тест будет более быстрым (без инициализации db) и более надежным (потому что он не потерпит неудачу при изменении кода инициализации db). Например, в этом случае обеспечение того, чтобы Клиент 1 был и всегда будет VIP, насколько это касается теста.
Выполнение тестов
Статика вызывает реальную проблему при совместном запуске комплектов модульных тестов (например, на сервере Continuous Integration). Представьте себе статическую карту сетевых объектов Socket, которая остается открытой от одного теста к другому. Первый тест может открыть Socket на порту 8080, но вы забыли очистить карту, когда тест будет сорван. Теперь, когда запускается второй тест, он может потерпеть крах, когда попытается создать новый сокет для порта 8080, поскольку порт все еще занят. Представьте также, что ссылки на сокеты в вашей статической коллекции не удаляются и (за исключением WeakHashMap) никогда не могут быть подвергнуты сборке мусора, что приводит к утечке памяти.
Это слишком обобщенный пример, но в больших системах эта проблема возникает ВСЕ ВРЕМЯ. Люди не думают о модульных тестах, запускающих и останавливающих свое программное обеспечение повторно в одной и той же JVM, но это хороший тест вашего программного обеспечения, и если у вас есть стремление к высокой доступности, это то, о чем вы должны знать.
Эти проблемы часто возникают с объектами инфраструктуры, например, с уровнями доступа к БД, уровнями кэширования, обмена сообщениями и ведения журнала. Если вы используете Java EE или некоторые из лучших в своем роде фреймворков, они, вероятно, справятся с этим для вас, но если вы, как и я, имеете дело с устаревшей системой, у вас может быть много пользовательских фреймворков для доступа к этим уровням.
Если конфигурация системы, которая применяется к этим компонентам инфраструктуры, изменяется между модульными тестами, а среда модульного тестирования не разрушает и не перестраивает компоненты, эти изменения не вступают в силу, и когда тест полагается на эти изменения, они завершатся неудачно. ,
Даже не-каркасные компоненты подвержены этой проблеме. Представьте себе статическую карту под названием OpenOrders. Вы пишете один тест, который создает несколько открытых ордеров, и проверяет, чтобы убедиться, что все они находятся в правильном состоянии, затем тест завершается. Другой разработчик пишет второй тест, который помещает необходимые ему заказы в карту OpenOrders, а затем утверждает, что количество заказов является точным. Выполненные по отдельности, эти тесты оба пройдут, но при запуске вместе в комплекте они не пройдут.
Хуже того, сбой может быть основан на порядке, в котором выполнялись тесты.
В этом случае, избегая статики, вы избегаете риска сохранения данных в разных тестовых экземплярах, обеспечивая лучшую надежность теста.
Тонкие ошибки
Если вы работаете в среде высокой доступности или в любом месте, где потоки могут запускаться и останавливаться, то же самое упомянутое выше замечание с комплектами модульных тестов может применяться, когда ваш код также работает в рабочей среде.
При работе с потоками вместо статического объекта для хранения данных лучше использовать объект, инициализированный на этапе запуска потока. Таким образом, каждый раз, когда поток запускается, создается новый экземпляр объекта (с потенциально новой конфигурацией), и вы избегаете передачи данных от одного экземпляра потока до следующего экземпляра.
Когда поток умирает, статический объект не получает сброс или сборку мусора. Представьте, что у вас есть поток с именем «EmailCustomers», и когда он запускается, он заполняет статическую коллекцию строк списком адресов электронной почты, а затем начинает отправлять электронные письма на каждый из адресов. Допустим, поток каким-то образом прерван или отменен, поэтому среда высокой доступности перезапускает поток. Затем, когда поток запускается, он перезагружает список клиентов. Но поскольку коллекция является статической, она может сохранить список адресов электронной почты из предыдущей коллекции. Теперь некоторые клиенты могут получить дубликаты писем.
В сторону: статический финал
Использование «static final» фактически является Java-эквивалентом C #define, хотя существуют технические различия в реализации. AC / C ++ #define выгружается из кода препроцессором перед компиляцией. Java «статический финал» в конечном итоге останется в стеке. Таким образом, она больше похожа на переменную «static const» в C ++, чем на #define.
Резюме
Я надеюсь, что это поможет объяснить несколько основных причин, по которым статика вызывает проблемы. Если вы используете современную среду Java, такую как Java EE или Spring и т. Д., Вы можете не сталкиваться со многими из этих ситуаций, но если вы работаете с большим объемом устаревшего кода, они могут стать гораздо более частыми.