Wednesday, November 19, 2014

Globals Maybe Evil

95% of the time, global variables are more evil than goto. Good software design demands that a variable not be more accessible than it should be. If other objects need the variable, then there are ways to pass the information without making it available to everyone and without checking for misuse. The only time a global should be public if its a static const (or just a const), since there is no possible misuse since other objects can only read the data. In game development, using static consts is how properties of objects, such as velocity at certain states or values that a conditional needs to pass, are kept and defined. The main complaint of this approach is that the value cannot be reassigned and the game will need to be recompiled in order to try a different value. This simply isn't true, with knowledge of debugging, static consts can be changed in memory to reflect a different value without recompiling. With these tools, Unity gives the use of creating public globals that can be edited from the inspector. These values can even be changed while the game is running.

To many, this option seems quite attractive. When I started in Unity 3 years, this option seemed attractive. Now that I am a much better software engineer now than I was then, I know better. The only value of this approach is passing in game object prefabs and textures than using GameObject.Load() which has worse performance than using a public variable. Even then, there is no reason to make it public for all to read and change without testing the data. Data should always be tested, no matter who's giving it. By using a single point of entry for data to be written to a variable, such as a setter, then there it can be safely assumed that the variable used from then on shouldn't need additional testing. However, if the data can be changed from anywhere at anytime, then the data will constantly need to be tested and vetted for correctness. This is a horrible practice and requires more code than is necessary.

In Unity, all game objects are spilt into two: the global prefab and a specific instance in the game. Public values can be changed in both with the prefab affecting all instances, but changing the value of an instance will not affect the prefab or any other instance. This seems to make sense on the surface, until you realize what these values are suppose to represent. These values are suppose to replace static consts that can be changed to get the right value. The speed of a game object should not be represented in the inspector to be changed arbitrarily, instead it should have const values that can affect the true speed such as acceleration, min/max speeds, or speeds depending on state. Why would these values need to be changed arbitrarily on instance. Why should one instance need to have a larger max speed than another? If such a difference needs to established, then the variable should never have been a static const in the first place. That value should be established in the object itself or established in the object that creates the entity and passed to the newly created entity.

If the public variable can be affected in two different places, that creates many subtle bugs that may be hard to track. When testing a game object, the values are generally changed in the instance inspector and not the prefab. When the right values are found, more often than those changes aren't saved back into the prefab. Sometimes they are, and sometimes the values are directly edited into the prefab; however, there have been many times that I saved a value in the prefab, but it didn't percolate into all the instances of the game object.

Despite everything I stated, there is always the biggest reason why using public variables to directly impact what should be a static const: anyone can access it. I already showed that when letting anyone access it, you need to test the data constantly; however, this approach gives more scope to the variable than it needs to. The only object that should access the variable is the inspector. No other game object, or Unity Engine, needs to access this variable and the fact they do have access is a very scary thing. I feel that using a public variable give way more power and control to everything than what is gained from the convenience of directly updating the variable at runtime. A global variable has created many subtle bugs that are hard to track down and fix.

At some point, the team at Unity decided this was one of the worst ideas they had, so they implemented measures so that you could use the inspector and keep scope. By making a variable private, but serializable the inspector can still gain access to the variable and the variable is given the correct scope so that no other object can access it. Using serialize also opens the door to many other options for the inspector. By creating structs or classes that encapsulate another object or makes a field more readable: for example

[System.Serializable]
struct Range
{
    float min;
    float max;
}

This creates greater readability and makes the inspector much nicer to read so that we don't need two variables for min and max. Despite these improvements, there are still problems. These values are still meant to represent static consts, but are still represented by private var. While only the inspector and the object they are assigned to can only update them, that means the object it is assigned to can still update it. This is not the behavior we want from the object and we have to create a contract with the programmer that they will not change these variables in the object. Even though I wish these values could be made static const and still accessed from the inspector, I understand the difficulty in doing so and why it may be non-existent on a their list of priorities when there is so much that hasn't been incorporated (such as a draw line method in the GUI). You can't win every battle and I will take compromise where I can. A private serialize value that can be edited is a hell of a lot better than a public global.

Are globals evil: yes, yes they are. But, there are many times in which they are a necessary evil.

No comments:

Post a Comment