So far, our programs have been relatively small and simple. Often, we have you only define a single function. But the reality is that most interesting programs are much larger and more complex.
Lines of code is not a good measure of program complexity, but it is still a relevant factor to consider. Humans can only keep so many things in their mind at once. As we develop larger programs, we need to find ways to manage that complexity. Functions will become more important as a way to break down and organize our code.
Our goal by the end of this curriculum is to be comfortable with programs that are hundreds of lines long. In the future, you will learn more advanced techniques for dealing with thousands and even millions of lines of code. However, ultimately there is no substitute for time and experience when it comes to learning how to manage complexity in large codebases. So in this unit, we will get some experience working with larger codebases and learn strategies for managing complexity.
As a running example, we are going to read and analyze the code for a word guessing game. Before you do anything else, take a few minutes to click the link shown and try the game.
Seriously, try running the program.
The rules of this game are simple. You are shown 4 blanks and need to figure out what the word is. You can guess one letter at a time, and the game will tell you if your letter is in the word or not. You cannot guess the same letter twice, and you can only guess lowercase letters (no digits or symbols). If you guess all the letters in the word before running out of turns, you win! Whether you win or lose, you will progress to the next level. At the end, you will see your score.
In general, running a program is a good first step to seeing what it does. However, it’s not always necessary or possible to run a program in order to understand it.
Sometimes, you can gain insights by simply reading the code and thinking about what it does. This is especially true for smaller programs or for specific functions within a larger program. In these cases, you can often understand the program’s behavior without actually running it.
Another important consideration is whether you can trust the code. If you’re working with code from an unknown source, it’s a good idea to read through it carefully and make sure you understand what it does before running it. This can help you avoid potential security risks or unintended consequences. You are always responsible for code that you choose to execute, regardless of the source. If the code deletes your entire hard drive, you can be upset that someone tricked you into running malicious code, but that doesn’t change that all your data is gone (hope you kept backups!).
If the code is safely sandboxed (i.e., has limited access to the system and cannot harm it), then it may be safer to run it without extensive review. Running code in a browser is usually safe, since modern browsers have built-in security features that help protect your system from malicious code. Dodona’s sandbox is similarly limited, for instance.
As you start to analyze a program, you need to understand its purpose. What problem is it trying to solve? What is the goal of the program? What are the main features and functionalities? Who is the intended user or audience? What are the inputs and outputs? This understanding will help you make sense of the code and how it works.
Think critically to yourself about the Word Guessing Game program, and try to answer these questions. It is okay if you are struggling to do so. First, not every question is directly applicable to every program. For example, games are not usually thought of as solving a specific problem, but rather as providing entertainment or a challenge (although Computer Scientists do have ways to describe the goals and mechanics of games in terms of solving a problem).
You might also struggle to understand the inputs and outputs of the program, as they may not be immediately obvious from the user experience. Consider what the user sees when they interact with the program. What information do they need to provide? What feedback do they receive? This can help you identify the inputs and outputs more clearly.
The Word Guessing Game is designed to be engaging and educational, helping users expand their vocabulary while having fun. We have identified a number of key components that contribute to the game’s functionality and user experience, like inputting the letters, providing feedback, and keeping track of the number of incorrect guesses and the score. These questions are usually straightforward, but if you cannot answer them then you do not understand the game.
A much more complicated activity is figuring out the inputs, outputs, and state of the program. However, since programs are fundamentally about transforming inputs to outputs, it is critical to figure out what the inputs and outputs look like. The same goes for the state: we need to understand what information is being stored and how it changes over time.
Just because we identified the important parts of the state doesn’t mean we fully understand how they work together, or that they will all be in a State object.
And of course, as we start developing an application, we often realize that we need to refactor our code and reorganize our state management to better suit our needs.
It’s a great idea to try and predict what state will look like, but you don’t need to feel committed to whatever you initially come up with.
In this game, we know that the players have to be able to enter their letters, and will also need to click buttons to submit their guesses. There also has to be a list of words somewhere. As the game progresses, the player is shown screens of information about the guessed letters, the current state of the word, and their progress in the game. This feedback correlates strongly with the current state, allowing the player to make informed decisions about their next guesses. Some of the state might not need to be displayed to the player at all times, such as their current overall score or the actual secret word.
Another thing to consider as you start looking at the code is whether there are any issues or obvious errors. In this case, we have shown you a working game. But we will soon be looking at an implementation of the game that has some bugs and issues for us to find and fix. It is still worth your time to try inputs and clicks that might break the game. Your goal, as we will see, is adversarial - you want to find ways to make the game fail. Computer Scientists must be very strict with their own code, to make sure that it works. There is nothing shameful about finding and fixing bugs. Indeed, such practices are essential for maintaining code quality and ensuring a smooth user experience, and you should celebrate every time you find and fix a bug.
Some aspects of the user interface might be considered to have issues. For instance, the level is initially displayed as “Level -1”, which is confusing for players, and then the actual first level is “Level 0”. Also, the debug information is being displayed at the bottom of the screen, which might be distracting for players. These are issues that could be addressed to improve the user experience, but might not be considered errors in the code itself. These issues are absolutely critically important, and addressing them is an entire field of Computer Science called Human Computer Interaction (in particular, the study of how people interact with computers and how to design user interfaces that are effective and enjoyable to use). For now, we will set aside how aesthetically pleasing the user experience is, and focus on the functional aspects of the game.