Why globals will make you miserable

Everyone knows they shouldn't be using global variables, right? Riiight??

But why is that the case? Because global variables make it very difficult to understand the flow of data through an application.

Instead of moving data around through functions calls -- which are (relatively) easy to trace -- globals are a kind of wormhole from one part of the application to the next. A wormhole where anything can jump in and change the behavior of the application.

It's a kind of multidimensional quantum entanglement where stuff from the other side of the application can change seemingly unrelated behavior.

Note that I am not a physicist so this metaphor isn't necessarily correct -- I've just watched a lot of Nova.

Simple functions

Consider a simple (made-up) function:

float calculate_repleneration (float phase_angle, float reactive_current) {
  return phase_angle * (reactive_current/2);
}

The value returned is simply a function of the input values. The return value does not depend on any other state of the application.

2021-08-25 14-06.jpeg

Technically this function is a pure function. This function is easy to read, understand and write unit tests against. I can look at it and quickly understand what it does.

Introducing a global

However, what if we introduced the use of a global variable called CalibrationFactor?

#include "config.h" // <-- Where calibration factor is defined as a global.

float calculate_repleneration (float phase_angle, float reactive_current) {
  return CalibrationFactor * phase_angle * (reactive_current/2);
}

The CalibrationFactor is a global variable defined in config.h. Now the return value is no longer only dependent on the input values to the function. We've opened a wormhole into this function via the global.

2021-08-25 14-08.jpeg

I can't just look at this function to understand how it works. Who can set the CalibrationFactor? How is it calculated? I'm going to have to dig through the rest of the application to find out how this all works.

Also, suppose I needed to change how the CalibrationFactor is calculated? I'd need to search through the rest of the code to see if CalibrationFactor was used anywhere else. If it was used in other places I'd have to investigate further to see if it was safe to change.

Module-level variables

Consider this alternative, where the calibration factor has module scope (i.e. file scope). The update_calibration() function has been added for setting the calibration factor.

static float calibration_factor = 0.95;

void update_calibration(float dingle_arm_angle){
  calibration_factor = 2 * (dingle_arm_angle / 360);
}

float calculate_repleneration (float phase_angle, float reactive_current) {
  return calibration_factor * phase_angle * (reactive_current/2);
}

Now I can see all the repleneration code in one place. I can look at these two functions and understand how the repleneration is calculated -- I don't have to go dig through the rest of the application to figure it out.

Sure, update_calibration() could be called from multiple places but I can see all the calculations that are happening right here.

Also in this example I know that no one else is using this calibration_factor since it can't be accessed from outside this module.

2021-08-25 14-13.jpeg

It would be quite straightforward to write tests for this as well, making calls to both update_calibration() and calculate_repleneration().

"But Matt!" you might say, "One global isn't so bad, this doesn't seem that bad to me!"

I'll agree that this simple case doesn't seem so bad. But once you get a few globals in your application, the complexity really starts to compound nd the application becomes more-and-more difficult to understand. This makes your code harder to work on -- like adding new features or making bug fixes -- without breaking it. Please avoid this situation if you can.