GML Why Yin-Yang if Blocks suck

Discussion in 'Tutorials' started by FrostyCat, Dec 5, 2019.

  1. FrostyCat

    FrostyCat Member

    Joined:
    Jun 26, 2016
    Posts:
    4,805
    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:
    Code:
    if (condition) {
        //...
    }
    if (!condition) {
        //...
    }
    
    There are also chained versions of this pattern with 3 or more adjacent if blocks checking the same value. Example:
    Code:
    if (value == 0) {
        //...
    }
    if (value == 1) {
        //...
    }
    if (value == 2) {
        //...
    }
    
    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:
    • 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;
    }
    
    A novice might think this flips variable between true and false. But a quick trace reveals a different conclusion:
    • 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.
    There is a reason why asking what this particular piece does is a common weed-out question for junior-level technical interviews. Answering the novice way not only tells the employer you are green enough to have never seen or fallen for it before, it also tells the employer that you can't follow code the way a computer does. Neither of these are desirable coder qualities.

    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;
    }
    
    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:
    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);
    }
    
    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.

    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;
    }
    
    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.

    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);
    }
    
    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.
    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;
    }
    
    Alternative 3: ! (for toggling variables)

    For toggling a variable between true and false, use ! (the Boolean not operator).
    Code:
    variable = !variable;
    
    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, ...):
    Code:
    variable++;
    
    Incrementing a variable by N (e.g. for N=2: 0, 2, 4, 6, 8, ...):
    Code:
    variable += N;
    
    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, ...)
    Code:
    variable = (variable+M) mod N;
    
    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, ...)
    Code:
    variable = (variable+N-M) mod N;
    
     
    Bentley, Desert Dog, Yal and 3 others like this.

Share This Page

  1. This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
    By continuing to use this site, you are consenting to our use of cookies.
    Dismiss Notice