왜 자바에서 static의 사용을 지양해야 하는가?

Categories Writings

자바에서 데이터를 가공하거나 특정 메서드를 수행할 때 새로운 클래스를 만들어서 이를 인스턴스화 해서 쓸건지 아니면 static 으로 쓸건지 고민하게 될 때가 있다. 사실 후자는 객체지향적 관점에서 그리 좋은 선택은 아니다. Vamsi Emani라는 프로그래머가 stack overflow에 남긴 질문 Why are static variables considered evil? 과 가장 많은 지지를 받은 두개의 답변을 번역했다.


Q by V. Emani

I am a Java programmer who is new to the corporate world. Recently I’ve developed an application using Groovy and Java. All through the code I’ve used quite a good number of statics. I was asked by the senior technical lot to cut down on the number of statics used. I’ve googled about the same, and I find that many programmers are fairly against using static variables.

저는 현업에 갓 뛰어든 자바 프로그래머입니다. 근래에 Groovy와 Java를 이용하는 어플리케이션을 개발하고 있습니다. 그동안 자바로 개발할 때 "static" 변수(그리고 static 메소드)를 꽤나 많이 이용하는 습관을 가지고 있었습니다. 근데 제 시니어는 static 의 개수를 줄이라고 말합니다. 그 이유가 궁금해서 구글에 검색을 해봤는데 많은 자바 프로그래머가 static 을 사용하는 것을 꺼린다는 것을 발견했습니다.

I find static variables more convenient to use. And I presume that they are efficient too (please correct me if I am wrong), because if I had to make 10,000 calls to a function within a class, I would be glad to make the method static and use a straightforward class.methodCall() on it instead of cluttering the memory with 10,000 instances of the class, right?

사실 저는 static 을 이용하는 것이 보다 편하고 효율적이라고 생각합니다. (제가 틀린 부분이 있으면 지적해주세요!) 어떤 클래스 내에서 만번의 함수 호출을 하게 된다면 그 함수를 static으로 만들어서 class.methodCall()의 형태로 사용하는 것이 만개의 인스턴스를 생성해서 메모리를 어지럽히는 것보다 훨씬 낫지 않을까요?

Moreover statics reduce the inter-dependencies on the other parts of the code. They can act as perfect state holders. Adding to this I find that statics are widely implemented in some languages like Smalltalk and Scala. So why is this oppression for statics prevalent among programmers (especially in the world of Java)?

PS: please do correct me if my assumptions about statics are wrong.

게다가 static 변수는 코드의 상호의존성(inter-dependency)을 줄여준다고 생각합니다. 상태를 저장하는데 있어서 static 변수들은 아주 유용하게 사용될 수 있습니다. 사실 static은 자바 뿐만 아니라 Smalltalk나 Scala 와 같은 프로그래밍 언어에서도 널리 이용되고 있습니다. 근데 왜 유독 자바 프로그래밍 세계에선 개발자들이 static의 사용을 꺼리는 걸까요?

PS: static 변수에 대한 제 생각에 틀린 부분을 지적해주시면 감사하겠습니다.


A by J. Skeet

Static variables represent global state. That’s hard to reason about and hard to test: if I create a new instance of an object, I can reason about its new state within tests. If I use code which is using static variables, it could be in any state – and anything could be modifying it.

Static 변수는 global state(역주: 전역 상태. 프로그램 혹은 한 모듈 전체의 상태)를 상징합니다. Global state는 추론과 테스트가 매우 까다롭습니다. 가령 코드에서 static 변수를 사용한다고 하면, 이 변수의 상태는 코드 여러 부분에서 영향을 받을 수 있게 되고 따라서 변화를 추적하기가 어려워집니다. 반면에 이를 객체화하여 인스턴스로 생성하면 테스트 중에 그 변수가 어떤 상태를 가지고 있는지 추론하는 것이 보다 간단해집니다.

I could go on for quite a while, but the bigger concept to think about is that the tighter the scope of something, the easier it is to reason about. We’re good at thinking about small things, but it’s hard to reason about the state of a million line system if there’s no modularity. This applies to all sorts of things, by the way – not just static variables.

프로그래머로서 제가 그동안 경험해온 바에 따르면 큰 개념에 대해서 그리기 위해선 일단 이해하고자 하는 범위를 좁혀 쉽게 추론할 수 있어야 합니다. 일반적으로 우리는 작으면 작을수록 그 대상을 쉽게 이해합니다. 다시 말해, 모듈화를 제대로 하지 않는다면 백만 줄 짜리 시스템의 상태에 대해서 추론하는 것은 굉장히 어려운 일입니다. 이것은 단순히 static 변수 뿐만 아니라 모든 프로그래밍 이슈에 대해서 적용할 수 있는 중요한 사실입니다.


A by A. Lockwood & J. Brown

