Trust Me! Your Code Isn’t That Bad

Trust Me! Your Code Isn’t That Bad Featured Image

Somewhere in the culture of tech we decided that software developers were geniuses. There may have been a time where learning to code required a lot of knowledge, such as building arcade games with hardware, programming FPGAs, and writing assembly.

Fortunately, for most of us, those days are long behind us. Now, just about anybody can pick up a language like Python or JavaScript and make something cool. Yet, the culture of the super genius programmer persists and leaves a lot of us in the field feeling like impostors. Today, I’m here to say that your code isn’t that bad, trust me.

Table of Contents

The Prevalence of Impostor Syndrome in Software Development

As someone who works with a lot of early career programmers, I find that imposter syndrome is wildly common. Just by a quick poll recently, I found that about half of my students were willing to openly admit to their imposter syndrome, and I suspect the actual numbers are worse. Though, some folks might identify with the following meme better:

Jokes aside, I was recently reflecting on the skills of my students who are, at best, three programming courses into their degrees. However, in such a short time, they’ve developed tremendously, and I would be comfortable with working with them as a part of a development team.

The idea of working with other students in a development team, of course, got me thinking back on my experiences as an undergrad. Specifically, I remember developing a video game with other undergrads. This was probably my first time developing software in a large team, and I recall seeing some of the worst code I’ve ever seen. To give my students some confidence, I decided to share the story about that code, and I figured I might as well share that same story with all of you.

What Makes Code Bad?

Thinking about coding as a craft rather than a science allows us to approach coding in a more subjective way. To me, this is a net positive for the discipline because it requires criticism to be reasoned. In other words, “x” practice being bad isn’t a matter of fact but rather a matter of argument.

Therefore, if you want to create a list of bad practices, you need to be able to justify them as bad, and this is often a part of our field that is lacking. Instead, what you tend to find in the tech community is a lot of dogma (e.g., “x” is bad because “y” says it’s bad). Very quickly these types of arguments start to mirror the kinds of arguments you hear from parents (i.e., cuz I said so), and ultimately they don’t hold any weight.

With that said, I’m going to forego the chance to argue my own perspective on what makes code bad (since that’s a very different style of article) and just share a bit of my thoughts on how to differentiate good code from bad code.

To start, I don’t really consider code bad if it meets a lot of the criteria that we generally associate with bad code. For example, poorly formatted code (e.g., inconsistent indentation, weird spacing, etc.) is often described as bad because it’s visually hard to look at. However, at least as of 2023, we have tools that can be used to account for issues of formatting. In fact, we have a ton of static analysis tools that can be used to identify potential bugs as well.

Instead, I would argue that code is bad if it is designed in a way that makes it hard to use and/or modify. Or as I often put it, there has to be a better way. I can’t think of a better example of this than the one I’m about to share.

Developing Games the Hard Way

About 8 years ago at this point, I was in undergrad taking a game design course. In this game design course, we were placed in large teams of around 10 developers and 10 artists. With teams this large, you have to get used to being in control of a small portion of the overall game.

Specifically, since we were developing a 2D tile-based resource management game, there were a lot of moving parts. For instance, just in terms of game objects, there were scripts for tiles, buildings, characters, NPCs, and more. In addition, there were scripts for the UI as well as scripts for the save state, audio, and game mechanics.

If I recall correctly, I was in charge of handling the save state of the game, though I went on to have my hand in many parts of the game. Specifically, I remember looking over the script for the tiles and uncovering some truly horrifying code.

See, in my mind, I would imagine that the tiles, which are just squares that make up the game grid like a chessboard, could be dynamically loaded into the game. In other words, if you wanted the player to have a small grid to start, you could generate a small 10×10 map for them from a tile object. Then, as they bought more land, you could expand the grid dynamically by loading in more tiles.

To my horror, there was no code to generate the game grid. Instead, what I found was the following script:

using UnityEngine;
using System.Collections;

