09.04.2013, 14:02
(
Last edited by Slice; 10/04/2013 at 09:55 AM.
)
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).
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.
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:
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):
If you have 100 of these in different include files, each one will still have its own unique value.
Warning!
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);
}
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.
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;
}
}
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);
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.
- 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.