As I know, on one hand, the main stream opinion about global variable is, it is eval[sic] because it may make the program unpredictable : some unknown class or functions can change the value of the global variable even if they don't need to do so...
This is part of the motivation to avoid global variables, but it is not the whole story. It is true that global variables are far too broadly scoped, such that basically all code in the program has access to them.
However, another problem has to do with instantiation. We cannot control the instantiation of global variables except by (re)starting the program.
Let's compare functions connected by global state with objects having methods connected by object state.
Because we cannot instantiate global state, we cannot have more than one unit of functionality when using functions connected by global state — whereas with objects, when you need one, you create it; if you need two you create another!
Completely separate parts of the code can easily share code (implementation) by each instantiating their own object as needed. Using functions connected by global state, each of those separate parts of the code risks interfering with the others.
This extends, from separate parts of the code possibly running at different times or even close to the same time, to separate threads. Because we cannot instantiate multiple copies of global state, global state tends to hamper multi-threaded programming, whereas one tool we use for safe threading is to instantiate separate objects for each thread.
Many units of functionality coded as functions connected by global state tends to lead to spaghetti, and classes tend to help reduce that intermixed clutter.
Abstraction is one of our best tools for improving the maintainability & longevity of code while reducing technical debt.
By grouping functions and state together into encapsulated objects, OOP encourages programmers to focus on creating abstractions: on interface design (i.e. abstraction), on the separation of interfaces from implementation, on the potential for multiple implementations of one interface, and on the ability to instantiate abstractions using varied implementations and of course, multiple instances of the same implementation. Global state doesn't encourage any of these.
ShowInfoWindow doesn't need to access user.userId, but it can do so actually.
One approach to addressing this is by establishing the right abstractions. It is possible that you have several abstractions mixed together, that should be teased apart into independent abstractions.
Another approach is to use encapsulation, as we have when using
private instance fields. Private fields are not accessible outside of the object's implementation, and are thus more stable. We should focus on the interface of the abstraction, rather than supplying direct access to internal implementation details.
And yet another approach is to use immutability as much as possible. Immutable fields are totally stable and easier to reason over.
but according to the "spirit" of "avoid global variables", isn't passing a object also bad?
Not usually. It is generally less error prone and easier to reason over good abstraction rather than int's and strings that are paired together. A good abstraction binds together the int and string into a single concept, and this binding is more difficult to accidentally mix and match.
Consider some code that has to deal with two things instead of one. For example, encode & decode. If clients have to deal separately with each of these implementation as a pair, it is possible to accidentally use the wrong decoder for an encoding or vise versa. Whereas if we present a single abstraction that binds together an encode & decode, the client (consuming programmer) will not inappropriately mix the implementations.
This could apply to a situation involving multiple users if some code needed to deal with two users at the same time, for example. If the client of the abstractions uses local variables
age1, along with
age2, there would be no (type system) error provided by accidentally pairing
age2; the pairing is being maintained implicitly by the client programmer — whereas once the a
age are bound into a
user abstraction, the pairing is explicit and there is no possibility for this accidental mixing.