Its not very object oriented: One reason statics might be considered "evil" by some people is they are contrary the object-oriented paradigm. In particular, it violates the principle that data is encapsulated in objects (that can be extended, information hiding, etc). Statics, in the way you are describing using them, are essentially to use them as a global variable to avoid dealing with issues like scope. However, global variables is one of the defining characteristics of procedural or imperative programming paradigm, not a characteristic of "good" object oriented code. This is not to say the procedural paradigm is bad, but I get the impression your supervisor expects you to be writing "good object oriented code" and you’re really wanting to write "good procedural code".

첫째로, static은 객체 지향적이지 않습니다: 개발자들이 static 변수를 ‘악’으로 규정하는 이유는 static 변수가 객체 지향의 패러다임과 상반되기 때문입니다. 특히나 static 변수는, 각 객체의 데이터들이 캡슐화되어야 한다는 객체지향 프로그래밍의 원칙(역주: 한 객체가 가지고 있는 데이터들은 외부에서 함부로 접근하여 수정할 수 없도록 해야 한다는 원칙)에 위반됩니다. 질문자께서 스스로 설명했듯이 static은 스코프(역주: 한 변수가 유효한 범위)를 고려할 필요가 없는 경우, 즉 전역 변수를 사용할 때에 유용합니다. 이는 절차지향적 프로그래밍 혹은 명령형 프로그래밍(역주: C가 대표적인 절차지향적, 명령형 프로그래밍 언어이며 Java 역시 큰 범위에서 절차지향적, 명령형 프로그래밍 언어라고 할 수 있다.)에서 매우 중요한 개념입니다. 하지만 이 것이 객체지향의 관점에서 좋은 코드라고 얘기하기는 힘듭니다. 절차지향 패러다임이 나쁘다는 것이 아닙니다. 다만, 당신의 시니어는 당신이 "객체지향적으로 좋은 코드"를 짜기를 바라는 것입니다. 반대로 당신은 "절차지향적으로 좋은 코드"를 짜기를 원하는 것이라고 말할 수 있을 것입니다.

There are many gotchyas in Java when you start using statics that are not always immediately obvious. For example, if you have two copies of your program running in the same VM, will they shre the static variable’s value and mess with the state of each other? Or what happens when you extend the class, can you override the static member? Is your VM running out of memory because you have insane numbers of statics and that memory cannot be reclaimed for other needed instance objects?

사실 자바에서 static을 사용하기 시작하면 예측이 어려운 문제가 많아지게 됩니다. 예를 들어서 하나의 가상머신에서 어떤 프로그램 두 카피가 돌고 있다고 가정해봅시다. 만약 이 두 카피가 동일한 static 변수를 공유하게 된다면, 서로의 상태에 영향을 주게 되지 않을까요? 더불어서 오버라이딩을 할 수 없는 static 멤버들 때문에 클래스를 확장하는게 어려워질 것입니다. 뿐만 아니라 지나치게 많은 static 변수를 사용하게 되면 이들로부터 메모리 회수를 할 수 없어서 가상머신이 메모리 부족을 겪게 될 것입니다.

Object Lifetime: Additionally, statics have a lifetime that matches the entire runtime of the program. This means, even once you’re done using your class, the memory from all those static variables cannot be garbage collected. If, for example, instead, you made your variables non-static, and in your main() function you made a single instance of your class, and then asked your class to execute a particular function 10,000 times, once those 10,000 calls were done, and you delete your references to the single instance, all your static variables could be garbage collected and reused.

객체의 라이프타임: 추가로, static 변수는 프로그램이 실행되고 있는 내내 살아있게 됩니다. 즉, 그 클래스를 이용한 작업을 끝내더라도 static 변수가 점유하고 있는 메모리는 garbage collector(역주: 사용하지 않는 메모리를 회수하는 기능)에 의해서 회수되지 않게 됩니다. 반대로, 프로그래머가 그 변수를 인스턴스화 해서 main() 함수 내에서 하나의 인스턴스로 생성하게 되면, 그리고 그 인스턴스에게 만번의 함수 호출을 시키게 되면 그 만번의 함수 호출이 끝난 후 인스턴스는 소멸됩니다. 따라서 메모리를 훨씬 절약할 수 있게 됩니다.

Prevents certain re-use: Also, static methods cannot be used to implement an interface, so static methods can prevent certain object oriented features from being usable.

static은 재사용성이 떨어집니다: 또한, static 메서드는 interface를 구현하는데 사용될 수 없습니다. 즉 static 메서드는 프로그래머가 (재사용성을 높여주는)이러한 자바의 유용한 객체지향적 기능들을 사용하는 것을 방해합니다.

Other Options: If efficiency is your primary concern, there might be other better ways to solve the speed problem than considering only the advantage of invocation being usually faster than creation. Consider whether the transient or volatile modifiers are needed anywhere. To preserve the ability to be inlined, a method could be marked as final instead of static. Method parameters and other variables can be marked final to permit certain compiler optimizations based on assumptions about what can change those variables. An instance object could be reused multiple times rather than creating a new instance each time. There may be complier optimization switches that should be turned on for the app in general. Perhaps, the design should be set up so that the 10,000 runs can be multi-threaded and take advantage of multi-processor cores. If portability isn’t a concern, maybe a native method would get you better speed than your statics do.

