Toggle methods - the right way

When dealing with Unity applications, one often encounters scripts and components that take care of toggling certain behaviours or states.

Common examples of toggles in Unity include:

  • lightswitch that toggles the light on and off when touched,
  • the visibility of elements is toggled upon keyboard button press
  • button that changes its color whenever interacted with

Oftentimes, a method signature similar to the following one is used for toggles:

private bool isLightOn = false;

public void ToggleLight(bool isLightOn) {
    this.isLightOn = isLightOn;
}

Personally, I find these few lines of code to be as wrong as they can get. Why is that?

Firstly, and most importantly, the code above violates a principle I hold very dear: the principle of least astonishment (POLA).

POLA - Principle of least astonishment

The principle of least astonishment is - sadly so - one of the lesser known principles. When applied to software development, the essence of POLA is to always build your features (or in this case: behaviours) in the way that is the least surprising to others.

E.g. if you implement a method void Move(Vector3 fromPosition, Vector3 toPosition) in your application, others will expect it to move something from fromPosition to toPosition. Furthermore it will be suspected that the movement will occur along a straight line between fromPosition and toPosition. Everything else, e.g. not moving an object or moving it along an elliptical curve will at least be mildly suprising and will make it harder to instantly grasp the functionality.

I can’t stretch enough just how important it is to do things in the most reasonable and expected way in order to keep up a high level of productivity. Always remember: Machines are fast but humans are slow, so try to write your code in a manner that is understandable by other humans instead of focusing on performance and elegance too early on.

Please note that POLA is not universally applicable, since e.g. for low-level programmers it is important to write code in the most performant manner, which more often than not comes at the cost of not being the easiest to understand at first sight.

Having established POLA, why exactly does the above code violate this principle? The reason is that the function signature is entirely misleading and does not match with the common understanding of a toggle switch.

What exactly is a toggle?

A toggle is a type of switch that is nowadays encountered very often in various user interfaces of e.g. smartphones.

Toggle green

As Wikipedia puts it, a toggle “is understood in the […] sense of a […] software switch that alternates between two states each time it is activated”.

Fixing flaws #1: Toggle

With the definition of a toggle in mind, the first and most important fix to the code above is to remove the method parameter of ToggleLight(bool isLightOn).

Applying the toggle logic to a light means: The toggle will not take any inputs to calculate the appropriate result of the operation, all required input is the current state which is already known. If the light is initially turned off (initialState of isLightOn = false) and the toggle method is called, it will toggle the state of the light from off (false) to on (true). If the toggle method is executed again afterwards, the lights will go off again and so on.

Consequently, the method signature of the example above above needs to be changed to the following:

private bool isLightOn = false;

public void ToggleLight() {
    if (isLightOn == true) {
        isLightOn = false;
        // ... do something else
    } else {
        isLightOn = true;
        // ... do something else
    }
}

Obviously, it can be desirable to set the state of the light to some specific value (on or off), but the appropriate way to do this is not through hacking a method parameter into a toggle method, but rather through implementing a dedicated setter method.

Fixing flaws #2: Setter

To set the state to whatever we would like it to be (e.g. to force the light to be on), we’ll just define a setter method like so:

private bool isLightOn = false;

public void SetLightState(bool state) {
    isLightOn = state;
    // do something else depending on the light's state
}

This will do exactly what we want it to do: Forcing the state to be whatever we want it to be. We can now turn on the light by calling SetLightState(true); no matter if the light is currently off or already turned on.

The final solution

Now that we have a clean toggle and an additional state setter method, all that is left to do is some refactoring in order to obey the DRY principle.

Right now, our logic for what happens, when the light is switched to either state (on or off) is located in both the ToggleLight and SetLightState method and would need to be updated in both places in case of a change. Instead, we’ll make use of the setter method inside the toggle method to keep things clean and simple:

private bool isLightOn = false;

public void SetLightState(bool state) {
    isLightOn = state;
    // do something else depending on the state
}

public void ToggleLight() {
    if (isLightOn == true) {
        SetLightState(false);
    } else {
        SetLightState(true);
    }
}

Our toggle now uses the light’s state setter internally which keeps all the logic related to state changes in one specific place while the toggle continues to act as a clean toggle switch with no unexpected strings / parameters attached.

We managed to abide by both the DRY principle, which makes it easy easier to manage and reuse our code and POLA, the principle of least astonishment, which helps understanding our code.

A word on Properties

The example of our lightswitch above can be further altered to use a property with a custom setter function instead of a dedicated setter function. The result could look like the following:

private bool isLightOn = false;

public bool IsLightOn {
    get {
        return isLightOn;
    }
    set {
        isLightOn = value;
        // do something else depending on the state
    }
}

public void ToggleLight() {
    if (IsLightOn == true) {
        IsLightOn = false;
    } else {
        IsLightOn = true;
    }
}

You can opt for either a property with a setter or a dedicated setter function, depending on whether setting the state has other side effects. If there are no side-effects and you’re simply updating some field(s), go for a property. If a state change involves a lot of changes and implies side-effects, go for a method. Microsoft compiled a good overview on when to go for which solution.