/* TileScript - Script attached to the Grid object in game; contains fields for to contain the
 * tiles, currently tiles are initialized by searching for GameObjects tagged as "Tile". Tiles 
 * are essentially hashed into an array by name, mimicing the current naming process within the editor. */

public class TileScript: MonoBehaviour {

  // Fields that are used to contain and maintain all the tiles
  private ArrayList tileObjects;
  private GameObject[, ] tiles;

  // Constants defined for array operations
  public int mapArea = 36;
  public int mapLength = 6;
  public int mapWidth = 6;

  void Start() {

    ArrayList namesOfTiles = new ArrayList();

    tileObjects = new ArrayList(GameObject.FindGameObjectsWithTag("Tile"));
    tiles = new GameObject[mapLength, mapWidth];

    int aisleCount = 0;

    foreach(GameObject t in tileObjects) {
      namesOfTiles.Add(t.name);
    }
    // I don't know why aisleCount is my other loop index, but it is
    //	this loop adds all of the found tiles into the 2D array
    while (aisleCount < mapWidth) {
      for (int i = 0; i < mapLength; i++) {
        tiles[i, aisleCount] = (GameObject) tileObjects[namesOfTiles.IndexOf("Tile " + i + " " + aisleCount)];
      }
      aisleCount++;
    }
  }

  // Public method that when given a tile returns an array of the 8 surrounding tiles (including the input tile)
  //		because the input tile is included, in most situations you should ignore index 4 in the array; it'll be null anyways
  public GameObject[] getAdjacentTiles(GameObject tile) {

    Tuple inputTile = hashTile(tile);
    GameObject[] toReturn = new GameObject[9];

    int index = 0;
    for (int j = inputTile.getY() - 1; j <= inputTile.getY() + 1; j++) {
      for (int i = inputTile.getX() - 1; i <= inputTile.getX() + 1; i++) {
        if (i < 0 || j < 0 || i > 5 || j > 5 || (i == inputTile.getX() && j == inputTile.getY())) {
          index++;
        } else {
          toReturn.SetValue(tiles[i, j], index);
          index++;
        }
      }
    }

    return toReturn;
  }

  // Method that when given an input tile, "hashes" the tile using its name, and returns a tuple
  //		which corresponds to the (x,y) coordinate of the tile in the 2D array (as well as in game)
  private Tuple hashTile(GameObject tile) {

    if (tile.name.IndexOf("T") != 0) {
      return new Tuple(-1, -1);
    } else {
      return new Tuple(int.Parse(tile.name.Split(' ')[1].ToString()), int.Parse(tile.name.Split(' ')[2].ToString()));
    }
  }
}

Before you move on to read my explanation of what’s happening, I encourage you to read through the code. It shouldn’t be long before you realize just how wild this code is.

Breaking Down the Grid Code

At a glance, I don’t think there is really anything too egregious in this code. It’s formatted okay. The comments are a little wonky, but they give you the gist of what’s going on. It’s what the code actually does that makes me very sad inside.

To start, I want to point you to line 19, which is the beginning of the Start() method, which is all I’ll be covering in this article. That said, I’d encourage you to take a peek at the getAdjacentTiles() method, which is glorious.

void Start() {

  ArrayList namesOfTiles = new ArrayList();

  tileObjects = new ArrayList(GameObject.FindGameObjectsWithTag("Tile"));
  tiles = new GameObject[mapLength, mapWidth];

  int aisleCount = 0;

  foreach(GameObject t in tileObjects) {
    namesOfTiles.Add(t.name);
  }
  // I don't know why aisleCount is my other loop index, but it is
  //	this loop adds all of the found tiles into the 2D array
  while (aisleCount < mapWidth) {
    for (int i = 0; i < mapLength; i++) {
      tiles[i, aisleCount] = (GameObject) tileObjects[namesOfTiles.IndexOf("Tile " + i + " " + aisleCount)];
    }
    aisleCount++;
  }
}

Right off the bat, we create an ArrayList of names of tiles. Why would we need the names of tiles, you ask? You’ll see.

