Python – Are we overlooking bad effects of global state in this design

design-patternsglobalspython

I’ve read the answers to Why is Global State so Evil?, and I think the negative consequences do not apply in this situation. However, that’s what everyone says just before they get hit by a falling piano and my software architecture lessons have been a few years ago, so…

We are developing a desktop chat application in Python. In that application, there is some inherently global state: account configuration, client classes (from the chat protocol library) for those accounts, open conversations, etc..

In the current design, we use single global instances for the Account controller and the Client controller (handling the client objects from the chat library we are using) as well as the Conversation controller (handling conversation abstractions for chat sessions including group chats). These global instances are available from everywhere via a simple import foo.app as app. The controllers have models associated and read-only views on those models can independently be instantiated at various places in the application.

The following remarks apply:

  • Unit testing works fine so far: we can easly mock away those global instances with Pythons unittest.mock (in fact, the instances are only initialised during actual application start up, so they are None otherwise -> instant and obvious error if not mocked but used in testing).
  • Modification of the global data state associated with the controllers goes through the controllers only; and modification of the state emits well-defined callback signals so that consumers of the state can obtain updates.
  • The code has concurrency, but it is using Python’s asyncio: this implies co-operative multi tasking, thus the points at which other tasks can interfere are well-defined and obvious in the code (when we use actual threads, global state will not be accessed directly). So it is safe to assume that global state does not change within a (non-coroutine) method.
  • Most uses of the global state are simple lookups of the type "Which Client object is associated with the Account X?", "Get me the conversation object on account X with peer Y", "Which accounts and identities exist?" and subscribing to updates.

Now I wonder: Have we overlooked anything? And if so, what would be the appropriate fix? I feel that this design could work well enough, but I’m afraid we might have overlooked anything relevant which will fall on our feet at some later point.

Best Answer

If you push any rule far enough its going to come down to a subjective choice. But 'global state is bad' is about as canon as you get.

We all know that sometimes it is quicker to use global state to solve a problem. Or perhaps a particular framework we are using restricts other options to such an extent that Global State is an acceptable solution.

But these are extreme positions where we accept we are doing a 'bad thing'(tm) and promise to be careful.

You have stated how you have managed to avoid some of the pitfalls of global state, but not what the extreme pressure, money, time or technical limitation was that forced you to go down that route!

Given that eliminating global state is fairly simple and is almost universally regarded as producing 'better' code. Why the hell would you not do it???

If you argument is 'global state is not bad, look at this code' then the only thing to do is write the same app both ways and see which has less lines of code, or whatever measure of goodness you want to apply. But you would have to post both full code bases or link to them on github or something.

Presumably they would both be fairly similar and we would be arguing over whether being able to run unit tests concurrently etc should be part of the 'measure of goodness'