Why are static variables considered evil?

前端 未结 30 2709
既然无缘
既然无缘 2020-11-21 05:04

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 wrote used quite a good numbe

相关标签:
30条回答
  • 2020-11-21 06:05

    If you are using the ‘static’ keyword without the ‘final’ keyword, this should be a signal to carefully consider your design. Even the presence of a ‘final’ is not a free pass, since a mutable static final object can be just as dangerous.

    I would estimate somewhere around 85% of the time I see a ‘static’ without a ‘final’, it is WRONG. Often, I will find strange workarounds to mask or hide these problems.

    Please don’t create static mutables. Especially Collections. In general, Collections should be initialized when their containing object is initialized and should be designed so that they are reset or forgotten about when their containing object is forgotten.

    Using statics can create very subtle bugs which will cause sustaining engineers days of pain. I know, because I’ve both created and hunted these bugs.

    If you would like more details, please read on…

    Why Not Use Statics?

    There are many issues with statics, including writing and executing tests, as well as subtle bugs that are not immediately obvious.

    Code that relies on static objects can’t be easily unit tested, and statics can’t be easily mocked (usually).

    If you use statics, it is not possible to swap the implementation of the class out in order to test higher level components. For example, imagine a static CustomerDAO that returns Customer objects it loads from the database. Now I have a class CustomerFilter, that needs to access some Customer objects. If CustomerDAO is static, I can’t write a test for CustomerFilter without first initializing my database and populating useful information.

    And database population and initialization takes a long time. And in my experience, your DB initialization framework will change over time, meaning data will morph, and tests may break. IE, imagine Customer 1 used to be a VIP, but the DB initialization framework changed, and now Customer 1 is no longer VIP, but your test was hard-coded to load Customer 1…

    A better approach is to instantiate a CustomerDAO, and pass it into the CustomerFilter when it is constructed. (An even better approach would be to use Spring or another Inversion of Control framework.

    Once you do this, you can quickly mock or stub out an alternate DAO in your CustomerFilterTest, allowing you to have more control over the test,

    Without the static DAO, the test will be faster (no db initialization) and more reliable (because it won’t fail when the db initialization code changes). For example, in this case ensuring Customer 1 is and always will be a VIP, as far as the test is concerned.

    Executing Tests

    Statics cause a real problem when running suites of unit tests together (for example, with your Continuous Integration server). Imagine a static map of network Socket objects that remains open from one test to another. The first test might open a Socket on port 8080, but you forgot to clear out the Map when the test gets torn down. Now when a second test launches, it is likely to crash when it tries to create a new Socket for port 8080, since the port is still occupied. Imagine also that Socket references in your static Collection are not removed, and (with the exception of WeakHashMap) are never eligible to be garbage collected, causing a memory leak.

    This is an over-generalized example, but in large systems, this problem happens ALL THE TIME. People don’t think of unit tests starting and stopping their software repeatedly in the same JVM, but it is a good test of your software design, and if you have aspirations towards high availability, it is something you need to be aware of.

    These problems often arise with framework objects, for example, your DB access, caching, messaging, and logging layers. If you are using Java EE or some best of breed frameworks, they probably manage a lot of this for you, but if like me you are dealing with a legacy system, you might have a lot of custom frameworks to access these layers.

    If the system configuration that applies to these framework components changes between unit tests, and the unit test framework doesn’t tear down and rebuild the components, these changes can’t take effect, and when a test relies on those changes, they will fail.

    Even non-framework components are subject to this problem. Imagine a static map called OpenOrders. You write one test that creates a few open orders, and checks to make sure they are all in the right state, then the test ends. Another developer writes a second test which puts the orders it needs into the OpenOrders map, then asserts the number of orders is accurate. Run individually, these tests would both pass, but when run together in a suite, they will fail.

    Worse, failure might be based on the order in which the tests were run.

    In this case, by avoiding statics, you avoid the risk of persisting data across test instances, ensuring better test reliability.

    Subtle Bugs

    If you work in high availability environment, or anywhere that threads might be started and stopped, the same concern mentioned above with unit test suites can apply when your code is running on production as well.

    When dealing with threads, rather than using a static object to store data, it is better to use an object initialized during the thread’s startup phase. This way, each time the thread is started, a new instance of the object (with a potentially new configuration) is created, and you avoid data from one instance of the thread bleeding through to the next instance.

    When a thread dies, a static object doesn’t get reset or garbage collected. Imagine you have a thread called “EmailCustomers”, and when it starts it populates a static String collection with a list of email addresses, then begins emailing each of the addresses. Lets say the thread is interrupted or canceled somehow, so your high availability framework restarts the thread. Then when the thread starts up, it reloads the list of customers. But because the collection is static, it might retain the list of email addresses from the previous collection. Now some customers might get duplicate emails.

    An Aside: Static Final

    The use of “static final” is effectively the Java equivalent of a C #define, although there are technical implementation differences. A C/C++ #define is swapped out of the code by the pre-processor, before compilation. A Java “static final” will end up memory resident on the stack. In that way, it is more similar to a “static const” variable in C++ than it is to a #define.

    Summary

    I hope this helps explain a few basic reasons why statics are problematic up. If you are using a modern Java framework like Java EE or Spring, etc, you may not encounter many of these situations, but if you are working with a large body of legacy code, they can become much more frequent.

    0 讨论(0)
  • 2020-11-21 06:05

    a) Reason about programs.

    If you have a small- to midsize-program, where the static variable Global.foo is accessed, the call to it normally comes from nowhere - there is no path, and therefore no timeline, how the variable comes to the place, where it is used. Now how do I know who set it to its actual value? How do I know, what happens, if I modify it right now? I have grep over the whole source, to collect all accesses, to know, what is going on.

    If you know how you use it, because you just wrote the code, the problem is invisible, but if you try to understand foreign code, you will understand.

    b) Do you really only need one?

    Static variables often prevent multiple programs of the same kind running in the same JVM with different values. You often don't foresee usages, where more than one instance of your program is useful, but if it evolves, or if it is useful for others, they might experience situations, where they would like to start more than one instance of your program.

    Only more or less useless code which will not be used by many people over a longer time in an intensive way might go well with static variables.

    0 讨论(0)
  • The issue of 'Statics being evil' is more of an issue about global state. The appropriate time for a variable to be static, is if it does not ever have more than one state; IE tools that should be accessible by the entire framework and always return the same results for the same method calls are never 'evil' as statics. As to your comment:

    I find static variables more convenient to use. And I presume that they are efficient too

    Statics are the ideal and efficient choice for variables/classes that do not ever change.

    The problem with global state is the inherent inconsistency that it can create. Documentation about unit tests often address this issue, since any time there is a global state that can be accessed by more than multiple unrelated objects, your unit tests will be incomplete, and not 'unit' grained. As mentioned in this article about global state and singletons, if object A and B are unrelated (as in one is not expressly given reference to another), then A should not be able to affect the state of B.

    There are some exceptions to the ban global state in good code, such as the clock. Time is global, and--in some sense--it changes the state of objects without having a coded relationship.

    0 讨论(0)
  • 2020-11-21 06:07

    I have just summarized some of the points made in the answers. If you find anything wrong please feel free to correct it.

    Scaling: We have exactly one instance of a static variable per JVM. Suppose we are developing a library management system and we decided to put the name of book a static variable as there is only one per book. But if system grows and we are using multiple JVMs then we dont have a way to figure out which book we are dealing with?

    Thread-Safety: Both instance variable and static variable need to be controlled when used in multi threaded environment. But in case of an instance variable it does not need protection unless it is explicitly shared between threads but in case of a static variable it is always shared by all the threads in the process.

    Testing: Though testable design does not equal to good design but we will rarely observe a good design that is not testable. As static variables represent global state and it gets very difficult to test them.

    Reasoning about state: If I create a new instance of a class then we can reason about the state of this instance but if it is having static variables then it could be in any state. Why? Because it is possible that the static variable has been modified by some different instance as static variable is shared across instances.

    Serialization: Serialization also does not work well with them.

    Creation and destruction: Creation and destruction of static variables can not be controlled. Usually they are created and destroyed at program loading and unloading time. It means they are bad for memory management and also add up the initialization time at start up.

    But what if we really need them?

    But sometimes we may have a genuine need of them. If we really feel the need of many static variables that are shared across the application then one option is to make use of Singleton Design pattern which will have all these variables. Or we can create some object which will have these static variable and can be passed around.

    Also if the static variable is marked final it becomes a constant and value assigned to it once cannot be changed. It means it will save us from all the problems we face due to its mutability.

    0 讨论(0)
  • 2020-11-21 06:08

    In my opinion it's hardly ever about performance, it's about design. I don't consider the use of static methods wrong as apposed of the use of static variables (but I guess you are actually talking about method calls).

    It's simply about how to isolate logic and give it a good place. Sometimes that justifies using static methods of which java.lang.Math is a good example. I think when you name most of your classes XxxUtil or Xxxhelper you'd better reconsider your design.

    0 讨论(0)
  • 2020-11-21 06:08

    It might be suggested that in most cases where you use a static variable, you really want to be using the singleton pattern.

    The problem with global states is that sometimes what makes sense as global in a simpler context, needs to be a bit more flexible in a practical context, and this is where the singleton pattern becomes useful.

    0 讨论(0)
提交回复
热议问题