Next, in line 23, we create an ArrayList by looking up all of the objects in the scene by the tag “Tile”. In other words, we have not generated any tiles. The tiles already exist in the scene. We are just looking them up.

From there, in line 24, we create our game object matrix which is meant to store the tile objects. Yes, I know this is confusing as we have an ArrayList called tileObjects which stores the tiles and another ArrayList called tiles which also stores the tiles, but this is the least of the sins committed.

After that, you’ll see that we create an integer variable called aisleCount which stores 0. Don’t worry if you have no clue what this name means; neither does the dev as seen in the comment on line 31.

Then, we move to lines 28-30, which are some of my personal favorite lines. In these lines, we iterate over the list of tiles that we generated when searching for game objects tagged “Tile”. It then puts the name of each tile into a separate ArrayList called namesOfTiles.

At this point, all that’s left to do is to put the list of tiles into a 2D matrix that mirrors the layout of the actual tiles in the grid. That’s where lines 33-38 come in.

Of course, you might be wondering, “how do we know which tile is which?” I’m so glad you asked! You see, each tile that is already in the scene has a name in the format, “Tile x y”, where x and y are the 2D indices of the tiles. Therefore, to populate our 2D matrix of tiles, we have to lookup each tile by crafting the expected string (e.g., “Tile 0 0” for the tile at [0, 0]). As it turns out, we’re in luck; we already have all the tile names in a list.

What’s Actually Wrong With This Design?

It’s really tough to pick a starting spot with just how bad this code was written. For instance, I could point to the weirdness of the pair of nested loops in lines 33 and 34 which use a while loop and a for loop, respectively, to do basically the same thing. I could also point to the weirdness that is generating a list of tile names from the tile objects themselves just so you can use IndexOf(). In fact, I could even point to the excessive use of variables in general when most of them could be inlined in places where they’re used.

Of course, all of that is just nitpicky. The code works. The problem I have with the design is that there is one major detail unaddressed above: how did the tiles get into the scene? Better yet, how did they get names and tags?

As it turns out, this particular dev hand placed all of the tiles in the scene. Specifically, the game at this point was made of 36 tiles in a 6×6 configuration. This dev placed all 36 tiles into the scene by hand, named each of them individually, and manually tagged them all as “Tile”. If it’s not clear why this is so absurd, it would be the equivalent to hardcoding the parameters and positions of all the tiles in code, one line at a time (see relevant meme below).

Apparently, at no point did it occur to them that there might just be a better way of creating the grid. Now, if the team ever wanted to increase the size of the grid, they’d have to manually update the constants in this file and manually add new tiles. Sorry gamers, but you’re stuck with a 6×6 grid.

To make matters way, way worse, this dev didn’t place the tiles in any meaningful location in the scene. They just dropped the first tile wherever and placed the remaining tiles around it. As a result, the entire game was positioned at a seemingly random location in space; not centered around, let’s say, the origin of the coordinate system.

Therefore, once I realized how absurd this was, there was no turning back. All of our game mechanics, such as the game camera, the building placement, and the character pathing, depended on this random starting position. As a result, any attempt to automatically generate the tiles required a precise understanding of trigonometry or enough patience to toy with constants until the tiles fell roughly in place. I’m sure you can imagine that I opted for the latter.

I Am Not Immune to Criticism

Now, I don’t share this story because I think I am some perfect developer. I also don’t share this story because I think this dev is some kind of idiot and deserves ridicule. I share it because I think we all need to see examples of bad code to build our confidence as developers. And don’t take my word for it, I think Pirate Software puts this beautifully:

And therefore, I’m going to share the bad code I helped develop to automatically generate the game grid:

using UnityEngine;
using System.Collections.Generic;

/**
 * The TileScript is a static, one-of-a-kind singleton object that serves as the
 * game grid from which all other game functions are derived
 */
public class TileScript: Manager {

  // Singleton variable so that only one grid variable is active per scene
  public static TileScript grid;

