FrostyCat
Redemption Seeker
Why Yin-Yang if Blocks suck
GM Version: N/A
Target Platform: All
Download: N/A
Links: N/A
Overview
This article aims to document the rookie anti-pattern of adjacent opposite if blocks (i.e. if (xyz) { ... } if (!xyz) { ... }), its pitfalls and what to use instead of it.
What are Yin-Yang if Blocks?
Many novices write "if A do X, otherwise do Y" behaviours in the following form, with 2 adjacent if blocks each carrying the condition and its manually inputted negation respectively:
There are also chained versions of this pattern with 3 or more adjacent if blocks checking the same value. Example:
These anti-patterns all fall under the term "Yin-Yang if Blocks," which I will refer to for the rest of this article. They are characterized by the following:
Why are Yin-Yang if Blocks bad?
Reason 1: They fail at mutual exclusion.
Many novices use Yin-Yang if Blocks with the hope that only one of the block bodies will execute on any given run. But when one block's body operates in a way that it enables some subsequent condition, this false hope becomes a big problem.
A particularly common example of such novice code is this:
A novice might think this flips variable between true and false. But a quick trace reveals a different conclusion:
Reason 2: It is easy to negate the expression improperly.
The 2-block form is especially prone to problems when && or || are part of the condition, and DeMorgan's Laws are improperly applied. Here is an example:
This has the unwanted effect of pulling the player through ground blocks and boxes, unless the player has both a ground block and a box immediately under foot.
The chained form is also often done improperly, especially with ranges. Here is an example:
An exact score of 100 or 200 will trigger two sounds at once.
As you will see shortly, properly done alternatives to Yin-Yang if Blocks leave no room for amateurish human errors like these.
Reason 3: It wastes time evaluating the same expression twice.
While Yin-Yang if Blocks don't always result in tangible bugs, they always duplicate effort. This might not matter much for a few arithmetic comparisons, but for more expensive functions such as collision checks against precise masks, the performance penalties can start to add up, especially if many instances are running those checks at once.
Reason 4: It tells everyone you have no clue how to code.
Because of the pitfalls listed above and how easy they are to avoid with basic techniques, Yin-Yang if Blocks are commonly considered a mark of the uninitiated. Using one not only puts your project at risk of bugs, it also tells others reading your code that you don't know what you're doing.
Competent Alternatives to Yin-Yang if Blocks
Alternative 1: else
For "if A do X, otherwise do Y" behaviours, state only the positive version of the condition, and leave the negative to else. The else keyword ensures that the second block never runs if the first block gets to, even if code in the first block makes the condition false in the process.
else can also form ladders with multiple if conditions to make them mutually exclusive. When writing subsequent conditions, remember to take advantage of what getting to the point of else has already ruled out.
Alternative 2: switch
When comparing one source value to many single target constants (and up to one optional no-match case), use a switch block.
Alternative 3: ! (for toggling variables)
For toggling a variable between true and false, use ! (the Boolean not operator).
The ! operator returns true if the operand is false, and false if the operand is true. This effectively flips the given value.
Alternative 4: Arithmetic operations (for incrementing values)
Whenever feasible, try to relate a variable to its future value using arithmetic expressions, instead of case-by-case handling.
Incrementing a variable by 1 (e.g. 0, 1, 2, 3, 4, ...):
Incrementing a variable by N (e.g. for N=2: 0, 2, 4, 6, 8, ...):
Loop-arounds should also be done using arithmetic expressions, and ideally rooted at 0 instead of 1. Like most counting patterns in computer science, 0 is a more sensible starting point than 1, allowing you to use simpler expressions for the task than if you insist on layman one-indexing approaches.
Incrementing a variable by M and looping around N (e.g. for M=1, N=4: 0, 1, 2, 3, 0, 1, 2, 3, ...)
Decrementing a variable by M and looping around N, with M<N (e.g. for M=1, N=4: 0, 3, 2, 1, 0, 3, 2, 1, ...)
GM Version: N/A
Target Platform: All
Download: N/A
Links: N/A
Overview
This article aims to document the rookie anti-pattern of adjacent opposite if blocks (i.e. if (xyz) { ... } if (!xyz) { ... }), its pitfalls and what to use instead of it.
What are Yin-Yang if Blocks?
Many novices write "if A do X, otherwise do Y" behaviours in the following form, with 2 adjacent if blocks each carrying the condition and its manually inputted negation respectively:
Code:
if (condition) {
//...
}
if (!condition) {
//...
}
Code:
if (value == 0) {
//...
}
if (value == 1) {
//...
}
if (value == 2) {
//...
}
- The if blocks contain multiple attempts to check the same value or condition.
- The if blocks are arranged with the intent of mutual exclusion (i.e. only one of them is meant to execute on any single run), but contain no else.
Why are Yin-Yang if Blocks bad?
Reason 1: They fail at mutual exclusion.
Many novices use Yin-Yang if Blocks with the hope that only one of the block bodies will execute on any given run. But when one block's body operates in a way that it enables some subsequent condition, this false hope becomes a big problem.
A particularly common example of such novice code is this:
Code:
if (variable == true) {
variable = false;
}
if (variable == false) {
variable = true;
}
- variable starts out false: The first block doesn't run, second block makes it true. So far so good.
- variable starts out true: The first block runs and makes it false. Then the second block runs because the first block made it false, and makes it true again.
Reason 2: It is easy to negate the expression improperly.
The 2-block form is especially prone to problems when && or || are part of the condition, and DeMorgan's Laws are improperly applied. Here is an example:
Code:
if (place_meeting(x, y+1, obj_ground) || place_meeting(x, y+1, obj_box)) {
ysp = 0;
}
if (!place_meeting(x, y+1, obj_ground) || !place_meeting(x, y+1, obj_box)) {
ysp = 5;
}
The chained form is also often done improperly, especially with ranges. Here is an example:
Code:
if (score <= 100) {
audio_play_sound(snd_yousuck, 1, false);
}
if ((score >= 100) && (score <= 200)) {
audio_play_sound(snd_couldbebetter, 1, false);
}
if (score >= 200) {
audio_play_sound(snd_yourock, 1, false);
}
As you will see shortly, properly done alternatives to Yin-Yang if Blocks leave no room for amateurish human errors like these.
Reason 3: It wastes time evaluating the same expression twice.
While Yin-Yang if Blocks don't always result in tangible bugs, they always duplicate effort. This might not matter much for a few arithmetic comparisons, but for more expensive functions such as collision checks against precise masks, the performance penalties can start to add up, especially if many instances are running those checks at once.
Reason 4: It tells everyone you have no clue how to code.
Because of the pitfalls listed above and how easy they are to avoid with basic techniques, Yin-Yang if Blocks are commonly considered a mark of the uninitiated. Using one not only puts your project at risk of bugs, it also tells others reading your code that you don't know what you're doing.
Competent Alternatives to Yin-Yang if Blocks
Alternative 1: else
For "if A do X, otherwise do Y" behaviours, state only the positive version of the condition, and leave the negative to else. The else keyword ensures that the second block never runs if the first block gets to, even if code in the first block makes the condition false in the process.
Code:
if (condition) {
//...
} else {
//...
}
Code:
if (place_meeting(x, y+1, obj_ground) || place_meeting(x, y+1, obj_box)) {
ysp = 0;
} else {
ysp = 5;
}
Code:
if (score <= 100) {
audio_play_sound(snd_yousuck, 1, false);
} else if (score <= 200) {
audio_play_sound(snd_couldbebetter, 1, false);
} else {
audio_play_sound(snd_yourock, 1, false);
}
When comparing one source value to many single target constants (and up to one optional no-match case), use a switch block.
Code:
switch (value) {
case 0:
//value is 0
break;
case 1:
//value is 1
break;
case 2:
//value is 2
break;
default:
//no match
break;
}
For toggling a variable between true and false, use ! (the Boolean not operator).
Code:
variable = !variable;
Alternative 4: Arithmetic operations (for incrementing values)
Whenever feasible, try to relate a variable to its future value using arithmetic expressions, instead of case-by-case handling.
Incrementing a variable by 1 (e.g. 0, 1, 2, 3, 4, ...):
Code:
variable++;
Code:
variable += N;
Incrementing a variable by M and looping around N (e.g. for M=1, N=4: 0, 1, 2, 3, 0, 1, 2, 3, ...)
Code:
variable = (variable+M) mod N;
Code:
variable = (variable+N-M) mod N;