PDA

View Full Version : [Tutorial] How to write readable and maintainable code



SergiuOfficial
06-06-17, 14:29
Hello Community,

I am making this tutorial because i most of the time am browsing the section "Scripting Help", there are a lot of beginners that want help, but their code is just not nice to read or edit. I hope i can influence at least some peoples scripting habbits by making this tutorial.

Through the whole tutorial we will use this code and try to make it a little bit better:

PHP Code:
new PLAYERNAME[24];
new PLAYERNAME2[24];
public onPlayerConnect(playerid)
{
GetPlayerName(playerid,PLAYERNAME2,24);
if(strlen(PLAYERNAME) >= 20)
{
SendClientMessage(playerid,-1,"You have got a long name ...");
print("A clientmessage has been sent");
print("Isn't that beautiful?");
GetPlayerName(playerid,PLAYERNAME2,24);
if(strlen(PLAYERNAME2) >= 23)
{
SendClientMessage(playerid,-1,"Maybe you should overthink that name.");
print("Another clientmessage has been sent");
}
}
}


Part 1: Indentation

As ylou might have noticed already, every line starts at index 0 (line beginning), but that just doesn't read very well, because your mind can't seperate the code blocks that easy, even though you have the brackets.

So, i recommend to always indent your code after every opening bracket, it should(could) look like this:

PHP Code:
new PLAYERNAME[24];
new PLAYERNAME2[24];
public onPlayerConnect(playerid)
{
GetPlayerName(playerid,PLAYERNAME2,24);
if(strlen(PLAYERNAME) >= 20)
{
SendClientMessage(playerid,-1,"You have got a long name ...");
print("A clientmessage has been sent");
print("Isn't that beautiful?");
GetPlayerName(playerid,PLAYERNAME2,24);
if(strlen(PLAYERNAME2) >= 23)
{
SendClientMessage(playerid,-1,"Maybe you should overthink that name.");
print("Another clientmessage has been sent");
}
}
}
Each time you use a bracket to open a code block, you should increase the indentation level:

PHP Code:
public onPlayerConnect(playerid) //Level 0
if(strlen(PLAYERNAME) >= 20) //Level 1
if(strlen(PLAYERNAME2) >= 23) //Level 2
Since our code is indented now, you can easily see there are 3 levels. It's up to you, how you indent, as long as it is consistent. You can use a specific amount of either spaces or tabs. Most people use a single tab per level (like I just did). Choose whatever works best for you!



Part 2: Whitespace

Now, let us divide the code even more by using whitespace, for that, we will now look at Level 1:

PHP Code:
SendClientMessage(playerid,-1,"You have got a long name ...");
print("A clientmessage has been sent");
print("Isn't that beautiful?");
GetPlayerName(playerid,PLAYERNAME2,24);
if(strlen(PLAYERNAME2) >= 23)
{
SendClientMessage(playerid,-1,"Maybe you should overthink that name.");
print("Another clientmessage has been sent");
}
There are diffrent things happening in level 1, let's have a closer look:

1: Sending a client message
2: printing something onto the console
3: retrieving the players name
4: entering an if-statement(level 2)

As you can see, we have 4 diffrent tasks at level 1, now we seperate each of them with an empty line:

PHP Code:
SendClientMessage(playerid,-1,"You have got a long name ...");

print("A clientmessage has been sent!!!!");
print("Isn't that beautiful?");

GetPlayerName(playerid,PLAYERNAME2,24);

if(strlen(PLAYERNAME2) >= 23)
{
SendClientMessage(playerid,-1,"Maybe you should overthink that name.");
print("Another clientmessage has been sent");
}


Part 3: Redundant Code

Doubling code just bloats your .pwn files and makes it harder to read the code, because you have to read more, it is like having to read the same function over and over, but every time you read the function, something might have changed. If you were to change the code, you'd have to change it at more than one place, this will most likely lead to mistakes.

As you can see in our code, we have two variables where we store player names in,
PHP Code:
PLAYERNAME
and
PHP Code:
PLAYERNAME2
and we call GetPlayerName(...) two times.First, you don't need two name variables, you can just reuse the first one. Second, do we really want to call GetPlayerName twice? We do have 3 diffrent input parameters, which is a potential error risk.

So, we create a function instead:

PHP Code:
new PLAYERNAME[24];
public onPlayerConnect(playerid)
{
PLAYERNAME = getName(playerid);

if(strlen(PLAYERNAME) >= 20)
{
SendClientMessage(playerid,-1,"You have got a long name ...");

print("A clientmessage has been sent");
print("Isn't that beautiful?");

PLAYERNAME = getName(playerid);

if(strlen(PLAYERNAME2) >= 23)
{
SendClientMessage(playerid,-1,"Maybe you should overthink that name.");

print("Another clientmessage has been sent");
}
}
}

