[Tutorial] Improving your code with try/catch/finally
#1

This post is for my exceptions include.

I'll start off with some example code so you'll see what this is all about.

Example code

As you can see, the code below is very intuitive and concise. It also informs the user exactly what's wrong without making a mess (huge functions, global variables).

pawn Code:
CMD:login(playerid, password[]) {
    // Enter the try statement..
    // If "throw" is executed anywhere inside the try statement,
    // it will abort what it's doing and jump to "catch".
    try {
        // Try logging in..
        LogIn(playerid, password);
    } catch (e) {
        // Something went wrong? Tell the user.
        SendClientMessage(playerid, COLOR_RED, "Failed to log in, reason:");
        SendClientMessage(playerid, COLOR_RED, e[Message]);
       
        // Don't do anything more
        return 1;
    }
   
    SendClientMessage(playerid, COLOR_GREEN, "Success!");
   
    g_IsLoggedIn[playerid] = true;
   
    return 1;
}

// The log in function
stock LogIn(playerid, password[]) {
    new name[MAX_PLAYER_NAME];
   
    GetPlayerName(playerid, name, sizeof(name));
   
    // Does the player exist in the database?
    if (!PlayerExistsInDatabase(name))
        throw new Error("The nickname is not registered.");
   
    // Does the password match?
    if (isequal(Hash(password), GetUserPassword(name)))
        throw new Error("The password did not match.");
   
    // Is the account locked?
    is (IsUserAccountLocked(name))
        throw new Error("The user account is locked.");
   
    // Everything seems OK, let's load the user data.
    LoadUserData(playerid, EscapeFileName(name));
   
    return 1;
}

// Load the user data from a file
stock LoadUserData(playerid, filename[]) {
    if (!fexist(filename))
        throw new Error("The userdata file is missing.");
   
    // Open a user file for reading (not recommended, use a database instead)
    new File:fp = fopen(filename, io_read);
   
    // Failed to open the file
    if (!fp)
        throw new Error("Unable to open the userdata file.");
   
    // <load stuff>
   
    // We will only be on this line if fp is not 0
    fclose(fp);
}
The three blocks

Error handling with exceptions generally consists of three blocks. The only required block is try.
When the server encounters a try block, it will set up a trampoline that will catch any errors falling from the code (including the code inside function calls). If an error is caught, it will execute the catch block, containing an error object (see below).
After the catch block -- regardless if an error was thrown or a return statement was encountered -- it will enter the finally block.
If you returned a value in try or catch, this value will be returned right after finally and nothing below it will be executed.
  • try
    This block is always executed.
  • catch (exception)
    This block is executed only if throw is invoked inside try.
    The variable exception will be created, containing information about the error:
    • e[Message] - The string given to ThrowError.
    • e[Code] - An optional error code. This is the second parameter in ThrowError.
  • finally
    This block is always executed, even when try or catch returns a value. Actually returning the value will automatically happen at the end of this block.
    The main purpose for this is cleaning up - close databases, files, and such that were opened in try or catch.
Throwing errors

When you throw an error, it will fall to the first try and end up in catch (if it exists).

To throw an error, you can either create a new one or throw an existing one, example:
pawn Code:
try {
    throw new Error("Error message", ERR_CODE);
} catch (e) {
    // This catch only knows how to take care of ERR_CODE_2
    // If another error occurred, throw it. Hopefully there's another try
    // statement outside that will catch the other error.
    if (e[Code] != ERR_CODE_2) {
        throw e;
    }
}
Error codes

To avoid having multiple error codes with the same values, there's a helper to create them.

Simply do this in the outer scope (not inside a function):
pawn Code:
new UniqueErrorCode<ERR_NOT_FOUND>;

// Now you can do this:
throw new Error("Unable to find that one thing", ERR_NOT_FOUND);
If you have 100 of these in different include files, each one will still have its own unique value.

Warning!
  • Don't use throw in a function invoked by CallLocalFunction (not supported yet).
  • Throwing errors outside try statements will abort the script (this can be avoided with OnUncaughtException).
  • Don't rely on catching runtime errors (array out of bounds, memory access error, etc.) - it's not fully supported.
FAQ
  • In a try statement, can I run a function with another try statement?
    Yes, but be careful with CallLocalFunction and CallRemoteFunction (see warnings above).
  • Is this bad for performance?
    Not at all!
  • What happens if an exception is thrown outside of any try statements?
    Refer to the trampoline picture. Imagine the same picture, but without the trampoline.
Nothing here yet.. Ask questions and they will be added here with their answer.
Reply
#2

Just... awesome.

Thanks.
Reply
#3

I am guessing this doesn't work with hooking, if it does, would make includes very very user friendly !
Reply
#4

Indeed, Nice include.

I'll use this in my script, thanks.

+rep
Reply
#5

Quote:
Originally Posted by Rajat_Pawar
View Post
I am guessing this doesn't work with hooking, if it does, would make includes very very user friendly !
What do you mean? It should work just fine. After try/catch/finally, it will just carry on as usual unless a return statement was made, in which case it will be the same as just doing return outside of try/catch/finally.
Reply
#6

Nice
Reply
#7

Quote:
Originally Posted by ******
View Post
They are probably referring to the CallLocalFunction hook method, though that's but supplanted.

Anyway, again very nice work!
As long as CallLocalFunction is not called within a try block it should work.

I'm looking into possible solutions, but honestly sometimes I just want to replace CallLocalFunction with the one I made.

Maybe I could hook it and use the custom one if inside a try block..
Reply
#8

I'll update it to support that. The performance shouldn't be an issue.
Reply
#9

Excellent work! This will surely improve the look and layout of my code in the future and make error handling much more effective - cheers
Reply
#10

As a Java Developer i kind of missed that, even tho this is not exactly the same but still gr8 job, 5 stars
Reply
#11

Quote:
Originally Posted by [Bios]Marcel
Посмотреть сообщение
As a Java Developer i kind of missed that, even tho this is not exactly the same but still gr8 job, 5 stars
Bumped quite an old thread here.

Although, it is quite an interesting idea to have that. It makes you think about error handling more, considering most don't because of the language.
Reply


Forum Jump:


Users browsing this thread: 1 Guest(s)