1

As I know, on one hand, the main stream opinion about global variable is, it is eval 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, so it is bad to see this:

public class User{ 
    public String name;
    public int age;
    public long userId;
    protected static User sharedInstance;
}

public class ShowInfoWindow{
    public ShowInfoWindow(){
        String nameString="Name:"+User.sharedInstance.name;
        String ageString="Age:"+User.sharedInstance.age;
        //UI code to display info
        .
        .
        .
    }
}

On other hand, passing a object as parameter is suggested instead of required variable only, the reason is :

  1. the object groups the related properties together
  2. it makes the function more readable because of short parameter list
  3. adding new required property into the object would be much easier than changing the signature of the function

so the modified the version should be:

public class ShowInfoWindow{
    public ShowInfoWindow(User user){
        String nameString="Name:"+user.name;
        String ageString="Age:"+user.age;
        //UI code to display info
        .
        .
        .
    }
}

but according to the "spirit" of "avoid global variables", isn't passing a object also bad? The reason is : ShowInfoWindow doesn't need to access user.userId, but it can do so actually. So it is bad as the case of global variables that some unknown classes or functions can change it even if they don't need it. So passing just required property would be better:

public ShowInfoWindow(String name,int age){
    .
    .
    .
}

Is that true? Is passing objects contradict with avoiding global variables? If not, how can the two guidelines not contradict to each other?

3 Answers 3

5

Passing objects does not contradict with the principle of "avoiding global variables", these are different things. However, the term for what you probably have in mind is to avoid "unwanted side effects" - both "global variables" and passing objects in the way you demonstrated it have this in common.

Indeed, passing mutable objects from a function to another introduces a certain risk of getting such side effects. Imagine ShowInfoWindow as a library function called by a couple of different applications, and now the maintainer of that library (who does not know about all that applications) changes the internals of ShowInfoWindow to modify some of the user objects attributes. This may break several of that applications, and the signature of ShowInfoWindow does not prevent this.

So how to avoid this risk? There are some ways to deal with it. As a designer of the method,

  • document externally that the method in stake does not have any unwanted side effects, and internally write a hint for the the maintainers it should stay that way

  • make the method only accept individual parameters (as you already suggested in your question)

  • make User an immutable object, so the called functions cannot (at least not easily) change the internal state of the object after its creation.

So all these measures should make users of the method trust it, without giving them the need to look into the implementation.

And at the callers site, if one has to deal with untrusted methods or methods which are known for unwanted side effects, one can

  • make a deep copy of the user object, and pass only the copy to the method.
1

but according to the "spirit" of "avoid global variables", isn't passing a object also bad?

Passing an object doesn't make a variable "global." Quite the opposite, in fact. The size of a variable doesn't have anything to do with its scope. You can make a string or an int global, and you can make an object private.

ShowInfoWindow doesn't need to access user.userId, but it can do so actually.

Yes, but it won't, because you've carefully written the ShowInfoWindow() function to avoid this behavior. Your code (and its behavior relative to the object parameter that it operates on) is still properly encapsulated within your ShowInfoWindow() function, so user.userID is still protected.

About the "Pass objects instead of their individual properties" rule

Sure, why not. It's a good idea for all of the reasons you mentioned. But sometimes I will still pass the individual properties instead of the entire object, especially if I only need to pass a few of them and doing so will make the function easier to understand.

0

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 name1 and age1, along with name2 and age2, there would be no (type system) error provided by accidentally pairing name1 with age2; the pairing is being maintained implicitly by the client programmer — whereas once the a name and age are bound into a user abstraction, the pairing is explicit and there is no possibility for this accidental mixing.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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