Doers of Stuff.org

A place to Do Stuff and see Stuff Done…

Why You Need to be Writing Pseudo Code

So, there are likely two primary groups of programmers who will read an article like this. There are new programmers who don’t know any better and will likely follow the advice because it seems to make sense. Then there are the old programmers who believe pseudo code can in fact only be written with a pen on a napkin. The napkin is of course instantly discarded upon completion of said pseudo code. I’m going to go ahead and make an attempt to convince both groups writing pseudo code right in your IDE is a good idea. However, I do recognize the fundamental flaw in this argument is I am ultimately trying to convince you that by doing more work, you will get done faster and finish with cleaner code.

First, what exactly do I mean by pseudo code? For some, even pseudo code has rules and a vague syntax. For the purposes of this article, pseudo code is anything you want it to be with an assumption it cannot in fact be compiled and executed in any meaningful manner. Additionally, we will assume pseudo code will be written directly into you code editor, right where the final code will be written. Therefore, the pseudo code cannot break anything. In general, this means it will be written as comments in whatever manner your programming language supports.

Finally, I acknowledge many of the benefits I will grant to the practice of pseudo code can in fact be realized in a variety of other ways. To this, I simply argue pseudo code is a cheaper, low impact solution one needs no particular experience, knowledge or tooling for.

One way to more easily grasp the value of pseudo code is to think of it as nothing more than a very low-level requirements document. In theory, you should never write any code not tied to a requirement. Pseudo code is then just a transitionary step between some larger design document and the code you actually plan to write.

Pseudo code is also a metric. If you have pseudo code still in your code, you’re not done. It’s kinda like a ToDo comment or a Post-It note.

Pseudo code helps you arrange your thinking. You can use this in at least three different ways. The first, more straight forward way is to just implement what you write out. The second is to use it as a cheap way to refactor your design before implementing anything. The third is to use it to do both.

By way of example, let’s look at the next step in the development of our player game object. We now have the ability to move our player object around using the WASD and arrow keys. So now it’s time to set some boundaries on the movement. We don’t want the player to move above the halfway mark up the screen, nor do we want to let it go below the bottom of the screen. If the player moves off the screen either to the left or right, it should “teleport” to the other side. Writing pseudo code might net us something like this:

using UnityEngine;

public class Player : MonoBehaviour
{
  [SerializeField] private float mySpeed = 5f;

  void Start()
  {
    transform.position = new Vector3(0, 0, 0);
  }

  void Update()
  {
    // movement code
    transform.Translate(new Vector3( Input.GetAxis("Horizontal")
                                   , Input.GetAxis("Vertical")
                                   , 0
                                   ) * Time.deltaTime * mySpeed);

    // boundary code

    // If the player goes above y =  0, reset y position to  0.

    // If the player goes below y = -4, reset y position to -4.

    // If the player goes beyond x =  11.25, reset x position to -11.25.

    // If the player goes beyond x = -11.25, reset x position to  11.25.

  }
}

It’s very simple. One rule per boundary/side. Every time the player moves, we check all four boundaries and adjust the appropriate value as needed. This can quickly be implemented as four simple if statements each with a transform.position() statement.

If we wish to pre-emptively refactor a bit, i.e. refactor the design, not the code, we might recognize that while we are checking four boundaries, we are only checking two values. That being ‘x’ (horizontal) and ‘y’ (vertical). So we might decide instead to use two Else-If statements.

using UnityEngine;

public class Player : MonoBehaviour
{
  [SerializeField] private float mySpeed = 5f;

  void Start()
  {
    transform.position = new Vector3(0, 0, 0);
  }

  void Update()
  {
    // movement code
    transform.Translate(new Vector3( Input.GetAxis("Horizontal")
                                   , Input.GetAxis("Vertical")
                                   , 0
                                   ) * Time.deltaTime * mySpeed);

    // boundary code

    // If      the player goes above y =  0, reset y position to  0.
    // Else-If the player goes below y = -4, reset y position to -4.

    // If      the player goes beyond x =  11.25, reset x position to -11.25.
    // Else-If the player goes beyond x = -11.25, reset x position to  11.25.

  }
}

Again, easy to implement. It’s not much of an optimization of course. There are still four checks and four transform.position() statements. They are however grouped logically and provide some coherence.

But what are we really trying to do here? Well, after we accept the player move command, we want to check and/or reset our position according to the boundary rules. Is there a way we can write that?

using UnityEngine;

public class Player : MonoBehaviour
{
  [SerializeField] private float mySpeed = 5f;

  void Start()
  {
    transform.position = new Vector3(0, 0, 0);
  }

  void Update()
  {
    // movement code
    transform.Translate(new Vector3( Input.GetAxis("Horizontal")
                                   , Input.GetAxis("Vertical")
                                   , 0
                                   ) * Time.deltaTime * mySpeed);

    // boundary code

    // Set Position  : If x goes beyond -11.25 or 11.25 jump to the other side
    //               : If y goes beyond -4 or 0, don't let it go any further
    //               : z is always 0

  }
}

Now, this might or might not be possible. Even if it is, you might not know how to do it. And here is where things might get a bit dicey based on what you consider best approach. Cutting to code with either of the first two pseudo code examples gets you to running code. This last one might be a snipe hunt. But, since I can read the future, let’s go with it.

As it turns out, the requirement for the vertical boundary is easy-peasy, though it comes from a source you might not expect. The Mathf() class provides a very useful function called Clamp(). The Mathf.Clamp() function takes three arguments. The first is the value to be clamped. The second and third are a minimum value and maximum value. This single function has the same effect as our ‘y’ if-else statement by refusing to allow the value of the first argument to ever extend beyond the boundaries set. The full command would be, Mathf.Clamp(transform.position.y, -4, 0)

