Dos and Don'ts in GameMaker
GM Version: GMS 2
Target Platform: ALL
Download: N/A
Links: Original
Summary
This “tutorial” will describe many guidelines on how to write clearer, “more elegant” code. More experienced programmers will find this tutorial to be “stating the obvious”. That’s exactly correct: this tutorial IS stating the obvious. However too many times I’ve seen people not knowing this, or at least not following the obvious better code. With this tutorial I hope people realize what a mess they make and how to improve it.
During this tutorial several patterns and anti patterns will be listed. They’ll be described why they should (not) be made and what better alternatives there are. I'll often talk about "functions", while -apart from build in- functions don't strictly exist in gamemaker, for all purposes scripts are functions, and should be handled as such.
This tutorial is not about teaching new code, instead it is about improving your own programming skil. Hence a basic knowlenge of gml is required. The tutorial is also not only for gamemaker, the rules for elegant code apply to almost any language.
Dos and Don’ts in gamemaker
This “tutorial” will describe many guidelines on how to write clearer, “more elegant” code. More experienced programmers will find this tutorial to be “stating the obvious”. That’s exactly correct: this tutorial IS stating the obvious. However too many times I’ve seen people not knowing this, or at least not following the obvious better code. With this tutorial I hope people realize what a mess they make and how to improve it. I'll often talk about "functions", while -apart from build in- functions don't strictly exist in gamemaker, for all purposes scripts are functions, and should be handled as such
During this tutorial several patterns and anti patterns will be listed. They’ll be described why they should (not) be made and what better alternatives there are.
First you have to realize that good code matters (heck there’s a book even named after this). On these forums coding often gets thrown to the background, with the argument “designing is most important once you design the code is simply a piece of cake”.
WRONG
Programming is not a piece of cake, it requires experience, careful planning and good designing. There are people who advocate that tools such as gamemaker make the need for programming (skills) obsolete. This is wrong, there will always be a step between your design, and a formal language a computer understands. And as long as such a translation is being made, it is important to write clean code. A good programmer is not somebody who knows all commands and how to apply them, that is what reference material is for (and as we see through this tutorial it is often not necessary). A good programmer is someone who writes elegant code and know how to design code.
To start off let’s look at an example of how we shouldn’t write code:
This entry for the iocc.. Found
at the IOCCC (international obfuscated c code challenge). Now if you desire to create such code I suggest you stop reading. Those who like to write "slightly" more readable code should read on.
What is clean code
To understand what clean code means, we have to notice the audience of your code. Let us look at the standard life-cycle of a program (and hence code): First you design your code, you work out algorithms, proof that those are working, than you write your code, after which maintenance starts. Maintenance takes generally more than 80%[1] of the project time. So if you manage to cut down on maintenance time it will pay off greatly. For this reason we want to write clean code.
So
we write code for future maintenance programmers.
This is important to realize and you should never forget this, you don’t write code for yourself. It’s written for someone who has not the experience you had during writing. And he or she shouldn’t spent more time than necessary. Even if you’re completely staying “indie”, a term popular around this community, you should still plan to write code for someone else. By the time you’ll review your old code you will have forgotten what you were thinking and you’re a “newcomer” to your code.
The main problem with maintenance of bad code is, is that it always lead to “code rot”. Code rot means that code becomes harder and harder to understand. Trying to “fix” code leads to more bugs elsewhere. Ugly code hence leads to maintenance programmers fearing changes and instead of fixing, the problem is patched – Which leads to even worse code. Eventually code is so ugly that no one understands it anymore and the only sensible thing to do is a complete rewrite. I’ve experienced this myself, “gpathfinding” is currently in a state where I can only rewrite the code from base.
The alternative, writing clean code from scratch, means that maintenance can operate without patching, instead maintenance programmers can fix the code. And by fixing instead of patching code rot is prevented and no rewrite is needed.
But but…
There are lots of counter arguments people bring up here to not write clean code. Some even more absurd than others.
The weirdest thing I’ve encountered was the argument “but I want people to not understand my code, so they can’t steal it”. This is a complete anti-argument, and it shows you have a complete misunderstanding of for whom code is meant – code is meant to be read. If you must hide your source for certain people use a tool as the final step to obfuscinate/compile your code. But that is not the code-base you store or share.
Another reason I’ve seen popping up often is “but it’s faster”. This argument shows some kind of ignorance. It shows that you are busy micro-optimizing during the programming phase. Not only do you not know the bottle necks, but you might actually prevent future optimizations on certain more important parts. On top of that you have to realize what tool you are using: game maker. Game maker itself isn’t the fastest tool, and if your aim is writing fast good in spite of readability I suggest not starting with game maker.
…but…
Now of course there are exceptions, I’m well aware of this. Gamemaker itself leads to quite a few ugly forms of code. (Not being able to pass arrays around, not having member function, terribly long arguments lists on functions, no scope definitions etc etc). You’ll often have to choose between two evils, but that doesn’t make it good. Always try to aim for clean code, look if you can’t redesign the code in a better manner, and keep the ugly parts to a minimum.
Clean code tips and tricks
As I started with programming is all about writing clean code. Programming can’t be learned in 1 day, and it requires experience. Clean code isn’t a “fail/pass” thing, it’s very subjective, difficult to define. During this part I’ll try to name some hang up points which you can use as guidelines to write clean code. Following these should make the code at least “readable”. Rmember these are guidelines, and not hard rules, always choose what works best in your specific case. I ordered the guidelines by their importance and easy-to-implement. The top are very important, yet are straight forward to implement in your program (and hence should always be applied).
Now before we go into detail first discus a few generic things. As we know by now, code is written for maintenance programmers. Your goal is to make it these people as easy as possible. Just like writing an article this means that everything is expected, follow the rule of the least surprise[2]. People shouldn’t have to look into the implementation details when only working on the interface. And when looking at the implementation everything should be at the location where it is expected.
- DO indent your code.
The very first and most important rule. Many articles never state this. Simply because it’s so obvious. Indent your code in a concise matter. The actual way isn’t too important, as long as it’s generally accept to be good. I myself use the K&R style as it reduces vertical scrolling.
- DO use whitespace well
Whitespace is the most powerful tool you as programmer have to create readable code. Whitespace allows you to let your users focus, it draws attention to things. Just like in an article adding paragraphs make code readable so does whitespace in programming. However too much and too little whitespace is bad. Too much whitespace leads to incoherent code, you don’t know what belongs to what. Too little whitespace lead to an overwhelming feel a “TL;DR” moment. Both are terrible.
Now what is good whitespace? Well there are a few simple guidelines one can follow
- always use whitespace around assignment/comparison/mathematical punctuation.
- use a whitespace symbol before an opening (left) parenthesis, and after a closing parenthesis
- exception to above is when the opening parenthesis is the start of an argument list of a function. As the argument list belongs to the function closely they should be tied together
- after a comma/semicolon follows whitespace.
These rules will give the basic white-spacing, though it requires a lot of experience and testing to see what is really “best”.
- When in rome DO as the romans do.
This saying counts for many things, and it goes hand in hand with the rule of least surprise. It means that whenever you use a certain engine or work with a team, you should adapt your style to what the engine/team have decided. Don’t mix & match styles.
- DON’T use magic numbers in code.
This is one of the most often violated anti pattern in gamemaker. And it’s also one of the oldest paradigms in programming. There is not a single reason to use a hard-coded magic number in code. An example is the “map_width”, ie let’s consider you make a chess game, now to see if a move would be legal we first check if it is “on the board”:
if (x < 8 && y < 8)
But what later, when you are maintaining your project, what did you mean by “8”, does it hold a special value? – Why “8”?. Much more readable would be:
if (x < MAP_WIDTH && y < MAP_HEIGHT)
It describes exactly what you mean, hence making code more readable. It also means maintaining the project (maybe you want a bigger map?) easier. As it prevents errors when forgetting to update.. This counts for any constant, way, way too often I see questions about blend modes, and then the user puts something like:
draw_set_blend_mode(3, 4); What does that mean? Nothing, nobody can understand that code without reading the help file. (which increases maintenance time).
- DON’T use "=" for comparison.
This isn't quite as trivial as it looks though. But in gamemaker "=" can be used for comparison, yet at the same time it also can mean an assignment. Assigning & comparing are 2 very distinct operations, having nothing to do with each other. So they shouldn't share the same symbol. Consider the following piece:
var a, b;
a = 0;
b = 0;
a = b = 1;
What will "a" hold afterwards? - Most people with other programming background, but also most without any knowledge of programming, will interpret the last line as “set b to 1, set a to b”. – thus both hold “1” afterwards. Gamemaker will not do this, gm’s engine will read this as:
a = (b = 1) which equals a = (b == 1) thus a would be “0”. Using “==” for comparison would’ve immediately removed the problem, we see that it is a comparison followed by an assignment.
- DO name your variables and functions well.
Nomenclature is a matter of making decisions as a team. Here the saying “do as the romans do” counts doubly hard. A good name should always describe what a function does. And as gamemaker lacks scope/namespaces the name should also implement the scope of the function/variable. A person who is adding code to your engine should never have to think about the name of a variable/function when adding/using it. The naming should immediately follow from the purpose. Of course names shouldn’t be bigger than necessary, using “i” as counter in loops is well understood by everybody and should be used as such.
Doing this prevents naming collision. But much more importantly: it makes code that is self-commenting and hence makes reading code a lot faster. An example nomenclature I use I’ll quickly describe:
- Any script/resource/constant I use for an engine starts with a 2 or 3 sign for the engine, followed by an underscore: ie “MLP_” for my mathematical/logical parser
- all resources except scripts are then followed by an undercase abbreviation for the type of resource
- Then the resource is named with separate words separated by capitals (not underscores). So a typical object would be: MLP_objUnaryOperator. A script would be: MLP_ParseFunction())
- constants are typed in all capitals, words separated by underscores.
- global variables start with 2 underscores followd by the engine ID: __MLPParserNumber
- Local variables don’t have any prefix, but each word is separated by a capital.
- script variables never use capitals, and use underscores to separate words.
These are just 1 way of describing a nomenclature, but you can see that whenever I add something I immediately know how the thing should be named. Rests only what they are named. And that I know from the description of the function.
- DO comment, DON’T overcomment
Anyone who followed education in a computer science related field, and got programming courses will immediately remember how the tutor would hammer on commenting more and more. However this isn’t useful to production level code, the tutor wants comments as he can grade you on that, he has to know if you understood what you wrote.
Clean code is self-commenting. The variable names, function names etc should explain the flow, not the comments. Commenting has always many, many drawbacks, and should be used sparingly.
Comments lead to distraction of the actual code when used too often. They will require extra time spent on maintaining/updating the comments when fixing parts of your code. Always with the problem that a comment might not get updated due to time constaints or simply missing one. And then comments not only become a distraction, they are wrong and describe things that aren’t there. Also be very careful that you keep the text in arguments minimal. You are not writing a book and shouldn’t write your thoughts down. (Keep a separate document for this).
Remember for who you are writing code: for maintenance programmers, programmers indeed. You can take it for granted that they know the language (GML) just as good/better than you know it.
Take the following abysmal use of commenting, it looks up the phone number of a student (id) (if the student id is value) and returns that number:
var student_id, student_phone_num, student_map;
student_id = argument0; //initialize student id
//if student exist
if (student_id < NUMBER_STUDENTS) {
student_instance = students[student_id];
//this is done after the checking for the correct number of instances
//to prevent out-of index errors on the arrays.
//The student_instance can be accessed for all the other data, like phone numbers, courses etc.
//Remember you need to check if the instance is still active before accessing this data.
if (instance_exists(student_instance)) {
//student exists
phone_number = student_instance.phone_number;
return phone_number; //return
}
}
return 0; //returns 0 when non existing user number
//initialize student id – No really? We can read too yes, we SEE you are assigning argument0 to student_id variable, don’t state the obvious
//if student exist We see you’re doing an if-statement here, and with a little rewriting the code would be self commenting.
//set the student_instance to the student used
//this is done after the checking for the correct number of instances
//to prevent out-of index errors on the arrays.
//The student_instance can be accessed for all the other data, like phone numbers, courses etc.
//Remember you need to check if the instance is still active before accessing this data.
You like writing books? This just distacts
//student exists Yup, we’re inside the if-statement, indentation told us that already.
//return Orly? What else would you expect from a line starting with “return”
//returns 0 when non existing user number a little rephrasing (maybe a constant) would mean the code is self commenting. Consider the following:
var student_id, student_phone_num, student_instance;
student_id = argument0;
if (student_exists(student_id)) {
student_instance = student_get_instance(student_id);
phone_number = student_instance.phone_number;
return phone_number;
}
return INVALID_STUDENT_ID;
//student_exists(student_id)
var student_id, student_instance;
student_id = argument0;
if (student_id < NUMBER_STUDENTS) {
student_instance = student_get_instance(student_id);
if (instance_exists(student_instance)) {
return true;
}
}
return false;
//student_get_instance(student_id);
var student_id;
student_id = argument0;
return students[student_id];
Notice how 0 comments are necessary, everything explains itself, the function names help you following the execution path and there is nothing to withdraw attention of what is important – the code.
Now what are good comments? Comments should speed up reading through functions, so the first commenting should always be a description of how a function is used, and what the arguments are. It should also describe the returned value. Make sure the header stands out clear from the rest of code, and follows an easy to read format. (For bigger engines I tend to use gmlscripts system of headers example).
Apart from that “header” argument you might wish to add “TODO” type comments, to notify the user that a certain feature is yet to be created. Other comments inside code are generally bad and you ought to rephrase/refactor your code.
- DO one thing in a function. Do one thing only.
This another of those programming paradigms that has been with programming forever. It means that a function should do 1 thing, and only act on 1 level of abstraction. This is a bit more difficult to implement, it requires the programmer to carefully think about his functions. What is 1 thing, what do I want my function to do.
Take for example our original student-phone number code, that script did 3 things in 1 function. Instead of splitting it up into 3 parts (which was done later). Our main goal is to get the phone number of a student (1). To do this we have to check whether a student exists (2), and also get the student_instance from the id (3).
Clean code[3] had a good method to describe what is “one thing”. They described the “TO”-method, where we describe the function starting with “TO <functionname>”
TO get_student_phone_number, we check if student exists. Then we get the student_instance which we use TO get the phone number
It should now be clear this function did 3 things.
A particular offense I see popping up quite a bit are that people pass “behaviour” variables to a function. I’ve seen an extension once that had a Boolean as second argument of an initializing function. If the Boolean was “false” the function would free the memory instead of initialize.
Initializing and freeing are 2 distinct things. And should never be in the same function.
- DON’T repeat yourself,
DRY, another of those base principles created at the very beginning of programming. And still valid today. If you have to do a certain action multiple times, do never ever copy-paste the code at two (or more locations). Put the code into a function such that you call this function.
Failing to do so will lead to confusion & maintenance problems, if you wish to change a certain feature you fear the functions go “out of sync”, and you’ll have to be very careful of fixing bugs.
- DO keep function bodies small.
A function in itself should be understandable without reading it more than a few times. It should completely fit in the “human working memory”. And as such don’t write function that are pages long. A small function leads immediately to self-commenting code. And as explained, self-commenting code leads to clean code.
The question is: what is “small”. There is no answer for this question, and it is completely personal preference. However when you reach the triple digits functions are getting long already and you should be on the lookout.
And a final, last but not least action everyone should undertake:
DO learn another langue.
Not because gamemaker is a bad language, but simply because it makes you a better, more complete programmer. By learning multiple languages you won’t stay around bad programming practices endorsed by a certain language. You will learn about other ideas and you will learn the morals of other programming languages. This means that when you look back to gamemaker you can make a well-funded reason why something should or should not be written in a certain way.
Following these rules will benefit you always in the long run. Some principles might require a bit of planning and careful design, all require discipline and efford. But if you follow above rules and write your code clean, programming won't ever be an annoyance that "must be done", it'll feel like a breeze when reading previous code!
How to write clean code
The remaining part is: how do you write such code as described above. It seems a terribly long list with lots of things you have to take notice from. This is indeed true, and being very chaotic myself I can fully see the problem. It is also never meant to say that you should write code "clean" from the go, first it is important that you get to fix the problem. As only after you fixed the problem you see what was necessary to fix it, and only then you can decide how the code would look best.
After that you should just "proof read" your code a few times. Be critical, maybe keep above list as a reminder next to you, and delete all those temporary comments. Refactor code so it is self describing. (Often this means splitting up functions into several parts). Rename variables so they're actually meaningfull. This requires indeed discipline and time, there's no direct gain from this. But I hope you understand by now the importance of clean code.
Conclusion
When writing code the first thing one should notice is that the audience are maintenance programmers, maintaining programmers typically takes about 80% of the programming time. For these programmers code should hence be clean and well written. Clean code will always share some traits, it means code is readable and self-commenting. It also means that code is "obvious", doesn't hold weird structures and where it must be done it is explained throughly. Finally clean code also means that code is formatted in a specific manner.
With this tutorial I hope you will now look into your code and start refactoring it, with each look over your code try to make it a little bit better. I especially hope that examples written are actually clean code. It's a mindset you should have. Programming is not something you should just as a quick obligatory thing to design a game. Instead it's a field which requires just as much designing, planning & experience as any other design.
[2]
http://en.wikipedia.org/wiki/Principle_of_least_astonishment (june 2011)
[3]Clean code: a Handbook of Agile Sofware Craftship, Robert C. Martin et al, 2009