How do you handle global state?



  • Continuing the discussion from Is this C++ code safe?:

    @tar said:

    @Gaska said:
    Burn in hell.

    That's the spirit! :D
    Filed under: seriously, singletons have never been good advice

    So, as we all know, sometimes you need a mechanism that holds data (e.g. a cache) for everyone and everything in a codebase. What do you use instead of singletons?

    Coming from Java EE Land, I use @Singleton EJBs and now @ApplicationScoped CDI beans. I have on occasion used a enum with no instances (to prevent instantiation by reflection) and all static methods, but that's most definitely a :wtf:



  • Define "global state".



  • Well, the proper way is to construct a single instance at the beginning of the program and pass it around to objects that need them via DI or factories or whatever.

    Screw that, though. A singleton, or even a simple static class is usually much cleaner.



  • I suppose it extends to global functions as well as state.

    Global state: application data of which there should be only one copy (e.g. configuration, cache)

    Global functions: functions that should be accessed through 0 (static) to 1 (singleton) object instances (e.g. hardware control, utility methods)



  • If I have a distributed application, do I need to replicate the entire cache across all of the application nodes?

    Also, why am I only allowed to load the configuration data once?



  • You're maybe allowed to load them multiple times, but any changes to those data would have to be immediately persisted to allow other parts of your program to see the changed state.

    A better example would be, say, currently logged in user. You'll need his data in multiple parts of an application, and any changes to the user object should be immediately visible.



  • Let's assume a non-distributed application.

    Configuration doesn't change after the app starts running, why would you waste time reading it twice when you have 65,536,000TB of RAM?

    Filed Under: Let's assume a spherical application in a vacuum



  • Assuming you guys are actually serious:

    • A shared singleton class requires that memory be in use throughout the entire application, even when the object isn't needed.
    • Singletons hide dependencies!
    • As a result of these hidden dependencies you can lose track of the coupling. Instead of meticulously passing each object you need (and only the objects you need) into a function, you instead call [AppUser sharedAppUser] to access your shared instance. While it feels cleaner since it can mean less lines of code to get the access you need, you can end up with very tightly coupled code.
    • You can't (really) subclass a singleton
    • Singletons are hard to test! (1) With hidden coupling it's difficult to create mock objects and (2) singletons generally exist for the lifetime of the app which presents a problem for unit testing, as each unit test should run independently of other unit tests.

    M'kay?

    But I really, really, really want to use a singleton

    There are still some cases where singletons are a valid choice.
    One is logging. Logging is so pervasive that unless you use some fairly heavy-handed dependency injection or AOP, it is not feasible to inject every class in the system with a logger. Instead, this is a case where using static methods or singletons are probably worth the compromise.
    Another common use is to obtain a handle to a service locator (think JNDI) that knows how to access everything else. This is actually one way to change your entire system from using many singletons to using one singleton that knows how to find the others. This is a valid technique that addresses some of the inherent problems (mockability, testing) with singletons and may be a less invasive change to a legacy code base.
    Swing or other client-side UIs are another case where singletons may be a valid choice. In the case of a Java desktop app, it is likely that you have more complete control over your deployment environment than you do in a server-side application. In this case, it can be acceptable to have a manager class managing some state as a singleton in the VM. However, my preference would be to go the extra step to expose that dependency.
    If you do really need to use a singleton, please make sure you initialize it safely!

    Filed under: The Gang of Four called. They needed to hit page count to make the book deal. They're sorry.


  • Discourse touched me in a no-no place

    The problems with dependencies and coupling are the really intractable issues that will systematically stuff you over. Testing, by comparison, is just annoying to do with singletons (since you need to fire up a subprocess for each test)…

    A decent DI framework helps a lot. In particular, it can make the links between entities explicit and it hoists the singleton-ness to the level that actually knows when there really should only be one instance of something. (I use Spring. It's reputation is deserved, but it helps keep that nasty old patternitis out of your code…)


  • Discourse touched me in a no-no place

    @Maciejasjmj said:

    A better example would be, say, currently logged in user. You'll need his data in multiple parts of an application, and any changes to the user object should be immediately visible.

    But it shouldn't be hard-coded to be a singleton; if it is, you'll be unable to ever convert the code to be multi-user. You might not think that's an issue now… but are you certain that it will continue to be that way? For example, we're seeing more of a move to allow things like word processors to allow multiple simultaneous users to work on a document — it's very useful for collaboration — and that sort of thing is going to spread wider.


  • FoxDev

    @IngenieurLogiciel said:

    Configuration doesn't change after the app starts running

    Depends what you mean by 'configuration'



  • @tar said:

    If I have a distributed application

    Those are completely different monsters by their own. So when you build a distributed application everything has to be distributed also (cache, conf, mq, etc)


    Filed under: do you hear the word cluster anymore?


  • BINNED


  • Discourse touched me in a no-no place

    @IngenieurLogiciel said:

    Configuration doesn't change after the app starts running

    It can and does on our systems - at least when the Project Engineers are invoking their cargo-cult version of configuration during (their) development.

    That said, no-one's actually got around to doing anything sensible about it other than rebooting it, since most of our in-house stuff only bothers reading it once on startup. (There are a couple of apps that are 'location aware' and are on the lookout for preconfigured dynamic changes due to where the hardware is - think "I'm now in Canada - I should really be using this other SIM card now...")


  • Banned

    There's no real difference between singleton and passing references to the same object everywhere around, except the singleton is irreplacable and dependencies are hidden. If you are bothered by stuffing seven references into every constructor, then you have flawed design - either too many responsibilities or too little encapsulation.




  • Banned

    But should I make a single cache or multiple caches? Should it be globally accessible or should I play the reference passing game?



  • @blakeyrat said:

    https://msdn.microsoft.com/en-us/library/system.web.caching.cache(v=vs.110).aspx

    Let the framework do it.


    Not terribly helpful. The argument here is that if a method uses a singleton based cache (like System.Web.Caching.Cache), then testing may be affected because running test #1 will leave something in cache that will affect test #2, possibly making it fail when there is no actual bug, or worse, making it pass even when there is a bug in the code test #2 is testing.

    The test harness would have to be aware of how caching is being performed in order to execute the tests properly.

    The presented solution is to use a Dependency Injection framework to access the cache so that if the harness isn't aware of the cache and doesn't offer a compatible dependency, both test #1 and test #2 fail due to a missing dependency. Once that is fixed, the author of the harness is forced to consider the issue of caching and is more likely to get it right.



  • I know (and I'm all for) what dependency injection is.

    I still have absolutely no clue why a "dependency injection framework" is required, or even desired, to do. Other than bloat up your code with massive amounts of buggy third-party library code.


  • Discourse touched me in a no-no place

    If done well, they reduce the number of constructors and factories and crap like that that you have to write. The DI framework can assemble the pieces based on things like declared annotations. For a large application it's quite a lot simpler, and it's certainly simpler for GUI tools to provide assistance with managing the composition and wiring up of the components when that's done declaratively.



  • I can't see that even possibly being worth adding tons more buggy third-party libraries into the mix. But hey whatever.


  • I survived the hour long Uno hand

    In theory, it'd help with testing: you can declare an override in your test that says "DI framework, now we're testing, send only mocks" and then when every object asks for, say, a DAO, the framework gives it a mock silently, saving you a series of "DAO dao = new mockDAO; MyObject object = new MyObject(mockDAO)" at the top of every test.

    That said, in practice, I've never seen a DI framework that didn't make that use case even fucking harder (here's looking at you, Require.JS).


  • Discourse touched me in a no-no place

    @blakeyrat said:

    I can't see that even possibly being worth adding tons more buggy third-party libraries into the mix. But hey whatever.

    My experience with Spring (and Java, FWIW) has been pretty good. It's evil inside, but the code one writes is much cleaner. It's also very well-tested. I don't know the .NET ecosystem anywhere near so well, but there's gotta be something like that there.



  • @blakeyrat said:

    I still have absolutely no clue why a "dependency injection framework" is required, or even desired, to do. Other than bloat up your code with massive amounts of buggy third-party library code.

    Notice that when I responded to you, I never said I agreed that DI is the way to go. In my opinion, DI solves the testing problem beautifully - at the expense of everything else. It's the ultimate (adopted) brain-child of the Test Driven Development movement to solve an important problem of theirs.

    If you aren't doing hard-core TDD, you probably don't want the extra complexity that DI adds to a solution.



  • @Jaime said:

    If you aren't doing hard-core TDD, you probably don't want the extra complexity that a DI framework adds to a solution.

    FTFY



  • I have some guesses, but according to you what DI implementations are not frameworks?



  • @Maciejasjmj said:

    A better example would be, say, currently logged in user.

    If Discourse has the currently logged in user as a singleton, that explains a lot.



  • @ben_lubar said:

    If Discourse has the currently logged in user as a singleton, that explains a lot.

    Actually, that doesn't explain anything...



  • @IngenieurLogiciel said:

    Actually, that doesn't explain anything...

    No, it doesn't explain anything, but:

    // file: discourse/models/user.js
    Discourse.User.reopenClass(Discourse.Singleton, {
    
    // Example usage
        if (!Discourse.User.currentProp("staff")) { return Ember.RSVP.resolve(null); }
    
    

  • Banned

    @IngenieurLogiciel said:

    I have some guesses, but according to you what DI implementations are not frameworks?

    class A
    {
        A(B& b);
    };
    


  • That's IoC, not DI.


  • Banned

    And what's the difference?

    Mind you - I'm the kind of person who thinks static method and global function are the same thing.



  • IoC is a pattern where you pass an object that is a "service provider" to a method that implements an algorithm on the information provided by the service.

    DI is when there is a scheme for the method to discover the service providers.


  • Banned

    In other words, DI is global variable in disguise?



  • It's much better than global variables. For example, it's much easier to determine dependencies with DI. Also, DI fails when there hasn't been a proper dependency offered. Global variables provide a default implementation, which usually bites you in the ass.

    That being said, I'm a big fan of IoC, but I prefer to stay away from DI.


  • Banned

    @Jaime said:

    It's much better than global variables.

    Yes, but essentially it's still global variables. You can't mock the DI framework itself, for example (which is doable with IoC).



  • Why would you want to mock your framework, which was put in place to facilitate mocking? IoC is a general concept and typically has no framework, so how can the framework be mockable?


  • Discourse touched me in a no-no place

    You can mock the effect of the framework. Just make the objects by hand and wire them up manually. (Going round testing third-party library/framework code is a good way to use a lot of time up doing busywork…)

    I use a DI framework so I don't have to do that sort of thing. 😉


  • Banned

    @Jaime said:

    Why would you want to mock your framework, which was put in place to facilitate mocking?

    I wouldn't. But the sheer ability to do this is a nice proof of decoupling.



  • There aren't references to B in A if you use DI. If you observe that the framework goes and gets you an instance of B, that's all the proof you'll ever need. Technically, that's proof of injection, not decoupling. Decoupling was proven by the fact that you didn't reference B in the first place.



  • @IngenieurLogiciel said:

    I have some guesses, but according to you what DI implementations are not frameworks?

    Hand-rolled, ad-hoc DI is a thing, as @Gaska already pointed out -- I've used ad-hoc DI as a "glue" technique before myself in both C++ and Python. (There are plenty of times where an existing program might benefit from a targeted application of DI as part of a refactoring or functional extension, but a framework is inappropriate.)


  • Banned

    In your example, both A and B's creator reference DI framework, and it's strong coupling (with the DI framework, which is orthogonal to A and B coupling).



  • @Gaska said:

    In your example, both A and B's creator reference DI framework, and it's strong coupling

    Are you suggesting that two C programs that #include <stdio.h> are strongly coupled? Mutual reference is not coupling.


  • Banned

    This post is deleted!

  • Banned

    Both C programs are strongly coupled to the stdio.h library. It's independent from whether there's coupling between the programs themselves.



  • @Gaska said:

    Both C programs are strongly coupled to the stdio.h library. It's independent from whether there's coupling between the programs themselves.

    OK. By the same logic, if A and B are both coupled to the DI framework, there is no implied coupling between A and B.

    The entire point of DI and IoC is to reduce coupling. What is your issue with coupling to the framework? Do you have a problem with coupling to the language too?


  • Banned

    I was kinda taking it to the extreme. But FWIW, Linux scipters do everything in their power to be language-independent - so the script can be run in bash, zsh, fish, dash and other sh all the same.


  • I survived the hour long Uno hand

    @Gaska said:

    language-independent

    So why don't my shell scripts run under DOS?


  • Banned

    Because DO$ is Micro$oft, and people hate Micro$oft.


  • kills Dumbledore

    Perl?


Log in to reply