3 Programming tips and tricks
Before you start writing your first code, we need to talk about the art of programming. As I already mentioned, it is not a code that works, it is about a code that is easy to comprehend. Correctly working code is a nice plus but if I have to choose between a spaghetti code that presently works correctly and clearly written and documented code that needs fixing, I will choose the latter any day. I can fix things I understand, I can only hope, if I don’t.
Below are some tips about writing and reading the code. Some may sound cryptic when you read them for the first time (they will become clear once we cover the necessary material). Some will feel like an overkill for simple projects that we will be implementing. I suggest that you read this section casually the very first time but return to it frequently once we start to program in earnest. Unfortunately, these tricks won’t work if you do not use them! So you should always use them and they should become your good habits, like using a seat belt. The seat belt does nothing useful on most (hopefully, all) days but you wear it because it might suddenly and very urgently become extremely useful and you can never be sure when this will happen. Same with coding. Quite often you will be tempted to write “quick-n-dirty” code because this is just a “simple test”, temporary solution, a prototype, a pilot experiment, etc. But as they say “There is nothing more permanent than a temporary solution”. More often than not, you will find that your toy code grew into a full blown experiment and it is a mess. Or you want to come back to that pilot experiment you did a few months ago but realize that it is easier to start from scratch than to understand how that monster works4. Thus, resist the temptation! Form the good habits and you future-you will be very grateful!
3.1 Writing the code
3.1.1 Use a linter
Linter is a program that analyses your code style and highlights any issues it finds: spaces where should be none, no spaces where should be some, wrong names, overly long lines, etc. These do not affect how the code runs but following linter’s advice results in a consistent standard if boring-looking5 Python code. Try to address all the problems that the linter raised. However, use your better judgment because sometimes lines that are longer than linter would prefer are more readable than two shorter ones. Similarly, a “bad” variable name by linter standards can be a meaningful name for a psychologist. Remember, your code is for people, not for the linter.
3.1.2 Document your code
Every time you create a new file: document it and update the documentation whenever you add/change/delete new functions or classes. Every time your create a new function: document it. New class: document it. New constant: unless it is super clear from the name alone, document it. You will learn a NumPy way of doing this in the book.
I cannot stress how important documenting your code is. VS Code (an editor that we will use) is smart enough to parse NumPy docstring, so it will show this help to you whenever you use your own functions (helps you to help you!). More importantly, writing documentation forces you to think and formulate (in human language!) what the function or class is doing, what type the arguments / attributes / methods are, what is the range of valid values, what are the defaults, what should a function return, etc. More often than not, you will realize that you have overlooked some important detail that may not be apparent from the code itself.
3.1.3 Add some air
Separate chunks of code with some empty lines. Think paragraphs in the normal text. You wouldn’t want your book to be a single paragraph nightmare? Put a comment before each chunk that explains what it does but not how it does it. E.g., in our typical PsychoPy-based game there will be a point when we draw all stimuli and update the window. That is a nice self-contained chunk that can be described as # drawing all stimuli
. The code provides details on what exactly is drawn, what is the drawing order, etc. But that single comment will help you to understand what this chunk is about and whether it is relevant for you at the moment. Same goes for # processing key presses
or # checking gameover conditions
, etc. But be careful and make sure that the comment describes the code correctly. E.g., if the comment says # drawing all stimuli
where should be no stimuli-drawing code anywhere else and no code that does something else!
3.1.4 Write your code one teeny-tiny step a time
Your motto should be “slow but steady”. This is the way I will guide you through the games. Always start with a something extremely simple like a static rectangle or image. Make sure it works. Add a minor functionality: Change in color, position, another rectangle, storing it as an attribute, etc. Make sure it works. Never go to the next step unless you fully understand what your current code is doing and you are 100% certain that it behaves as it should. And I mean 100% seriously! If you have even a shadow of a doubt, check again. Otherwise, that shadow will grow and make you progressively uncertain about your code. This tortoise-speed approach may feel silly and overly slow but it is still faster than writing a large chunk of code and then trying to make it work. It is much easier to solve simple problems one at a time than a lot of them simultaneously.
3.1.5 There is nothing wrong with StackOverflow
Yes, you can always try to find a solution to your problem on StackOverflow6. I do it all the time! However, you should use the provided solution only if you understand it! Do not copy-paste the code that seems to solve a problem like yours. If you do that and you are lucky, it might work. Or, again if you are lucky, it won’t work in an obvious manner. But if you are not so lucky, it will (sometimes) work incorrectly in a subtle way. And, since you did not really know what the code was doing when you pasted it, you will be even more confused. So use StackOverflow as a source of knowledge, not as a source of copy-pastable code!
3.2 Reading the code
Reading code is easy because computers are dumb and you are smart. This means that instructions you give the computer must necessarily be very simple and, therefore, are very easy to understand for a human. Reading code is also hard because computers are dumb and you are smart. You are so smart that you don’t even need to read the entire code to understand what it is doing, you just read the key bits and fill in the gaps. Unfortunately, this means that you will tend to read over mistakes. This is not unique to programming, if you ever proofraed a text, you now how hard it is to find tipos. Your brain corrects them on the fly using the context and you read the word as it should be, not as it is actually written7.
My experience with programming in general and on this seminar in particular is that most problems you get stuck with are simple to the point of being dumb and obvious in retrospect8. Do not despair! It is not you, but just a consequence of how wonderfully your brain is wired for pattern-recognition. Below are several suggestions that could help you to make reading code more robust.
3.2.1 Think like a computer
Read the code line-by-line and “execute” it the way the compute would. Use pen-and-paper to keep the track of variables. Trace which chunks of code can be reached and when. Slow yourself down and make sure you understand each line and are able to keep track of the variables. Once you do that it will be easy to spot a mistake.
3.2.2 Pretend that you’ve never seen this code in your life
Assume that you have no idea what the code is doing. As I wrote, quite often you literally do not see a mistake because your brain fills-in details and bends the reality to match your expectations9. You know what this chunk of code should be doing, so instead of reading it you skim through it and, unless it looks obviously terribly wrong, assume that it does what it should. Turning your expectations off is hard but is immensely helpful.
3.2.3 Do not search only under the street lamp
Whenever you are using some new code or need to implement something that feels complicated and your code does not work as it should, you will tend to assume that a problem is with the new fancy code. Simply because it is new, fancy, and complicated. But, in my experience, the error will typically hide in plain sight in the simpler “trivial” code nearby which you never properly look at because it is simple and trivial. Check everything, not just the places where you would expect to have made a mistake.
3.2.4 Use the debugger
In the book, you will learn how to pause an execution of your game, so you can investigate its state. Use this knowledge! Put breakpoints and execute the code step-by-step. Check values of variables using “Watch” tab. Use debug console to check whether functions return results that they should. For complex conditions or mathematical formulas, split them into small bits, copy and execute these bits in the debug console and check whether numbers add up. Make sure that a code chunk checks out and then proceed to analyze the next one. Debugging is particularly helpful to identify the code that is not reached or reached at the wrong moment.
3.3 Zen of Python
I found Zen of Python to be good inspiration on how to approach programming.