0

I don't dislike global state, but that could be due to the lack of experience. I was thinking about what the usual implementation of global state is:

A big variable where data flows in a non-consistent, unpredictable but most importantly, non-standardized way, referring to all CRUD operations.

An implementation like global $steps; where you would always do global $steps; $steps['insert']['key_name']['and_value']; to get a value is, in my opinion, why global state is hated and I believe, partially, because people have become a bit too accommodated to objects and for no good reason.

I'll try to naively show how to try to overcome these in order to aid my question.

The problem with just polluting the global state with all kinds of data inside of a variable is that that this way has absolutely not consistent ways to perform CRUD, nor it has rules. Anything can happen, where as objects must respect interfaces, return types and so on, a variable can be...anything. It's extremely hard to predict what goes in and out, or worse yet, in what form.

But what if that global state was accessed in very well-thought, documented & clear ways, so that it's always predictable?

Take my cache object example, that serves as a temporary cache, per PHP request:

class Cache
{
    /**
     * Holds all of our data in a key=>value manner.
     *
     * @var array
     */
    private $data;

    /**
     * Adds data to a key.
     *
     * @param string $key The key, used as an identifier.
     * @param mixed $data The data that we're adding to that identifier.
     */
    public function addData( $key, $data )
    {
        //Should perform integrity checks, etc.
        $this->data[$key][] = $data;
    }

    /**
     * Retrieves data based on a key.
     *
     * @param string $key The key, used as an identifier.
     * @return mixed
     */
    public function getData( $key )
    {
        return $this->data[$key];
    }


    /**
     * Changes data, where data is the value of a key in the big array.
     *
     * @param string $key The key, used as an identifier.
     * @return void
     */
    public function changeData( $key )
    {
        if( checkIntegrityAndOtherStuff( $this->data[$key] ) ) {
            $this->data[$key] = $data;
            return True;
        }

        //If we failed our checks
        return False;
    }
}

It's a very minimal implementation, but, assuming we had strong checks & rules in place (I will come back to this extremely vital point which I think could be the Achilles' hill of it all in a bit), then we have a predictable system that we can refer to:

global $cache = new Cache;

that we can always rely on to work in just one specific way and nothing else:

$cache->addData( 'user_list', [['name' => 'John'], ['name' => 'Jen']] );
$cache->getData( 'user_list' );

We have all the clear signs of good implementation: good naming, predictability, testability, it's concise, but most importantly, easy to use.

So what is wrong here?

The possible Achilles' heel.The one thing that defeats it all could be the fact that you cannot impose any type of contract / pattern on the data being added to these keys, unlike objects where you can set return types / interfaces and know what to expect when you retrieve something, here, you can't, the developer must know beforehand what he's getting, otherwise he's in the dark, with the other, worse side to it that anyone, even if well-intended can change the data contents (and therefore structure) without any consequences, rendering code that relies on it unusable.

If we had things such as "data contracts" that would be bound & required to the data we add (for retrieval later on), then no one could nor add the wrong data type / structure, nor retrieve it, creating a perfectly predictable, well-structured & ruly environment that everyone can benefit from and access.

It might look something like this:

public function addData( $key, $data, DataScheme $data_scheme )
{
    $structure = $data_scheme->getStructure();

    if( dataDoesNotRespectScheme( $data, $data_scheme ) ) {
        //Break, do not allow it.
    }
    $this->data[$key][] = ['data' => $data, 'scheme' => $data_scheme];
}


public function changeData( $key, $new_data )
{

    if( dataDoesNotRespectScheme( $this->data[$key]['scheme'], $new_data ) ) {
        //Fail.
    } else {
        //Add the new data which is 100% identical in scheme to the old one.
    }

    //If we failed our checks
    return False;
}

As such, the developer only has to know about the data structure, but he's 100% guaranteed to get it, in essence, creating a data interface which means that no matter what, code relying on retrieving this saved cannot fail.

