[Tutorial] How to write readable and maintainable code
#1

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 код:
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 код:
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 код:
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 код:
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 код:
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 код:
PLAYERNAME 
and
PHP код:
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 код:
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 код:
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 код:
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 код:
PLAYERNAME 
everywhere in our script, other than
PHP код:
playenName 
which we can only use inside of the
PHP код:
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 код:
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 код:
PLAYERNAME 
variable is now within
PHP код:
public OnPlayerConnect(playerid
, we can name it just as we did in the
PHP код:
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 код:
/*
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
Reply
#2

Good. This is needed. However, only constants (definitions and enumerators) should be written in all upper case. Naming conventions are another big part to creating readable code.
Reply
#3

Quote:
Originally Posted by Vince
Посмотреть сообщение
Good. This is needed. However, only constants (definitions and enumerators) should be written in all upper case. Naming conventions are another big part to creating readable code.
Thanks for the feedback
Reply
#4

Great mate, but there is only a tiny thing that's bugging me in most codes, It's not something huge or wrong, but people keep using this
PHP код:
new PLAYERNAME[24]; 
which I'm totally fine with, but I prefer seeing this instead
PHP код:
new PLAYERNAME[MAX_PLAYER_NAME]; 
overall, I liked your presentation, good job and keep up
Reply
#5

Quote:
Originally Posted by Eoussama
Посмотреть сообщение
Great mate, but there is only a tiny thing that's bugging me in most codes, It's not something huge or wrong, but people keep using this
PHP код:
new PLAYERNAME[24]; 
which I'm totally fine with, but I prefer seeing this instead
PHP код:
new PLAYERNAME[MAX_PLAYER_NAME]; 
overall, I liked your presentation, good job and keep up
Well, in my opinion, it's not that terrible, but yeah people should use the define instead.
I just used the 24 as a bad example for the missing usage of a define, changed it at "Part 4: Defines"
Reply
#6

Good Job
Reply
#7

Good work and every bad beginner should see it also
Reply
#8

Very nice guide. Really enjoyed reading it.
Reply
#9

Quote:
Originally Posted by Vince
View Post
Good. This is needed. However, only constants (definitions and enumerators) should be written in all upper case. Naming conventions are another big part to creating readable code.
^ this.
Good tutorial, this should be sticky I hate when I see those kinds of unreadable code!

BTW, how to untab? "Right SHIFT + TAB" :P
Reply
#10

Thanks guys.

@Swedky Yeah, this or something similar to this should be sticky, no more ugly code in the Sceipting Help section please ^^
Reply
#11

Good job sir.
Reply
#12

Quote:
Originally Posted by coool
View Post
Good work and every bad beginner should see it also
One can't be a bad beginner.
Reply
#13

Quote:
Originally Posted by Eoussama
Посмотреть сообщение
Great mate, but there is only a tiny thing that's bugging me in most codes, It's not something huge or wrong, but people keep using this
PHP код:
new PLAYERNAME[24]; 
which I'm totally fine with, but I prefer seeing this instead
PHP код:
new PLAYERNAME[MAX_PLAYER_NAME]; 
overall, I liked your presentation, good job and keep up
This bugs me even more, make it MAX_PLAYER_NAME + 1 since a name can be 24 characters, but in PAWN we have a string terminator...
Reply
#14

Quote:
Originally Posted by NaS
Посмотреть сообщение
This bugs me even more, make it MAX_PLAYER_NAME + 1 since a name can be 24 characters, but in PAWN we have a string terminator...
Theoretically , even that is uneccessary (not in all cases), because:

Quote:

A player's name can be up to 24 characters long (as of 0.3d R2) by using SetPlayerName. This is defined in a_samp.inc as MAX_PLAYER_NAME. However, the client can only join with a nickname between 3 and 20 characters, otherwise the connection will be rejected and the player has to quit to choose a valid name.

So, if you don't use SetPlayerName, you can actually use a size of 20 + null terminator.
Correct if i am wrong :P
Reply
#15

Quote:
Originally Posted by [Bios]Marcel
Посмотреть сообщение
Theoretically , even that is uneccessary (not in all cases), because:



So, if you don't use SetPlayerName, you can actually use a size of 20 + null terminator.
Correct if i am wrong :P
That is correct, I just think covering all cases makes generally sense. But yea, if you're not gonna use it you can totally ignore that
Reply
#16

Quote:
Originally Posted by NaS
Посмотреть сообщение
That is correct, I just think covering all cases makes generally sense. But yea, if you're not gonna use it you can totally ignore that
Wait, actually you can have 23 characters + 1(null terminator), at 24 characters + 1(null terminator) the server crashed, so a string size of 24 is enough in all cases.
Reply
#17

Oh yeah, i am calling getName(...) a little Later with a size of 24. Oops! You were right, sorry man!
Reply
#18

You should've included PAWN OOP, more people should start doing some OOP with PAWN, it's really easy. How to do it is simple:

Start a new gamemode normally, then include all pawn includes, then basically add something like this:
PHP Code:
#include "../Assets/class.database.pwn" 
the path will be **THE SERVER DIR**/Assets/class.database.pwn
I would've use y_hooks to have a more readable code in the classes, a simple class of a database manager would be like:
http://pastebin.com/wVui4Met

It makes all files much more readable and easy to edit small parts without having to search by 100.000lines, it will be much more documentated without any effort needed.
Reply
#19

Quote:
Originally Posted by Luicy.
View Post
You should've included PAWN OOP, more people should start doing some OOP with PAWN, it's really easy. How to do it is simple:

Start a new gamemode normally, then include all pawn includes, then basically add something like this:
PHP Code:
#include "../Assets/class.database.pwn" 
the path will be **THE SERVER DIR**/Assets/class.database.pwn
I would've use y_hooks to have a more readable code in the classes, a simple class of a database manager would be like:
http://pastebin.com/PhAZZwSd

It makes all files much more readable and easy to edit small parts without having to search by 100.000lines, it will be much more documentated without any effort needed.
OOP in pawn? ehhh thats not OOP , you just make it look like its oop ...
If you really want to do OOP Look at Samp-Sharp or Shoebill
Reply


Forum Jump:


Users browsing this thread: 1 Guest(s)