The rule for the ‘x’ position is a little less obvious, but again, it is Mathf() to the rescue. The important clue here is that both our left and right boundaries are just the negative of each other and that “greater than” is defined as beyond each in the direction indicated. In other words, we can check both values at the same time if we check the absolute value of ‘x.’ Then, if needed, we simply negate the value of ‘x’ to move it to the other side. This can be accomplished cleanly and in one line using one of my favorite operators, the ternary operator.

The ternary operator is a one line statement that checks some boolean condition. If true, it returns one value. If false, it returns an alternate. It takes the form: <comparison> ? <true value> : <false value>; That means we can check both the left and right side boundaries with a statement like:


Mathf.Abs(transform.position.x) < 11.25 ? transform.position.x : -transform.position.x

Mathf.Abs(transform.position.x) < 11.25 is our boolean comparison. If the ‘x’ value of our position is between -11.25 and 11.25, then our true value is returned. i.e. it just returns itself. If however, we have travelled beyond either -11.25 or 11.25, then we know we have crossed a boundary. The kicker is, we don’t actually care which one because our rule says if we bust the right or left boundary, just jump to the other side. In other words, no matter which side we bust, we teleport to the equivalent, but opposite value. This leaves us with the following.

using UnityEngine;

public class Player : MonoBehaviour
{
    [SerializeField] private float mySpeed = 5f;

    void Start()
    {
        transform.position = new Vector3(0, 0, 0);
    }

    void Update()
    {
        transform.Translate(new Vector3( Input.GetAxis("Horizontal")
                                       , Input.GetAxis("Vertical")
                                       , 0
                                       ) * Time.deltaTime * mySpeed);
        transform.position = new Vector3( Mathf.Abs(transform.position.x) < 11.25 ? transform.position.x : -transform.position.x
                                        , Mathf.Clamp(transform.position.y, -4, 0)
                                        , 0);
    }
}

Now, we have a single command to move our player game object, and a single command to adjust the player game object’s new position, according to our boundary rules.

We could, but don’t have to end here. The above is not too bad to read but may or may not be quite so obvious at first glance “why” we are doing all this. In truth, our pseudo code could have gone one step further and recognized for both the ‘x’ and ‘y’ components of our position, we were checking our boundary rules.


    // boundary code

    // Set Position  : Check left and right boundary
    //               : Check top and bottom boundary
    //               : z is always 0

This might lead to the following code, which is arguably more readable and separates the “what” from the “why.”

using UnityEngine;

public class Player : MonoBehaviour
{
    [SerializeField] private float mySpeed = 5f;

    void Start()
    {
        transform.position = new Vector3(0, 0, 0);
    }

    void Update()
    {
        transform.Translate(new Vector3( Input.GetAxis("Horizontal")
                                       , Input.GetAxis("Vertical")
                                       , 0
                                       ) * Time.deltaTime * mySpeed);
        transform.position = new Vector3( CheckLeftRight(transform.position.x)
                                        , CheckTopBottom(transform.position.y)
                                        , 0);
    }

    float CheckLeftRight(float x) { return Mathf.Abs(x) < 11.25 ? x : -x; }

    float CheckTopBottom(float y) { return Mathf.Clamp(y, -4, 0); }
}

With this, you can decide whether the Check* functions are readable enough and can refactor for readability if you want, without reducing the readability of the main line of code. Arguably, you could increase readability just a bit more by taking the arbitrary seeming values of 11.25, -4 and 0 and assigning them to descriptively named variables.

Taking it further, we might have adjusted our pseudo code earlier. As we can see, our Update() function is currently doing two things. Again, we can see “what” but not “why.” If we switch back to pseudo code, we see an opportunity to increase the self-documentation-ish-ness of our code.

    void Update()
    {
        // move player
        // check boundaries
    }

Thinking in this way, leads us to something that may seem excessive at first. But what it really does is allow the developer (you or another) to read only as deep as they need to. If the problem is not with player movement, there is no reason to read any deeper. If the player is getting lost off screen however, there might be a problem in the boundary checking.

using UnityEngine;

public class Player : MonoBehaviour
{
    [SerializeField] private float mySpeed = 5f;

    void Start()
    {
        transform.position = new Vector3(0, 0, 0);
    }

    void Update()
    {
        MovePlayer();
        CheckBoundaries();
    }

    void MovePlayer()
    {
        transform.Translate(new Vector3( Input.GetAxis("Horizontal")
                                       , Input.GetAxis("Vertical")
                                       , 0
                                       ) * Time.deltaTime * mySpeed);
    }

    void CheckBoundaries()
    {
        transform.position = new Vector3( CheckLeftRight(transform.position.x)
                                        , CheckTopBottom(transform.position.y)
                                        , 0);
    }

    float CheckLeftRight(float x) {
        float leftRightBoundary = 11.25f;
        return Mathf.Abs(x) < leftRightBoundary ? x : -x; 
    }

    float CheckTopBottom(float y) {
        float topBoundary    =  0;
        float bottomBoundary = -4;
        return Mathf.Clamp(y, bottomBoundary, topBoundary); 
    }
}

Now, as you scan down the code, you can see “when” something is going to happen e.g. on Update(). You can see “what” is going to happen e.g. CheckBoundaries() and you can see “how” it’s going to happen. Hopefully, this makes the “why” it’s happening much clearer.

I hope from all this you see how pseudo code can be used, not just for brainstorming, but as a valuable, interactive code analysis tool.

Leave a Reply

Why You Need to be Writing Pseudo Code

by Robert time to read: 8 min
0