  // Fields that are used to contain and maintain all the tiles
  public Tile[] tiles {
    get;
    set;
  }

  // Public fields
  public int gridX;
  public int gridY;

  /**
   * Initializes the list of tiles
   * Generates the game grid
   */
  override public void Start() {

    if (grid == null) {
      grid = this;
    } else if (grid != this) {
      Destroy(gameObject);
    }
    tiles = new Tile[gridX * gridY];
  }

  /**
   * The main game grid building method
   * All nested function calls are private
   * to avoid users generating a half baked
   * grid object
   */
  public void BuildGameGrid() {
    GenerateTiles(PrefabManager.prefabManager.tiles, PrefabManager.prefabManager.borders, transform.position, gridY, gridX);
    GiveNeighbors();
  }

  /**
   * This will add all of the users buildings to the grid
   */
  public void PopulateGameGrid() {

    BuildingManager.buildingManager.PlaceBuilding(SaveState.state.hq, GetTile(SaveState.state.hqLocation), false);

    foreach(KeyValuePair < Tuple, ResourceBuilding > entry in SaveState.state.resourceBuildings) {
      BuildingManager.buildingManager.PlaceBuilding(entry.Value, GetTile(entry.Key), false);
    }
    foreach(KeyValuePair < Tuple, DecorativeBuilding > entry in SaveState.state.decorativeBuildings) {
      BuildingManager.buildingManager.PlaceBuilding(entry.Value, GetTile(entry.Key), false);
    }
    foreach(KeyValuePair < Tuple, ResidenceBuilding > entry in SaveState.state.residenceBuildings) {
      BuildingManager.buildingManager.PlaceBuilding(entry.Value, GetTile(entry.Key), false);
    }
  }

  /**
   * A method used for building the game grid
   */
  private void GenerateTiles(Tile[] tile, GameObject[] borders, Vector3 orig, int width, int height) {
    // Create a grid object to attach all of the tiles
    GameObject grid = new GameObject();
    grid.name = "Grid";

    // Builds the tile grid
    int tilesGenerated = 0;
    for (int i = 0; i < gridY; i++) {
      for (int j = 0; j < gridX; j++) {
        Vector3 location = orig + new Vector3(0.55 f * (i + j - 5), .32 f * (j - i), -2);
        Tile myTile = Instantiate(tile[(i + j) % tile.Length], location, Quaternion.identity) as Tile;
        myTile.transform.parent = grid.transform; // Make tile a child of the grid object
        myTile.index = new Tuple(i, j);
        myTile.id = tilesGenerated;
        myTile.isVacant = true;
        tiles[tilesGenerated] = myTile;
        tilesGenerated++;
      }
    }

    // All these magic numbers have to do with making sure the border is the correct distance away from the tiles. 
    int borderGenerated = 0;
    for (int i = 0; i < gridY * 0.8; i++) {
      for (int j = 0; j < gridX * 0.8; j++) {
        bool clear = true;
        Vector3 location = orig + new Vector3(2.56 f * (i + j - 9), 1.7 f * (j - i), -2);
        foreach(Tile t in tiles) {
          if (Mathf.Abs(t.transform.position.x - location.x) < 1 & Mathf.Abs(t.transform.position.y - location.y) < 2.2) {
            clear = false;
          }
        }
        if (clear) {
          GameObject myTile = Instantiate(borders[(i + j) % borders.Length], location, Quaternion.identity) as GameObject;
          myTile.transform.parent = grid.transform; // Make tile a child of the grid object

          borderGenerated++;
        }
      }
    }

  }

