Якщо ви використовуєте "статичне" ключове слово без "остаточного" ключового слова, це повинно бути сигналом для ретельного розгляду вашого дизайну. Навіть наявність "остаточного" не є вільним пропуском, оскільки змінений статичний кінцевий об'єкт може бути настільки ж небезпечним.
Я б оцінив десь у 85% часу, коли я бачу "статику" без "остаточного", це WRONG. Часто мені трапляються дивні способи вирішення проблем, щоб замаскувати або приховати ці проблеми.
Будь ласка, не створюйте статичні мутабелі. Особливо колекції. Загалом, колекції повинні бути ініціалізовані, коли об'єкт, що містить вміст, ініціалізовані та розроблені таким чином, щоб вони були скинуті або забуті про те, коли забутий їх об'єкт забутий.
Використання статики може створити дуже тонкі помилки, що спричинить тривалий день інженерів. Я знаю, бо я одночасно створював і полював на цих клопів.
Якщо ви хочете отримати більше деталей, будь ласка, читайте далі ...
Чому б не використовувати статику?
Існує багато проблем зі статикою, включаючи написання та виконання тестів, а також тонких помилок, які не відразу очевидні.
Код, який спирається на статичні об'єкти, не може бути легко перевірений одиницею, і статику не можна легко глузувати (як правило).
Якщо ви використовуєте статику, неможливо замінити реалізацію класу, щоб перевірити компоненти більш високого рівня. Наприклад, уявіть статичний CustomerDAO, який повертає клієнтські об'єкти, які він завантажує з бази даних. Тепер у мене є клас CustomerFilter, який потребує доступу до деяких об'єктів клієнта. Якщо CustomerDAO статичний, я не можу написати тест для CustomerFilter, попередньо не ініціалізуючи свою базу даних і не заповнивши корисну інформацію.
І популяція бази даних, і ініціалізація займають багато часу. На мій досвід, ваша ініціалізація бази даних з часом зміниться, тобто дані перетворяться, і тести можуть зламатися. IE, уявіть, що Customer 1 раніше був VIP, але структура ініціалізації БД змінилася, і тепер Замовник 1 вже не є VIP, але ваш тест був жорстко кодований для завантаження клієнта 1…
Кращим підходом є створення екземпляра CustomerDAO та передавання його у CustomerFilter під час його створення. (Ще кращим підходом було б використання весни чи іншої структури інверсії управління.
Після цього ви зможете швидко знущатись або заглушувати альтернативний DAO у своєму CustomerFilterTest, що дозволяє вам більше контролювати тест,
Без статичного DAO тест буде швидшим (без ініціалізації db) та надійнішим (оскільки він не вийде з ладу при зміні коду ініціалізації db). Наприклад, у цьому випадку забезпечення Замовника 1 є та завжди буде VIP, що стосується тесту.
Виконання тестів
Статистика спричиняє справжню проблему під час спільного використання пакетів тестів одиниць (наприклад, із сервером постійної інтеграції). Уявіть статичну карту мережевих об'єктів Socket, яка залишається відкритою від одного тесту до іншого. Перший тест може відкрити Socket на порту 8080, але ви забули очистити карту, коли тест зірветься. Тепер, коли запускається другий тест, він, ймовірно, вийде з ладу, коли він намагається створити новий Socket для порту 8080, оскільки порт все ще зайнятий. Уявіть також, що посилання Socket у вашій статичній колекції не видаляються, і (за винятком WeakHashMap) ніколи не можуть бути зібрані сміття, що спричинить витік пам'яті.
Це надто узагальнений приклад, але у великих системах ця проблема трапляється ВСЕ ЧАС. Люди не замислюються над тестуванням одиниць, які запускають та зупиняють своє програмне забезпечення неодноразово в одному JVM, але це хороший тест вашої розробки програмного забезпечення, і якщо у вас є прагнення до високої доступності, це вам потрібно знати.
Ці проблеми часто виникають із рамними об'єктами, наприклад, вашими шарами доступу до БД, кешування, обміну повідомленнями та ведення журналів. Якщо ви використовуєте Java EE або якісь найкращі рамки породи, вони, ймовірно, керують цим для вас багато, але, як і я, ви маєте справу зі спадковою системою, у вас може бути багато спеціальних рамок для доступу до цих шарів.
Якщо конфігурація системи, що застосовується до цих компонентів рамки, змінюється між тестовими одиницями, і рамка тестового блоку не руйнує та не відновлює компоненти, ці зміни не можуть набути чинності, і коли тест покладається на ці зміни, вони не зможуть .
Навіть не рамкові компоненти є предметом цієї проблеми. Уявіть статичну карту під назвою OpenOrders. Ви пишете один тест, який створює кілька відкритих замовлень, і перевіряє, чи всі вони в правильному стані, потім тест закінчується. Інший розробник пише другий тест, який вносить необхідні замовлення на карту OpenOrders, а потім стверджує, що кількість замовлень є точною. Запускатись індивідуально, ці тести обидва проходили б, але якщо працювати разом у наборі, вони не зможуть.
Гірше, помилка може базуватися на порядку, в якому проводились тести.
У цьому випадку, уникаючи статики, ви уникаєте ризику збереження даних у тестових випадках, забезпечуючи кращу надійність тесту.
Тонкі помилки
Якщо ви працюєте в середовищі з високою доступністю або в будь-якому іншому місці, де потоки можуть бути запущені та зупинені, те саме, що було зазначено вище, для тестових наборів модулів може застосовуватися і тоді, коли ваш код працює на виробництві.
При роботі з потоками, а не за допомогою статичного об'єкта для зберігання даних, краще використовувати об’єкт, ініціалізований під час фази запуску потоку. Таким чином, кожного разу, коли запускається потік, створюється новий екземпляр об'єкта (з потенційно новою конфігурацією), і ви уникаєте даних з одного екземпляра потоку, що переходить у наступний екземпляр.
Коли нитка відмирає, статичний об'єкт не буде скинутий або зібраний сміття. Уявіть, що у вас є нитка під назвою "EmailCustomers", і коли вона запускається, вона заповнює статичну колекцію String зі списком адрес електронної пошти, а потім починає надсилати електронну пошту кожної з адрес. Скажімо, нитка перервана або скасована якось, тому ваша рамка високої доступності перезавантажує потік. Потім, коли нитка запускається, вона перезавантажує список клієнтів. Але оскільки колекція статична, вона може зберігати список адрес електронної пошти з попередньої колекції. Тепер деякі клієнти можуть отримати копії електронних листів.
Вбік: Статичний фінал
Використання "статичного остаточного" є фактично Java-еквівалентом C #define, хоча є технічні відмінності в реалізації. AC / C ++ #define вимикається з коду попередньо процесором перед компіляцією. Java "статичний фінал" в кінцевому підсумку залишатиметься резидентом пам'яті. Таким чином, він більше схожий на змінну "статичний const" в C ++, ніж на #define.
Підсумок
Я сподіваюся, що це допоможе пояснити декілька основних причин, чому статистика є проблематичною. Якщо ви використовуєте сучасну систему Java, на зразок Java EE або Spring тощо, можливо, ви не стикаєтесь із багатьма з цих ситуацій, але якщо ви працюєте з великим кодом застарілого коду, вони можуть стати набагато частішими.