'final'키워드없이 'static'키워드를 사용하는 경우 디자인을 신중하게 고려해야한다는 신호입니다. 변경 가능한 정적 최종 객체가 위험 할 수 있기 때문에 '최종'의 존재조차도 자유 패스가 아닙니다.
나는 '최종'이없는 '정적'을 볼 때의 약 85 % 어딘가에 추정 할 것입니다. 종종 이러한 문제를 숨기거나 숨기는 이상한 해결 방법을 찾을 수 있습니다.
정적 변수를 만들지 마십시오. 특히 컬렉션. 일반적으로 컬렉션은 포함하는 객체가 초기화 될 때 초기화되어야하며 포함하는 객체를 잊었을 때 재설정되거나 잊혀지도록 설계되어야합니다.
스태틱을 사용하면 매우 미묘한 버그가 생겨 엔지니어에게 고통을 줄 수 있습니다. 나는이 버그를 만들고 사냥했기 때문에 알고 있습니다.
자세한 내용은 다음을 참조하십시오.
왜 정적을 사용하지 않습니까?
테스트 작성 및 실행은 물론 눈에 띄지 않는 미묘한 버그를 포함하여 정적 관련 문제가 많이 있습니다.
정적 객체에 의존하는 코드는 쉽게 단위 테스트를 할 수 없으며 정적을 쉽게 조롱 할 수 없습니다 (보통).
정적을 사용하는 경우 더 높은 수준의 구성 요소를 테스트하기 위해 클래스 구현을 교체 할 수 없습니다. 예를 들어 데이터베이스에서로드하는 Customer 객체를 반환하는 정적 CustomerDAO를 상상해보십시오. 이제 CustomerFilter 클래스가 있는데,이 클래스는 일부 Customer 객체에 액세스해야합니다. CustomerDAO가 정적이면 먼저 데이터베이스를 초기화하고 유용한 정보를 채우지 않고 CustomerFilter에 대한 테스트를 작성할 수 없습니다.
데이터베이스 채우기 및 초기화에 시간이 오래 걸립니다. 내 경험상 DB 초기화 프레임 워크는 시간이 지남에 따라 변경되므로 데이터가 변형되고 테스트가 중단 될 수 있습니다. IE는 고객 1이 VIP 였지만 DB 초기화 프레임 워크가 바뀌었고 이제 고객 1은 더 이상 VIP가 아니라고 테스트했지만 고객 1을로드하도록 테스트를 하드 코딩했습니다.
더 좋은 방법은 CustomerDAO를 인스턴스화하여 생성 될 때 CustomerFilter에 전달하는 것입니다. (더 나은 접근 방식은 Spring 또는 다른 Inversion of Control 프레임 워크를 사용하는 것입니다.
이 작업을 수행하면 CustomerFilterTest에서 대체 DAO를 신속하게 조롱하거나 스터브 아웃 할 수 있으므로 테스트를보다 효과적으로 제어 할 수 있습니다.
정적 DAO가 없으면 테스트가 더 빠르며 (db 초기화 없음) 더 안정적입니다 (db 초기화 코드가 변경 될 때 실패하지 않기 때문에). 예를 들어,이 경우 테스트 1과 관련하여 고객 1은 항상 VIP가됩니다.
테스트 실행
정적은 단위 테스트 세트를 함께 실행할 때 (예 : Continuous Integration 서버에서) 실제 문제를 일으 킵니다. 한 테스트에서 다른 테스트로 열려있는 네트워크 소켓 객체의 정적 맵을 상상해보십시오. 첫 번째 테스트는 포트 8080에서 소켓을 열 수 있지만 테스트가 종료 될 때 맵을 지우는 것을 잊었습니다. 이제 두 번째 테스트가 시작되면 포트가 여전히 점유되어 있기 때문에 포트 8080에 대한 새 소켓을 만들려고 할 때 충돌이 발생할 수 있습니다. 정적 컬렉션의 소켓 참조가 제거되지 않고 WeakHashMap을 제외하고는 가비지 수집 대상이되지 않아 메모리 누수가 발생한다고 상상해보십시오.
이것은 지나치게 일반화 된 예이지만 대규모 시스템에서는이 문제가 항상 발생합니다. 사람들은 단위 테스트가 동일한 JVM에서 소프트웨어를 반복적으로 시작하고 중지하는 것을 생각하지 않지만, 소프트웨어 디자인에 대한 좋은 테스트이며, 고 가용성에 대한 열망이있는 경우이를 알아야합니다.
이러한 문제는 종종 DB 액세스, 캐싱, 메시징 및 로깅 계층과 같은 프레임 워크 개체에서 발생합니다. Java EE 또는 최상의 품종 프레임 워크를 사용하는 경우 아마도이를 위해 많은 것을 관리하지만 레거시 시스템을 다루는 경우 이러한 계층에 액세스하기위한 많은 사용자 정의 프레임 워크가있을 수 있습니다.
이러한 프레임 워크 구성 요소에 적용되는 시스템 구성이 단위 테스트간에 변경되고 단위 테스트 프레임 워크가 구성 요소를 분리 및 재 구축하지 않으면 이러한 변경 사항이 적용되지 않으며 테스트가 해당 변경 사항에 의존하면 실패합니다. .
비 프레임 워크 구성 요소도이 문제의 영향을받습니다. OpenOrders라는 정적지도를 상상해보십시오. 하나의 테스트를 작성하여 미결 주문을 작성하고 모두 올바른 상태인지 확인한 다음 테스트를 종료합니다. 다른 개발자는 필요한 주문을 OpenOrders 맵에 넣는 두 번째 테스트를 작성한 다음 주문 수가 정확하다고 주장합니다. 개별적으로 실행하면 이러한 테스트는 모두 통과되지만 스위트에서 함께 실행하면 실패합니다.
더 나쁜 것은 실패는 테스트가 실행 된 순서에 따라 결정될 수 있습니다.
이 경우 정적을 피함으로써 테스트 인스턴스 전체에서 데이터가 지속될 위험을 피하여 테스트 안정성을 향상시킬 수 있습니다.
미묘한 버그
고 가용성 환경에서 작업하거나 스레드가 시작 및 중지 될 수있는 곳에서 작업하는 경우 코드가 프로덕션 환경에서 실행될 때 유닛 테스트 스위트와 동일한 문제가 적용될 수 있습니다.
정적 객체를 사용하여 데이터를 저장하는 대신 스레드를 처리 할 때는 스레드 시작 단계에서 초기화 된 객체를 사용하는 것이 좋습니다. 이렇게하면 스레드가 시작될 때마다 새로운 잠재적 개체 구성을 가진 객체의 새 인스턴스가 만들어지고 스레드의 한 인스턴스에서 다음 인스턴스로 데이터가 번지는 것을 피할 수 있습니다.
스레드가 죽으면 정적 객체는 재설정되거나 가비지 수집되지 않습니다. "EmailCustomers"라는 스레드가 있고 시작시 정적 문자열 콜렉션을 이메일 주소 목록으로 채우고 각 주소로 이메일을 보내기 시작한다고 가정하십시오. 스레드가 어떻게 든 중단되거나 취소되었다고 가정하면 고 가용성 프레임 워크가 스레드를 다시 시작합니다. 그런 다음 스레드가 시작되면 고객 목록을 다시로드합니다. 그러나 모음은 정적이므로 이전 모음의 전자 메일 주소 목록이 유지 될 수 있습니다. 이제 일부 고객은 이메일이 중복 될 수 있습니다.
제쳐두고 : 정적 결승
기술적 인 구현상의 차이가 있지만 "정적 최종"의 사용은 사실상 C #define과 동등한 Java입니다. AC / C ++ #define은 컴파일 전에 프리 프로세서에 의해 코드에서 교체됩니다. Java "정적 최종"은 스택에 상주하는 메모리를 종료합니다. 이런 식으로 C ++의 "정적 const"변수와 #define보다 유사합니다.
요약
이것이 정적이 문제가되는 몇 가지 기본 이유를 설명하는 데 도움이되기를 바랍니다. Java EE 또는 Spring 등과 같은 최신 Java 프레임 워크를 사용하는 경우 이러한 상황이 많이 발생하지 않을 수 있지만 많은 레거시 코드로 작업하는 경우 훨씬 더 빈번해질 수 있습니다.