50

I'm currently reading "Code Complete" by Steve McConnell. In section 13.3 "Global data" in last paragraph "Don’t pretend you’re not using global data by putting all your data into a monster object and passing it everywhere", he said:

Putting everything into one huge object might satisfy the letter of the law by avoiding global variables, but it’s pure overhead, producing none of the benefits of true encapsulation. If you use global data, do it openly. Don’t try to disguise it with obese objects

Is that point applicable to tools like Redux in front-end? If it is overhead, so should we use just a globalThis or Window object to store global variables (or state) openly? If we already have everything we need to do staff right (openly), why such tools as Redux, Mobx, etc. appeared?

0

3 Answers 3

45

The problem with global variables is that they are difficult to reason about. Where is that global variable being modified? How would you even know?

When you pass around a context object (i.e. a bundle of state), you need some mechanism to control access. Simply passing around a god object without having intelligent access capabilities is not particularly useful. That's what McConnell means when he refers to "monster" objects.

Think of Redux as "smart global variables." In a way, this is not a new idea; databases are essentially a persistent store for giant global variables, except that you get all sorts of new capabilities: ACID transactions, indexing, querying and so forth.

The Redux web site describes some of the capabilities that this approach enables:

  • Centralizing your application's state and logic enables powerful capabilities like undo/redo and state persistence.

  • It serves as a centralized store for state that needs to be used across your entire application, with rules ensuring that the state can only be updated in a predictable fashion.

  • The patterns and tools provided by Redux make it easier to understand when, where, why, and how the state in your application is being updated, and how your application logic will behave when those changes occur.

Vue has a similar mechanism.

2
  • 2
    +1 for the database part. Yeah they are (pretty much) global. But we are SO open about it that its ok
    – Hobbamok
    Dec 14, 2021 at 10:35
  • 11
    +1; BTW, a comment for the OP: please be aware, though, that while Redux is "smart globals", it is not "smart" in the sense that no matter how well or how badly you use it, it'll solve the problem of global variables for you. On the contrary, you have to have some understanding of how it's meant to be used and of how that fits with what you're doing, and you have to be disciplined and deliberate about where and why you expose state, making judicious use of the concepts and APIs provided by Redux. It's just a tool; it can't write good code for you. Dec 14, 2021 at 17:55
23

The quote from the book is concerned more about code that looks like this:

var data = {
   some: {
       huge: {
           deeply: {
               nested: {
                   object: {
                   }
               }
           }
       }
    }
};

controller1.foo(data);
controller2.foo(data);
    /* which calls: */ service1.bar(data);
somethingElse.baz(data);
eventQueue.push(new WhateverAppEvent(data));
fooRepository.add(data.foos);

All of these objects are directly coupled to data and everything it contains. Sure, passing data as constructor or method arguments seems to follow dependency injection and inversion, but the same exact object, which is large and complex, gets passed around to multiple layers of the application. This indirectly couples these different layers together. Each layer must know the structure of data, and since they use the same data object, changes to one of its members could affect the other objects that use data. If somethingElse.baz(data) modifies data.foos, this could impact the call to fooRepository.add(data.foos) in a way that is difficult to identify.

The reason why Redux and similar frameworks do not fall into this trap is because they use events to communicate between components. The big, huge data structure is not passed around to everyone at every layer. The central state management system has the Giant Data Object. Individual components in your application have a subset of the Giant Data Object, or snapshots of it. Modifications to data are achieved through events. These events are used by the other components to only update the small subset of data that component cares about. This dramatically reduces the knowledge required by each component, which makes state changes easier to manage. When something goes wrong, it limits the number of places the defect could occur, when compared to every component receiving the same Giant Data Object.

2
  • 4
    I don't think he's specifically talking about highly nested objects. His point would be just as valid if it were a big, flat object -- there's little difference between the global variable foo and data.foo.
    – Barmar
    Dec 14, 2021 at 16:22
  • 6
    @Barmar: I guess my point was the object is big, and passed around to a bunch of other objects. Dec 14, 2021 at 17:05
4

While other answers here are completely valid I want to give an alternative perspective that frontend data stores CAN be an anti-pattern in certain situations. In the component hierarchy used by React and Vue and friends each component instance takes data via props similarly to how functions take data via arguments. The more context awareness (and therefore more responsibility) a component has, the more difficult it will be to decouple, unit test, and re-use.

An example using the database access analogy: you wouldn't give a helper function a direct socket to the database to perform a calculation on a piece of data when you could easily pass in that piece of data as an argument.

In other words the concept itself is in no way an anti-pattern, but any good pattern can become an anti-pattern if you implement it wrong enough.

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