Is this what the global state must overcome to be accepted?

  • What you're describing is a generic repository, not global state. – Robert Harvey Apr 15 at 2:37
  • How is generic repository, accessible from anywhere, different from global state? Its implementation? Doesn't that make the generic repository the answer to the global state's problems, then? Sorry, I have absolutely no knowledge of CS overall, it just occured to me while abstracting about my code. – coolpasta Apr 15 at 2:38
  • 1
    A key feature of a cache is that, anything you can do with the cache can be done without the cache, and the cache doesn't change the result at all, only gets it more quickly. So that's not so bad. Read-only configuration data is also not so bad (until it impedes testing). – immibis Apr 15 at 4:16
  • 1
    Now an example I know of global state being bad: When Minecraft added the Nether (an alternate game world), the code still had a global variable for "the game world". When you travelled to or from the Nether, it would save your entire game and load up an alternate saved game. For obvious reasons, this didn't work on a multiplayer server, so the Nether was unavailable in multiplayer until about 6 months later when they'd done all the needed refactoring to delete the global variable. (It was another year after that before singleplayer games could have both worlds running at the same time) – immibis Apr 15 at 4:18
  • 3
    Possible duplicate of Why is Global State so Evil? – gnat Apr 15 at 17:29
13

Is this what the global state must overcome to be accepted?

No. Adding contracts to what goes into and out of global state is as easy as using a language with real types. There are lots of those and global state is still evil there.

Things you're missing:

  • Who fubar'd my data?!? - the biggest, most obvious problem with global mutable state is that anyone can change it. What happens when a bug pops up because the contents of the data aren't what you expected? Literally any part of your program could be the culprit.
  • I want to reuse this thing. - Oops, you can't. It relies on global mutable state along with the entire rest of your program. That broad coupling tends to make things less modular and encourages people to add functionality to your God Object.
  • Why is this thing slow?!? - Less of a problem in php, but global mutable state is really, really unfriendly to concurrency. That limits scalability and usually performance of that code. And since almost every unit test framework will parallelize test runs, your global mutable state will probably break that too.

(along with a few smaller things)

  • 6
    Note: Global state won't make your thing slow, but it will stop you from making it fast. – immibis Apr 15 at 4:26
  • So what would be a better solution to this or is the original intent of "i want to access data everywhere" a bad way of doing things? – coolpasta Apr 15 at 5:15
  • 1
    @immibis it can make it slower because the compiler can't inline some code, prove some code is dead, prove some variable will always be of some runtime type (even if it's closed over, a Javascript variable can only be seen by so much code. If it's accessible from the global scope, nothing can be proven), it may not be able to parallelize your loops... – John Dvorak Apr 15 at 6:29
  • 10
    "is the original intent of "i want to access data everywhere" a bad way of doing things?" - That is the fundamental position that leads to the problems of global state. Accessing data everywhere means that any attempt to reason about one part of the program must include reasoning about every other part of the program. All of software architecture is really about making reasoning easier, and being able to limit your reasoning to part of a program is the most important aspect of that. – Sebastian Redl Apr 15 at 7:04
1

You question actually concern two different things:

  1. Global variables

  2. An untyped key-value store versus a strongly typed repository with input validation, consistency and access control.

Your question is if global variables are considered evil because they are typically used for untyped and unconstrained data.

The answer is no. Global variables are considered bad due to them being global - i.e. directly accessible from anywhere in the program. This creates a tight coupling between all parts of the program which defeats all other architectural constraints like layering and encapsulation.

A global variable can be anything, eg. just a boolean flag. The problem is when this flag can be sat in some component and then affect behavior in some distant, seemingly unrelated component.

Of course using a strongly typed repository have many advantages compared to an untyped store. This is just independent of the question of whether global variables are bad.

  • The OP isn't really asking whether global state is bad or not... he's asking whether his particular flavor of global state is bad. – Robert Harvey Apr 15 at 14:55
  • @RobertHarvey: Yes, and my answer is that the proposed solution (a well-defined schema and validation of the data added to the global data) does not mitigate the fundamental problems of global muitable state, and therefore it is still bad. – JacquesB Apr 15 at 15:51
  • Would you consider a database equally bad? It's essentially the same thing you've described in your answer. – Robert Harvey Apr 15 at 16:30
  • Not really what I said, but OK. – Robert Harvey Apr 15 at 17:59
  • 1
    @JacquesB And then what is the mechanism to make it available to everyone in the codebase, but make these who use it accountable (as in, we can see who did what and where and make sure they have the right permissions) without injecting the "database query" object to each object that wants to use the database? – coolpasta Apr 15 at 23:21

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.