getName(playerid)
{
new VARIABLE[24];
GetPlayerName(playerid,VARIABLE,24);
return VARIABLE;
}
Don't put 'stock' infront if your functions unless it is inside of an include file or not in use (in which case you should comment it out)!!!



Part 4: Defines

In PAWN you have the option to use defines, those are constants that are replaced on compiletime, which means they make no difference in performance but they higher your codes maintainability. Whenever you use a string or a number more than once (or you change it very often), you should probably use a define instead. Looking at our code, we can see us using the number '24' three times. In our case there is already an existing definition, called MAX_PLAYER_NAME and it defines the maximum length of a players name.

Let's use it:

PHP Code:
new PLAYERNAME[MAX_PLAYER_NAME];
public onPlayerConnect(playerid)
{
PLAYERNAME = getName(playerid);

if(strlen(PLAYERNAME) >= 20)
{
SendClientMessage(playerid,-1,"You have got a long name ...");

print("A clientmessage has been sent");
print("Isn't that beautiful?");

PLAYERNAME = getName(playerid);

if(strlen(PLAYERNAME2) >= 23)
{
SendClientMessage(playerid,-1,"Maybe you should overthink that name.");

print("Another clientmessage has been sent");
}
}
}

getName(playerid)
{
new VARIABLE[MAX_PLAYER_NAME];
GetPlayerName(playerid,VARIABLE,MAX_PLAYER_NAME);
return VARIABLE;
}
If SA-MP would now cut the player names to a size of 10, our script would automatically adapt as soon as we recompile the script.



Part 5: Variable Names

Variable names are very important, if a variable is called 'tree' for example, you would probably think it has todo something with trees, but what if that's not true because someone called it 'tree' just because he felt like it? So, you should always give your variables proper names which describe what they are there for.

Looking at the function that we created, where we just called the variable, that we use to store the players name in, 'VARIABLE', which is complete crap. So, what if we call it 'NAME' instead? Hmmm nah still not good. But why is 'NAME' not good? Well, usually there is two types of variables, cosntants(variables that stay the same) and "normal" variables. our constants are the defines and the const variables and our "normal" variables are the ... variables. But how do we know if a variable is a constant or a variable?? Naming conventions are the key, if we simply use uppercase letters only for our constants and camel case naming for our normal variables, we always know what type it is.

So we will call our variable 'playerName':

PHP Code:
getName(playerid)
{
new playerName[MAX_PLAYER_NAME];
GetPlayerName(playerid,playerName,MAX_PLAYER_NAME) ;
return playerName;
}
But we still have the name variable at the top of our script and that's where the next topic comes in handy.



Part 6: Variable Visibility

Every variable has a scope where it is visible(useable), in our case, we can use
PHP Code:
PLAYERNAME
everywhere in our script, other than
PHP Code:
playenName
which we can only use inside of the
PHP Code:
getName(playerid)
function.

So what do we do?

We will try not to make a global variable as long as it is not neccessary:

PHP Code:
public onPlayerConnect(playerid)
{
new playerName[MAX_PLAYER_NAME];
playerName = getName(playerid);

if(strlen(playerName) >= 20)
{
SendClientMessage(playerid,-1,"You have got a long name ...");

print("A clientmessage has been sent");
print("Isn't that beautiful?");

playerName = getName(playerid);

if(strlen(playername) >= 23)
{
SendClientMessage(playerid,-1,"Maybe you should overthink that name.");

print("Another clientmessage has been sent");
}
}
}

getName(playerid)
{
new playerName[MAX_PLAYER_NAME];
GetPlayerName(playerid,playerName,MAX_PLAYER_NAME) ;
return playerName;
}
Since the visibility of the former
PHP Code:
PLAYERNAME
variable is now within
PHP Code:
public OnPlayerConnect(playerid)
, we can name it just as we did in the
PHP Code:
getName(playerid)
function, because we will not get a naming conflict this time.



Part 7: Documentation

In addition of making the code itself readable, you can also document the code, so people don't have to read the actual code of a function to know what it is doing.

As an exmaple, let us document our function:

PHP Code:
/*
This function returns the name of the player with the given playerid
*/
getName(playerid)
{
//the variable that we store the name in
new playerName[MAX_PLAYER_NAME];
//puts the players name into the prepared variable
GetPlayerName(playerid,playerName,MAX_PLAYER_NAME) ;
//returning the players name to the function caller
return playerName;
}
If you now read the comment above the function, you instantly know what the function is doing, in addition to that, you can document the single lines / blocks of the function.



END

I hope you learned something, i'll gladly accept any feedback. Also, i know there are already other topics like this, but i kind of wanted to make my own, since it is something based on opinion (mostly).

greetings Marcel