  /**
   * Gives each tile references to their neighbors
   */
  public void GiveNeighbors() {
    foreach(Tile t in tiles) {
      // Assigns the upper tile
      if (TileExistsAt(t.id - gridX)) {
        t.upTile = tiles[t.id - gridX];
      }
      // Assigns the lower tile
      if (TileExistsAt(t.id + gridX)) {
        t.downTile = tiles[t.id + gridX];
      }
      // Assigns the left tile
      if (TileExistsAt(t.id - 1) && (t.id % gridX != 0)) {
        t.leftTile = tiles[t.id - 1];
      }
      // Assigns the right tile
      if (TileExistsAt(t.id + 1) && ((t.id + 1) % gridX != 0)) {
        t.rightTile = tiles[t.id + 1];
      }
      // Assigns the upper left tile
      if (TileExistsAt(t.id - gridX - 1) && (t.id % gridX != 0)) {
        t.upLeftTile = tiles[t.id - gridX - 1];
      }
      // Assigns the upper right tile
      if (TileExistsAt(t.id - gridX + 1) && ((t.id + 1) % gridX != 0)) {
        t.upRightTile = tiles[t.id - gridX + 1];
      }
      // Assigns the lower left tile
      if (TileExistsAt(t.id + gridX - 1) && (t.id % gridX != 0)) {
        t.downLeftTile = tiles[t.id + gridX - 1];
      }
      // Assigns the lower right tile
      if (TileExistsAt(t.id + gridX + 1) && ((t.id + 1) % gridX != 0)) {
        t.downRightTile = tiles[t.id + gridX + 1];
      }
    }
  }

  /**
   * A helper method for determining if
   * a tile exists at the target id
   */
  private bool TileExistsAt(int targetID) {
    if (targetID >= 0 && targetID < tiles.Length) {
      return true;
    }
    return false;
  }

  /**
   * Given a tuple, this method will be able to produce
   * a list of legal tuples within a distance 1 from the start tuple
   *
   * This method takes advantage of the fact that tiles are indexed
   * by a tuple and treats that tuple as one of the corners of the tile
   * rather than the center
   */
  public List < Tuple > GetPossiblePaths(Tuple start) {
    List < Tuple > possiblePaths = new List < Tuple > ();

    // Add the right path
    if (start.x < gridX - 1) {
      possiblePaths.Add(new Tuple(start.x + 1, start.y));
    }
    // Add the left path
    if (start.x > 0) {
      possiblePaths.Add(new Tuple(start.x - 1, start.y));
    }
    // Add the top path
    if (start.y < gridY - 1) {
      possiblePaths.Add(new Tuple(start.x, start.y + 1));
    }
    // Add the bottom path
    if (start.y > 0) {
      possiblePaths.Add(new Tuple(start.x, start.y - 1));
    }

    return possiblePaths;
  }

  /**
   * A quick method for retrieving a tile based
   * on a Tuple 
   */
  public Tile GetTile(Tuple index) {
    foreach(Tile test in tiles) {
      // Implement equals in tuple
      if (test.index.Equals(index)) {
        return test;
      }
    }
    return null;
  }
}

Perhaps the most hilarious part about this design to me is that it took me two seconds to find a grid generation video on YouTube which does what we did above but in a much, much easier way.

Talk about giving me impostor syndrome! Of course, I kid. As Thor says, the player does not care because the player cannot see the source code.

And with that, let’s call it for the day! I had a lot of fun writing this one, and hopefully you got a good laugh out of it. As always, I write once a week, so feel free to come back here next Friday for some more content. Or feel free to browse that 500+ existing articles at the time of writing, starting with one of these:

You can also support the site by heading over to my list of ways to grow the site. Or, you can call it a day! See you next time.

Jeremy Grifski

Jeremy grew up in a small town where he enjoyed playing soccer and video games, practicing taekwondo, and trading Pokémon cards. Once out of the nest, he pursued a Bachelors in Computer Engineering with a minor in Game Design. After college, he spent about two years writing software for a major engineering company. Then, he earned a master's in Computer Science and Engineering. Today, he pursues a PhD in Engineering Education in order to ultimately land a teaching gig. In his spare time, Jeremy enjoys spending time with his wife, playing Overwatch and Phantasy Star Online 2, practicing trombone, watching Penguins hockey, and traveling the world.

Recent Posts