static의 대안들: 프로그래머에게 효율(여기서는 속도)이 가장 중요한 문제여서 객체를 생성할 때 마다 생기는 사소한 불이익에도 민감한 상황일 수 있습니다. 이 경우에도 여전히 static 대신에 다른 방법들을 사용하는 것이 가능합니다. 먼저 "transient"나 "volatile"과 같은 제어자(modifier)를 쓸 수 있는지 먼저 고려해봅니다. 실행 속도를 빠르게 해주는 메소드 인라이닝(역주: 실제 메소드를 호출하지 않고 바로 결과값을 돌려주는 방식)을 위해 "final" 메서드를 사용하는 것도 생각해볼 수 있습니다. 또한 메서드 파라미터들과 변수들이 final로 선언되면 컴파일러 단에서의 최적화 작업이 가능해집니다. 인스턴스를 사용할 때마다 새로 생성하는 대신에 여러번 재사용할 수도 있습니다. 아마도 컴파일러 단의 최적화 작업이 switches that should be turned on for the app in general. 어쩌면 멀티스레드를 이용해서 멀티코어 프로세스의 장점을 극대화하기 위해선 이런 디자인이 필수적일 수도 있습니다. 이식성(역주: 다른 플랫폼으로 쉽게 옮길 수 있는 특성)이 중요한 것이 아니라면, native 메서드를 사용해서 static을 사용하는 것보다 더 빠르게 만들 수도 있을 것입니다.

If for some reason you do not want multiple copies of an object, the singleton design pattern, has advantages over static objects, such as thread-safety (presuming your singleton is coded well), permitting lazy-initialization, guaranteeing the object has been properly initialized when it is used, sub-classing, advantages in testing and refactoring your code, not to mention, if at some point you change your mind about only wanting one instance of an object it is MUCH easier to remove the code to prevent duplicate instances than it is to refactor all your static variable code to use instance variables. I’ve had to do that before, its not fun, and you end up having to edit a lot more classes, which increases your risk of introducing new bugs…so much better to set things up "right" the first time, even if it seems like it has its disadvantages. For me, the re-work required should you decide down the road you need multiple copies of something is probably one of most compelling reasons to use statics as infrequently as possible. And thus I would also disagree with your statement that statics reduce inter-dependencies, I think you will end up with code that is more coupled if you have lots of statics that can be directly accessed, rather than an object that "knows how to do something" on itself.

만약 여러개의 인스턴스를 만드는 것을 피하고 싶다면 싱글톤 디자인 패턴을 이용하는 것이 훌륭한 대안이 될 수 있습니다. 싱글톤 디자인은 (싱글톤을 제대로 구현했다는 전제하에) 스레드 안정성을 가지고, lazy-initialization(역주: 객체가 필요할 때마다 만들어 쓰는 기법)을 허용하며, 객체가 사용될 때마다 제대로 초기화 된다는 것을 보장합니다. 뿐만 아니라 서브 클래싱(sub-classing) 기법을 가능하게 하고, 테스트와 리팩토링이 매우 용이합니다. 다음의 상황을 가정해봅시다. 프로그래밍을 하다가 어느 시점에서 지금까지의 설계를 바꿔야겠다는 생각이 들게 되면 두말할 것도 없이 하나의 인스턴스를 수정하는 것이 모든 static 변수들을 리팩토링 하는 것보다 훨씬 편할 것입니다. 사실 static을 사용하다가 refactoring을 해야하는 상황은 매우 흔한 일입니다. 그것은 유쾌하지 않은 일일 뿐 아니라 훨씬 많은 클래스를 수정하게 만들기도 합니다. 이렇게 또다시 클래스들을 수정하다보면 새로운 버그를 만들어낼 소지가 매우 커집니다. 이런 상황을 피하기 위해서 처음에 "제대로"(위에서 언급한 방식들대로) 디자인하여 코딩하는 것이, 그 방식이 몇가지 단점을 가지고 있는 것 처럼 보여도 훨씬 나은 선택입니다. 사실 이런 끔찍한 재수정 작업이 요구될지도 모른다는 소지가 제가 static을 되도록 쓰지 않으려는 가장 큰 이유 중 하나입니다. 정리하자면, 저는 질문자께서 static이 코드의 상호의존성(inter-dependency)을 줄여준다고 말하신 것에 동의할 수 없습니다. 인스턴스화 되어 있는 객체들을 쓰지 않고 static 변수에 직접 접근하는 방식으로 코드를 짜다보면, 결국 작성한 모듈들이 서로 더 많이 엮이는 (바람직하지 않은) 상황에 처하게 될 것입니다.